2024-10-22 13:35:23 +02:00
|
|
|
package graphdb
|
2021-09-21 19:18:20 +02:00
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"encoding/hex"
|
|
|
|
|
"testing"
|
|
|
|
|
|
2025-06-04 12:50:31 +02:00
|
|
|
"github.com/lightningnetwork/lnd/fn/v2"
|
2024-10-22 12:57:00 +02:00
|
|
|
"github.com/lightningnetwork/lnd/graph/db/models"
|
2021-09-21 19:18:20 +02:00
|
|
|
"github.com/lightningnetwork/lnd/lnwire"
|
|
|
|
|
"github.com/lightningnetwork/lnd/routing/route"
|
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
var (
|
|
|
|
|
pubKey1Bytes, _ = hex.DecodeString(
|
|
|
|
|
"0248f5cba4c6da2e4c9e01e81d1404dfac0cbaf3ee934a4fc117d2ea9a64" +
|
|
|
|
|
"22c91d",
|
|
|
|
|
)
|
|
|
|
|
pubKey2Bytes, _ = hex.DecodeString(
|
|
|
|
|
"038155ba86a8d3b23c806c855097ca5c9fa0f87621f1e7a7d2835ad057f6" +
|
|
|
|
|
"f4484f",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
pubKey1, _ = route.NewVertexFromBytes(pubKey1Bytes)
|
|
|
|
|
pubKey2, _ = route.NewVertexFromBytes(pubKey2Bytes)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// TestGraphCacheAddNode tests that a channel going from node A to node B can be
|
|
|
|
|
// cached correctly, independent of the direction we add the channel as.
|
|
|
|
|
func TestGraphCacheAddNode(t *testing.T) {
|
2021-11-02 20:11:55 -07:00
|
|
|
t.Parallel()
|
|
|
|
|
|
2021-09-21 19:18:20 +02:00
|
|
|
runTest := func(nodeA, nodeB route.Vertex) {
|
|
|
|
|
t.Helper()
|
|
|
|
|
|
2021-09-21 19:18:22 +02:00
|
|
|
channelFlagA, channelFlagB := 0, 1
|
|
|
|
|
if nodeA == pubKey2 {
|
|
|
|
|
channelFlagA, channelFlagB = 1, 0
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-04 12:50:31 +02:00
|
|
|
inboundFee := lnwire.Fee{
|
|
|
|
|
BaseFee: 10,
|
|
|
|
|
FeeRate: 20,
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-09 12:03:42 +02:00
|
|
|
outPolicy1 := &models.CachedEdgePolicy{
|
2021-09-21 19:18:20 +02:00
|
|
|
ChannelID: 1000,
|
2021-09-21 19:18:22 +02:00
|
|
|
ChannelFlags: lnwire.ChanUpdateChanFlags(channelFlagA),
|
2025-06-09 12:03:42 +02:00
|
|
|
ToNodePubKey: func() route.Vertex {
|
|
|
|
|
return nodeB
|
|
|
|
|
},
|
2022-07-05 15:01:18 +02:00
|
|
|
// Define an inbound fee.
|
2025-06-04 12:50:31 +02:00
|
|
|
InboundFee: fn.Some(inboundFee),
|
2021-09-21 19:18:20 +02:00
|
|
|
}
|
2025-06-09 12:03:42 +02:00
|
|
|
inPolicy1 := &models.CachedEdgePolicy{
|
2021-09-21 19:18:20 +02:00
|
|
|
ChannelID: 1000,
|
2021-09-21 19:18:22 +02:00
|
|
|
ChannelFlags: lnwire.ChanUpdateChanFlags(channelFlagB),
|
2025-06-09 12:03:42 +02:00
|
|
|
ToNodePubKey: func() route.Vertex {
|
|
|
|
|
return nodeA
|
|
|
|
|
},
|
2021-09-21 19:18:20 +02:00
|
|
|
}
|
2021-09-21 19:18:25 +02:00
|
|
|
cache := NewGraphCache(10)
|
2025-02-10 17:08:08 +02:00
|
|
|
cache.AddNodeFeatures(nodeA, lnwire.EmptyFeatureVector())
|
2025-06-09 12:08:18 +02:00
|
|
|
cache.AddChannel(&models.CachedEdgeInfo{
|
graph/db: remove unnecessary AddNode method on GraphCache
The AddNode method on the GraphCache calls `AddNodeFeatures` underneath
and then iterates through all the node's persisted channels and adds
them to the cache too via `AddChannel`.
This is, however, not required since at the time the cache is populated
in `NewChannelGraph`, the cache is populated will all persisted nodes
and all persisted channels. Then, once any new channels come in, via
`AddChannelEdge`, they are added to the cache via AddChannel. If any new
nodes come in via `AddLightningNode`, then currently the cache's AddNode
method is called which the both adds the node and again iterates through
all persisted channels and re-adds them to the cache. This is definitely
redundent since the initial cache population and updates via
AddChannelEdge should keep the cache fresh in terms of channels.
So we remove this for 2 reasons: 1) to remove the redundent DB calls and
2) this requires a kvdb.RTx to be passed in to the GraphCache calls
which will make it hard to extract the cache out of the CRUD layer
and be used more generally.
The AddNode method made sense when the cache was first added in the
code-base
[here](https://github.com/lightningnetwork/lnd/commit/369c09be6152b76a24915050a8a5fe6bccf2b8f0#diff-ae36bdb6670644d20c4e43f3a0ed47f71886c2bcdf3cc2937de24315da5dc072R213)
since then during graph cache population, nodes and channels would be
added to the cache in a single DB transaction. This was, however,
changed [later
on](https://github.com/lightningnetwork/lnd/commit/352008a0c22ffd9e97b9481ffa44dbc50fca2ddc)
to be done in 2 separate DB calls for efficiency reasons.
2025-02-10 16:29:00 +02:00
|
|
|
ChannelID: 1000,
|
|
|
|
|
// Those are direction independent!
|
|
|
|
|
NodeKey1Bytes: pubKey1,
|
|
|
|
|
NodeKey2Bytes: pubKey2,
|
|
|
|
|
Capacity: 500,
|
|
|
|
|
}, outPolicy1, inPolicy1)
|
2021-09-21 19:18:20 +02:00
|
|
|
|
2021-09-21 19:18:22 +02:00
|
|
|
var fromChannels, toChannels []*DirectedChannel
|
|
|
|
|
_ = cache.ForEachChannel(nodeA, func(c *DirectedChannel) error {
|
|
|
|
|
fromChannels = append(fromChannels, c)
|
|
|
|
|
return nil
|
|
|
|
|
})
|
|
|
|
|
_ = cache.ForEachChannel(nodeB, func(c *DirectedChannel) error {
|
|
|
|
|
toChannels = append(toChannels, c)
|
|
|
|
|
return nil
|
|
|
|
|
})
|
2021-09-21 19:18:20 +02:00
|
|
|
|
|
|
|
|
require.Len(t, fromChannels, 1)
|
|
|
|
|
require.Len(t, toChannels, 1)
|
|
|
|
|
|
2021-09-21 19:18:22 +02:00
|
|
|
require.Equal(t, outPolicy1 != nil, fromChannels[0].OutPolicySet)
|
|
|
|
|
assertCachedPolicyEqual(t, inPolicy1, fromChannels[0].InPolicy)
|
2021-09-21 19:18:20 +02:00
|
|
|
|
2021-09-21 19:18:22 +02:00
|
|
|
require.Equal(t, inPolicy1 != nil, toChannels[0].OutPolicySet)
|
|
|
|
|
assertCachedPolicyEqual(t, outPolicy1, toChannels[0].InPolicy)
|
2021-10-19 16:04:23 -07:00
|
|
|
|
|
|
|
|
// Now that we've inserted two nodes into the graph, check that
|
2025-02-05 12:18:11 +02:00
|
|
|
// we'll recover the same set of channels during forEachNode.
|
2021-10-19 16:04:23 -07:00
|
|
|
nodes := make(map[route.Vertex]struct{})
|
|
|
|
|
chans := make(map[uint64]struct{})
|
|
|
|
|
_ = cache.ForEachNode(func(node route.Vertex,
|
|
|
|
|
edges map[uint64]*DirectedChannel) error {
|
|
|
|
|
|
|
|
|
|
nodes[node] = struct{}{}
|
2022-07-05 15:01:18 +02:00
|
|
|
for chanID, directedChannel := range edges {
|
2021-10-19 16:04:23 -07:00
|
|
|
chans[chanID] = struct{}{}
|
2022-07-05 15:01:18 +02:00
|
|
|
|
|
|
|
|
if node == nodeA {
|
|
|
|
|
require.NotZero(
|
|
|
|
|
t, directedChannel.InboundFee,
|
|
|
|
|
)
|
|
|
|
|
} else {
|
|
|
|
|
require.Zero(
|
|
|
|
|
t, directedChannel.InboundFee,
|
|
|
|
|
)
|
|
|
|
|
}
|
2021-10-19 16:04:23 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
require.Len(t, nodes, 2)
|
|
|
|
|
require.Len(t, chans, 1)
|
2021-09-21 19:18:20 +02:00
|
|
|
}
|
2021-09-21 19:18:22 +02:00
|
|
|
|
2021-09-21 19:18:20 +02:00
|
|
|
runTest(pubKey1, pubKey2)
|
|
|
|
|
runTest(pubKey2, pubKey1)
|
|
|
|
|
}
|
2021-09-21 19:18:22 +02:00
|
|
|
|
2025-06-09 12:03:42 +02:00
|
|
|
func assertCachedPolicyEqual(t *testing.T, original,
|
2023-11-08 11:18:45 +02:00
|
|
|
cached *models.CachedEdgePolicy) {
|
2021-09-21 19:18:22 +02:00
|
|
|
|
|
|
|
|
require.Equal(t, original.ChannelID, cached.ChannelID)
|
|
|
|
|
require.Equal(t, original.MessageFlags, cached.MessageFlags)
|
|
|
|
|
require.Equal(t, original.ChannelFlags, cached.ChannelFlags)
|
|
|
|
|
require.Equal(t, original.TimeLockDelta, cached.TimeLockDelta)
|
|
|
|
|
require.Equal(t, original.MinHTLC, cached.MinHTLC)
|
|
|
|
|
require.Equal(t, original.MaxHTLC, cached.MaxHTLC)
|
|
|
|
|
require.Equal(t, original.FeeBaseMSat, cached.FeeBaseMSat)
|
|
|
|
|
require.Equal(
|
|
|
|
|
t, original.FeeProportionalMillionths,
|
|
|
|
|
cached.FeeProportionalMillionths,
|
|
|
|
|
)
|
2025-06-09 12:03:42 +02:00
|
|
|
if original.ToNodePubKey != nil {
|
|
|
|
|
require.Equal(t, original.ToNodePubKey(), cached.ToNodePubKey())
|
|
|
|
|
}
|
2021-09-21 19:18:22 +02:00
|
|
|
}
|
2025-11-19 10:32:04 +01:00
|
|
|
|
|
|
|
|
// TestGraphCacheDisabledPoliciesRegression is a regression test for the bug
|
|
|
|
|
// where channels with both policies disabled were not added to the graph cache
|
|
|
|
|
// during population, preventing future policy updates from working.
|
|
|
|
|
//
|
|
|
|
|
// The bug flow was:
|
|
|
|
|
// 1. Channel with both policies disabled exists in DB.
|
|
|
|
|
// 2. populateCache skips adding it to graph cache entirely.
|
|
|
|
|
// 3. Later, a policy update arrives enabling one direction.
|
|
|
|
|
// 4. UpdateEdgePolicy updates the DB successfully.
|
|
|
|
|
// 5. UpdateEdgePolicy tries to update graph cache but channel not found.
|
|
|
|
|
// 6. Channel never becomes usable for routing.
|
|
|
|
|
func TestGraphCacheDisabledPoliciesRegression(t *testing.T) {
|
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
|
|
// Create a simple cache instance.
|
|
|
|
|
cache := NewGraphCache(10)
|
|
|
|
|
|
|
|
|
|
// Simulate a channel with both policies disabled.
|
|
|
|
|
chanID := uint64(12345)
|
|
|
|
|
node1 := pubKey1
|
|
|
|
|
node2 := pubKey2
|
|
|
|
|
|
|
|
|
|
edgeInfo := &models.CachedEdgeInfo{
|
|
|
|
|
ChannelID: chanID,
|
|
|
|
|
NodeKey1Bytes: node1,
|
|
|
|
|
NodeKey2Bytes: node2,
|
|
|
|
|
Capacity: 1000000,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Create two disabled policies.
|
|
|
|
|
disabledPolicy1 := &models.CachedEdgePolicy{
|
|
|
|
|
ChannelID: chanID,
|
|
|
|
|
ChannelFlags: lnwire.ChanUpdateDisabled,
|
|
|
|
|
}
|
|
|
|
|
disabledPolicy2 := &models.CachedEdgePolicy{
|
|
|
|
|
ChannelID: chanID,
|
|
|
|
|
ChannelFlags: lnwire.ChanUpdateDisabled |
|
|
|
|
|
lnwire.ChanUpdateDirection,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Add the channel with both policies disabled (simulating
|
|
|
|
|
// populateCache).
|
|
|
|
|
cache.AddChannel(edgeInfo, disabledPolicy1, disabledPolicy2)
|
|
|
|
|
|
|
|
|
|
// Verify the channel structure was added to cache.
|
|
|
|
|
var foundChannels []*DirectedChannel
|
|
|
|
|
err := cache.ForEachChannel(node1, func(c *DirectedChannel) error {
|
|
|
|
|
if c.ChannelID == chanID {
|
|
|
|
|
foundChannels = append(foundChannels, c)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
})
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
require.Len(t, foundChannels, 1,
|
|
|
|
|
"channel structure should be in cache even when both "+
|
|
|
|
|
"policies are disabled")
|
|
|
|
|
|
|
|
|
|
// Verify policies were NOT added (both disabled).
|
|
|
|
|
require.False(t, foundChannels[0].OutPolicySet,
|
|
|
|
|
"disabled outgoing policy should not be set in cache")
|
|
|
|
|
require.Nil(t, foundChannels[0].InPolicy,
|
|
|
|
|
"disabled incoming policy should not be set in cache")
|
|
|
|
|
|
|
|
|
|
// Now simulate receiving a fresh update enabling one direction.
|
|
|
|
|
enabledPolicy1 := &models.CachedEdgePolicy{
|
|
|
|
|
ChannelID: chanID,
|
|
|
|
|
ChannelFlags: 0, // NOT disabled anymore
|
|
|
|
|
TimeLockDelta: 40,
|
|
|
|
|
MinHTLC: lnwire.MilliSatoshi(1000),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update the policy (simulating what UpdateEdgePolicy does).
|
|
|
|
|
cache.UpdatePolicy(enabledPolicy1, node1, node2)
|
|
|
|
|
|
|
|
|
|
// Verify the policy update succeeded. Before the fix, UpdatePolicy
|
|
|
|
|
// would log "Channel not found in graph cache" and return early,
|
|
|
|
|
// so the policy would never be added.
|
|
|
|
|
foundChannels = nil
|
|
|
|
|
err = cache.ForEachChannel(node1, func(c *DirectedChannel) error {
|
|
|
|
|
if c.ChannelID == chanID {
|
|
|
|
|
foundChannels = append(foundChannels, c)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
})
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
require.Len(t, foundChannels, 1)
|
|
|
|
|
|
|
|
|
|
// The policy should now be set.
|
|
|
|
|
require.True(t, foundChannels[0].OutPolicySet,
|
|
|
|
|
"REGRESSION: policy update should work even for channels that "+
|
|
|
|
|
"had both policies disabled initially")
|
|
|
|
|
|
|
|
|
|
// Verify we can also see it from node2's perspective.
|
|
|
|
|
foundChannels = nil
|
|
|
|
|
err = cache.ForEachChannel(node2, func(c *DirectedChannel) error {
|
|
|
|
|
if c.ChannelID == chanID {
|
|
|
|
|
foundChannels = append(foundChannels, c)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
})
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
require.Len(t, foundChannels, 1)
|
|
|
|
|
require.NotNil(t, foundChannels[0].InPolicy,
|
|
|
|
|
"incoming policy should be set after policy update")
|
|
|
|
|
require.Equal(t, uint16(40), foundChannels[0].InPolicy.TimeLockDelta)
|
|
|
|
|
}
|