package localchans import ( "encoding/hex" "testing" "time" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/discovery" "github.com/lightningnetwork/lnd/graph/db/models" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/routing" "github.com/stretchr/testify/require" ) // TestManager tests that the local channel manager properly propagates fee // updates to gossiper and links. func TestManager(t *testing.T) { t.Parallel() type channel struct { edgeInfo *models.ChannelEdgeInfo } var ( chanPointValid = wire.OutPoint{Hash: chainhash.Hash{1}, Index: 2} chanCap = btcutil.Amount(1000) chanPointMissing = wire.OutPoint{Hash: chainhash.Hash{2}, Index: 2} maxPendingAmount = lnwire.MilliSatoshi(999000) minHTLC = lnwire.MilliSatoshi(2000) expectedNumUpdates int channelSet []channel ) sp := [33]byte{} _, _ = hex.Decode(sp[:], []byte("028d7500dd4c12685d1f568b4c2b5048e85"+ "34b873319f3a8daa612b469132ec7f7")) rp := [33]byte{} _, _ = hex.Decode(rp[:], []byte("034f355bdcb7cc0af728ef3cceb9615d906"+ "84bb5b2ca5f859ab0f0b704075871aa")) selfpub, _ := btcec.ParsePubKey(sp[:]) remotepub, _ := btcec.ParsePubKey(rp[:]) localMultisigPrivKey, _ := btcec.NewPrivateKey() localMultisigKey := localMultisigPrivKey.PubKey() remoteMultisigPrivKey, _ := btcec.NewPrivateKey() remoteMultisigKey := remoteMultisigPrivKey.PubKey() newPolicy := routing.ChannelPolicy{ FeeSchema: routing.FeeSchema{ BaseFee: 100, FeeRate: 200, }, TimeLockDelta: 80, MaxHTLC: 5000, } currentPolicy := models.ChannelEdgePolicy{ MinHTLC: minHTLC, MessageFlags: lnwire.ChanUpdateRequiredMaxHtlc, } updateForwardingPolicies := func( chanPolicies map[wire.OutPoint]models.ForwardingPolicy) { if len(chanPolicies) == 0 { return } if len(chanPolicies) != 1 { t.Fatal("unexpected number of policies to apply") } policy := chanPolicies[chanPointValid] if policy.TimeLockDelta != newPolicy.TimeLockDelta { t.Fatal("unexpected time lock delta") } if policy.BaseFee != newPolicy.BaseFee { t.Fatal("unexpected base fee") } if uint32(policy.FeeRate) != newPolicy.FeeRate { t.Fatal("unexpected base fee") } if policy.MaxHTLC != newPolicy.MaxHTLC { t.Fatal("unexpected max htlc") } } propagateChanPolicyUpdate := func( edgesToUpdate []discovery.EdgeWithInfo) error { if len(edgesToUpdate) != expectedNumUpdates { t.Fatalf("unexpected number of updates,"+ " expected %d got %d", expectedNumUpdates, len(edgesToUpdate)) } for _, edge := range edgesToUpdate { policy := edge.Edge if !policy.MessageFlags.HasMaxHtlc() { t.Fatal("expected max htlc flag") } if policy.TimeLockDelta != uint16(newPolicy.TimeLockDelta) { t.Fatal("unexpected time lock delta") } if policy.FeeBaseMSat != newPolicy.BaseFee { t.Fatal("unexpected base fee") } if uint32(policy.FeeProportionalMillionths) != newPolicy.FeeRate { t.Fatal("unexpected base fee") } if policy.MaxHTLC != newPolicy.MaxHTLC { t.Fatal("unexpected max htlc") } } return nil } forAllOutgoingChannels := func(cb func(*models.ChannelEdgeInfo, *models.ChannelEdgePolicy) error) error { for _, c := range channelSet { if err := cb(c.edgeInfo, ¤tPolicy); err != nil { return err } } return nil } fetchChannel := func(chanPoint wire.OutPoint) (*channeldb.OpenChannel, error) { if chanPoint == chanPointMissing { return &channeldb.OpenChannel{}, channeldb.ErrChannelNotFound } bounds := channeldb.ChannelStateBounds{ MaxPendingAmount: maxPendingAmount, MinHTLC: minHTLC, } return &channeldb.OpenChannel{ FundingOutpoint: chanPointValid, IdentityPub: remotepub, LocalChanCfg: channeldb.ChannelConfig{ ChannelStateBounds: bounds, MultiSigKey: keychain.KeyDescriptor{ PubKey: localMultisigKey, }, }, RemoteChanCfg: channeldb.ChannelConfig{ ChannelStateBounds: bounds, MultiSigKey: keychain.KeyDescriptor{ PubKey: remoteMultisigKey, }, }, }, nil } addEdge := func(edge *models.ChannelEdgeInfo) error { return nil } manager := Manager{ UpdateForwardingPolicies: updateForwardingPolicies, PropagateChanPolicyUpdate: propagateChanPolicyUpdate, ForAllOutgoingChannels: forAllOutgoingChannels, FetchChannel: fetchChannel, SelfPub: selfpub, DefaultRoutingPolicy: models.ForwardingPolicy{ MinHTLCOut: minHTLC, MaxHTLC: maxPendingAmount, BaseFee: lnwire.MilliSatoshi(1000), FeeRate: lnwire.MilliSatoshi(0), InboundFee: models.InboundFee{}, TimeLockDelta: 80, }, AddEdge: addEdge, } // Policy with no max htlc value. MaxHTLCPolicy := currentPolicy MaxHTLCPolicy.MaxHTLC = newPolicy.MaxHTLC noMaxHtlcPolicy := newPolicy noMaxHtlcPolicy.MaxHTLC = 0 tests := []struct { name string currentPolicy models.ChannelEdgePolicy newPolicy routing.ChannelPolicy channelSet []channel specifiedChanPoints []wire.OutPoint createMissingEdge bool expectedNumUpdates int expectedUpdateFailures []lnrpc.UpdateFailure expectErr error }{ { name: "valid channel", currentPolicy: currentPolicy, newPolicy: newPolicy, channelSet: []channel{ { edgeInfo: &models.ChannelEdgeInfo{ Capacity: chanCap, ChannelPoint: chanPointValid, }, }, }, specifiedChanPoints: []wire.OutPoint{chanPointValid}, createMissingEdge: false, expectedNumUpdates: 1, expectedUpdateFailures: []lnrpc.UpdateFailure{}, expectErr: nil, }, { name: "all channels", currentPolicy: currentPolicy, newPolicy: newPolicy, channelSet: []channel{ { edgeInfo: &models.ChannelEdgeInfo{ Capacity: chanCap, ChannelPoint: chanPointValid, }, }, }, specifiedChanPoints: []wire.OutPoint{}, createMissingEdge: false, expectedNumUpdates: 1, expectedUpdateFailures: []lnrpc.UpdateFailure{}, expectErr: nil, }, { name: "missing channel", currentPolicy: currentPolicy, newPolicy: newPolicy, channelSet: []channel{ { edgeInfo: &models.ChannelEdgeInfo{ Capacity: chanCap, ChannelPoint: chanPointValid, }, }, }, specifiedChanPoints: []wire.OutPoint{chanPointMissing}, createMissingEdge: false, expectedNumUpdates: 0, expectedUpdateFailures: []lnrpc.UpdateFailure{ lnrpc.UpdateFailure_UPDATE_FAILURE_NOT_FOUND, }, expectErr: nil, }, { // Here, no max htlc is specified, the max htlc value // should be kept unchanged. name: "no max htlc specified", currentPolicy: MaxHTLCPolicy, newPolicy: noMaxHtlcPolicy, channelSet: []channel{ { edgeInfo: &models.ChannelEdgeInfo{ Capacity: chanCap, ChannelPoint: chanPointValid, }, }, }, specifiedChanPoints: []wire.OutPoint{chanPointValid}, createMissingEdge: false, expectedNumUpdates: 1, expectedUpdateFailures: []lnrpc.UpdateFailure{}, expectErr: nil, }, { // Here, the edge is missing, causing the edge to be // recreated. name: "missing edge recreated", currentPolicy: models.ChannelEdgePolicy{}, newPolicy: newPolicy, channelSet: []channel{}, specifiedChanPoints: []wire.OutPoint{chanPointValid}, createMissingEdge: true, expectedNumUpdates: 1, expectedUpdateFailures: []lnrpc.UpdateFailure{}, expectErr: nil, }, { // Here, the edge is missing, but the edge will not be // recreated, because createMissingEdge is false. name: "missing edge ignored", currentPolicy: models.ChannelEdgePolicy{}, newPolicy: newPolicy, channelSet: []channel{}, specifiedChanPoints: []wire.OutPoint{chanPointValid}, createMissingEdge: false, expectedNumUpdates: 0, expectedUpdateFailures: []lnrpc.UpdateFailure{ lnrpc.UpdateFailure_UPDATE_FAILURE_UNKNOWN, }, expectErr: nil, }, } for _, test := range tests { test := test t.Run(test.name, func(t *testing.T) { currentPolicy = test.currentPolicy channelSet = test.channelSet expectedNumUpdates = test.expectedNumUpdates failedUpdates, err := manager.UpdatePolicy(test.newPolicy, test.createMissingEdge, test.specifiedChanPoints...) if len(failedUpdates) != len(test.expectedUpdateFailures) { t.Fatalf("wrong number of failed policy updates") } if len(test.expectedUpdateFailures) > 0 { if failedUpdates[0].Reason != test.expectedUpdateFailures[0] { t.Fatalf("wrong expected policy update failure") } } require.Equal(t, test.expectErr, err) }) } } // Tests creating a new edge in the manager where the local pubkey is the // lexicographically lesser or the two. func TestCreateEdgeLower(t *testing.T) { sp := [33]byte{} _, _ = hex.Decode(sp[:], []byte("028d7500dd4c12685d1f568b4c2b5048e85"+ "34b873319f3a8daa612b469132ec7f7")) rp := [33]byte{} _, _ = hex.Decode(rp[:], []byte("034f355bdcb7cc0af728ef3cceb9615d906"+ "84bb5b2ca5f859ab0f0b704075871aa")) selfpub, _ := btcec.ParsePubKey(sp[:]) remotepub, _ := btcec.ParsePubKey(rp[:]) localMultisigPrivKey, _ := btcec.NewPrivateKey() localMultisigKey := localMultisigPrivKey.PubKey() remoteMultisigPrivKey, _ := btcec.NewPrivateKey() remoteMultisigKey := remoteMultisigPrivKey.PubKey() timestamp := time.Now() defaultPolicy := models.ForwardingPolicy{ MinHTLCOut: 1, MaxHTLC: 2, BaseFee: 3, FeeRate: 4, InboundFee: models.InboundFee{ Base: 5, Rate: 6, }, TimeLockDelta: 7, } channel := &channeldb.OpenChannel{ IdentityPub: remotepub, LocalChanCfg: channeldb.ChannelConfig{ MultiSigKey: keychain.KeyDescriptor{ PubKey: localMultisigKey, }, }, RemoteChanCfg: channeldb.ChannelConfig{ MultiSigKey: keychain.KeyDescriptor{ PubKey: remoteMultisigKey, }, }, ShortChannelID: lnwire.NewShortChanIDFromInt(8), ChainHash: *chaincfg.RegressionNetParams.GenesisHash, Capacity: 9, FundingOutpoint: wire.OutPoint{ Hash: chainhash.Hash([32]byte{}), Index: 0, }, } expectedInfo := &models.ChannelEdgeInfo{ ChannelID: 8, ChainHash: channel.ChainHash, Features: []byte{0, 0}, Capacity: 9, ChannelPoint: channel.FundingOutpoint, NodeKey1Bytes: sp, NodeKey2Bytes: rp, BitcoinKey1Bytes: [33]byte( localMultisigKey.SerializeCompressed()), BitcoinKey2Bytes: [33]byte( remoteMultisigKey.SerializeCompressed()), AuthProof: nil, ExtraOpaqueData: nil, } expectedEdge := &models.ChannelEdgePolicy{ ChannelID: 8, LastUpdate: timestamp, TimeLockDelta: 7, ChannelFlags: 0, MessageFlags: lnwire.ChanUpdateRequiredMaxHtlc, FeeBaseMSat: 3, FeeProportionalMillionths: 4, MinHTLC: 1, MaxHTLC: 2, SigBytes: nil, ToNode: rp, ExtraOpaqueData: nil, } manager := Manager{ SelfPub: selfpub, DefaultRoutingPolicy: defaultPolicy, } info, edge, err := manager.createEdge(channel, timestamp) require.NoError(t, err) require.Equal(t, expectedInfo, info) require.Equal(t, expectedEdge, edge) } // Tests creating a new edge in the manager where the local pubkey is the // lexicographically higher or the two. func TestCreateEdgeHigher(t *testing.T) { sp := [33]byte{} _, _ = hex.Decode(sp[:], []byte("034f355bdcb7cc0af728ef3cceb9615d906"+ "84bb5b2ca5f859ab0f0b704075871aa")) rp := [33]byte{} _, _ = hex.Decode(rp[:], []byte("028d7500dd4c12685d1f568b4c2b5048e85"+ "34b873319f3a8daa612b469132ec7f7")) selfpub, _ := btcec.ParsePubKey(sp[:]) remotepub, _ := btcec.ParsePubKey(rp[:]) localMultisigPrivKey, _ := btcec.NewPrivateKey() localMultisigKey := localMultisigPrivKey.PubKey() remoteMultisigPrivKey, _ := btcec.NewPrivateKey() remoteMultisigKey := remoteMultisigPrivKey.PubKey() timestamp := time.Now() defaultPolicy := models.ForwardingPolicy{ MinHTLCOut: 1, MaxHTLC: 2, BaseFee: 3, FeeRate: 4, InboundFee: models.InboundFee{ Base: 5, Rate: 6, }, TimeLockDelta: 7, } channel := &channeldb.OpenChannel{ IdentityPub: remotepub, LocalChanCfg: channeldb.ChannelConfig{ MultiSigKey: keychain.KeyDescriptor{ PubKey: localMultisigKey, }, }, RemoteChanCfg: channeldb.ChannelConfig{ MultiSigKey: keychain.KeyDescriptor{ PubKey: remoteMultisigKey, }, }, ShortChannelID: lnwire.NewShortChanIDFromInt(8), ChainHash: *chaincfg.RegressionNetParams.GenesisHash, Capacity: 9, FundingOutpoint: wire.OutPoint{ Hash: chainhash.Hash([32]byte{}), Index: 0, }, } expectedInfo := &models.ChannelEdgeInfo{ ChannelID: 8, ChainHash: channel.ChainHash, Features: []byte{0, 0}, Capacity: 9, ChannelPoint: channel.FundingOutpoint, NodeKey1Bytes: rp, NodeKey2Bytes: sp, BitcoinKey1Bytes: [33]byte( remoteMultisigKey.SerializeCompressed()), BitcoinKey2Bytes: [33]byte( localMultisigKey.SerializeCompressed()), AuthProof: nil, ExtraOpaqueData: nil, } expectedEdge := &models.ChannelEdgePolicy{ ChannelID: 8, LastUpdate: timestamp, TimeLockDelta: 7, ChannelFlags: 1, MessageFlags: lnwire.ChanUpdateRequiredMaxHtlc, FeeBaseMSat: 3, FeeProportionalMillionths: 4, MinHTLC: 1, MaxHTLC: 2, SigBytes: nil, ToNode: rp, ExtraOpaqueData: nil, } manager := Manager{ SelfPub: selfpub, DefaultRoutingPolicy: defaultPolicy, } info, edge, err := manager.createEdge(channel, timestamp) require.NoError(t, err) require.Equal(t, expectedInfo, info) require.Equal(t, expectedEdge, edge) }