Skip to content

Commit

Permalink
gossipd: zombify inactive channels instead of pruning
Browse files Browse the repository at this point in the history
Though BOLT 7 says a channel may be pruned when one side becomes inactive
and fails to refresh their channel_update, in practice, the
channel_announcement can be difficult to recover if deleted entirely.
Here the channel_announcement is tagged as zombie such that gossip_store
consumers may safely ignore it, but it may be retained should the channel
come back online in the future. Node_announcements and channel_updates may
also be retained in such a fashion until the channel is ready to be
resurrected.

Changelog-Fixed: Pruned channels are more reliably restored.
  • Loading branch information
endothermicdev committed Dec 21, 2022
1 parent e64a108 commit 2ef7574
Show file tree
Hide file tree
Showing 8 changed files with 322 additions and 10 deletions.
3 changes: 3 additions & 0 deletions common/gossmap.c
Original file line number Diff line number Diff line change
Expand Up @@ -620,6 +620,9 @@ static bool map_catchup(struct gossmap *map, size_t *num_rejected)
if (be32_to_cpu(ghdr.len) & GOSSIP_STORE_LEN_DELETED_BIT)
continue;

if (be32_to_cpu(ghdr.len) & GOSSIP_STORE_LEN_ZOMBIE_BIT)
continue;

/* Partial write, this can happen. */
if (map->map_end + reclen > map->map_size)
break;
Expand Down
93 changes: 93 additions & 0 deletions gossipd/gossip_store.c
Original file line number Diff line number Diff line change
Expand Up @@ -651,6 +651,99 @@ void gossip_store_mark_channel_deleted(struct gossip_store *gs,
0, false, false, false, NULL);
}

/* 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)
{
beint32_t belen;
u32 index = bcast->index;

/* Should never get here during loading! */
assert(gs->writable);

/* Should never try to overwrite version */
assert(index);

#if DEVELOPER
const u8 *msg = gossip_store_get(tmpctx, gs, index);
assert(fromwire_peektype(msg) == WIRE_CHANNEL_ANNOUNCEMENT);
#endif

if (pread(gs->fd, &belen, sizeof(belen), index) != sizeof(belen))
status_failed(STATUS_FAIL_INTERNAL_ERROR,
"Failed reading len to zombie channel @%u: %s",
index, strerror(errno));

assert((be32_to_cpu(belen) & GOSSIP_STORE_LEN_DELETED_BIT) == 0);
belen |= cpu_to_be32(GOSSIP_STORE_LEN_ZOMBIE_BIT);
if (pwrite(gs->fd, &belen, sizeof(belen), index) != sizeof(belen))
status_failed(STATUS_FAIL_INTERNAL_ERROR,
"Failed writing len to zombie channel @%u: %s",
index, strerror(errno));
}

/* 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)
{
beint32_t belen;
u32 index = bcast->index;

/* Should never get here during loading! */
assert(gs->writable);

/* Should never try to overwrite version */
assert(index);

#if DEVELOPER
const u8 *msg = gossip_store_get(tmpctx, gs, index);
assert(fromwire_peektype(msg) == WIRE_CHANNEL_UPDATE);
#endif

if (pread(gs->fd, &belen, sizeof(belen), index) != sizeof(belen))
status_failed(STATUS_FAIL_INTERNAL_ERROR,
"Failed reading len to zombie channel update @%u: %s",
index, strerror(errno));

assert((be32_to_cpu(belen) & GOSSIP_STORE_LEN_DELETED_BIT) == 0);
belen |= cpu_to_be32(GOSSIP_STORE_LEN_ZOMBIE_BIT);
if (pwrite(gs->fd, &belen, sizeof(belen), index) != sizeof(belen))
status_failed(STATUS_FAIL_INTERNAL_ERROR,
"Failed writing len to zombie channel update @%u: %s",
index, strerror(errno));
}

/* Marks the length field of a node_announcement with the zombie flag bit */
void gossip_store_mark_nannounce_zombie(struct gossip_store *gs,
struct broadcastable *bcast)
{
beint32_t belen;
u32 index = bcast->index;

/* Should never get here during loading! */
assert(gs->writable);

/* Should never try to overwrite version */
assert(index);

#if DEVELOPER
const u8 *msg = gossip_store_get(tmpctx, gs, index);
assert(fromwire_peektype(msg) == WIRE_NODE_ANNOUNCEMENT);
#endif

if (pread(gs->fd, &belen, sizeof(belen), index) != sizeof(belen))
status_failed(STATUS_FAIL_INTERNAL_ERROR,
"Failed reading len to zombie node announcement @%u: %s",
index, strerror(errno));

assert((be32_to_cpu(belen) & GOSSIP_STORE_LEN_DELETED_BIT) == 0);
belen |= cpu_to_be32(GOSSIP_STORE_LEN_ZOMBIE_BIT);
if (pwrite(gs->fd, &belen, sizeof(belen), index) != sizeof(belen))
status_failed(STATUS_FAIL_INTERNAL_ERROR,
"Failed writing len to zombie channel update @%u: %s",
index, strerror(errno));
}

const u8 *gossip_store_get(const tal_t *ctx,
struct gossip_store *gs,
u64 offset)
Expand Down
14 changes: 14 additions & 0 deletions gossipd/gossip_store.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,20 @@ void gossip_store_delete(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);

void gossip_store_mark_nannounce_zombie(struct gossip_store *gs,
struct broadcastable *bcast);

/**
* Direct store accessor: loads gossip msg back from store.
*
Expand Down
13 changes: 13 additions & 0 deletions gossipd/gossipd.c
Original file line number Diff line number Diff line change
Expand Up @@ -322,17 +322,30 @@ static void handle_local_private_channel(struct daemon *daemon, const u8 *msg)
u8 *features;
struct short_channel_id scid;
const u8 *cannounce;
struct chan *zombie;

if (!fromwire_gossipd_local_private_channel(msg, msg,
&id, &capacity, &scid,
&features))
master_badmsg(WIRE_GOSSIPD_LOCAL_PRIVATE_CHANNEL, msg);

status_debug("received private channel announcement from channeld for %s",
type_to_string(tmpctx, struct short_channel_id, &scid));
cannounce = private_channel_announcement(tmpctx,
&scid,
&daemon->id,
&id,
features);
/* If there is already a zombie announcement for this channel in the
* store we can disregard this one. */
zombie = get_channel(daemon->rstate, &scid);
if (zombie && (zombie->half[0].zombie || zombie->half[1].zombie)){
status_debug("received channel announcement for %s,"
" but it is a zombie; discarding",
type_to_string(tmpctx, struct short_channel_id,
&scid));
return;
}

if (!routing_add_private_channel(daemon->rstate, &id, capacity,
cannounce, 0)) {
Expand Down
Loading

0 comments on commit 2ef7574

Please sign in to comment.