gossipd: remove zombie handling.

We never enabled it, because we seemed to be eliminating valid
channels.  We discard zombie-marked records on loading.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
This commit is contained in:
Rusty Russell 2024-01-31 15:03:11 +10:30
parent af64d30407
commit e7ceffd565
9 changed files with 37 additions and 276 deletions

View File

@ -39,11 +39,6 @@ struct gossip_rcvd_filter;
*/
#define GOSSIP_STORE_RATELIMIT_BIT 0x2000U
/**
* Bit of flags used to mark a channel announcement as inactive (needs channel updates.)
*/
#define GOSSIP_STORE_ZOMBIE_BIT 0x1000U
/**
* Bit of flags used to mark a channel announcement closed (not deleted for 12 blocks)
*/

View File

@ -651,9 +651,6 @@ static bool map_catchup(struct gossmap *map, bool *changed)
if (flags & GOSSIP_STORE_DELETED_BIT)
continue;
if (flags & GOSSIP_STORE_ZOMBIE_BIT)
continue;
/* Partial write, this can happen. */
if (map->map_end + reclen > map->map_size)
break;

View File

@ -17,7 +17,6 @@ GOSSIP_STORE_MAJOR_VERSION_MASK = 0xE0
GOSSIP_STORE_LEN_DELETED_BIT = 0x8000
GOSSIP_STORE_LEN_PUSH_BIT = 0x4000
GOSSIP_STORE_LEN_RATELIMIT_BIT = 0x2000
GOSSIP_STORE_ZOMBIE_BIT = 0x1000
# These duplicate constants in lightning/gossipd/gossip_store_wiregen.h
WIRE_GOSSIP_STORE_PRIVATE_CHANNEL = 4104
@ -92,7 +91,6 @@ class GossipStoreMsgHeader(object):
self.off = off
self.deleted = (self.flags & GOSSIP_STORE_LEN_DELETED_BIT) != 0
self.ratelimit = (self.flags & GOSSIP_STORE_LEN_RATELIMIT_BIT) != 0
self.zombie = (self.flags & GOSSIP_STORE_ZOMBIE_BIT) != 0
class GossmapHalfchannel(object):
@ -624,8 +622,6 @@ class Gossmap(object):
break
if hdr.deleted: # Skip deleted records
continue
if hdr.zombie:
continue
rectype, = struct.unpack(">H", rec[:2])
if rectype == channel_announcement.number:

View File

