routing: implement 2-week zombie channel pruning
This commit implements 2-week zombie channel pruning. This means that every GraphPruneInterval (currently set to one hour), we’ll scan the channel graph, marking any channels which haven’t had *both* edges updated in 2 weeks as a “zombie”. During the second pass, all “zombie” channel are removed from the channel graph all together. Adding this functionality means we’ll ensure that we maintain a “healthy” network view, which will cut down on the number of failed HTLC routing attempts, and also reflect an active portion of the graph.
This commit is contained in:
parent
e81689057a
commit
3b7855e449
@ -110,6 +110,16 @@ type Config struct {
|
||||
// payment was unsuccessful.
|
||||
SendToSwitch func(firstHop *btcec.PublicKey, htlcAdd *lnwire.UpdateAddHTLC,
|
||||
circuit *sphinx.Circuit) ([sha256.Size]byte, error)
|
||||
|
||||
// ChannelPruneExpiry is the duration used to determine if a channel
|
||||
// should be pruned or not. If the delta between now and when the
|
||||
// channel was last updated is greater than ChannelPruneExpiry, then
|
||||
// the channel is marked as a zombie channel eligible for pruning.
|
||||
ChannelPruneExpiry time.Duration
|
||||
|
||||
// GraphPruneInterval is used as an interval to determine how often we
|
||||
// should examine the channel graph to garbage collect zombie channels.
|
||||
GraphPruneInterval time.Duration
|
||||
}
|
||||
|
||||
// routeTuple is an entry within the ChannelRouter's route cache. We cache
|
||||
@ -382,7 +392,8 @@ func (r *ChannelRouter) syncGraphWithChain() error {
|
||||
func (r *ChannelRouter) networkHandler() {
|
||||
defer r.wg.Done()
|
||||
|
||||
// TODO(roasbeef): ticker to check if should prune in two weeks or not
|
||||
graphPruneTicker := time.NewTicker(r.cfg.GraphPruneInterval)
|
||||
defer graphPruneTicker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
@ -508,6 +519,75 @@ func (r *ChannelRouter) networkHandler() {
|
||||
exit: make(chan struct{}),
|
||||
}
|
||||
|
||||
// The graph prune ticker has ticked, so we'll examine the
|
||||
// state of the known graph to filter out any zombie channels
|
||||
// for pruning.
|
||||
case <-graphPruneTicker.C:
|
||||
|
||||
var chansToPrune []wire.OutPoint
|
||||
chanExpiry := r.cfg.ChannelPruneExpiry
|
||||
|
||||
log.Infof("Examining Channel Graph for zombie channels")
|
||||
|
||||
// First, we'll collect all the channels which are
|
||||
// eligible for garbage collection due to being
|
||||
// zombies.
|
||||
filterPruneChans := func(info *channeldb.ChannelEdgeInfo,
|
||||
e1, e2 *channeldb.ChannelEdgePolicy) error {
|
||||
|
||||
// If *both* edges haven't been updated for a
|
||||
// period of chanExpiry, then we'll mark the
|
||||
// channel itself as eligible for graph
|
||||
// pruning.
|
||||
e1Zombie, e2Zombie := true, true
|
||||
if e1 != nil {
|
||||
e1Zombie = time.Since(e1.LastUpdate) >= chanExpiry
|
||||
log.Tracef("Edge #1 of ChannelPoint(%v) "+
|
||||
"last update: %v",
|
||||
info.ChannelPoint, e1.LastUpdate)
|
||||
}
|
||||
if e2 != nil {
|
||||
e2Zombie = time.Since(e2.LastUpdate) >= chanExpiry
|
||||
log.Tracef("Edge #2 of ChannelPoint(%v) "+
|
||||
"last update: %v",
|
||||
info.ChannelPoint, e2.LastUpdate)
|
||||
}
|
||||
if e1Zombie && e2Zombie {
|
||||
log.Infof("ChannelPoint(%v) is a "+
|
||||
"zombie, collecting to prune",
|
||||
info.ChannelPoint)
|
||||
|
||||
// TODO(roasbeef): add ability to
|
||||
// delete single directional edge
|
||||
chansToPrune = append(chansToPrune,
|
||||
info.ChannelPoint)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
err := r.cfg.Graph.ForEachChannel(filterPruneChans)
|
||||
if err != nil {
|
||||
log.Errorf("Unable to local zombie chans: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
log.Infof("Pruning %v Zombie Channels", len(chansToPrune))
|
||||
|
||||
// With the set zombie-like channels obtained, we'll do
|
||||
// another pass to delete al zombie channels from the
|
||||
// channel graph.
|
||||
for _, chanToPrune := range chansToPrune {
|
||||
log.Tracef("Pruning zombie chan ChannelPoint(%v)",
|
||||
chanToPrune)
|
||||
|
||||
err := r.cfg.Graph.DeleteChannelEdge(&chanToPrune)
|
||||
if err != nil {
|
||||
log.Errorf("Unable to prune zombie "+
|
||||
"chans: %v", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// The router has been signalled to exit, to we exit our main
|
||||
// loop so the wait group can be decremented.
|
||||
case <-r.quit:
|
||||
|
@ -84,6 +84,8 @@ func createTestCtx(startingHeight uint32, testGraph ...string) (*testCtx, func()
|
||||
_ *lnwire.UpdateAddHTLC, _ *sphinx.Circuit) ([32]byte, error) {
|
||||
return [32]byte{}, nil
|
||||
},
|
||||
ChannelPruneExpiry: time.Hour * 24,
|
||||
GraphPruneInterval: time.Hour * 2,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to create router %v", err)
|
||||
|
@ -266,6 +266,8 @@ func newServer(listenAddrs []string, chanDB *channeldb.DB, cc *chainControl,
|
||||
|
||||
return s.htlcSwitch.SendHTLC(firstHopPub, htlcAdd, errorDecryptor)
|
||||
},
|
||||
ChannelPruneExpiry: time.Duration(time.Hour * 24 * 14),
|
||||
GraphPruneInterval: time.Duration(time.Hour),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't create router: %v", err)
|
||||
|
Loading…
Reference in New Issue
Block a user