pytest: fix bogus test_gossip_store_compact_noappend test.
What changed, and why it matters
This commit fixes a bug in Core Lightning's gossip store compaction code. The bug caused the program to crash with a 'bad version' assertion when starting up with an empty or freshly-created gossip store file. The fix changes an internal length variable from 0 to 1 so the version header is written correctly. The commit also updates related tests, including one that now checks the program handles a corrupted gossip store gracefully rather than crashing.
Apply the patch. Review whether other callers of append_msg can pass a zero-length store. Consider adding defensive checks or clearer error handling for corrupt gossip store files to avoid daemon crashes.
Security signals we found
Daemon abort/crash on startup triggered by empty gossip store state
Assertion failure in append_msg due to zero-length store
Corrupted gossip store file could previously cause fatal signal
Fix ensures graceful overwrite of outdated/corrupt gossip store version
Evidence from the diff
In gossipd/gossip_store.c, gossip_store_compact() initializes old_len to 0, then calls append_msg() to write a version header to a new temporary gossip store. append_msg() asserts that the store length is non-zero before appending, so with old_len=0 the assertion fails and gossipd aborts. The fix sets old_len=1 so the version byte is appended successfully. The test changes remove an obsolete test for a removed dev command and add a new test verifying that a gossip_store file with an invalid version (‘07deadbeef’) is detected and replaced rather than causing a crash or truncation loop.
Changed components
gossipd/gossip_store.ctests/test_gossip.pyInspect captured patch +13 / −9
diff --git a/gossipd/gossip_store.c b/gossipd/gossip_store.c
index eff86c67..075cb196 100644
--- a/gossipd/gossip_store.c
+++ b/gossipd/gossip_store.c
@@ -199,7 +199,7 @@ static int gossip_store_compact(struct daemon *daemon,
const char *bad;
*populated = false;
- old_len = 0;
+ old_len = 1;
new_fd = open(GOSSIP_STORE_TEMP_FILENAME, O_RDWR|O_TRUNC|O_CREAT, 0600);
if (new_fd < 0) {
diff --git a/tests/test_gossip.py b/tests/test_gossip.py
index 64980a15..ad3f057c 100644
--- a/tests/test_gossip.py
+++ b/tests/test_gossip.py
@@ -1294,7 +1294,7 @@ def test_gossip_store_load_amount_truncated(node_factory):
# 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: 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 0 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 1 bytes \(populated=false\)'))
assert os.path.exists(os.path.join(l1.daemon.lightning_dir, TEST_NETWORK, 'gossip_store.corrupt'))
@@ -1581,8 +1581,8 @@ def test_getroute_exclude(node_factory, bitcoind):
l1.rpc.getroute(l4.info['id'], 1, 1, exclude=[chan_l2l3, l5.info['id'], chan_l2l4])
-def setup_gossip_store_test(node_factory, bitcoind):
- l1, l2, l3 = node_factory.line_graph(3, wait_for_announce=True)
+def setup_gossip_store_test(node_factory, bitcoind, opts=None):
+ l1, l2, l3 = node_factory.line_graph(3, wait_for_announce=True, opts=opts)
# Now, replace the one channel_update, so it's past the node announcements.
l2.rpc.setchannel(l3.info['id'], 20, 1000)
@@ -1608,16 +1608,20 @@ def setup_gossip_store_test(node_factory, bitcoind):
return l2
-def test_gossip_store_compact_noappend(node_factory, bitcoind):
- l2 = setup_gossip_store_test(node_factory, bitcoind)
+def test_gossip_store_corrupt(node_factory, bitcoind):
+ l2 = setup_gossip_store_test(node_factory, bitcoind, opts=[{}, {'broken_log': 'gossipd:.*bad version'}, {}])
+ l2.stop()
# It should truncate this, not leave junk!
- with open(os.path.join(l2.daemon.lightning_dir, TEST_NETWORK, 'gossip_store.tmp'), 'wb') as f:
+ with open(os.path.join(l2.daemon.lightning_dir, TEST_NETWORK, 'gossip_store'), 'wb') as f:
f.write(bytearray.fromhex("07deadbeef"))
+ l2.start()
- l2.restart()
wait_for(lambda: l2.daemon.is_in_log('gossip_store: Read '))
- assert not l2.daemon.is_in_log('gossip_store:.*truncate')
+ assert l2.daemon.is_in_log('gossip_store_compact: bad version')
+
+ # Will simply overwrite, due to old version.
+ assert not os.path.exists(os.path.join(l2.daemon.lightning_dir, TEST_NETWORK, 'gossip_store.corrupt'))
def test_gossip_store_load_complex(node_factory, bitcoind):
Why this scored 34/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.