diff --git a/channeldb/error.go b/channeldb/error.go index 859af9746..629cd93c6 100644 --- a/channeldb/error.go +++ b/channeldb/error.go @@ -43,6 +43,10 @@ var ( // created. ErrMetaNotFound = fmt.Errorf("unable to locate meta information") + // ErrClosedScidsNotFound is returned when the closed scid bucket + // hasn't been created. + ErrClosedScidsNotFound = fmt.Errorf("closed scid bucket doesn't exist") + // ErrGraphNotFound is returned when at least one of the components of // graph doesn't exist. ErrGraphNotFound = fmt.Errorf("graph bucket not initialized") diff --git a/channeldb/graph.go b/channeldb/graph.go index 464398b40..86cbe9aa9 100644 --- a/channeldb/graph.go +++ b/channeldb/graph.go @@ -153,6 +153,14 @@ var ( // case we'll remove all entries from the prune log with a block height // that no longer exists. pruneLogBucket = []byte("prune-log") + + // closedScidBucket is a top-level bucket that stores scids for + // channels that we know to be closed. This is used so that we don't + // need to perform expensive validation checks if we receive a channel + // announcement for the channel again. + // + // maps: scid -> []byte{} + closedScidBucket = []byte("closed-scid") ) const ( @@ -318,6 +326,7 @@ var graphTopLevelBuckets = [][]byte{ nodeBucket, edgeBucket, graphMetaBucket, + closedScidBucket, } // Wipe completely deletes all saved state within all used buckets within the @@ -3884,6 +3893,53 @@ func (c *ChannelGraph) NumZombies() (uint64, error) { return numZombies, nil } +// PutClosedScid stores a SCID for a closed channel in the database. This is so +// that we can ignore channel announcements that we know to be closed without +// having to validate them and fetch a block. +func (c *ChannelGraph) PutClosedScid(scid lnwire.ShortChannelID) error { + return kvdb.Update(c.db, func(tx kvdb.RwTx) error { + closedScids, err := tx.CreateTopLevelBucket(closedScidBucket) + if err != nil { + return err + } + + var k [8]byte + byteOrder.PutUint64(k[:], scid.ToUint64()) + + return closedScids.Put(k[:], []byte{}) + }, func() {}) +} + +// IsClosedScid checks whether a channel identified by the passed in scid is +// closed. This helps avoid having to perform expensive validation checks. +// TODO: Add an LRU cache to cut down on disc reads. +func (c *ChannelGraph) IsClosedScid(scid lnwire.ShortChannelID) (bool, error) { + var isClosed bool + err := kvdb.View(c.db, func(tx kvdb.RTx) error { + closedScids := tx.ReadBucket(closedScidBucket) + if closedScids == nil { + return ErrClosedScidsNotFound + } + + var k [8]byte + byteOrder.PutUint64(k[:], scid.ToUint64()) + + if closedScids.Get(k[:]) != nil { + isClosed = true + return nil + } + + return nil + }, func() { + isClosed = false + }) + if err != nil { + return false, err + } + + return isClosed, nil +} + func putLightningNode(nodeBucket kvdb.RwBucket, aliasBucket kvdb.RwBucket, // nolint:dupl updateIndex kvdb.RwBucket, node *LightningNode) error { diff --git a/channeldb/graph_test.go b/channeldb/graph_test.go index b05f3daaa..89197a0a8 100644 --- a/channeldb/graph_test.go +++ b/channeldb/graph_test.go @@ -4037,3 +4037,28 @@ func TestGraphLoading(t *testing.T) { graphReloaded.graphCache.nodeFeatures, ) } + +// TestClosedScid tests that we can correctly insert a SCID into the index of +// closed short channel ids. +func TestClosedScid(t *testing.T) { + t.Parallel() + + graph, err := MakeTestGraph(t) + require.Nil(t, err) + + scid := lnwire.ShortChannelID{} + + // The scid should not exist in the closedScidBucket. + exists, err := graph.IsClosedScid(scid) + require.Nil(t, err) + require.False(t, exists) + + // After we call PutClosedScid, the call to IsClosedScid should return + // true. + err = graph.PutClosedScid(scid) + require.Nil(t, err) + + exists, err = graph.IsClosedScid(scid) + require.Nil(t, err) + require.True(t, exists) +}