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:
Olaoluwa Osuntokun 2017-10-04 19:39:38 -07:00
parent e81689057a
commit 3b7855e449
No known key found for this signature in database
GPG Key ID: 964EA263DD637C21
3 changed files with 85 additions and 1 deletions

@ -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)