channeldb+routing: extend edge lookup methods with zombie index check

In this commit, we extend the graph's FetchChannelEdgesByID and
HasChannelEdge methods to also check the zombie index whenever the edge
to be looked up doesn't exist within the edge index. We do this to
signal to callers that the edge is known, but only as a zombie, and the
only information that we have about the edge are the node public keys of
the two parties involved in the edge.

In the event that an edge does exist within the zombie index, we make
an additional check on edge policies to ensure they are not within the
router's pruning window, indicating that it is a fresh update.
This commit is contained in:
Wilmer Paulino 2019-03-27 13:06:57 -07:00
parent e98f4d6d9d
commit c82d73a826
No known key found for this signature in database
GPG Key ID: 6DF57B9F9514972F
5 changed files with 166 additions and 43 deletions

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

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

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

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

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