diff --git a/channeldb/error.go b/channeldb/error.go index c0548106..e0e75452 100644 --- a/channeldb/error.go +++ b/channeldb/error.go @@ -1,6 +1,9 @@ package channeldb -import "fmt" +import ( + "errors" + "fmt" +) var ( // ErrNoChanDBExists is returned when a channel bucket hasn't been @@ -79,6 +82,10 @@ var ( // can't be found. ErrEdgeNotFound = fmt.Errorf("edge not found") + // ErrZombieEdge is an error returned when we attempt to look up an edge + // but it is marked as a zombie within the zombie index. + ErrZombieEdge = errors.New("edge marked as zombie") + // ErrEdgeAlreadyExist is returned when edge with specific // channel id can't be added because it already exist. ErrEdgeAlreadyExist = fmt.Errorf("edge already exist") diff --git a/channeldb/graph.go b/channeldb/graph.go index a2b72281..11a875fc 100644 --- a/channeldb/graph.go +++ b/channeldb/graph.go @@ -595,17 +595,20 @@ func (c *ChannelGraph) addChannelEdge(tx *bbolt.Tx, edge *ChannelEdgeInfo) error // HasChannelEdge returns true if the database knows of a channel edge with the // passed channel ID, and false otherwise. If an edge with that ID is found // within the graph, then two time stamps representing the last time the edge -// was updated for both directed edges are returned along with the boolean. -func (c *ChannelGraph) HasChannelEdge(chanID uint64) (time.Time, time.Time, bool, error) { - // TODO(roasbeef): check internal bloom filter first +// was updated for both directed edges are returned along with the boolean. If +// it is not found, then the zombie index is checked and its result is returned +// as the second boolean. +func (c *ChannelGraph) HasChannelEdge(chanID uint64, +) (time.Time, time.Time, bool, bool, error) { var ( node1UpdateTime time.Time node2UpdateTime time.Time exists bool + isZombie bool ) - if err := c.db.View(func(tx *bbolt.Tx) error { + err := c.db.View(func(tx *bbolt.Tx) error { edges := tx.Bucket(edgeBucket) if edges == nil { return ErrGraphNoEdgesFound @@ -617,12 +620,21 @@ func (c *ChannelGraph) HasChannelEdge(chanID uint64) (time.Time, time.Time, bool var channelID [8]byte byteOrder.PutUint64(channelID[:], chanID) + + // If the edge doesn't exist, then we'll also check our zombie + // index. if edgeIndex.Get(channelID[:]) == nil { exists = false + zombieIndex := edges.Bucket(zombieBucket) + if zombieIndex != nil { + isZombie, _, _ = isZombieEdge(zombieIndex, chanID) + } + return nil } exists = true + isZombie = false // If the channel has been found in the graph, then retrieve // the edges itself so we can return the last updated @@ -648,11 +660,9 @@ func (c *ChannelGraph) HasChannelEdge(chanID uint64) (time.Time, time.Time, bool } return nil - }); err != nil { - return time.Time{}, time.Time{}, exists, err - } + }) - return node1UpdateTime, node2UpdateTime, exists, nil + return node1UpdateTime, node2UpdateTime, exists, isZombie, err } // UpdateChannelEdge retrieves and update edge of the graph database. Method @@ -2537,7 +2547,8 @@ func (c *ChannelEdgePolicy) Signature() (*btcec.Signature, error) { // found, then ErrEdgeNotFound is returned. A struct which houses the general // information for the channel itself is returned as well as two structs that // contain the routing policies for the channel in either direction. -func (c *ChannelGraph) FetchChannelEdgesByOutpoint(op *wire.OutPoint) (*ChannelEdgeInfo, *ChannelEdgePolicy, *ChannelEdgePolicy, error) { +func (c *ChannelGraph) FetchChannelEdgesByOutpoint(op *wire.OutPoint, +) (*ChannelEdgeInfo, *ChannelEdgePolicy, *ChannelEdgePolicy, error) { var ( edgeInfo *ChannelEdgeInfo @@ -2615,7 +2626,12 @@ func (c *ChannelGraph) FetchChannelEdgesByOutpoint(op *wire.OutPoint) (*ChannelE // ErrEdgeNotFound is returned. A struct which houses the general information // for the channel itself is returned as well as two structs that contain the // routing policies for the channel in either direction. -func (c *ChannelGraph) FetchChannelEdgesByID(chanID uint64) (*ChannelEdgeInfo, *ChannelEdgePolicy, *ChannelEdgePolicy, error) { +// +// ErrZombieEdge an be returned if the edge is currently marked as a zombie +// within the database. In this case, the ChannelEdgePolicy's will be nil, and +// the ChannelEdgeInfo will only include the public keys of each node. +func (c *ChannelGraph) FetchChannelEdgesByID(chanID uint64, +) (*ChannelEdgeInfo, *ChannelEdgePolicy, *ChannelEdgePolicy, error) { var ( edgeInfo *ChannelEdgeInfo @@ -2646,13 +2662,48 @@ func (c *ChannelGraph) FetchChannelEdgesByID(chanID uint64) (*ChannelEdgeInfo, * byteOrder.PutUint64(channelID[:], chanID) + // Now, attempt to fetch edge. edge, err := fetchChanEdgeInfo(edgeIndex, channelID[:]) + + // If it doesn't exist, we'll quickly check our zombie index to + // see if we've previously marked it as so. + if err == ErrEdgeNotFound { + // If the zombie index doesn't exist, or the edge is not + // marked as a zombie within it, then we'll return the + // original ErrEdgeNotFound error. + zombieIndex := edges.Bucket(zombieBucket) + if zombieIndex == nil { + return ErrEdgeNotFound + } + + isZombie, pubKey1, pubKey2 := isZombieEdge( + zombieIndex, chanID, + ) + if !isZombie { + return ErrEdgeNotFound + } + + // Otherwise, the edge is marked as a zombie, so we'll + // populate the edge info with the public keys of each + // party as this is the only information we have about + // it and return an error signaling so. + edgeInfo = &ChannelEdgeInfo{ + NodeKey1Bytes: pubKey1, + NodeKey2Bytes: pubKey2, + } + return ErrZombieEdge + } + + // Otherwise, we'll just return the error if any. if err != nil { return err } + edgeInfo = &edge edgeInfo.db = c.db + // Then we'll attempt to fetch the accompanying policies of this + // edge. e1, e2, err := fetchChanEdgePolicies( edgeIndex, edges, nodes, channelID[:], c.db, ) @@ -2664,6 +2715,9 @@ func (c *ChannelGraph) FetchChannelEdgesByID(chanID uint64) (*ChannelEdgeInfo, * policy2 = e2 return nil }) + if err == ErrZombieEdge { + return edgeInfo, nil, nil, err + } if err != nil { return nil, nil, nil, err } diff --git a/channeldb/graph_test.go b/channeldb/graph_test.go index 43d1c84c..f8df6448 100644 --- a/channeldb/graph_test.go +++ b/channeldb/graph_test.go @@ -526,29 +526,38 @@ func TestDisconnectBlockAtHeight(t *testing.T) { } // The two first edges should be removed from the db. - _, _, has, err := graph.HasChannelEdge(edgeInfo.ChannelID) + _, _, has, isZombie, err := graph.HasChannelEdge(edgeInfo.ChannelID) if err != nil { t.Fatalf("unable to query for edge: %v", err) } if has { t.Fatalf("edge1 was not pruned from the graph") } - _, _, has, err = graph.HasChannelEdge(edgeInfo2.ChannelID) + if isZombie { + t.Fatal("reorged edge1 should not be marked as zombie") + } + _, _, has, isZombie, err = graph.HasChannelEdge(edgeInfo2.ChannelID) if err != nil { t.Fatalf("unable to query for edge: %v", err) } if has { t.Fatalf("edge2 was not pruned from the graph") } + if isZombie { + t.Fatal("reorged edge2 should not be marked as zombie") + } // Edge 3 should not be removed. - _, _, has, err = graph.HasChannelEdge(edgeInfo3.ChannelID) + _, _, has, isZombie, err = graph.HasChannelEdge(edgeInfo3.ChannelID) if err != nil { t.Fatalf("unable to query for edge: %v", err) } if !has { t.Fatalf("edge3 was pruned from the graph") } + if isZombie { + t.Fatal("edge3 was marked as zombie") + } // PruneTip should be set to the blockHash we specified for the block // at height 155. @@ -759,12 +768,16 @@ func TestEdgeInfoUpdates(t *testing.T) { // Check for existence of the edge within the database, it should be // found. - _, _, found, err := graph.HasChannelEdge(chanID) + _, _, found, isZombie, err := graph.HasChannelEdge(chanID) if err != nil { t.Fatalf("unable to query for edge: %v", err) - } else if !found { + } + if !found { t.Fatalf("graph should have of inserted edge") } + if isZombie { + t.Fatal("live edge should not be marked as zombie") + } // We should also be able to retrieve the channelID only knowing the // channel point of the channel. diff --git a/routing/router.go b/routing/router.go index 7d836a89..5ef2cb94 100644 --- a/routing/router.go +++ b/routing/router.go @@ -76,7 +76,7 @@ type ChannelGraphSource interface { IsPublicNode(node Vertex) (bool, error) // IsKnownEdge returns true if the graph source already knows of the - // passed channel ID. + // passed channel ID either as a live or zombie edge. IsKnownEdge(chanID lnwire.ShortChannelID) bool // IsStaleEdgePolicy returns true if the graph source has a channel @@ -1009,12 +1009,19 @@ func (r *ChannelRouter) processUpdate(msg interface{}) error { // Prior to processing the announcement we first check if we // already know of this channel, if so, then we can exit early. - _, _, exists, err := r.cfg.Graph.HasChannelEdge(msg.ChannelID) + _, _, exists, isZombie, err := r.cfg.Graph.HasChannelEdge( + msg.ChannelID, + ) if err != nil && err != channeldb.ErrGraphNoEdgesFound { return errors.Errorf("unable to check for edge "+ "existence: %v", err) - } else if exists { - return newErrf(ErrIgnored, "Ignoring msg for known "+ + } + if isZombie { + return newErrf(ErrIgnored, "ignoring msg for zombie "+ + "chan_id=%v", msg.ChannelID) + } + if exists { + return newErrf(ErrIgnored, "ignoring msg for known "+ "chan_id=%v", msg.ChannelID) } @@ -1130,19 +1137,29 @@ func (r *ChannelRouter) processUpdate(msg interface{}) error { r.channelEdgeMtx.Lock(msg.ChannelID) defer r.channelEdgeMtx.Unlock(msg.ChannelID) - edge1Timestamp, edge2Timestamp, exists, err := r.cfg.Graph.HasChannelEdge( - msg.ChannelID, - ) + edge1Timestamp, edge2Timestamp, exists, isZombie, err := + r.cfg.Graph.HasChannelEdge(msg.ChannelID) if err != nil && err != channeldb.ErrGraphNoEdgesFound { return errors.Errorf("unable to check for edge "+ "existence: %v", err) } + // If the channel is marked as a zombie in our database, and + // we consider this a stale update, then we should not apply the + // policy. + isStaleUpdate := time.Since(msg.LastUpdate) > r.cfg.ChannelPruneExpiry + if isZombie && isStaleUpdate { + return newErrf(ErrIgnored, "ignoring stale update "+ + "(flags=%v|%v) for zombie chan_id=%v", + msg.MessageFlags, msg.ChannelFlags, + msg.ChannelID) + } + // If the channel doesn't exist in our database, we cannot // apply the updated policy. if !exists { - return newErrf(ErrIgnored, "Ignoring update "+ + return newErrf(ErrIgnored, "ignoring update "+ "(flags=%v|%v) for unknown chan_id=%v", msg.MessageFlags, msg.ChannelFlags, msg.ChannelID) @@ -2241,12 +2258,12 @@ func (r *ChannelRouter) IsPublicNode(node Vertex) (bool, error) { } // IsKnownEdge returns true if the graph source already knows of the passed -// channel ID. +// channel ID either as a live or zombie edge. // // NOTE: This method is part of the ChannelGraphSource interface. func (r *ChannelRouter) IsKnownEdge(chanID lnwire.ShortChannelID) bool { - _, _, exists, _ := r.cfg.Graph.HasChannelEdge(chanID.ToUint64()) - return exists + _, _, exists, isZombie, _ := r.cfg.Graph.HasChannelEdge(chanID.ToUint64()) + return exists || isZombie } // IsStaleEdgePolicy returns true if the graph soruce has a channel edge for @@ -2256,14 +2273,19 @@ func (r *ChannelRouter) IsKnownEdge(chanID lnwire.ShortChannelID) bool { func (r *ChannelRouter) IsStaleEdgePolicy(chanID lnwire.ShortChannelID, timestamp time.Time, flags lnwire.ChanUpdateChanFlags) bool { - edge1Timestamp, edge2Timestamp, exists, err := r.cfg.Graph.HasChannelEdge( - chanID.ToUint64(), - ) + edge1Timestamp, edge2Timestamp, exists, isZombie, err := + r.cfg.Graph.HasChannelEdge(chanID.ToUint64()) if err != nil { return false } + // If we know of the edge as a zombie, then we'll check the timestamp of + // this message to determine whether it's fresh. + if isZombie { + return time.Since(timestamp) > r.cfg.ChannelPruneExpiry + } + // If we don't know of the edge, then it means it's fresh (thus not // stale). if !exists { @@ -2275,7 +2297,6 @@ func (r *ChannelRouter) IsStaleEdgePolicy(chanID lnwire.ShortChannelID, // already have the most up to date information for that edge. If so, // then we can exit early. switch { - // A flag set of 0 indicates this is an announcement for the "first" // node in the channel. case flags&lnwire.ChanUpdateDirection == 0: diff --git a/routing/router_test.go b/routing/router_test.go index a8249183..df9ad9e6 100644 --- a/routing/router_test.go +++ b/routing/router_test.go @@ -1549,21 +1549,27 @@ func TestWakeUpOnStaleBranch(t *testing.T) { } // Check that the fundingTxs are in the graph db. - _, _, has, err := ctx.graph.HasChannelEdge(chanID1) + _, _, has, isZombie, err := ctx.graph.HasChannelEdge(chanID1) if err != nil { t.Fatalf("error looking for edge: %v", chanID1) } if !has { t.Fatalf("could not find edge in graph") } + if isZombie { + t.Fatal("edge was marked as zombie") + } - _, _, has, err = ctx.graph.HasChannelEdge(chanID2) + _, _, has, isZombie, err = ctx.graph.HasChannelEdge(chanID2) if err != nil { t.Fatalf("error looking for edge: %v", chanID2) } if !has { t.Fatalf("could not find edge in graph") } + if isZombie { + t.Fatal("edge was marked as zombie") + } // Stop the router, so we can reorg the chain while its offline. if err := ctx.router.Stop(); err != nil { @@ -1607,22 +1613,27 @@ func TestWakeUpOnStaleBranch(t *testing.T) { // The channel with chanID2 should not be in the database anymore, // since it is not confirmed on the longest chain. chanID1 should // still be. - _, _, has, err = ctx.graph.HasChannelEdge(chanID1) + _, _, has, isZombie, err = ctx.graph.HasChannelEdge(chanID1) if err != nil { t.Fatalf("error looking for edge: %v", chanID1) } if !has { t.Fatalf("did not find edge in graph") } + if isZombie { + t.Fatal("edge was marked as zombie") + } - _, _, has, err = ctx.graph.HasChannelEdge(chanID2) + _, _, has, isZombie, err = ctx.graph.HasChannelEdge(chanID2) if err != nil { t.Fatalf("error looking for edge: %v", chanID2) } if has { t.Fatalf("found edge in graph") } - + if isZombie { + t.Fatal("reorged edge should not be marked as zombie") + } } // TestDisconnectedBlocks checks that the router handles a reorg happening when @@ -1755,21 +1766,27 @@ func TestDisconnectedBlocks(t *testing.T) { } // Check that the fundingTxs are in the graph db. - _, _, has, err := ctx.graph.HasChannelEdge(chanID1) + _, _, has, isZombie, err := ctx.graph.HasChannelEdge(chanID1) if err != nil { t.Fatalf("error looking for edge: %v", chanID1) } if !has { t.Fatalf("could not find edge in graph") } + if isZombie { + t.Fatal("edge was marked as zombie") + } - _, _, has, err = ctx.graph.HasChannelEdge(chanID2) + _, _, has, isZombie, err = ctx.graph.HasChannelEdge(chanID2) if err != nil { t.Fatalf("error looking for edge: %v", chanID2) } if !has { t.Fatalf("could not find edge in graph") } + if isZombie { + t.Fatal("edge was marked as zombie") + } // Create a 15 block fork. We first let the chainView notify the router // about stale blocks, before sending the now connected blocks. We do @@ -1796,22 +1813,27 @@ func TestDisconnectedBlocks(t *testing.T) { // chanID2 should not be in the database anymore, since it is not // confirmed on the longest chain. chanID1 should still be. - _, _, has, err = ctx.graph.HasChannelEdge(chanID1) + _, _, has, isZombie, err = ctx.graph.HasChannelEdge(chanID1) if err != nil { t.Fatalf("error looking for edge: %v", chanID1) } if !has { t.Fatalf("did not find edge in graph") } + if isZombie { + t.Fatal("edge was marked as zombie") + } - _, _, has, err = ctx.graph.HasChannelEdge(chanID2) + _, _, has, isZombie, err = ctx.graph.HasChannelEdge(chanID2) if err != nil { t.Fatalf("error looking for edge: %v", chanID2) } if has { t.Fatalf("found edge in graph") } - + if isZombie { + t.Fatal("reorged edge should not be marked as zombie") + } } // TestChansClosedOfflinePruneGraph tests that if channels we know of are @@ -1876,13 +1898,16 @@ func TestRouterChansClosedOfflinePruneGraph(t *testing.T) { } // The router should now be aware of the channel we created above. - _, _, hasChan, err := ctx.graph.HasChannelEdge(chanID1.ToUint64()) + _, _, hasChan, isZombie, err := ctx.graph.HasChannelEdge(chanID1.ToUint64()) if err != nil { t.Fatalf("error looking for edge: %v", chanID1) } if !hasChan { t.Fatalf("could not find edge in graph") } + if isZombie { + t.Fatal("edge was marked as zombie") + } // With the transaction included, and the router's database state // updated, we'll now mine 5 additional blocks on top of it. @@ -1957,13 +1982,16 @@ func TestRouterChansClosedOfflinePruneGraph(t *testing.T) { // At this point, the channel that was pruned should no longer be known // by the router. - _, _, hasChan, err = ctx.graph.HasChannelEdge(chanID1.ToUint64()) + _, _, hasChan, isZombie, err = ctx.graph.HasChannelEdge(chanID1.ToUint64()) if err != nil { t.Fatalf("error looking for edge: %v", chanID1) } if hasChan { t.Fatalf("channel was found in graph but shouldn't have been") } + if isZombie { + t.Fatal("closed channel should not be marked as zombie") + } } // TestFindPathFeeWeighting tests that the findPath method will properly prefer