devtools/gossmap-compress: decompress code.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
This commit is contained in:
Rusty Russell
2024-08-07 10:15:55 +09:30
parent cf936d296e
commit c93b4aafb2
2 changed files with 339 additions and 5 deletions

View File

@@ -57,7 +57,7 @@ devtools/bolt11-cli: $(DEVTOOLS_COMMON_OBJS) $(JSMN_OBJS) $(BITCOIN_OBJS) wire/f
devtools/encodeaddr: common/utils.o common/bech32.o devtools/encodeaddr.o
devtools/gossmap-compress: $(DEVTOOLS_COMMON_OBJS) $(BITCOIN_OBJS) wire/fromwire.o wire/towire.o wire/tlvstream.o common/gossmap.o common/fp16.o devtools/gossmap-compress.o
devtools/gossmap-compress: $(DEVTOOLS_COMMON_OBJS) $(BITCOIN_OBJS) wire/fromwire.o wire/towire.o wire/tlvstream.o common/gossmap.o common/fp16.o devtools/gossmap-compress.o gossipd/gossip_store_wiregen.o
devtools/bolt12-cli: $(DEVTOOLS_COMMON_OBJS) $(BITCOIN_OBJS) wire/bolt12_wiregen.o wire/fromwire.o wire/towire.o common/bolt12.o common/bolt12_merkle.o devtools/bolt12-cli.o common/setup.o common/iso4217.o

View File

