Merge pull request #5138 from Roasbeef/strict-zombie

channeldb+discovery: implement strict zombie pruning
This commit is contained in:
Olaoluwa Osuntokun 2021-04-21 14:03:50 -07:00 committed by GitHub
commit 8f940a5ea3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 278 additions and 131 deletions

@ -188,6 +188,7 @@ type ChannelGraph struct {
// returned instance has its own unique reject cache and channel cache.
func newChannelGraph(db *DB, rejectCacheSize, chanCacheSize int,
batchCommitInterval time.Duration) *ChannelGraph {
g := &ChannelGraph{
db: db,
rejectCache: newRejectCache(rejectCacheSize),
@ -199,6 +200,7 @@ func newChannelGraph(db *DB, rejectCacheSize, chanCacheSize int,
g.nodeScheduler = batch.NewTimeScheduler(
db.Backend, nil, batchCommitInterval,
)
return g
}
@ -953,7 +955,7 @@ func (c *ChannelGraph) PruneGraph(spentOutputs []*wire.OutPoint,
// was successfully pruned.
err = delChannelEdge(
edges, edgeIndex, chanIndex, zombieIndex, nodes,
chanID, false,
chanID, false, false,
)
if err != nil && err != ErrEdgeNotFound {
return err
@ -1202,7 +1204,7 @@ func (c *ChannelGraph) DisconnectBlockAtHeight(height uint32) ([]*ChannelEdgeInf
for _, k := range keys {
err = delChannelEdge(
edges, edgeIndex, chanIndex, zombieIndex, nodes,
k, false,
k, false, false,
)
if err != nil && err != ErrEdgeNotFound {
return err
@ -1301,11 +1303,14 @@ func (c *ChannelGraph) PruneTip() (*chainhash.Hash, uint32, error) {
return &tipHash, tipHeight, nil
}
// DeleteChannelEdges removes edges with the given channel IDs from the database
// and marks them as zombies. This ensures that we're unable to re-add it to our
// database once again. If an edge does not exist within the database, then
// ErrEdgeNotFound will be returned.
func (c *ChannelGraph) DeleteChannelEdges(chanIDs ...uint64) error {
// DeleteChannelEdges removes edges with the given channel IDs from the
// database and marks them as zombies. This ensures that we're unable to re-add
// it to our database once again. If an edge does not exist within the
// database, then ErrEdgeNotFound will be returned. If strictZombiePruning is
// true, then when we mark these edges as zombies, we'll set up the keys such
// that we require the node that failed to send the fresh update to be the one
// that resurrects the channel from its zombie state.
func (c *ChannelGraph) DeleteChannelEdges(strictZombiePruning bool, chanIDs ...uint64) error {
// TODO(roasbeef): possibly delete from node bucket if node has no more
// channels
// TODO(roasbeef): don't delete both edges?
@ -1340,7 +1345,7 @@ func (c *ChannelGraph) DeleteChannelEdges(chanIDs ...uint64) error {
byteOrder.PutUint64(rawChanID[:], chanID)
err := delChannelEdge(
edges, edgeIndex, chanIndex, zombieIndex, nodes,
rawChanID[:], true,
rawChanID[:], true, strictZombiePruning,
)
if err != nil {
return err
@ -1929,7 +1934,7 @@ func delEdgeUpdateIndexEntry(edgesBucket kvdb.RwBucket, chanID uint64,
}
func delChannelEdge(edges, edgeIndex, chanIndex, zombieIndex,
nodes kvdb.RwBucket, chanID []byte, isZombie bool) error {
nodes kvdb.RwBucket, chanID []byte, isZombie, strictZombie bool) error {
edgeInfo, err := fetchChanEdgeInfo(edgeIndex, chanID)
if err != nil {
@ -1997,12 +2002,57 @@ func delChannelEdge(edges, edgeIndex, chanIndex, zombieIndex,
return nil
}
nodeKey1, nodeKey2 := edgeInfo.NodeKey1Bytes, edgeInfo.NodeKey2Bytes
if strictZombie {
nodeKey1, nodeKey2 = makeZombiePubkeys(&edgeInfo, edge1, edge2)
}
return markEdgeZombie(
zombieIndex, byteOrder.Uint64(chanID), edgeInfo.NodeKey1Bytes,
edgeInfo.NodeKey2Bytes,
zombieIndex, byteOrder.Uint64(chanID), nodeKey1, nodeKey2,
)
}
// makeZombiePubkeys derives the node pubkeys to store in the zombie index for a
// particular pair of channel policies. The return values are one of:
// 1. (pubkey1, pubkey2)
// 2. (pubkey1, blank)
// 3. (blank, pubkey2)
//
// A blank pubkey means that corresponding node will be unable to resurrect a
// channel on its own. For example, node1 may continue to publish recent
// updates, but node2 has fallen way behind. After marking an edge as a zombie,
// we don't want another fresh update from node1 to resurrect, as the edge can
// only become live once node2 finally sends something recent.
//
// In the case where we have neither update, we allow either party to resurrect
// the channel. If the channel were to be marked zombie again, it would be
// marked with the correct lagging channel since we received an update from only
// one side.
func makeZombiePubkeys(info *ChannelEdgeInfo,
e1, e2 *ChannelEdgePolicy) ([33]byte, [33]byte) {
switch {
// If we don't have either edge policy, we'll return both pubkeys so
// that the channel can be resurrected by either party.
case e1 == nil && e2 == nil:
return info.NodeKey1Bytes, info.NodeKey2Bytes
// If we're missing edge1, or if both edges are present but edge1 is
// older, we'll return edge1's pubkey and a blank pubkey for edge2. This
// means that only an update from edge1 will be able to resurrect the
// channel.
case e1 == nil || (e2 != nil && e1.LastUpdate.Before(e2.LastUpdate)):
return info.NodeKey1Bytes, [33]byte{}
// Otherwise, we're missing edge2 or edge2 is the older side, so we
// return a blank pubkey for edge1. In this case, only an update from
// edge2 can resurect the channel.
default:
return [33]byte{}, info.NodeKey2Bytes
}
}
// UpdateEdgePolicy updates the edge routing policy for a single directed edge
// within the database for the referenced channel. The `flags` attribute within
// the ChannelEdgePolicy determines which of the directed edges are being

@ -368,7 +368,7 @@ func TestEdgeInsertionDeletion(t *testing.T) {
// Next, attempt to delete the edge from the database, again this
// should proceed without any issues.
if err := graph.DeleteChannelEdges(chanID); err != nil {
if err := graph.DeleteChannelEdges(false, chanID); err != nil {
t.Fatalf("unable to delete edge: %v", err)
}
@ -387,7 +387,7 @@ func TestEdgeInsertionDeletion(t *testing.T) {
// Finally, attempt to delete a (now) non-existent edge within the
// database, this should result in an error.
err = graph.DeleteChannelEdges(chanID)
err = graph.DeleteChannelEdges(false, chanID)
if err != ErrEdgeNotFound {
t.Fatalf("deleting a non-existent edge should fail!")
}
@ -1756,7 +1756,7 @@ func TestFilterKnownChanIDs(t *testing.T) {
if err := graph.AddChannelEdge(&channel); err != nil {
t.Fatalf("unable to create channel edge: %v", err)
}
err := graph.DeleteChannelEdges(channel.ChannelID)
err := graph.DeleteChannelEdges(false, channel.ChannelID)
if err != nil {
t.Fatalf("unable to mark edge zombie: %v", err)
}
@ -2038,7 +2038,7 @@ func TestFetchChanInfos(t *testing.T) {
if err := graph.AddChannelEdge(&zombieChan); err != nil {
t.Fatalf("unable to create channel edge: %v", err)
}
err = graph.DeleteChannelEdges(zombieChan.ChannelID)
err = graph.DeleteChannelEdges(false, zombieChan.ChannelID)
if err != nil {
t.Fatalf("unable to delete and mark edge zombie: %v", err)
}
@ -2654,7 +2654,7 @@ func TestNodeIsPublic(t *testing.T) {
// graph. This will make Alice be seen as a private node as it no longer
// has any advertised edges.
for _, graph := range graphs {
err := graph.DeleteChannelEdges(aliceBobEdge.ChannelID)
err := graph.DeleteChannelEdges(false, aliceBobEdge.ChannelID)
if err != nil {
t.Fatalf("unable to remove edge: %v", err)
}
@ -2671,7 +2671,7 @@ func TestNodeIsPublic(t *testing.T) {
// completely remove the edge as it is not possible for her to know of
// it without it being advertised.
for i, graph := range graphs {
err := graph.DeleteChannelEdges(bobCarolEdge.ChannelID)
err := graph.DeleteChannelEdges(false, bobCarolEdge.ChannelID)
if err != nil {
t.Fatalf("unable to remove edge: %v", err)
}
@ -2779,7 +2779,7 @@ func TestDisabledChannelIDs(t *testing.T) {
}
// Delete the channel edge and ensure it is removed from the disabled list.
if err = graph.DeleteChannelEdges(edgeInfo.ChannelID); err != nil {
if err = graph.DeleteChannelEdges(false, edgeInfo.ChannelID); err != nil {
t.Fatalf("unable to delete channel edge: %v", err)
}
disabledChanIds, err = graph.DisabledChannelIDs()
@ -3017,7 +3017,7 @@ func TestGraphZombieIndex(t *testing.T) {
// If we delete the edge and mark it as a zombie, then we should expect
// to see it within the index.
err = graph.DeleteChannelEdges(edge.ChannelID)
err = graph.DeleteChannelEdges(false, edge.ChannelID)
if err != nil {
t.Fatalf("unable to mark edge as zombie: %v", err)
}

@ -47,6 +47,10 @@ var (
// gossip syncer corresponding to a gossip query message received from
// the remote peer.
ErrGossipSyncerNotFound = errors.New("gossip syncer not found")
// emptyPubkey is used to compare compressed pubkeys against an empty
// byte array.
emptyPubkey [33]byte
)
// optionalMsgFields is a set of optional message fields that external callers
@ -1881,44 +1885,13 @@ func (d *AuthenticatedGossiper) processNetworkAnnouncement(
break
case channeldb.ErrZombieEdge:
// Since we've deemed the update as not stale above,
// before marking it live, we'll make sure it has been
// signed by the correct party. The least-significant
// bit in the flag on the channel update tells us which
// edge is being updated.
var pubKey *btcec.PublicKey
switch {
case msg.ChannelFlags&lnwire.ChanUpdateDirection == 0:
pubKey, _ = chanInfo.NodeKey1()
case msg.ChannelFlags&lnwire.ChanUpdateDirection == 1:
pubKey, _ = chanInfo.NodeKey2()
}
err := routing.VerifyChannelUpdateSignature(msg, pubKey)
err = d.processZombieUpdate(chanInfo, msg)
if err != nil {
err := fmt.Errorf("unable to verify channel "+
"update signature: %v", err)
log.Error(err)
log.Warn(err)
nMsg.err <- err
return nil, false
}
// With the signature valid, we'll proceed to mark the
// edge as live and wait for the channel announcement to
// come through again.
err = d.cfg.Router.MarkEdgeLive(msg.ShortChannelID)
if err != nil {
err := fmt.Errorf("unable to remove edge with "+
"chan_id=%v from zombie index: %v",
msg.ShortChannelID, err)
log.Error(err)
nMsg.err <- err
return nil, false
}
log.Debugf("Removed edge with chan_id=%v from zombie "+
"index", msg.ShortChannelID)
// We'll fallthrough to ensure we stash the update until
// we receive its corresponding ChannelAnnouncement.
// This is needed to ensure the edge exists in the graph
@ -2447,6 +2420,54 @@ func (d *AuthenticatedGossiper) processNetworkAnnouncement(
}
}
// processZombieUpdate determines whether the provided channel update should
// resurrect a given zombie edge.
func (d *AuthenticatedGossiper) processZombieUpdate(
chanInfo *channeldb.ChannelEdgeInfo, msg *lnwire.ChannelUpdate) error {
// The least-significant bit in the flag on the channel update tells us
// which edge is being updated.
isNode1 := msg.ChannelFlags&lnwire.ChanUpdateDirection == 0
// Since we've deemed the update as not stale above, before marking it
// live, we'll make sure it has been signed by the correct party. If we
// have both pubkeys, either party can resurect the channel. If we've
// already marked this with the stricter, single-sided resurrection we
// will only have the pubkey of the node with the oldest timestamp.
var pubKey *btcec.PublicKey
switch {
case isNode1 && chanInfo.NodeKey1Bytes != emptyPubkey:
pubKey, _ = chanInfo.NodeKey1()
case !isNode1 && chanInfo.NodeKey2Bytes != emptyPubkey:
pubKey, _ = chanInfo.NodeKey2()
}
if pubKey == nil {
return fmt.Errorf("incorrect pubkey to resurrect zombie "+
"with chan_id=%v", msg.ShortChannelID)
}
err := routing.VerifyChannelUpdateSignature(msg, pubKey)
if err != nil {
return fmt.Errorf("unable to verify channel "+
"update signature: %v", err)
}
// With the signature valid, we'll proceed to mark the
// edge as live and wait for the channel announcement to
// come through again.
err = d.cfg.Router.MarkEdgeLive(msg.ShortChannelID)
if err != nil {
return fmt.Errorf("unable to remove edge with "+
"chan_id=%v from zombie index: %v",
msg.ShortChannelID, err)
}
log.Debugf("Removed edge with chan_id=%v from zombie "+
"index", msg.ShortChannelID)
return nil
}
// fetchNodeAnn fetches the latest signed node announcement from our point of
// view for the node with the given public key.
func (d *AuthenticatedGossiper) fetchNodeAnn(

@ -397,7 +397,9 @@ func (r *mockGraphSource) MarkEdgeZombie(chanID lnwire.ShortChannelID, pubKey1,
r.mu.Lock()
defer r.mu.Unlock()
r.zombies[chanID.ToUint64()] = [][33]byte{pubKey1, pubKey2}
return nil
}
@ -2317,15 +2319,34 @@ func TestProcessZombieEdgeNowLive(t *testing.T) {
t.Fatalf("unable to sign update with new timestamp: %v", err)
}
// We'll also add the edge to our zombie index.
// We'll also add the edge to our zombie index, provide a blank pubkey
// for the first node as we're simulating the sitaution where the first
// ndoe is updating but the second node isn't. In this case we only
// want to allow a new update from the second node to allow the entire
// edge to be resurrected.
chanID := batch.chanAnn.ShortChannelID
err = ctx.router.MarkEdgeZombie(
chanID, batch.chanAnn.NodeID1, batch.chanAnn.NodeID2,
chanID, [33]byte{}, batch.chanAnn.NodeID2,
)
if err != nil {
t.Fatalf("unable mark channel %v as zombie: %v", chanID, err)
}
// If we send a new update but for the other direction of the channel,
// then it should still be rejected as we want a fresh update from the
// one that was considered stale.
batch.chanUpdAnn1.Timestamp = uint32(time.Now().Unix())
if err := signUpdate(remoteKeyPriv1, batch.chanUpdAnn1); err != nil {
t.Fatalf("unable to sign update with new timestamp: %v", err)
}
processAnnouncement(batch.chanUpdAnn1, true, true)
// At this point, the channel should still be consiered a zombie.
_, _, _, err = ctx.router.GetChannelByID(chanID)
if err != channeldb.ErrZombieEdge {
t.Fatalf("channel should still be a zombie")
}
// Attempting to process the current channel update should fail due to
// its edge being considered a zombie and its timestamp not being within
// the live horizon. We should not expect an error here since it is just

@ -2,5 +2,7 @@ package lncfg
// Routing holds the configuration options for routing.
type Routing struct {
AssumeChannelValid bool `long:"assumechanvalid" description:"DEPRECATED: This is now turned on by default for Neutrino (use neutrino.validatechannels=true to turn off) and shouldn't be used for any other backend! (default: false)"`
AssumeChannelValid bool `long:"assumechanvalid" description:"Skip checking channel spentness during graph validation. This speedup comes at the risk of using an unvalidated view of the network for routing. (default: false)"`
StrictZombiePruning bool `long:"strictgraphpruning" description:"If true, then the graph will be pruned more aggressively for zombies. In practice this means that edges with a single stale edge will be considered a zombie."`
}

@ -342,6 +342,13 @@ type Config struct {
// Clock is mockable time provider.
Clock clock.Clock
// StrictZombiePruning determines if we attempt to prune zombie
// channels according to a stricter criteria. If true, then we'll prune
// a channel if only *one* of the edges is considered a zombie.
// Otherwise, we'll only prune the channel when both edges have a very
// dated last update.
StrictZombiePruning bool
}
// EdgeLocator is a struct used to identify a specific edge.
@ -824,30 +831,39 @@ func (r *ChannelRouter) pruneZombieChans() error {
return nil
}
// If *both* edges haven't been updated for a period of
// If either edge hasn't been updated for a period of
// chanExpiry, then we'll mark the channel itself as eligible
// for graph pruning.
var e1Zombie, e2Zombie bool
if e1 != nil {
e1Zombie = time.Since(e1.LastUpdate) >= chanExpiry
if e1Zombie {
log.Tracef("Edge #1 of ChannelID(%v) last "+
"update: %v", info.ChannelID,
e1.LastUpdate)
}
e1Zombie := e1 == nil || time.Since(e1.LastUpdate) >= chanExpiry
e2Zombie := e2 == nil || time.Since(e2.LastUpdate) >= chanExpiry
if e1Zombie {
log.Tracef("Node1 pubkey=%x of chan_id=%v is zombie",
info.NodeKey1Bytes, info.ChannelID)
}
if e2 != nil {
e2Zombie = time.Since(e2.LastUpdate) >= chanExpiry
if e2Zombie {
log.Tracef("Edge #2 of ChannelID(%v) last "+
"update: %v", info.ChannelID,
e2.LastUpdate)
}
if e2Zombie {
log.Tracef("Node2 pubkey=%x of chan_id=%v is zombie",
info.NodeKey2Bytes, info.ChannelID)
}
// If the channel is not considered zombie, we can move on to
// the next.
if !e1Zombie || !e2Zombie {
// If we're using strict zombie pruning, then a channel is only
// considered live if both edges have a recent update we know
// of.
var channelIsLive bool
switch {
case r.cfg.StrictZombiePruning:
channelIsLive = !e1Zombie && !e2Zombie
// Otherwise, if we're using the less strict variant, then a
// channel is considered live if either of the edges have a
// recent update.
default:
channelIsLive = !e1Zombie || !e2Zombie
}
// Return early if the channel is still considered to be live
// with the current set of configuration parameters.
if channelIsLive {
return nil
}
@ -908,7 +924,8 @@ func (r *ChannelRouter) pruneZombieChans() error {
toPrune = append(toPrune, chanID)
log.Tracef("Pruning zombie channel with ChannelID(%v)", chanID)
}
if err := r.cfg.Graph.DeleteChannelEdges(toPrune...); err != nil {
err = r.cfg.Graph.DeleteChannelEdges(r.cfg.StrictZombiePruning, toPrune...)
if err != nil {
return fmt.Errorf("unable to delete zombie channels: %v", err)
}

@ -69,16 +69,17 @@ func (c *testCtx) RestartRouter() error {
return nil
}
func createTestCtxFromGraphInstance(startingHeight uint32, graphInstance *testGraphInstance) (
*testCtx, func(), error) {
func createTestCtxFromGraphInstance(startingHeight uint32, graphInstance *testGraphInstance,
strictPruning bool) (*testCtx, func(), error) {
return createTestCtxFromGraphInstanceAssumeValid(
startingHeight, graphInstance, false,
startingHeight, graphInstance, false, strictPruning,
)
}
func createTestCtxFromGraphInstanceAssumeValid(startingHeight uint32,
graphInstance *testGraphInstance, assumeValid bool) (*testCtx, func(), error) {
graphInstance *testGraphInstance, assumeValid bool,
strictPruning bool) (*testCtx, func(), error) {
// We'll initialize an instance of the channel router with mock
// versions of the chain and channel notifier. As we don't need to test
@ -134,9 +135,10 @@ func createTestCtxFromGraphInstanceAssumeValid(startingHeight uint32,
next := atomic.AddUint64(&uniquePaymentID, 1)
return next, nil
},
PathFindingConfig: pathFindingConfig,
Clock: clock.NewTestClock(time.Unix(1, 0)),
AssumeChannelValid: assumeValid,
PathFindingConfig: pathFindingConfig,
Clock: clock.NewTestClock(time.Unix(1, 0)),
AssumeChannelValid: assumeValid,
StrictZombiePruning: strictPruning,
})
if err != nil {
return nil, nil, fmt.Errorf("unable to create router %v", err)
@ -187,7 +189,7 @@ func createTestCtxSingleNode(startingHeight uint32) (*testCtx, func(), error) {
cleanUp: cleanup,
}
return createTestCtxFromGraphInstance(startingHeight, graphInstance)
return createTestCtxFromGraphInstance(startingHeight, graphInstance, false)
}
func createTestCtxFromFile(startingHeight uint32, testGraph string) (*testCtx, func(), error) {
@ -198,7 +200,7 @@ func createTestCtxFromFile(startingHeight uint32, testGraph string) (*testCtx, f
return nil, nil, fmt.Errorf("unable to create test graph: %v", err)
}
return createTestCtxFromGraphInstance(startingHeight, graphInstance)
return createTestCtxFromGraphInstance(startingHeight, graphInstance, false)
}
// TestFindRoutesWithFeeLimit asserts that routes found by the FindRoutes method
@ -365,9 +367,9 @@ func TestChannelUpdateValidation(t *testing.T) {
const startingBlockHeight = 101
ctx, cleanUp, err := createTestCtxFromGraphInstance(startingBlockHeight,
testGraph)
ctx, cleanUp, err := createTestCtxFromGraphInstance(
startingBlockHeight, testGraph, true,
)
defer cleanUp()
if err != nil {
t.Fatalf("unable to create router: %v", err)
@ -1028,7 +1030,7 @@ func TestIgnoreChannelEdgePolicyForUnknownChannel(t *testing.T) {
defer testGraph.cleanUp()
ctx, cleanUp, err := createTestCtxFromGraphInstance(
startingBlockHeight, testGraph,
startingBlockHeight, testGraph, false,
)
if err != nil {
t.Fatalf("unable to create router: %v", err)
@ -1946,8 +1948,8 @@ func TestPruneChannelGraphStaleEdges(t *testing.T) {
freshTimestamp := time.Now()
staleTimestamp := time.Unix(0, 0)
// We'll create the following test graph so that only the last channel
// is pruned.
// We'll create the following test graph so that two of the channels
// are pruned.
testChannels := []*testChannel{
// No edges.
{
@ -1960,7 +1962,7 @@ func TestPruneChannelGraphStaleEdges(t *testing.T) {
// Only one edge with a stale timestamp.
{
Node1: &testChannelEnd{
Alias: "a",
Alias: "d",
testChannelPolicy: &testChannelPolicy{
LastUpdate: staleTimestamp,
},
@ -1970,6 +1972,20 @@ func TestPruneChannelGraphStaleEdges(t *testing.T) {
ChannelID: 2,
},
// Only one edge with a stale timestamp, but it's the source
// node so it won't get pruned.
{
Node1: &testChannelEnd{
Alias: "a",
testChannelPolicy: &testChannelPolicy{
LastUpdate: staleTimestamp,
},
},
Node2: &testChannelEnd{Alias: "b"},
Capacity: 100000,
ChannelID: 3,
},
// Only one edge with a fresh timestamp.
{
Node1: &testChannelEnd{
@ -1980,10 +1996,11 @@ func TestPruneChannelGraphStaleEdges(t *testing.T) {
},
Node2: &testChannelEnd{Alias: "b"},
Capacity: 100000,
ChannelID: 3,
ChannelID: 4,
},
// One edge fresh, one edge stale.
// One edge fresh, one edge stale. This will be pruned with
// strict pruning activated.
{
Node1: &testChannelEnd{
Alias: "c",
@ -1998,47 +2015,57 @@ func TestPruneChannelGraphStaleEdges(t *testing.T) {
},
},
Capacity: 100000,
ChannelID: 4,
ChannelID: 5,
},
// Both edges fresh.
symmetricTestChannel("g", "h", 100000, &testChannelPolicy{
LastUpdate: freshTimestamp,
}, 5),
}, 6),
// Both edges stale, only one pruned.
// Both edges stale, only one pruned. This should be pruned for
// both normal and strict pruning.
symmetricTestChannel("e", "f", 100000, &testChannelPolicy{
LastUpdate: staleTimestamp,
}, 6),
}, 7),
}
// We'll create our test graph and router backed with these test
// channels we've created.
testGraph, err := createTestGraphFromChannels(testChannels, "a")
if err != nil {
t.Fatalf("unable to create test graph: %v", err)
for _, strictPruning := range []bool{true, false} {
// We'll create our test graph and router backed with these test
// channels we've created.
testGraph, err := createTestGraphFromChannels(testChannels, "a")
if err != nil {
t.Fatalf("unable to create test graph: %v", err)
}
defer testGraph.cleanUp()
const startingHeight = 100
ctx, cleanUp, err := createTestCtxFromGraphInstance(
startingHeight, testGraph, strictPruning,
)
if err != nil {
t.Fatalf("unable to create test context: %v", err)
}
defer cleanUp()
// All of the channels should exist before pruning them.
assertChannelsPruned(t, ctx.graph, testChannels)
// Proceed to prune the channels - only the last one should be pruned.
if err := ctx.router.pruneZombieChans(); err != nil {
t.Fatalf("unable to prune zombie channels: %v", err)
}
// We expect channels that have either both edges stale, or one edge
// stale with both known.
var prunedChannels []uint64
if strictPruning {
prunedChannels = []uint64{2, 5, 7}
} else {
prunedChannels = []uint64{2, 7}
}
assertChannelsPruned(t, ctx.graph, testChannels, prunedChannels...)
}
defer testGraph.cleanUp()
const startingHeight = 100
ctx, cleanUp, err := createTestCtxFromGraphInstance(
startingHeight, testGraph,
)
if err != nil {
t.Fatalf("unable to create test context: %v", err)
}
defer cleanUp()
// All of the channels should exist before pruning them.
assertChannelsPruned(t, ctx.graph, testChannels)
// Proceed to prune the channels - only the last one should be pruned.
if err := ctx.router.pruneZombieChans(); err != nil {
t.Fatalf("unable to prune zombie channels: %v", err)
}
prunedChannel := testChannels[len(testChannels)-1].ChannelID
assertChannelsPruned(t, ctx.graph, testChannels, prunedChannel)
}
// TestPruneChannelGraphDoubleDisabled test that we can properly prune channels
@ -2147,7 +2174,7 @@ func testPruneChannelGraphDoubleDisabled(t *testing.T, assumeValid bool) {
const startingHeight = 100
ctx, cleanUp, err := createTestCtxFromGraphInstanceAssumeValid(
startingHeight, testGraph, assumeValid,
startingHeight, testGraph, assumeValid, false,
)
if err != nil {
t.Fatalf("unable to create test context: %v", err)
@ -2529,9 +2556,9 @@ func TestUnknownErrorSource(t *testing.T) {
const startingBlockHeight = 101
ctx, cleanUp, err := createTestCtxFromGraphInstance(startingBlockHeight,
testGraph)
ctx, cleanUp, err := createTestCtxFromGraphInstance(
startingBlockHeight, testGraph, false,
)
defer cleanUp()
if err != nil {
t.Fatalf("unable to create router: %v", err)
@ -2668,7 +2695,7 @@ func TestSendToRouteStructuredError(t *testing.T) {
const startingBlockHeight = 101
ctx, cleanUp, err := createTestCtxFromGraphInstance(
startingBlockHeight, testGraph,
startingBlockHeight, testGraph, false,
)
if err != nil {
t.Fatalf("unable to create router: %v", err)
@ -2904,7 +2931,7 @@ func TestSendToRouteMaxHops(t *testing.T) {
const startingBlockHeight = 101
ctx, cleanUp, err := createTestCtxFromGraphInstance(
startingBlockHeight, testGraph,
startingBlockHeight, testGraph, false,
)
if err != nil {
t.Fatalf("unable to create router: %v", err)
@ -3018,7 +3045,7 @@ func TestBuildRoute(t *testing.T) {
const startingBlockHeight = 101
ctx, cleanUp, err := createTestCtxFromGraphInstance(
startingBlockHeight, testGraph,
startingBlockHeight, testGraph, false,
)
if err != nil {
t.Fatalf("unable to create router: %v", err)

@ -2344,7 +2344,7 @@ func abandonChanFromGraph(chanGraph *channeldb.ChannelGraph,
// If the channel ID is still in the graph, then that means the channel
// is still open, so we'll now move to purge it from the graph.
return chanGraph.DeleteChannelEdges(chanID)
return chanGraph.DeleteChannelEdges(false, chanID)
}
// AbandonChannel removes all channel state from the database except for a

@ -489,6 +489,12 @@ bitcoin.node=btcd
; other backend!
; --routing.assumechanvalid=true
; If set to true, then we'll prune a channel if only a single edge is seen as
; being stale. This results in a more compact channel graph, and also is helpful
; for neutrino nodes as it means they'll only maintain edges where both nodes are
; seen as being live from it's PoV.
; --routing.strictgraphpruning=true
[Btcd]
; The base directory that contains the node's data, logs, configuration file,

@ -768,6 +768,8 @@ func newServer(cfg *Config, listenAddrs []net.Addr,
s.controlTower = routing.NewControlTower(paymentControl)
strictPruning := (cfg.Bitcoin.Node == "neutrino" ||
cfg.Routing.StrictZombiePruning)
s.chanRouter, err = routing.New(routing.Config{
Graph: chanGraph,
Chain: cc.ChainIO,
@ -784,6 +786,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr,
NextPaymentID: sequencer.NextID,
PathFindingConfig: pathFindingConfig,
Clock: clock.NewDefaultClock(),
StrictZombiePruning: strictPruning,
})
if err != nil {
return nil, fmt.Errorf("can't create router: %v", err)