gossipd: write uuid record on startup.

This is the first record, and ignored by everything else.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
This commit is contained in:
Rusty Russell
2026-02-12 09:16:10 +10:30
parent 25131d2ea2
commit 5dcf39867c
3 changed files with 43 additions and 7 deletions

View File

@@ -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",

View File

@@ -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);

View File

@@ -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'))