lnd/lnrpc/invoicesrpc/addinvoice_test.go
alpeb-btc 0cae501009
lnrpc: provide hop hints when adding an invoice with no amount
With this change we allow adding hop hints when adding an invoice, even
if its amount is zero.

A couple of new unit test case have been added, and the
`testInvoiceRoutingHints` itest was expanded to account for this
scenario.
2022-11-12 05:01:10 -05:00

888 lines
21 KiB
Go

package invoicesrpc
import (
"encoding/hex"
"fmt"
"testing"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/zpay32"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
)
type hopHintsConfigMock struct {
mock.Mock
}
// IsPublicNode mocks node public state lookup.
func (h *hopHintsConfigMock) IsPublicNode(pubKey [33]byte) (bool, error) {
args := h.Mock.Called(pubKey)
return args.Bool(0), args.Error(1)
}
// IsChannelActive is used to generate valid hop hints.
func (h *hopHintsConfigMock) IsChannelActive(chanID lnwire.ChannelID) bool {
args := h.Mock.Called(chanID)
return args.Bool(0)
}
// GetAlias allows the peer's alias SCID to be retrieved for private
// option_scid_alias channels.
func (h *hopHintsConfigMock) GetAlias(
chanID lnwire.ChannelID) (lnwire.ShortChannelID, error) {
args := h.Mock.Called(chanID)
return args.Get(0).(lnwire.ShortChannelID), args.Error(1)
}
// FetchAllChannels retrieves all open channels currently stored
// within the database.
func (h *hopHintsConfigMock) FetchAllChannels() ([]*channeldb.OpenChannel,
error) {
args := h.Mock.Called()
return args.Get(0).([]*channeldb.OpenChannel), args.Error(1)
}
// FetchChannelEdgesByID attempts to lookup the two directed edges for
// the channel identified by the channel ID.
func (h *hopHintsConfigMock) FetchChannelEdgesByID(chanID uint64) (
*channeldb.ChannelEdgeInfo, *channeldb.ChannelEdgePolicy,
*channeldb.ChannelEdgePolicy, error) {
args := h.Mock.Called(chanID)
// If our error is non-nil, we expect nil responses otherwise. Our
// casts below will fail with nil values, so we check our error and
// return early on failure first.
err := args.Error(3)
if err != nil {
return nil, nil, nil, err
}
edgeInfo := args.Get(0).(*channeldb.ChannelEdgeInfo)
policy1 := args.Get(1).(*channeldb.ChannelEdgePolicy)
policy2 := args.Get(2).(*channeldb.ChannelEdgePolicy)
return edgeInfo, policy1, policy2, err
}
// getTestPubKey returns a valid parsed pub key to be used in our tests.
func getTestPubKey() *btcec.PublicKey {
pubkeyBytes, _ := hex.DecodeString(
"598ec453728e0ffe0ae2f5e174243cf58f2" +
"a3f2c83d2457b43036db568b11093",
)
pubKeyY := new(btcec.FieldVal)
_ = pubKeyY.SetByteSlice(pubkeyBytes)
pubkey := btcec.NewPublicKey(
new(btcec.FieldVal).SetInt(4),
pubKeyY,
)
return pubkey
}
var shouldIncludeChannelTestCases = []struct {
name string
setupMock func(*hopHintsConfigMock)
channel *channeldb.OpenChannel
alreadyIncluded map[uint64]bool
cfg *SelectHopHintsCfg
hopHint zpay32.HopHint
remoteBalance lnwire.MilliSatoshi
include bool
}{{
name: "already included channels should not be included " +
"again",
alreadyIncluded: map[uint64]bool{1: true},
channel: &channeldb.OpenChannel{
ShortChannelID: lnwire.NewShortChanIDFromInt(1),
},
include: false,
}, {
name: "public channels should not be included",
setupMock: func(h *hopHintsConfigMock) {
fundingOutpoint := wire.OutPoint{
Index: 0,
}
chanID := lnwire.NewChanIDFromOutPoint(&fundingOutpoint)
h.Mock.On(
"IsChannelActive", chanID,
).Once().Return(true)
},
channel: &channeldb.OpenChannel{
FundingOutpoint: wire.OutPoint{
Index: 0,
},
ChannelFlags: lnwire.FFAnnounceChannel,
},
}, {
name: "not active channels should not be included",
setupMock: func(h *hopHintsConfigMock) {
fundingOutpoint := wire.OutPoint{
Index: 0,
}
chanID := lnwire.NewChanIDFromOutPoint(&fundingOutpoint)
h.Mock.On(
"IsChannelActive", chanID,
).Once().Return(false)
},
channel: &channeldb.OpenChannel{
FundingOutpoint: wire.OutPoint{
Index: 0,
},
},
include: false,
}, {
name: "a channel with a not public peer should not be included",
setupMock: func(h *hopHintsConfigMock) {
fundingOutpoint := wire.OutPoint{
Index: 0,
}
chanID := lnwire.NewChanIDFromOutPoint(&fundingOutpoint)
h.Mock.On(
"IsChannelActive", chanID,
).Once().Return(true)
h.Mock.On(
"IsPublicNode", mock.Anything,
).Once().Return(false, nil)
},
channel: &channeldb.OpenChannel{
FundingOutpoint: wire.OutPoint{
Index: 0,
},
IdentityPub: getTestPubKey(),
},
include: false,
}, {
name: "if we are unable to fetch the edge policy for the channel it " +
"should not be included",
setupMock: func(h *hopHintsConfigMock) {
fundingOutpoint := wire.OutPoint{
Index: 0,
}
chanID := lnwire.NewChanIDFromOutPoint(&fundingOutpoint)
h.Mock.On(
"IsChannelActive", chanID,
).Once().Return(true)
h.Mock.On(
"IsPublicNode", mock.Anything,
).Once().Return(true, nil)
h.Mock.On(
"FetchChannelEdgesByID", mock.Anything,
).Once().Return(nil, nil, nil, fmt.Errorf("no edge"))
// TODO(positiveblue): check that the func is called with the
// right scid when we have access to the `confirmedscid` form
// here.
h.Mock.On(
"FetchChannelEdgesByID", mock.Anything,
).Once().Return(nil, nil, nil, fmt.Errorf("no edge"))
},
channel: &channeldb.OpenChannel{
FundingOutpoint: wire.OutPoint{
Index: 0,
},
IdentityPub: getTestPubKey(),
},
include: false,
}, {
name: "channels with the option-scid-alias but not assigned alias " +
"yet should not be included",
setupMock: func(h *hopHintsConfigMock) {
fundingOutpoint := wire.OutPoint{
Index: 0,
}
chanID := lnwire.NewChanIDFromOutPoint(&fundingOutpoint)
h.Mock.On(
"IsChannelActive", chanID,
).Once().Return(true)
h.Mock.On(
"IsPublicNode", mock.Anything,
).Once().Return(true, nil)
h.Mock.On(
"FetchChannelEdgesByID", mock.Anything,
).Once().Return(
&channeldb.ChannelEdgeInfo{},
&channeldb.ChannelEdgePolicy{},
&channeldb.ChannelEdgePolicy{}, nil,
)
h.Mock.On(
"GetAlias", mock.Anything,
).Once().Return(lnwire.ShortChannelID{}, nil)
},
channel: &channeldb.OpenChannel{
FundingOutpoint: wire.OutPoint{
Index: 0,
},
IdentityPub: getTestPubKey(),
ChanType: channeldb.ScidAliasFeatureBit,
},
include: false,
}, {
name: "channels with the option-scid-alias and an alias that has " +
"already been included should not be included again",
alreadyIncluded: map[uint64]bool{5: true},
setupMock: func(h *hopHintsConfigMock) {
fundingOutpoint := wire.OutPoint{
Index: 0,
}
chanID := lnwire.NewChanIDFromOutPoint(&fundingOutpoint)
h.Mock.On(
"IsChannelActive", chanID,
).Once().Return(true)
h.Mock.On(
"IsPublicNode", mock.Anything,
).Once().Return(true, nil)
h.Mock.On(
"FetchChannelEdgesByID", mock.Anything,
).Once().Return(
&channeldb.ChannelEdgeInfo{},
&channeldb.ChannelEdgePolicy{},
&channeldb.ChannelEdgePolicy{}, nil,
)
alias := lnwire.ShortChannelID{TxPosition: 5}
h.Mock.On(
"GetAlias", mock.Anything,
).Once().Return(alias, nil)
},
channel: &channeldb.OpenChannel{
FundingOutpoint: wire.OutPoint{
Index: 0,
},
IdentityPub: getTestPubKey(),
ChanType: channeldb.ScidAliasFeatureBit,
},
include: false,
}, {
name: "channels that pass all the checks should be " +
"included, using policy 1",
alreadyIncluded: map[uint64]bool{5: true},
setupMock: func(h *hopHintsConfigMock) {
fundingOutpoint := wire.OutPoint{
Index: 1,
}
chanID := lnwire.NewChanIDFromOutPoint(&fundingOutpoint)
h.Mock.On(
"IsChannelActive", chanID,
).Once().Return(true)
h.Mock.On(
"IsPublicNode", mock.Anything,
).Once().Return(true, nil)
var selectedPolicy [33]byte
copy(selectedPolicy[:], getTestPubKey().SerializeCompressed())
h.Mock.On(
"FetchChannelEdgesByID", mock.Anything,
).Once().Return(
&channeldb.ChannelEdgeInfo{
NodeKey1Bytes: selectedPolicy,
},
&channeldb.ChannelEdgePolicy{
FeeBaseMSat: 1000,
FeeProportionalMillionths: 20,
TimeLockDelta: 13,
},
&channeldb.ChannelEdgePolicy{},
nil,
)
},
channel: &channeldb.OpenChannel{
FundingOutpoint: wire.OutPoint{
Index: 1,
},
IdentityPub: getTestPubKey(),
ShortChannelID: lnwire.NewShortChanIDFromInt(12),
},
hopHint: zpay32.HopHint{
NodeID: getTestPubKey(),
FeeBaseMSat: 1000,
FeeProportionalMillionths: 20,
ChannelID: 12,
CLTVExpiryDelta: 13,
},
include: true,
}, {
name: "channels that pass all the checks should be " +
"included, using policy 2",
alreadyIncluded: map[uint64]bool{5: true},
setupMock: func(h *hopHintsConfigMock) {
fundingOutpoint := wire.OutPoint{
Index: 1,
}
chanID := lnwire.NewChanIDFromOutPoint(&fundingOutpoint)
h.Mock.On(
"IsChannelActive", chanID,
).Once().Return(true)
h.Mock.On(
"IsPublicNode", mock.Anything,
).Once().Return(true, nil)
h.Mock.On(
"FetchChannelEdgesByID", mock.Anything,
).Once().Return(
&channeldb.ChannelEdgeInfo{},
&channeldb.ChannelEdgePolicy{},
&channeldb.ChannelEdgePolicy{
FeeBaseMSat: 1000,
FeeProportionalMillionths: 20,
TimeLockDelta: 13,
}, nil,
)
},
channel: &channeldb.OpenChannel{
FundingOutpoint: wire.OutPoint{
Index: 1,
},
IdentityPub: getTestPubKey(),
ShortChannelID: lnwire.NewShortChanIDFromInt(12),
},
hopHint: zpay32.HopHint{
NodeID: getTestPubKey(),
FeeBaseMSat: 1000,
FeeProportionalMillionths: 20,
ChannelID: 12,
CLTVExpiryDelta: 13,
},
include: true,
}, {
name: "channels that pass all the checks and have an alias " +
"should be included with the alias",
alreadyIncluded: map[uint64]bool{5: true},
setupMock: func(h *hopHintsConfigMock) {
fundingOutpoint := wire.OutPoint{
Index: 1,
}
chanID := lnwire.NewChanIDFromOutPoint(&fundingOutpoint)
h.Mock.On(
"IsChannelActive", chanID,
).Once().Return(true)
h.Mock.On(
"IsPublicNode", mock.Anything,
).Once().Return(true, nil)
h.Mock.On(
"FetchChannelEdgesByID", mock.Anything,
).Once().Return(
&channeldb.ChannelEdgeInfo{},
&channeldb.ChannelEdgePolicy{},
&channeldb.ChannelEdgePolicy{
FeeBaseMSat: 1000,
FeeProportionalMillionths: 20,
TimeLockDelta: 13,
}, nil,
)
aliasSCID := lnwire.NewShortChanIDFromInt(15)
h.Mock.On(
"GetAlias", mock.Anything,
).Once().Return(aliasSCID, nil)
},
channel: &channeldb.OpenChannel{
FundingOutpoint: wire.OutPoint{
Index: 1,
},
IdentityPub: getTestPubKey(),
ShortChannelID: lnwire.NewShortChanIDFromInt(12),
ChanType: channeldb.ScidAliasFeatureBit,
},
hopHint: zpay32.HopHint{
NodeID: getTestPubKey(),
FeeBaseMSat: 1000,
FeeProportionalMillionths: 20,
ChannelID: 15,
CLTVExpiryDelta: 13,
},
include: true,
}}
func TestShouldIncludeChannel(t *testing.T) {
for _, tc := range shouldIncludeChannelTestCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
// Create mock and prime it for the test case.
mock := &hopHintsConfigMock{}
if tc.setupMock != nil {
tc.setupMock(mock)
}
defer mock.AssertExpectations(t)
cfg := &SelectHopHintsCfg{
IsPublicNode: mock.IsPublicNode,
IsChannelActive: mock.IsChannelActive,
FetchChannelEdgesByID: mock.FetchChannelEdgesByID,
GetAlias: mock.GetAlias,
}
hopHint, remoteBalance, include := shouldIncludeChannel(
cfg, tc.channel, tc.alreadyIncluded,
)
require.Equal(t, tc.include, include)
if include {
require.Equal(t, tc.hopHint, hopHint)
require.Equal(
t, tc.remoteBalance, remoteBalance,
)
}
})
}
}
var sufficientHintsTestCases = []struct {
name string
nHintsLeft int
currentAmount lnwire.MilliSatoshi
targetAmount lnwire.MilliSatoshi
done bool
}{{
name: "not enough hints neither bandwidth",
nHintsLeft: 3,
currentAmount: 100,
targetAmount: 200,
done: false,
}, {
name: "enough hints",
nHintsLeft: 0,
done: true,
}, {
name: "enough bandwidth",
nHintsLeft: 1,
currentAmount: 200,
targetAmount: 200,
done: true,
}, {
name: "no amount provided",
nHintsLeft: 1,
currentAmount: 100,
targetAmount: 0,
done: false,
}}
func TestSufficientHints(t *testing.T) {
for _, tc := range sufficientHintsTestCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
enoughHints := sufficientHints(
tc.nHintsLeft, tc.currentAmount,
tc.targetAmount,
)
require.Equal(t, tc.done, enoughHints)
})
}
}
var populateHopHintsTestCases = []struct {
name string
setupMock func(*hopHintsConfigMock)
amount lnwire.MilliSatoshi
maxHopHints int
forcedHints [][]zpay32.HopHint
expectedHopHints [][]zpay32.HopHint
}{{
name: "populate hop hints with forced hints",
maxHopHints: 1,
forcedHints: [][]zpay32.HopHint{
{
{ChannelID: 12},
},
},
expectedHopHints: [][]zpay32.HopHint{
{
{ChannelID: 12},
},
},
}, {
name: "populate hop hints stops when we reached the max number of " +
"hop hints allowed",
setupMock: func(h *hopHintsConfigMock) {
fundingOutpoint := wire.OutPoint{Index: 9}
chanID := lnwire.NewChanIDFromOutPoint(&fundingOutpoint)
allChannels := []*channeldb.OpenChannel{
{
FundingOutpoint: fundingOutpoint,
ShortChannelID: lnwire.NewShortChanIDFromInt(9),
IdentityPub: getTestPubKey(),
},
// Have one empty channel that we should not process
// because we have already finished.
{},
}
h.Mock.On(
"FetchAllChannels",
).Once().Return(allChannels, nil)
h.Mock.On(
"IsChannelActive", chanID,
).Once().Return(true)
h.Mock.On(
"IsPublicNode", mock.Anything,
).Once().Return(true, nil)
h.Mock.On(
"FetchChannelEdgesByID", mock.Anything,
).Once().Return(
&channeldb.ChannelEdgeInfo{},
&channeldb.ChannelEdgePolicy{},
&channeldb.ChannelEdgePolicy{}, nil,
)
},
maxHopHints: 1,
amount: 1_000_000,
expectedHopHints: [][]zpay32.HopHint{
{
{
NodeID: getTestPubKey(),
ChannelID: 9,
},
},
},
}, {
name: "populate hop hints stops when we reached the targeted bandwidth",
setupMock: func(h *hopHintsConfigMock) {
fundingOutpoint := wire.OutPoint{Index: 9}
chanID := lnwire.NewChanIDFromOutPoint(&fundingOutpoint)
remoteBalance := lnwire.MilliSatoshi(10_000_000)
allChannels := []*channeldb.OpenChannel{
{
LocalCommitment: channeldb.ChannelCommitment{
RemoteBalance: remoteBalance,
},
FundingOutpoint: fundingOutpoint,
ShortChannelID: lnwire.NewShortChanIDFromInt(9),
IdentityPub: getTestPubKey(),
},
// Have one empty channel that we should not process
// because we have already finished.
{},
}
h.Mock.On(
"FetchAllChannels",
).Once().Return(allChannels, nil)
h.Mock.On(
"IsChannelActive", chanID,
).Once().Return(true)
h.Mock.On(
"IsPublicNode", mock.Anything,
).Once().Return(true, nil)
h.Mock.On(
"FetchChannelEdgesByID", mock.Anything,
).Once().Return(
&channeldb.ChannelEdgeInfo{},
&channeldb.ChannelEdgePolicy{},
&channeldb.ChannelEdgePolicy{}, nil,
)
},
maxHopHints: 10,
amount: 1_000_000,
expectedHopHints: [][]zpay32.HopHint{
{
{
NodeID: getTestPubKey(),
ChannelID: 9,
},
},
},
}, {
name: "populate hop hints tries to use the channels with higher " +
"remote balance frist",
setupMock: func(h *hopHintsConfigMock) {
fundingOutpoint := wire.OutPoint{Index: 9}
chanID := lnwire.NewChanIDFromOutPoint(&fundingOutpoint)
remoteBalance := lnwire.MilliSatoshi(10_000_000)
allChannels := []*channeldb.OpenChannel{
// Because the channels with higher remote balance have
// enough bandwidth we should never use this one.
{},
{
LocalCommitment: channeldb.ChannelCommitment{
RemoteBalance: remoteBalance,
},
FundingOutpoint: fundingOutpoint,
ShortChannelID: lnwire.NewShortChanIDFromInt(9),
IdentityPub: getTestPubKey(),
},
}
h.Mock.On(
"FetchAllChannels",
).Once().Return(allChannels, nil)
h.Mock.On(
"IsChannelActive", chanID,
).Once().Return(true)
h.Mock.On(
"IsPublicNode", mock.Anything,
).Once().Return(true, nil)
h.Mock.On(
"FetchChannelEdgesByID", mock.Anything,
).Once().Return(
&channeldb.ChannelEdgeInfo{},
&channeldb.ChannelEdgePolicy{},
&channeldb.ChannelEdgePolicy{}, nil,
)
},
maxHopHints: 1,
amount: 1_000_000,
expectedHopHints: [][]zpay32.HopHint{
{
{
NodeID: getTestPubKey(),
ChannelID: 9,
},
},
},
}, {
name: "populate hop hints stops after having considered all the open " +
"channels",
setupMock: func(h *hopHintsConfigMock) {
chanID1, chanID2 := setupMockTwoChannels(h)
// Prepare the mock for the first channel.
h.Mock.On(
"IsChannelActive", chanID1,
).Once().Return(true)
h.Mock.On(
"IsPublicNode", mock.Anything,
).Once().Return(true, nil)
h.Mock.On(
"FetchChannelEdgesByID", mock.Anything,
).Once().Return(
&channeldb.ChannelEdgeInfo{},
&channeldb.ChannelEdgePolicy{},
&channeldb.ChannelEdgePolicy{}, nil,
)
// Prepare the mock for the second channel.
h.Mock.On(
"IsChannelActive", chanID2,
).Once().Return(true)
h.Mock.On(
"IsPublicNode", mock.Anything,
).Once().Return(true, nil)
h.Mock.On(
"FetchChannelEdgesByID", mock.Anything,
).Once().Return(
&channeldb.ChannelEdgeInfo{},
&channeldb.ChannelEdgePolicy{},
&channeldb.ChannelEdgePolicy{}, nil,
)
},
maxHopHints: 10,
amount: 100_000_000,
expectedHopHints: [][]zpay32.HopHint{
{
{
NodeID: getTestPubKey(),
ChannelID: 9,
},
}, {
{
NodeID: getTestPubKey(),
ChannelID: 2,
},
},
},
}, {
name: "consider all the open channels when amount is zero",
setupMock: func(h *hopHintsConfigMock) {
chanID1, chanID2 := setupMockTwoChannels(h)
// Prepare the mock for the first channel.
h.Mock.On(
"IsChannelActive", chanID1,
).Once().Return(true)
h.Mock.On(
"IsPublicNode", mock.Anything,
).Once().Return(true, nil)
h.Mock.On(
"FetchChannelEdgesByID", mock.Anything,
).Once().Return(
&channeldb.ChannelEdgeInfo{},
&channeldb.ChannelEdgePolicy{},
&channeldb.ChannelEdgePolicy{}, nil,
)
// Prepare the mock for the second channel.
h.Mock.On(
"IsChannelActive", chanID2,
).Once().Return(true)
h.Mock.On(
"IsPublicNode", mock.Anything,
).Once().Return(true, nil)
h.Mock.On(
"FetchChannelEdgesByID", mock.Anything,
).Once().Return(
&channeldb.ChannelEdgeInfo{},
&channeldb.ChannelEdgePolicy{},
&channeldb.ChannelEdgePolicy{}, nil,
)
},
maxHopHints: 10,
amount: 0,
expectedHopHints: [][]zpay32.HopHint{
{
{
NodeID: getTestPubKey(),
ChannelID: 9,
},
}, {
{
NodeID: getTestPubKey(),
ChannelID: 2,
},
},
},
}, {
name: "consider all the open channels when amount is zero" +
" up to maxHopHints",
setupMock: func(h *hopHintsConfigMock) {
chanID1, _ := setupMockTwoChannels(h)
// Prepare the mock for the first channel.
h.Mock.On(
"IsChannelActive", chanID1,
).Once().Return(true)
h.Mock.On(
"IsPublicNode", mock.Anything,
).Once().Return(true, nil)
h.Mock.On(
"FetchChannelEdgesByID", mock.Anything,
).Once().Return(
&channeldb.ChannelEdgeInfo{},
&channeldb.ChannelEdgePolicy{},
&channeldb.ChannelEdgePolicy{}, nil,
)
},
maxHopHints: 1,
amount: 0,
expectedHopHints: [][]zpay32.HopHint{
{
{
NodeID: getTestPubKey(),
ChannelID: 9,
},
},
},
}}
func setupMockTwoChannels(h *hopHintsConfigMock) (lnwire.ChannelID,
lnwire.ChannelID) {
fundingOutpoint1 := wire.OutPoint{Index: 9}
chanID1 := lnwire.NewChanIDFromOutPoint(&fundingOutpoint1)
remoteBalance1 := lnwire.MilliSatoshi(10_000_000)
fundingOutpoint2 := wire.OutPoint{Index: 2}
chanID2 := lnwire.NewChanIDFromOutPoint(&fundingOutpoint2)
remoteBalance2 := lnwire.MilliSatoshi(1_000_000)
allChannels := []*channeldb.OpenChannel{
// After sorting we will first process chanID1 and then
// chanID2.
{
LocalCommitment: channeldb.ChannelCommitment{
RemoteBalance: remoteBalance2,
},
FundingOutpoint: fundingOutpoint2,
ShortChannelID: lnwire.NewShortChanIDFromInt(2),
IdentityPub: getTestPubKey(),
},
{
LocalCommitment: channeldb.ChannelCommitment{
RemoteBalance: remoteBalance1,
},
FundingOutpoint: fundingOutpoint1,
ShortChannelID: lnwire.NewShortChanIDFromInt(9),
IdentityPub: getTestPubKey(),
},
}
h.Mock.On(
"FetchAllChannels",
).Once().Return(allChannels, nil)
return chanID1, chanID2
}
func TestPopulateHopHints(t *testing.T) {
for _, tc := range populateHopHintsTestCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
// Create mock and prime it for the test case.
mock := &hopHintsConfigMock{}
if tc.setupMock != nil {
tc.setupMock(mock)
}
defer mock.AssertExpectations(t)
cfg := &SelectHopHintsCfg{
IsPublicNode: mock.IsPublicNode,
IsChannelActive: mock.IsChannelActive,
FetchChannelEdgesByID: mock.FetchChannelEdgesByID,
GetAlias: mock.GetAlias,
FetchAllChannels: mock.FetchAllChannels,
MaxHopHints: tc.maxHopHints,
}
hopHints, err := PopulateHopHints(
cfg, tc.amount, tc.forcedHints,
)
require.NoError(t, err)
// We shuffle the elements in the hop hint list so we
// need to compare the elements here.
require.ElementsMatch(t, tc.expectedHopHints, hopHints)
})
}
}