-
Notifications
You must be signed in to change notification settings - Fork 2.2k
graphdb: add caching for isPublicNode query #10363
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
a74ce81
31beedb
93c8948
0714ca4
d7b8c9b
0a47ac3
590351c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| //go:build test_db_postgres || test_db_sqlite | ||
|
|
||
| package graphdb | ||
|
|
||
| import ( | ||
| "testing" | ||
|
|
||
| "github.com/stretchr/testify/require" | ||
| ) | ||
|
|
||
| // TestNodeIsPublicCacheInvalidation ensures that we invalidate correctly our | ||
| // cache we use when determining if a node is public or not. | ||
| func TestNodeIsPublicCacheInvalidation(t *testing.T) { | ||
| t.Parallel() | ||
| ctx := t.Context() | ||
|
|
||
| graph := MakeTestGraph(t) | ||
|
|
||
| node1 := createTestVertex(t) | ||
| node2 := createTestVertex(t) | ||
|
|
||
| require.NoError(t, graph.AddNode(ctx, node1)) | ||
| require.NoError(t, graph.AddNode(ctx, node2)) | ||
|
|
||
| edge, _ := createEdge(10, 0, 0, 0, node1, node2) | ||
| require.NoError(t, graph.AddChannelEdge(ctx, &edge)) | ||
|
|
||
| // First IsPublic call should populate cache. | ||
| isPublic1, err := graph.IsPublicNode(node1.PubKeyBytes) | ||
| require.NoError(t, err) | ||
| require.True(t, isPublic1) | ||
|
|
||
| // Test invalidation scenarios: | ||
|
|
||
| // 1. DeleteChannelEdges: | ||
| // Above, the channel being public should be cached, but we expect that | ||
| // DeleteChannelEdge will invalidate the cache for both nodes else when | ||
| // we call IsPublic, we will hit the cache. | ||
| err = graph.DeleteChannelEdges(false, true, edge.ChannelID) | ||
| require.NoError(t, err) | ||
| isPublic1, err = graph.IsPublicNode(node1.PubKeyBytes) | ||
| require.NoError(t, err) | ||
| require.False(t, isPublic1) | ||
|
|
||
| isPublic2, err := graph.IsPublicNode(node2.PubKeyBytes) | ||
| require.NoError(t, err) | ||
| require.False(t, isPublic2) | ||
|
|
||
| // 2. AddChannelEdge: | ||
| // Now we know that the last `IsPublicNode` call above will cache our | ||
| // nodes with `isPublic` = false. But add a new channel edge should | ||
| // invalidate the cache such that when we call `IsPublic` it should | ||
| // return `True`. | ||
| edge2, _ := createEdge(10, 1, 0, 1, node1, node2) | ||
| require.NoError(t, graph.AddChannelEdge(ctx, &edge2)) | ||
| isPublic1, err = graph.IsPublicNode(node1.PubKeyBytes) | ||
| require.NoError(t, err) | ||
| require.True(t, isPublic1) | ||
|
|
||
| isPublic2, err = graph.IsPublicNode(node2.PubKeyBytes) | ||
| require.NoError(t, err) | ||
| require.True(t, isPublic2) | ||
|
|
||
| // 3. DeleteNode: | ||
| // Again, the last two sets of `IsPublic` should have cached our nodes | ||
| // as `True`. Now we can delete a node and expect the next call to be | ||
| // False. | ||
| // | ||
| // NOTE: We don't get an error calling `IsPublicNode` because of how the | ||
| // SQL query is implemented to check for the existence of public nodes. | ||
| require.NoError(t, graph.DeleteNode(ctx, node1.PubKeyBytes)) | ||
| isPublic1, err = graph.IsPublicNode(node1.PubKeyBytes) | ||
| require.NoError(t, err) | ||
| require.False(t, isPublic1) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,6 +21,8 @@ import ( | |
| "github.com/btcsuite/btcd/btcutil" | ||
| "github.com/btcsuite/btcd/chaincfg/chainhash" | ||
| "github.com/btcsuite/btcd/wire" | ||
| "github.com/lightninglabs/neutrino/cache" | ||
| "github.com/lightninglabs/neutrino/cache/lru" | ||
| "github.com/lightningnetwork/lnd/aliasmgr" | ||
| "github.com/lightningnetwork/lnd/batch" | ||
| "github.com/lightningnetwork/lnd/fn/v2" | ||
|
|
@@ -176,13 +178,27 @@ type SQLStore struct { | |
| rejectCache *rejectCache | ||
| chanCache *channelCache | ||
|
|
||
| publicNodeCache *lru.Cache[[33]byte, *cachedPublicNode] | ||
|
|
||
| chanScheduler batch.Scheduler[SQLQueries] | ||
| nodeScheduler batch.Scheduler[SQLQueries] | ||
|
|
||
| srcNodes map[lnwire.GossipVersion]*srcNodeInfo | ||
| srcNodeMu sync.Mutex | ||
| } | ||
|
|
||
Abdulkbk marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| // cachedPublicNode is a simple wrapper for a boolean value that can be | ||
| // stored in an LRU cache. The LRU cache requires a Size() method. | ||
| type cachedPublicNode struct { | ||
| isPublic bool | ||
| } | ||
|
|
||
| // Size returns the size of the cache entry. We return 1 as we just want to | ||
| // limit the number of entries rather than their actual memory size. | ||
| func (c *cachedPublicNode) Size() (uint64, error) { | ||
| return 1, nil | ||
| } | ||
|
|
||
| // A compile-time assertion to ensure that SQLStore implements the V1Store | ||
| // interface. | ||
| var _ V1Store = (*SQLStore)(nil) | ||
|
|
@@ -217,7 +233,10 @@ func NewSQLStore(cfg *SQLStoreConfig, db BatchedSQLQueries, | |
| db: db, | ||
| rejectCache: newRejectCache(opts.RejectCacheSize), | ||
| chanCache: newChannelCache(opts.ChannelCacheSize), | ||
| srcNodes: make(map[lnwire.GossipVersion]*srcNodeInfo), | ||
| publicNodeCache: lru.NewCache[[33]byte, *cachedPublicNode]( | ||
| uint64(opts.PublicNodeCacheSize), | ||
| ), | ||
| srcNodes: make(map[lnwire.GossipVersion]*srcNodeInfo), | ||
| } | ||
|
|
||
| s.chanScheduler = batch.NewTimeScheduler( | ||
|
|
@@ -404,6 +423,8 @@ func (s *SQLStore) DeleteNode(ctx context.Context, | |
| return fmt.Errorf("unable to delete node: %w", err) | ||
| } | ||
|
|
||
| s.removePublicNodeCache(pubKey) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. even here, I am acutally not sure if we should remove it from the cache, its an LRU cache so it cycles unused values out, so we still might get some infos to this node if the gossip is delayed ?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't we be as cautious as possible? Let's assume initially the node is public. After a |
||
|
|
||
| return nil | ||
| } | ||
|
|
||
|
|
@@ -714,6 +735,10 @@ func (s *SQLStore) AddChannelEdge(ctx context.Context, | |
| default: | ||
| s.rejectCache.remove(edge.ChannelID) | ||
| s.chanCache.remove(edge.ChannelID) | ||
| s.removePublicNodeCache( | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think we can remove the edge here just because we fail to add the edge, we are dealing with nodes here not channels. I don't acutally think we need to delte them here.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree this is unnecessary. |
||
| edge.NodeKey1Bytes, edge.NodeKey2Bytes, | ||
| ) | ||
|
|
||
| return nil | ||
| } | ||
| }, | ||
|
|
@@ -1730,6 +1755,7 @@ func (s *SQLStore) MarkEdgeZombie(chanID uint64, | |
|
|
||
| s.rejectCache.remove(chanID) | ||
| s.chanCache.remove(chanID) | ||
| s.removePublicNodeCache(pubKey1, pubKey2) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same here, we cannot do this here, they can still have other channels
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure. At the
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the node was ever public the train is passed and we should keep it in the cache. A node either remains private the entire time or remains private the entire time. It does not really sense to switch from public to private. |
||
|
|
||
| return nil | ||
| } | ||
|
|
@@ -1957,6 +1983,14 @@ func (s *SQLStore) DeleteChannelEdges(strictZombiePruning, markZombie bool, | |
| s.chanCache.remove(chanID) | ||
| } | ||
|
|
||
| var pubkeys [][33]byte | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. no need to delete here, its an LRU cache, if a node was public we don't bother because the pubkey already was annoucned. |
||
| for _, edge := range edges { | ||
| pubkeys = append( | ||
| pubkeys, edge.NodeKey1Bytes, edge.NodeKey2Bytes, | ||
| ) | ||
| } | ||
| s.removePublicNodeCache(pubkeys...) | ||
|
|
||
| return edges, nil | ||
| } | ||
|
|
||
|
|
@@ -2292,8 +2326,19 @@ func (s *SQLStore) ChannelID(chanPoint *wire.OutPoint) (uint64, error) { | |
| func (s *SQLStore) IsPublicNode(pubKey [33]byte) (bool, error) { | ||
| ctx := context.TODO() | ||
|
|
||
| // Check the cache first and return early if there is a hit. | ||
| cached, err := s.publicNodeCache.Get(pubKey) | ||
| if err == nil && cached != nil { | ||
| return cached.isPublic, nil | ||
| } | ||
|
|
||
| // Log any error other than NotFound. | ||
| if err != nil && !errors.Is(err, cache.ErrElementNotFound) { | ||
| log.Warnf("Unable to check cache if node is public: %v", err) | ||
| } | ||
|
|
||
| var isPublic bool | ||
| err := s.db.ExecTx(ctx, sqldb.ReadTxOpt(), func(db SQLQueries) error { | ||
| err = s.db.ExecTx(ctx, sqldb.ReadTxOpt(), func(db SQLQueries) error { | ||
| var err error | ||
| isPublic, err = db.IsPublicV1Node(ctx, pubKey[:]) | ||
|
|
||
|
|
@@ -2304,6 +2349,14 @@ func (s *SQLStore) IsPublicNode(pubKey [33]byte) (bool, error) { | |
| "public: %w", err) | ||
| } | ||
|
|
||
| // Store the result in cache. | ||
| _, err = s.publicNodeCache.Put(pubKey, &cachedPublicNode{ | ||
| isPublic: isPublic, | ||
| }) | ||
| if err != nil { | ||
| log.Warnf("Unable to store node info in cache: %v", err) | ||
| } | ||
|
|
||
| return isPublic, nil | ||
| } | ||
|
|
||
|
|
@@ -2655,6 +2708,9 @@ func (s *SQLStore) PruneGraph(spentOutputs []*wire.OutPoint, | |
| for _, channel := range closedChans { | ||
| s.rejectCache.remove(channel.ChannelID) | ||
| s.chanCache.remove(channel.ChannelID) | ||
| s.removePublicNodeCache( | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not able to delete |
||
| channel.NodeKey1Bytes, channel.NodeKey2Bytes, | ||
| ) | ||
| } | ||
|
|
||
| return closedChans, prunedNodes, nil | ||
|
|
@@ -2923,6 +2979,9 @@ func (s *SQLStore) DisconnectBlockAtHeight(height uint32) ( | |
| for _, channel := range removedChans { | ||
| s.rejectCache.remove(channel.ChannelID) | ||
| s.chanCache.remove(channel.ChannelID) | ||
| s.removePublicNodeCache( | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same here, cannot b e deleted |
||
| channel.NodeKey1Bytes, channel.NodeKey2Bytes, | ||
| ) | ||
| } | ||
Abdulkbk marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| s.cacheMu.Unlock() | ||
|
|
||
|
|
@@ -5869,3 +5928,14 @@ func handleZombieMarking(ctx context.Context, db SQLQueries, | |
| }, | ||
| ) | ||
| } | ||
|
|
||
| // removePublicNodeCache takes in a list of public keys and removes the | ||
| // corresponding nodes info from the cache if it exists. | ||
| // | ||
| // NOTE: This can safely be called without holding a lock since the lru is | ||
| // thread safe. | ||
| func (s *SQLStore) removePublicNodeCache(pubkeys ...[33]byte) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think we need this if we remove all the callsites |
||
| for _, pubkey := range pubkeys { | ||
| s.publicNodeCache.Delete(pubkey) | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.