@@ -1,18 +1,25 @@
#include "config.h"
#include <bitcoin/privkey.h>
#include <bitcoin/pubkey.h>
#include <ccan/asort/asort.h>
#include <ccan/crc32c/crc32c.h>
#include <ccan/err/err.h>
#include <ccan/mem/mem.h>
#include <ccan/opt/opt.h>
#include <ccan/read_write_all/read_write_all.h>
#include <ccan/tal/grab_file/grab_file.h>
#include <ccan/tal/str/str.h>
#include <common/bigsize.h>
#include <common/gossip_store.h>
#include <common/gossmap.h>
#include <common/setup.h>
#include <errno.h>
#include <fcntl.h>
#include <gossipd/gossip_store_wiregen.h>
#include <unistd.h>
#include <wire/peer_wiregen.h>
static bool verbose = false;
static unsigned int verbose = 0;
/* All {numbers} are bigsize.
*
@@ -54,6 +61,7 @@ static bool verbose = false;
#define GC_HEADER "GOSSMAP_COMPRESSv1"
#define GC_HEADERLEN (sizeof(GC_HEADER))
#define GOSSIP_STORE_VER ((0 << 5) | 14)
static int cmp_node_num_chans(struct gossmap_node *const *a,
struct gossmap_node *const *b,
@@ -72,6 +80,34 @@ static void write_bigsize(int outfd, u64 val)
errx(1, "Writing bigsize");
}
static u64 read_bigsize(int infd)
{
u64 val;
u8 buf[BIGSIZE_MAX_LEN];
if (!read_all(infd, buf, 1))
errx(1, "Reading bigsize");
switch (buf[0]) {
case 0xfd:
if (!read_all(infd, buf+1, 2))
errx(1, "Reading bigsize");
break;
case 0xfe:
if (!read_all(infd, buf+1, 4))
errx(1, "Reading bigsize");
break;
case 0xff:
if (!read_all(infd, buf+1, 8))
errx(1, "Reading bigsize");
break;
}
if (bigsize_get(buf, sizeof(buf), &val) == 0)
errx(1, "Bad bigsize");
return val;
}
static int cmp_u64(const u64 *a,
const u64 *b,
void *unused)
@@ -221,14 +257,204 @@ static u64 get_delay(struct gossmap *gossmap,
return chan->half[dir].delay;
}
static void pubkey_for_node(size_t nodeidx, struct pubkey *key)
{
struct secret seckey;
memset(&seckey, 1, sizeof(seckey));
memcpy(&seckey, &nodeidx, sizeof(nodeidx));
if (!pubkey_from_secret(&seckey, key))
abort();
}
static void write_msg_to_gstore(int outfd, const u8 *msg TAKES)
{
struct gossip_hdr hdr;
hdr.flags = 0;
hdr.len = cpu_to_be16(tal_bytelen(msg));
hdr.timestamp = 0;
hdr.crc = cpu_to_be32(crc32c(0, msg, tal_bytelen(msg)));
if (!write_all(outfd, &hdr, sizeof(hdr))
|| !write_all(outfd, msg, tal_bytelen(msg))) {
err(1, "Writing gossip_store");
}
if (taken(msg))
tal_free(msg);
}
/* BOLT #7:
* 1. type: 256 (`channel_announcement`)
* 2. data:
* * [`signature`:`node_signature_1`]
* * [`signature`:`node_signature_2`]
* * [`signature`:`bitcoin_signature_1`]
* * [`signature`:`bitcoin_signature_2`]
* * [`u16`:`len`]
* * [`len*byte`:`features`]
* * [`chain_hash`:`chain_hash`]
* * [`short_channel_id`:`short_channel_id`]
* * [`point`:`node_id_1`]
* * [`point`:`node_id_2`]
* * [`point`:`bitcoin_key_1`]
* * [`point`:`bitcoin_key_2`]
*/
static void write_announce(int outfd,
size_t node1,
size_t node2,
u64 capacity,
size_t i)
{
struct {
secp256k1_ecdsa_signature sig;
struct bitcoin_blkid chain_hash;
} vals;
u8 *msg;
struct short_channel_id scid;
struct pubkey id1, id2;
struct node_id nodeid1, nodeid2;
memset(&vals, 0, sizeof(vals));
pubkey_for_node(node1, &id1);
pubkey_for_node(node2, &id2);
/* Nodes in pubkey order */
if (pubkey_cmp(&id1, &id2) < 0) {
node_id_from_pubkey(&nodeid1, &id1);
node_id_from_pubkey(&nodeid2, &id2);
} else {
node_id_from_pubkey(&nodeid1, &id2);
node_id_from_pubkey(&nodeid2, &id1);
}
/* Use i to avoid clashing scids even if two nodes have > 1 channel */
if (!mk_short_channel_id(&scid, node1, node2, i & 0xFFFF))
abort();
msg = towire_channel_announcement(NULL, &vals.sig, &vals.sig, &vals.sig, &vals.sig,
NULL, &vals.chain_hash, scid,
&nodeid1, &nodeid2,
&id1, &id1);
write_msg_to_gstore(outfd, take(msg));
msg = towire_gossip_store_channel_amount(NULL, amount_sat(capacity));
write_msg_to_gstore(outfd, take(msg));
}
/* BOLT #7:
* 1. type: 258 (`channel_update`)
* 2. data:
* * [`signature`:`signature`]
* * [`chain_hash`:`chain_hash`]
* * [`short_channel_id`:`short_channel_id`]
* * [`u32`:`timestamp`]
* * [`byte`:`message_flags`]
* * [`byte`:`channel_flags`]
* * [`u16`:`cltv_expiry_delta`]
* * [`u64`:`htlc_minimum_msat`]
* * [`u32`:`fee_base_msat`]
* * [`u32`:`fee_proportional_millionths`]
* * [`u64`:`htlc_maximum_msat`]
*/
static void write_update(int outfd,
size_t node1,
size_t node2,
size_t i,
int dir,
bool disabled,
u64 htlc_min, u64 htlc_max,
u64 basefee,
u32 propfee,
u16 delay)
{
struct vals {
secp256k1_ecdsa_signature sig;
struct bitcoin_blkid chain_hash;
u32 timestamp;
} vals;
u8 *msg;
u8 message_flags, channel_flags;
struct pubkey id1, id2;
struct short_channel_id scid;
memset(&vals, 0, sizeof(vals));
/* Use i to avoid clashing scids even if two nodes have > 1 channel */
if (!mk_short_channel_id(&scid, node1, node2, i & 0xFFFF))
abort();
/* If node ids are backward, dir is reversed */
pubkey_for_node(node1, &id1);
pubkey_for_node(node2, &id2);
if (pubkey_cmp(&id1, &id2) > 0)
dir = !dir;
/* BOLT #7:
* The `channel_flags` bitfield is used to indicate the direction of
* the channel: it identifies the node that this update originated
* from and signals various options concerning the channel. The
* following table specifies the meaning of its individual bits:
*
* | Bit Position | Name | Meaning |
* | ------------- | ----------- | -------------------------------- |
* | 0 | `direction` | Direction this update refers to. |
* | 1 | `disable` | Disable the channel. |
*
* The `message_flags` bitfield is used to provide additional details about the message:
*
* | Bit Position | Name |
* | ------------- | ---------------|
* | 0 | `must_be_one` |
* | 1 | `dont_forward` |
*/
channel_flags = dir ? 1 : 0;
if (disabled)
channel_flags |= 2;
message_flags = 1;
msg = towire_channel_update(NULL, &vals.sig, &vals.chain_hash, scid,
0, message_flags, channel_flags,
delay,
amount_msat(htlc_min),
basefee, propfee,
amount_msat(htlc_max));
write_msg_to_gstore(outfd, take(msg));
}
static const u64 *read_template(const tal_t *ctx, int infd, const char *what)
{
size_t count = read_bigsize(infd);
u64 *template = tal_arr(ctx, u64, count);
for (size_t i = 0; i < count; i++)
template[i] = read_bigsize(infd);
if (verbose)
printf("%zu unique %s\n", count, what);
return template;
}
static u64 read_val(int infd, const u64 *template)
{
size_t idx = read_bigsize(infd);
assert(idx < tal_count(template));
return template[idx];
}
static char *opt_add_one(unsigned int *val)
{
(*val)++;
return NULL;
}
int main(int argc, char *argv[])
{
int infd, outfd;
common_setup(argv[0]);
setup_locale();
opt_register_noarg("--verbose|-v", opt_set_bool, &verbose,
"Print details.");
opt_register_noarg("--verbose|-v", opt_add_one, &verbose,
"Print details (each additional gives more!).");
opt_register_noarg("--help|-h", opt_usage_and_exit,
"[decompress|compress] infile outfile"
"Compress or decompress a gossmap file",
@@ -355,9 +581,117 @@ int main(int argc, char *argv[])
write_bidir_perchan(outfd, gossmap, chans, get_propfee, "propfee");
write_bidir_perchan(outfd, gossmap, chans, get_delay, "delay");
} else if (streq(argv[1], "decompress")) {
errx(1, "NYI");
char hdr[GC_HEADERLEN];
size_t channel_count, chanidx;
const u64 *template;
struct fakechan {
size_t node1, node2;
u64 capacity;
struct halffake {
u64 htlc_min, htlc_max;
u32 basefee, propfee;
u32 delay;
bool disabled;
} half[2];
} *chans;
const u8 version = GOSSIP_STORE_VER;
size_t disabled_count;
if (!read_all(infd, hdr, sizeof(hdr))
|| !memeq(hdr, sizeof(hdr), GC_HEADER, GC_HEADERLEN))
errx(1, "Not a valid compressed gossmap header");
channel_count = read_bigsize(infd);
if (verbose)
printf("%zu channels\n", channel_count);
chans = tal_arrz(tmpctx, struct fakechan, channel_count);
for (size_t i = 0; i < channel_count; i++)
chans[i].node1 = read_bigsize(infd);
for (size_t i = 0; i < channel_count; i++)
chans[i].node2 = read_bigsize(infd);
if (verbose >= 2) {
for (size_t i = 0; i < channel_count; i++) {
struct pubkey id1, id2;
pubkey_for_node(chans[i].node1, &id1);
pubkey_for_node(chans[i].node2, &id2);
printf("Channel %zu: %s -> %s\n",
i,
fmt_pubkey(tmpctx, &id1),
fmt_pubkey(tmpctx, &id2));
}
}
disabled_count = 0;
while ((chanidx = read_bigsize(infd)) < channel_count*2) {
disabled_count++;
chans[chanidx/2].half[chanidx%2].disabled = true;
}
if (verbose)
printf("%zu disabled\n", disabled_count);
template = read_template(tmpctx, infd, "capacities");
for (size_t i = 0; i < channel_count; i++)
chans[i].capacity = read_val(infd, template);
template = read_template(tmpctx, infd, "htlc_min");
for (size_t i = 0; i < channel_count; i++) {
for (size_t dir = 0; dir < 2; dir++) {
chans[i].half[dir].htlc_min = read_val(infd, template);
}
}
template = read_template(tmpctx, infd, "htlc_max");
for (size_t i = 0; i < channel_count; i++) {
for (size_t dir = 0; dir < 2; dir++) {
u64 v = read_val(infd, template);
if (v == 0)
v = chans[i].capacity;
else if (v == 1)
v = chans[i].capacity * 0.99;
chans[i].half[dir].htlc_max = v;
}
}
template = read_template(tmpctx, infd, "basefee");
for (size_t i = 0; i < channel_count; i++) {
for (size_t dir = 0; dir < 2; dir++) {
chans[i].half[dir].basefee = read_val(infd, template);
}
}
template = read_template(tmpctx, infd, "propfee");
for (size_t i = 0; i < channel_count; i++) {
for (size_t dir = 0; dir < 2; dir++) {
chans[i].half[dir].propfee = read_val(infd, template);
}
}
template = read_template(tmpctx, infd, "delay");
for (size_t i = 0; i < channel_count; i++) {
for (size_t dir = 0; dir < 2; dir++) {
chans[i].half[dir].delay = read_val(infd, template);
}
}
/* Now write out gossmap */
write(outfd, &version, 1);
for (size_t i = 0; i < channel_count; i++) {
write_announce(outfd,
chans[i].node1,
chans[i].node2,
chans[i].capacity,
i);
for (size_t dir = 0; dir < 2; dir++) {
write_update(outfd,
chans[i].node1, chans[i].node2, i, dir,
chans[i].half[dir].disabled,
chans[i].half[dir].htlc_min,
chans[i].half[dir].htlc_max,
chans[i].half[dir].basefee,
chans[i].half[dir].propfee,
chans[i].half[dir].delay);
}
}
} else
opt_usage_and_exit("Unknown command");
close(outfd);
common_shutdown();
}