From 5dcf39867c35ffa417a851d499ea5b0bd6ddec43 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Thu, 12 Feb 2026 09:16:10 +1030 Subject: [PATCH] gossipd: write uuid record on startup. This is the first record, and ignored by everything else. Signed-off-by: Rusty Russell --- common/gossmap.c | 3 +++ gossipd/gossip_store.c | 35 ++++++++++++++++++++++++++++++++++- tests/test_gossip.py | 12 ++++++------ 3 files changed, 43 insertions(+), 7 deletions(-) diff --git a/common/gossmap.c b/common/gossmap.c index 09d9272c4..69a3dbefe 100644 --- a/common/gossmap.c +++ b/common/gossmap.c @@ -832,6 +832,9 @@ static bool map_catchup(struct gossmap *map, bool must_be_clean, bool *changed) } else if (type == WIRE_GOSSIP_STORE_CHAN_DYING) { /* We don't really care until it's deleted */ continue; + } else if (type == WIRE_GOSSIP_STORE_UUID) { + /* We handled this reopen, otherwise we don't care. */ + continue; } else { map->logcb(map->cbarg, LOG_BROKEN, "Unknown record %u@%u (size %zu) in gossmap: ignoring", diff --git a/gossipd/gossip_store.c b/gossipd/gossip_store.c index 32c2ae7a7..91c6c28fe 100644 --- a/gossipd/gossip_store.c +++ b/gossipd/gossip_store.c @@ -179,6 +179,18 @@ static bool upgrade_field(u8 oldversion, return true; } +static u8 *new_uuid_record(const tal_t *ctx, int fd, u64 *off) +{ + u8 *uuid = tal_arr(ctx, u8, 32); + + for (size_t i = 0; i < tal_bytelen(uuid); i++) + uuid[i] = pseudorand(256); + append_msg(fd, towire_gossip_store_uuid(tmpctx, uuid), 0, off, NULL); + /* append_msg does not change file offset, so do that now. */ + lseek(fd, 0, SEEK_END); + return uuid; +} + /* Read gossip store entries, copy non-deleted ones. Check basic * validity, but this code is written as simply and robustly as * possible! @@ -198,6 +210,7 @@ static int gossip_store_compact(struct daemon *daemon, struct stat st; struct timemono start = time_mono(); const char *bad; + u8 *uuid = NULL; *populated = false; old_len = 1; @@ -241,6 +254,10 @@ static int gossip_store_compact(struct daemon *daemon, cur_off = old_len = sizeof(oldversion); + /* Make up uuid for old version */ + if (oldversion < 16) + uuid = new_uuid_record(tmpctx, new_fd, total_len); + /* Read everything, write non-deleted ones to new_fd. If something goes wrong, * we end up with truncated store. */ while (read_all(old_fd, &hdr, sizeof(hdr))) { @@ -327,6 +344,13 @@ static int gossip_store_compact(struct daemon *daemon, case WIRE_NODE_ANNOUNCEMENT: nannounces++; break; + case WIRE_GOSSIP_STORE_UUID: + uuid = tal_arr(tmpctx, u8, 32); + if (!fromwire_gossip_store_uuid(msg, uuid)) { + bad = "Corrupt uuid"; + goto badmsg; + } + break; } if (!write_all(new_fd, &hdr, sizeof(hdr)) @@ -348,6 +372,16 @@ static int gossip_store_compact(struct daemon *daemon, } rename_new: + /* If we didn't copy a uuid, do so now. */ + if (!uuid) { + if (lseek(new_fd, 0, SEEK_END) != 1) { + bad = tal_fmt(tmpctx, + "missing uuid in version %u", oldversion); + goto badmsg; + } + uuid = new_uuid_record(tmpctx, new_fd, total_len); + } + if (rename(GOSSIP_STORE_TEMP_FILENAME, GOSSIP_STORE_FILENAME) != 0) { status_failed(STATUS_FAIL_INTERNAL_ERROR, "gossip_store_compact: rename failed: %s", @@ -357,7 +391,6 @@ rename_new: /* Create end marker now new file exists. */ if (old_fd != -1) { /* FIXME: real uuid! */ - u8 uuid[32] = {0}; append_msg(old_fd, towire_gossip_store_ended(tmpctx, *total_len, uuid), 0, &old_len, NULL); close(old_fd); diff --git a/tests/test_gossip.py b/tests/test_gossip.py index b29f3b972..e6a2bb075 100644 --- a/tests/test_gossip.py +++ b/tests/test_gossip.py @@ -1207,7 +1207,7 @@ def test_gossip_store_load(node_factory): l1.start() # May preceed the Started msg waited for in 'start'. - wait_for(lambda: l1.daemon.is_in_log('Read 1/1/1/0 cannounce/cupdate/nannounce/delete from store in 832 bytes, now 778 bytes')) + wait_for(lambda: l1.daemon.is_in_log('Read 1/1/1/0 cannounce/cupdate/nannounce/delete from store in 832 bytes, now 824 bytes')) assert not l1.daemon.is_in_log('gossip_store.*truncating') @@ -1275,13 +1275,13 @@ def test_gossip_store_load_announce_before_update(node_factory): l1.start() # May preceed the Started msg waited for in 'start'. - wait_for(lambda: l1.daemon.is_in_log('Read 1/1/1/1 cannounce/cupdate/nannounce/delete from store in 950 bytes, now 778 bytes')) + wait_for(lambda: l1.daemon.is_in_log('Read 1/1/1/1 cannounce/cupdate/nannounce/delete from store in 982 bytes, now 824 bytes')) assert not l1.daemon.is_in_log('gossip_store.*truncating') def test_gossip_store_load_amount_truncated(node_factory): """Make sure we can read canned gossip store with truncated amount""" - l1 = node_factory.get_node(start=False, broken_log=r'gossip_store only processed 1 bytes of 445 \(expected 445\)|Moving to gossip_store.corrupt|plugin-cln-renepay:.*unable to fetch channel capacity') + l1 = node_factory.get_node(start=False, broken_log=r'gossip_store only processed 47 bytes of 491 \(expected 491\)|Moving to gossip_store.corrupt|plugin-cln-renepay:.*unable to fetch channel capacity') with open(os.path.join(l1.daemon.lightning_dir, TEST_NETWORK, 'gossip_store'), 'wb') as f: f.write(bytearray.fromhex("0c" # GOSSIP_STORE_VERSION "000001b0" # len @@ -1292,9 +1292,9 @@ def test_gossip_store_load_amount_truncated(node_factory): l1.start() # May preceed the Started msg waited for in 'start'. - wait_for(lambda: l1.daemon.is_in_log(r'\*\*BROKEN\*\* gossipd: gossip_store only processed 1 bytes of 445 \(expected 445\)')) + wait_for(lambda: l1.daemon.is_in_log(r'\*\*BROKEN\*\* gossipd: gossip_store only processed 47 bytes of 491 \(expected 491\)')) wait_for(lambda: l1.daemon.is_in_log(r'\*\*BROKEN\*\* gossipd: gossip_store: Moving to gossip_store.corrupt')) - wait_for(lambda: l1.daemon.is_in_log(r'gossip_store: Read 0/0/0/0 cannounce/cupdate/nannounce/delete from store in 1 bytes, now 1 bytes \(populated=false\)')) + wait_for(lambda: l1.daemon.is_in_log(r'gossip_store: Read 0/0/0/0 cannounce/cupdate/nannounce/delete from store in 1 bytes, now 47 bytes \(populated=false\)')) assert os.path.exists(os.path.join(l1.daemon.lightning_dir, TEST_NETWORK, 'gossip_store.corrupt')) @@ -1658,7 +1658,7 @@ def test_gossip_store_load_no_channel_update(node_factory): l1.start() # May preceed the Started msg waited for in 'start'. - wait_for(lambda: l1.daemon.is_in_log('Read 1/0/1/0 cannounce/cupdate/nannounce/delete from store in 650 bytes, now 628 bytes')) + wait_for(lambda: l1.daemon.is_in_log('Read 1/0/1/0 cannounce/cupdate/nannounce/delete from store in 682 bytes, now 674 bytes')) assert not os.path.exists(os.path.join(l1.daemon.lightning_dir, TEST_NETWORK, 'gossip_store.corrupt'))