@ -68,13 +68,12 @@ int main(int argc, char *argv[])
u16 flags = be16_to_cpu(hdr.flags);
u16 msglen = be16_to_cpu(hdr.len);
u8 *msg, *inner;
bool deleted, push, ratelimit, zombie, dying;
bool deleted, push, ratelimit, dying;
u32 blockheight;
deleted = (flags & GOSSIP_STORE_DELETED_BIT);
push = (flags & GOSSIP_STORE_PUSH_BIT);
ratelimit = (flags & GOSSIP_STORE_RATELIMIT_BIT);
zombie = (flags & GOSSIP_STORE_ZOMBIE_BIT);
dying = (flags & GOSSIP_STORE_DYING_BIT);
msg = tal_arr(NULL, u8, msglen);
@ -85,11 +84,10 @@ int main(int argc, char *argv[])
!= crc32c(be32_to_cpu(hdr.timestamp), msg, msglen))
warnx("Checksum verification failed");
printf("%zu: %s%s%s%s%s", off,
printf("%zu: %s%s%s%s", off,
deleted ? "DELETED " : "",
push ? "PUSH " : "",
ratelimit ? "RATE-LIMITED " : "",
zombie ? "ZOMBIE " : "",
dying ? "DYING " : "");
if (print_timestamp)
printf("T=%u ", be32_to_cpu(hdr.timestamp));

View File

@ -15,9 +15,12 @@
#include <unistd.h>
#include <wire/peer_wire.h>
/* Obsolete ZOMBIE bit */
#define GOSSIP_STORE_ZOMBIE_BIT_V13 0x1000U
#define GOSSIP_STORE_TEMP_FILENAME "gossip_store.tmp"
/* We write it as major version 0, minor version 13 */
#define GOSSIP_STORE_VER ((0 << 5) | 13)
/* We write it as major version 0, minor version 14 */
#define GOSSIP_STORE_VER ((0 << 5) | 14)
struct gossip_store {
/* Back pointer. */
@ -59,7 +62,7 @@ static ssize_t gossip_pwritev(int fd, const struct iovec *iov, int iovcnt,
#endif /* !HAVE_PWRITEV */
static bool append_msg(int fd, const u8 *msg, u32 timestamp,
bool zombie, bool spam, bool dying, u64 *len)
bool spam, bool dying, u64 *len)
{
struct gossip_hdr hdr;
u32 msglen;
@ -73,8 +76,6 @@ static bool append_msg(int fd, const u8 *msg, u32 timestamp,
hdr.flags = 0;
if (spam)
hdr.flags |= CPU_TO_BE16(GOSSIP_STORE_RATELIMIT_BIT);
if (zombie)
hdr.flags |= CPU_TO_BE16(GOSSIP_STORE_ZOMBIE_BIT);
if (dying)
hdr.flags |= CPU_TO_BE16(GOSSIP_STORE_DYING_BIT);
hdr.crc = cpu_to_be32(crc32c(timestamp, msg, msglen));
@ -97,10 +98,11 @@ static bool append_msg(int fd, const u8 *msg, u32 timestamp,
* v11 mandated channel_updates use the htlc_maximum_msat field
* v12 added the zombie flag for expired channel updates
* v13 removed private gossip entries
* v14 removed zombie flags
*/
static bool can_upgrade(u8 oldversion)
{
return oldversion >= 9 && oldversion <= 12;
return oldversion >= 9 && oldversion <= 13;
}
/* On upgrade, do best effort on private channels: hand them to
@ -154,6 +156,7 @@ static void give_lightningd_canned_private_update(struct daemon *daemon,
static bool upgrade_field(u8 oldversion,
struct daemon *daemon,
u16 hdr_flags,
u8 **msg)
{
int type = fromwire_peektype(*msg);
@ -175,6 +178,12 @@ static bool upgrade_field(u8 oldversion,
*msg = tal_free(*msg);
}
}
if (oldversion <= 13) {
/* Discard any zombies */
if (hdr_flags & GOSSIP_STORE_ZOMBIE_BIT_V13) {
*msg = tal_free(*msg);
}
}
return true;
}
@ -250,7 +259,8 @@ static u32 gossip_store_compact_offline(struct daemon *daemon)
}
if (oldversion != version) {
if (!upgrade_field(oldversion, daemon, &msg)) {
if (!upgrade_field(oldversion, daemon,
be16_to_cpu(hdr.flags), &msg)) {
tal_free(msg);
goto close_and_delete;
}
@ -299,7 +309,7 @@ static u32 gossip_store_compact_offline(struct daemon *daemon)
oldlen = lseek(old_fd, SEEK_END, 0);
newlen = lseek(new_fd, SEEK_END, 0);
append_msg(old_fd, towire_gossip_store_ended(tmpctx, newlen),
0, false, false, false, &oldlen);
0, false, false, &oldlen);
close(old_fd);
status_debug("gossip_store_compact_offline: %zu deleted, %zu copied",
deleted, count);
@ -357,7 +367,7 @@ struct gossip_store *gossip_store_new(struct daemon *daemon)
}
u64 gossip_store_add(struct gossip_store *gs, const u8 *gossip_msg,
u32 timestamp, bool zombie,
u32 timestamp,
bool spam, bool dying, const u8 *addendum)
{
u64 off = gs->len;
@ -365,12 +375,12 @@ u64 gossip_store_add(struct gossip_store *gs, const u8 *gossip_msg,
/* Should never get here during loading! */
assert(gs->writable);
if (!append_msg(gs->fd, gossip_msg, timestamp, zombie, spam, dying, &gs->len)) {
if (!append_msg(gs->fd, gossip_msg, timestamp, spam, dying, &gs->len)) {
status_broken("Failed writing to gossip store: %s",
strerror(errno));
return 0;
}
if (addendum && !append_msg(gs->fd, addendum, 0, false, false, false, &gs->len)) {
if (addendum && !append_msg(gs->fd, addendum, 0, false, false, &gs->len)) {
status_broken("Failed writing addendum to gossip store: %s",
strerror(errno));
return 0;
@ -504,53 +514,7 @@ void gossip_store_mark_channel_deleted(struct gossip_store *gs,
const struct short_channel_id *scid)
{
gossip_store_add(gs, towire_gossip_store_delete_chan(tmpctx, scid),
0, false, false, false, NULL);
}
static void mark_zombie(struct gossip_store *gs,
const struct broadcastable *bcast,
enum peer_wire expected_type)
{
beint16_t beflags;
u32 index = bcast->index;
/* We assume flags is the first field! */
BUILD_ASSERT(offsetof(struct gossip_hdr, flags) == 0);
/* Should never get here during loading! */
assert(gs->writable);
assert(index);
const u8 *msg = gossip_store_get(tmpctx, gs, index);
assert(fromwire_peektype(msg) == expected_type);
if (pread(gs->fd, &beflags, sizeof(beflags), index) != sizeof(beflags))
status_failed(STATUS_FAIL_INTERNAL_ERROR,
"Failed reading flags to zombie %s @%u: %s",
peer_wire_name(expected_type),
index, strerror(errno));
assert((be16_to_cpu(beflags) & GOSSIP_STORE_DELETED_BIT) == 0);
beflags |= cpu_to_be16(GOSSIP_STORE_ZOMBIE_BIT);
if (pwrite(gs->fd, &beflags, sizeof(beflags), index) != sizeof(beflags))
status_failed(STATUS_FAIL_INTERNAL_ERROR,
"Failed writing flags to zombie %s @%u: %s",
peer_wire_name(expected_type),
index, strerror(errno));
}
/* Marks the length field of a channel_announcement with the zombie flag bit */
void gossip_store_mark_channel_zombie(struct gossip_store *gs,
struct broadcastable *bcast)
{
mark_zombie(gs, bcast, WIRE_CHANNEL_ANNOUNCEMENT);
}
/* Marks the length field of a channel_update with the zombie flag bit */
void gossip_store_mark_cupdate_zombie(struct gossip_store *gs,
struct broadcastable *bcast)
{
mark_zombie(gs, bcast, WIRE_CHANNEL_UPDATE);
0, false, false, NULL);
}
u32 gossip_store_get_timestamp(struct gossip_store *gs, u64 offset)
@ -737,7 +701,7 @@ u32 gossip_store_load(struct gossip_store *gs)
if (!routing_add_channel_update(gs->daemon->rstate,
take(msg), gs->len,
NULL, false,
spam, false)) {
spam)) {
bad = "Bad channel_update";
goto badmsg;
}

View File

@ -32,14 +32,13 @@ u32 gossip_store_load(struct gossip_store *gs);
* @gs: gossip store
* @gossip_msg: the gossip message to insert.
* @timestamp: the timestamp for filtering of this messsage.
* @zombie: true if this channel is missing a current channel_update.
* @spam: true if this message is rate-limited and squelched to peers.
* @dying: true if this message is for a dying channel.
* @addendum: another message to append immediately after this
* (for appending amounts to channel_announcements for internal use).
*/
u64 gossip_store_add(struct gossip_store *gs, const u8 *gossip_msg,
u32 timestamp, bool zombie, bool spam, bool dying,
u32 timestamp, bool spam, bool dying,
const u8 *addendum);
@ -80,17 +79,6 @@ void gossip_store_flag(struct gossip_store *gs,
void gossip_store_mark_channel_deleted(struct gossip_store *gs,
const struct short_channel_id *scid);
/*
* Marks the length field of a channel announcement with a zombie flag bit.
* This allows the channel_announcement to be retained in the store while
* waiting for channel updates to reactivate it.
*/
void gossip_store_mark_channel_zombie(struct gossip_store *gs,
struct broadcastable *bcast);
void gossip_store_mark_cupdate_zombie(struct gossip_store *gs,
struct broadcastable *bcast);
/**
* Mark this channel_announcement/channel_update as dying.
*

View File

@ -312,25 +312,6 @@ static struct node *new_node(struct routing_state *rstate,
return n;
}
static bool is_chan_zombie(struct chan *chan)
{
if (chan->half[0].zombie || chan->half[1].zombie)
return true;
return false;
}
static bool is_node_zombie(struct node* node)
{
struct chan_map_iter i;
struct chan *c;
for (c = first_chan(node, &i); c; c = next_chan(node, &i)) {
if (!is_chan_zombie(c))
return false;
}
return true;
}
/* We can *send* a channel_announce for a channel attached to this node:
* we only send once we have a channel_update. */
bool node_has_broadcastable_channels(const struct node *node)
@ -339,8 +320,6 @@ bool node_has_broadcastable_channels(const struct node *node)
struct chan *c;
for (c = first_chan(node, &i); c; c = next_chan(node, &i)) {
if (is_chan_zombie(c))
continue;
if (is_halfchan_defined(&c->half[0])
|| is_halfchan_defined(&c->half[1]))
return true;
@ -354,10 +333,6 @@ static bool node_announce_predates_channels(const struct node *node)
struct chan *c;
for (c = first_chan(node, &i); c; c = next_chan(node, &i)) {
/* Zombies don't count! */
if (is_chan_zombie(c))
continue;
if (c->bcast.index < node->bcast.index)
return false;
}
@ -381,7 +356,6 @@ static void force_node_announce_rexmit(struct routing_state *rstate,
node->bcast.timestamp,
false,
false,
false,
NULL);
if (node->rgraph.index == initial_bcast_index){
node->rgraph.index = node->bcast.index;
@ -393,7 +367,6 @@ static void force_node_announce_rexmit(struct routing_state *rstate,
node->rgraph.index = gossip_store_add(rstate->daemon->gs,
announce,
node->rgraph.timestamp,
false,
true,
false,
NULL);
@ -490,7 +463,6 @@ static void init_half_chan(struct routing_state *rstate,
broadcastable_init(&c->bcast);
broadcastable_init(&c->rgraph);
c->tokens = TOKEN_MAX;
c->zombie = false;
}
static void bad_gossip_order(const u8 *msg,
@ -677,7 +649,6 @@ static void add_channel_announce_to_broadcast(struct routing_state *rstate,
chan->bcast.timestamp,
false,
false,
false,
addendum);
}
@ -702,8 +673,7 @@ static void delete_chan_messages_from_store(struct routing_state *rstate,
static void remove_channel_from_store(struct routing_state *rstate,
struct chan *chan)
{
/* Put in tombstone marker. Zombie channels will have one already. */
if (!is_chan_zombie(chan))
/* Put in tombstone marker. */
gossip_store_mark_channel_deleted(rstate->daemon->gs, &chan->scid);
/* Now delete old entries. */
@ -1115,8 +1085,7 @@ bool routing_add_channel_update(struct routing_state *rstate,
/* NULL if it's us */
const struct node_id *source_peer,
bool ignore_timestamp,
bool force_spam_flag,
bool force_zombie_flag)
bool force_spam_flag)
{
secp256k1_ecdsa_signature signature;
struct short_channel_id short_channel_id;
@ -1133,7 +1102,6 @@ bool routing_add_channel_update(struct routing_state *rstate,
u8 direction;
struct amount_sat sat;
bool spam;
bool zombie;
bool dying;
/* Make sure we own msg, even if we don't save it. */
@ -1155,7 +1123,6 @@ bool routing_add_channel_update(struct routing_state *rstate,
if (chan) {
uc = NULL;
sat = chan->sat;
zombie = is_chan_zombie(chan);
dying = is_chan_dying(rstate, &short_channel_id);
} else {
/* Maybe announcement was waiting for this update? */
@ -1172,8 +1139,6 @@ bool routing_add_channel_update(struct routing_state *rstate,
return false;
}
sat = uc->sat;
/* When loading zombies from the store. */
zombie = force_zombie_flag;
dying = false;
}
@ -1197,9 +1162,6 @@ bool routing_add_channel_update(struct routing_state *rstate,
assert(!chan);
chan = new_chan(rstate, &short_channel_id,
&uc->id[0], &uc->id[1], sat);
/* Assign zombie flag if loading zombie from store */
if (force_zombie_flag)
chan->half[direction].zombie = true;
}
/* Discard older updates */
@ -1302,68 +1264,6 @@ bool routing_add_channel_update(struct routing_state *rstate,
uc->index);
}
/* Handle resurrection of zombie channels if the other side of the
* zombie channel has a recent timestamp. */
if (zombie && timestamp_reasonable(rstate->daemon,
chan->half[!direction].bcast.timestamp) &&
chan->half[!direction].bcast.index && !index) {
status_peer_debug(source_peer,
"Resurrecting zombie channel %s.",
type_to_string(tmpctx,
struct short_channel_id,
&chan->scid));
const u8 *zombie_announcement = NULL;
const u8 *zombie_addendum = NULL;
const u8 *zombie_update[2] = {NULL, NULL};
/* Resurrection is a careful process. First delete the zombie-
* flagged channel_announcement which has already been
* tombstoned, and re-add to the store without zombie flag. */
zombie_announcement = gossip_store_get(tmpctx, rstate->daemon->gs,
chan->bcast.index);
u32 offset = tal_count(zombie_announcement) +
sizeof(struct gossip_hdr);
/* The channel_announcement addendum reminds us of its size. */
zombie_addendum = gossip_store_get(tmpctx, rstate->daemon->gs,
chan->bcast.index + offset);
gossip_store_delete(rstate->daemon->gs, &chan->bcast,
WIRE_CHANNEL_ANNOUNCEMENT);
chan->bcast.index =
gossip_store_add(rstate->daemon->gs, zombie_announcement,
chan->bcast.timestamp,
false, false, false, zombie_addendum);
/* Deletion of the old addendum is optional. */
/* This opposing channel_update has been stashed away. Now that
* there are two valid updates, this one gets restored. */
/* FIXME: Handle spam case probably needs a helper f'n */
zombie_update[0] = gossip_store_get(tmpctx, rstate->daemon->gs,
chan->half[!direction].bcast.index);
if (chan->half[!direction].bcast.index != chan->half[!direction].rgraph.index) {
/* Don't forget the spam channel_update */
zombie_update[1] = gossip_store_get(tmpctx, rstate->daemon->gs,
chan->half[!direction].rgraph.index);
gossip_store_delete(rstate->daemon->gs, &chan->half[!direction].rgraph,
WIRE_CHANNEL_UPDATE);
}
gossip_store_delete(rstate->daemon->gs, &chan->half[!direction].bcast,
WIRE_CHANNEL_UPDATE);
chan->half[!direction].bcast.index =
gossip_store_add(rstate->daemon->gs, zombie_update[0],
chan->half[!direction].bcast.timestamp,
false, false, false, NULL);
if (zombie_update[1])
chan->half[!direction].rgraph.index =
gossip_store_add(rstate->daemon->gs, zombie_update[1],
chan->half[!direction].rgraph.timestamp,
false, true, false, NULL);
else
chan->half[!direction].rgraph.index = chan->half[!direction].bcast.index;
/* It's a miracle! */
chan->half[0].zombie = false;
chan->half[1].zombie = false;
zombie = false;
}
/* If we're loading from store, this means we don't re-add to store. */
if (index) {
if (!spam)
@ -1372,7 +1272,7 @@ bool routing_add_channel_update(struct routing_state *rstate,
} else {
hc->rgraph.index
= gossip_store_add(rstate->daemon->gs, update, timestamp,
zombie, spam, dying, NULL);
spam, dying, NULL);
if (!spam)
hc->bcast.index = hc->rgraph.index;
@ -1540,7 +1440,7 @@ u8 *handle_channel_update(struct routing_state *rstate, const u8 *update TAKES,
}
routing_add_channel_update(rstate, take(serialized), 0, source_peer, force,
false, false);
false);
return NULL;
}
@ -1674,8 +1574,7 @@ bool routing_add_node_announcement(struct routing_state *rstate,
if (!pna) {
if (was_unknown)
*was_unknown = true;
/* Don't complain if it's a zombie node! */
if (!node || !is_node_zombie(node)) {
if (!node) {
bad_gossip_order(msg, source_peer,
type_to_string(tmpctx, struct node_id,
&node_id));
@ -1788,7 +1687,7 @@ bool routing_add_node_announcement(struct routing_state *rstate,
} else {
node->rgraph.index
= gossip_store_add(rstate->daemon->gs, msg, timestamp,
false, spam, false, NULL);
spam, false, NULL);
if (!spam)
node->bcast.index = node->rgraph.index;
@ -1882,10 +1781,6 @@ void route_prune(struct routing_state *rstate)
for (struct chan *chan = uintmap_first(&rstate->chanmap, &idx);
chan;
chan = uintmap_after(&rstate->chanmap, &idx)) {
/* These have been pruned already */
if (is_chan_zombie(chan))
continue;
/* BOLT #7:
* - if the `timestamp` of the latest `channel_update` in
* either direction is older than two weeks (1209600 seconds):
@ -2061,7 +1956,7 @@ void routing_channel_spent(struct routing_state *rstate,
/* Save to gossip_store in case we restart */
msg = towire_gossip_store_chan_dying(tmpctx, &chan->scid, deadline);
index = gossip_store_add(rstate->daemon->gs, msg, 0, false, false, false, NULL);
index = gossip_store_add(rstate->daemon->gs, msg, 0, false, false, NULL);
/* Mark it dying, so we don't gossip it */
gossip_store_mark_dying(rstate->daemon->gs, &chan->bcast,

View File

@ -30,9 +30,6 @@ struct half_chan {
/* Token bucket */
u8 tokens;
/* Disabled channel waiting for a channel_update from both sides. */
bool zombie;
};
struct chan {
@ -319,8 +316,7 @@ bool routing_add_channel_update(struct routing_state *rstate,
u32 index,
const struct node_id *source_peer TAKES,
bool ignore_timestamp,
bool force_spam_flag,
bool force_zombie_flag);
bool force_spam_flag);
/**
* Add a node_announcement to the network view without checking it
*
@ -364,7 +360,7 @@ bool would_ratelimit_cupdate(struct routing_state *rstate,
const struct half_chan *hc,
u32 timestamp);
/* Does this node have public, non-zombie channels? */
/* Does this node have public channels? */
bool node_has_broadcastable_channels(const struct node *node);
/* Returns an error string if there are unfinalized entries after load */

View File

@ -1616,7 +1616,7 @@ def test_gossip_store_load_no_channel_update(node_factory):
# This should actually result in an empty store.
with open(os.path.join(l1.daemon.lightning_dir, TEST_NETWORK, 'gossip_store'), "rb") as f:
assert bytearray(f.read()) == bytearray.fromhex("0d")
assert bytearray(f.read()) == bytearray.fromhex("0e")
def test_gossip_store_compact_on_load(node_factory, bitcoind):
@ -2094,74 +2094,6 @@ def test_gossip_not_dying(node_factory, bitcoind):
assert len(get_gossip(l1)) == 2
@pytest.mark.skip("Zombie research had unexpected side effects")
def test_channel_resurrection(node_factory, bitcoind):
"""When a node goes offline long enough to prune a channel, the
channel_announcement should be retained in case the node comes back online.
"""
opts = {'dev-fast-gossip-prune': None,
'may_reconnect': True}
l1, l2 = node_factory.get_nodes(2, opts=opts)
opts.update({'log-level': 'debug'})
l3, = node_factory.get_nodes(1, opts=opts)
l1.rpc.connect(l2.info['id'], 'localhost', l2.port)
l3.rpc.connect(l2.info['id'], 'localhost', l2.port)
scid, _ = l1.fundchannel(l2, 10**6, True, True)
bitcoind.generate_block(6)
sync_blockheight(bitcoind, [l1, l2, l3])
l3.wait_channel_active(scid)
start_time = int(time.time())
# Channel_update should now be refreshed.
refresh_due = start_time + 44
prune_due = start_time + 61
l2.rpc.call('dev-gossip-set-time', [refresh_due])
l3.rpc.call('dev-gossip-set-time', [refresh_due])
# Automatic reconnect is too fast, so shutdown l1 instead of disconnecting
l1.stop()
l2.daemon.wait_for_log('Sending keepalive channel_update')
l3.daemon.wait_for_log('Received channel_update for channel 103x1')
# Wait for the next pruning cycle
l2.rpc.call('dev-gossip-set-time', [prune_due])
l3.rpc.call('dev-gossip-set-time', [prune_due])
# Make sure l1 is recognized as disconnected
wait_for(lambda: only_one(l2.rpc.listpeers(l1.info['id'])['peers'])['connected'] is False)
# Wait for the channel to be pruned.
l3.daemon.wait_for_log("Pruning channel")
assert l3.rpc.listchannels()['channels'] == []
l1.start()
time.sleep(1)
l1.rpc.call('dev-gossip-set-time', [prune_due])
time.sleep(1)
l1.rpc.call('dev-gossip-set-time', [prune_due])
wait_for(lambda: [c['active'] for c in l2.rpc.listchannels()['channels']] == [True, True])
l1.rpc.call('dev-gossip-set-time', [prune_due + 30])
l2.rpc.call('dev-gossip-set-time', [prune_due + 30])
l3.rpc.call('dev-gossip-set-time', [prune_due + 30])
# l2 should recognize its own channel as announceable
wait_for(lambda: [[c['public'], c['active']] for c in l2.rpc.listchannels()['channels']] == [[True, True], [True, True]], timeout=30)
# l3 should be able to recover the zombie channel
wait_for(lambda: [c['active'] for c in l3.rpc.listchannels()['channels']] == [True, True], timeout=30)
# Now test spending the outpoint and removing a zombie channel from the store.
l2.stop()
prune_again = prune_due + 91
l1.rpc.call('dev-gossip-set-time', [prune_again])
l3.rpc.call('dev-gossip-set-time', [prune_again])
l3.daemon.wait_for_log("Pruning channel")
txid = l1.rpc.close(l2.info['id'], 1)['txid']
bitcoind.generate_block(13, txid)
l3.daemon.wait_for_log(f"Deleting channel {scid} due to the funding "
"outpoint being spent", 30)
# gossip_store is cleaned of zombie channels once outpoint is spent.
gs_path = os.path.join(l3.daemon.lightning_dir, TEST_NETWORK, 'gossip_store')
gs = subprocess.run(['devtools/dump-gossipstore', '--print-deleted', gs_path],
check=True, timeout=TIMEOUT, stdout=subprocess.PIPE)
print(gs.stdout.decode())
for l in gs.stdout.decode().splitlines():
if "ZOMBIE" in l:
assert ("DELETED" in l)
def test_dump_own_gossip(node_factory):
"""We *should* send all self-related gossip unsolicited, if we have any"""
l1, l2 = node_factory.line_graph(2, wait_for_announce=True)