2017-03-21 02:25:54 +01:00
|
|
|
package routing
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"fmt"
|
2017-07-14 21:32:00 +02:00
|
|
|
"image/color"
|
2019-10-11 21:46:10 +02:00
|
|
|
"math"
|
2017-10-02 17:54:29 +02:00
|
|
|
"math/rand"
|
2024-06-17 01:30:01 +02:00
|
|
|
"net"
|
2024-05-05 13:36:26 +02:00
|
|
|
"strings"
|
2024-06-17 01:30:01 +02:00
|
|
|
"sync"
|
2019-05-16 15:27:28 +02:00
|
|
|
"sync/atomic"
|
2017-03-21 02:25:54 +01:00
|
|
|
"testing"
|
2017-07-14 21:32:00 +02:00
|
|
|
"time"
|
2017-03-21 02:25:54 +01:00
|
|
|
|
2022-02-23 14:48:00 +01:00
|
|
|
"github.com/btcsuite/btcd/btcec/v2"
|
|
|
|
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
|
|
|
|
"github.com/btcsuite/btcd/btcutil"
|
2018-08-18 20:05:58 +02:00
|
|
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
2019-01-16 15:47:43 +01:00
|
|
|
"github.com/btcsuite/btcd/wire"
|
2017-03-30 03:01:28 +02:00
|
|
|
"github.com/davecgh/go-spew/spew"
|
2024-06-17 01:30:01 +02:00
|
|
|
"github.com/go-errors/errors"
|
2022-12-15 22:48:56 +01:00
|
|
|
sphinx "github.com/lightningnetwork/lightning-onion"
|
2019-01-16 15:47:43 +01:00
|
|
|
"github.com/lightningnetwork/lnd/channeldb"
|
2023-11-08 10:18:45 +01:00
|
|
|
"github.com/lightningnetwork/lnd/channeldb/models"
|
2020-03-05 13:54:03 +01:00
|
|
|
"github.com/lightningnetwork/lnd/clock"
|
2024-07-31 14:32:48 +02:00
|
|
|
"github.com/lightningnetwork/lnd/fn"
|
2024-06-17 01:30:01 +02:00
|
|
|
"github.com/lightningnetwork/lnd/graph"
|
2019-01-16 15:47:43 +01:00
|
|
|
"github.com/lightningnetwork/lnd/htlcswitch"
|
2024-06-17 01:30:01 +02:00
|
|
|
"github.com/lightningnetwork/lnd/input"
|
2018-08-08 11:09:30 +02:00
|
|
|
"github.com/lightningnetwork/lnd/lntypes"
|
2024-06-17 01:30:01 +02:00
|
|
|
"github.com/lightningnetwork/lnd/lnwallet"
|
2017-03-21 02:25:54 +01:00
|
|
|
"github.com/lightningnetwork/lnd/lnwire"
|
2020-04-01 00:13:27 +02:00
|
|
|
"github.com/lightningnetwork/lnd/record"
|
2019-04-05 17:36:11 +02:00
|
|
|
"github.com/lightningnetwork/lnd/routing/route"
|
2018-12-22 20:38:03 +01:00
|
|
|
"github.com/lightningnetwork/lnd/zpay32"
|
2022-02-07 13:58:21 +01:00
|
|
|
"github.com/stretchr/testify/mock"
|
|
|
|
"github.com/stretchr/testify/require"
|
2017-03-21 02:25:54 +01:00
|
|
|
)
|
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
var (
|
|
|
|
uniquePaymentID uint64 = 1 // to be used atomically
|
|
|
|
|
|
|
|
testAddr = &net.TCPAddr{IP: (net.IP)([]byte{0xA, 0x0, 0x0, 0x1}),
|
|
|
|
Port: 9000}
|
|
|
|
testAddrs = []net.Addr{testAddr}
|
|
|
|
|
|
|
|
testFeatures = lnwire.NewFeatureVector(nil, lnwire.Features)
|
|
|
|
|
|
|
|
testHash = [32]byte{
|
|
|
|
0xb7, 0x94, 0x38, 0x5f, 0x2d, 0x1e, 0xf7, 0xab,
|
|
|
|
0x4d, 0x92, 0x73, 0xd1, 0x90, 0x63, 0x81, 0xb4,
|
|
|
|
0x4f, 0x2f, 0x6f, 0x25, 0x88, 0xa3, 0xef, 0xb9,
|
|
|
|
0x6a, 0x49, 0x18, 0x83, 0x31, 0x98, 0x47, 0x53,
|
|
|
|
}
|
|
|
|
|
|
|
|
testTime = time.Date(2018, time.January, 9, 14, 00, 00, 0, time.UTC)
|
|
|
|
|
|
|
|
priv1, _ = btcec.NewPrivateKey()
|
|
|
|
bitcoinKey1 = priv1.PubKey()
|
|
|
|
|
|
|
|
priv2, _ = btcec.NewPrivateKey()
|
|
|
|
bitcoinKey2 = priv2.PubKey()
|
|
|
|
)
|
2019-05-16 15:27:28 +02:00
|
|
|
|
2017-03-21 02:25:54 +01:00
|
|
|
type testCtx struct {
|
|
|
|
router *ChannelRouter
|
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
graphBuilder *mockGraphBuilder
|
|
|
|
|
2017-03-21 02:25:54 +01:00
|
|
|
graph *channeldb.ChannelGraph
|
|
|
|
|
2019-04-05 17:36:11 +02:00
|
|
|
aliases map[string]route.Vertex
|
2017-03-21 02:25:54 +01:00
|
|
|
|
2018-12-10 13:32:21 +01:00
|
|
|
privKeys map[string]*btcec.PrivateKey
|
|
|
|
|
2021-04-10 12:39:25 +02:00
|
|
|
channelIDs map[route.Vertex]map[route.Vertex]uint64
|
2017-03-21 02:25:54 +01:00
|
|
|
}
|
|
|
|
|
2021-04-10 12:39:25 +02:00
|
|
|
func (c *testCtx) getChannelIDFromAlias(t *testing.T, a, b string) uint64 {
|
|
|
|
vertexA, ok := c.aliases[a]
|
|
|
|
require.True(t, ok, "cannot find aliases for %s", a)
|
|
|
|
|
|
|
|
vertexB, ok := c.aliases[b]
|
|
|
|
require.True(t, ok, "cannot find aliases for %s", b)
|
|
|
|
|
|
|
|
channelIDMap, ok := c.channelIDs[vertexA]
|
|
|
|
require.True(t, ok, "cannot find channelID map %s(%s)", vertexA, a)
|
|
|
|
|
|
|
|
channelID, ok := channelIDMap[vertexB]
|
|
|
|
require.True(t, ok, "cannot find channelID using %s(%s)", vertexB, b)
|
|
|
|
|
|
|
|
return channelID
|
|
|
|
}
|
|
|
|
|
2023-11-17 14:07:53 +01:00
|
|
|
var mockClosedSCIDs map[lnwire.ShortChannelID]struct{}
|
2023-11-16 13:53:43 +01:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
func createTestCtxFromGraphInstance(t *testing.T, startingHeight uint32,
|
|
|
|
graphInstance *testGraphInstance) *testCtx {
|
2017-03-21 02:25:54 +01:00
|
|
|
|
2021-02-18 03:55:56 +01:00
|
|
|
return createTestCtxFromGraphInstanceAssumeValid(
|
2024-06-17 01:30:01 +02:00
|
|
|
t, startingHeight, graphInstance,
|
2021-02-18 03:55:56 +01:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2021-04-14 06:58:12 +02:00
|
|
|
func createTestCtxFromGraphInstanceAssumeValid(t *testing.T,
|
2024-06-17 01:30:01 +02:00
|
|
|
startingHeight uint32, graphInstance *testGraphInstance) *testCtx {
|
2021-02-18 03:55:56 +01:00
|
|
|
|
2018-08-16 21:35:38 +02:00
|
|
|
// We'll initialize an instance of the channel router with mock
|
2017-03-21 02:25:54 +01:00
|
|
|
// versions of the chain and channel notifier. As we don't need to test
|
|
|
|
// any p2p functionality, the peer send and switch send messages won't
|
|
|
|
// be populated.
|
|
|
|
chain := newMockChain(startingHeight)
|
2019-05-23 20:05:30 +02:00
|
|
|
|
2019-06-26 13:00:35 +02:00
|
|
|
pathFindingConfig := PathFindingConfig{
|
2020-09-08 13:07:35 +02:00
|
|
|
MinProbability: 0.01,
|
|
|
|
AttemptCost: 100,
|
2019-06-20 12:03:45 +02:00
|
|
|
}
|
|
|
|
|
2023-01-20 11:25:53 +01:00
|
|
|
aCfg := AprioriConfig{
|
|
|
|
PenaltyHalfLife: time.Hour,
|
|
|
|
AprioriHopProbability: 0.9,
|
|
|
|
AprioriWeight: 0.5,
|
2023-02-24 11:59:44 +01:00
|
|
|
CapacityFraction: testCapacityFraction,
|
2019-06-20 12:03:45 +02:00
|
|
|
}
|
2023-01-20 11:25:53 +01:00
|
|
|
estimator, err := NewAprioriEstimator(aCfg)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
mcConfig := &MissionControlConfig{Estimator: estimator}
|
2019-06-20 12:03:45 +02:00
|
|
|
|
2024-08-13 18:52:16 +02:00
|
|
|
mcController, err := NewMissionController(
|
2021-09-21 19:18:18 +02:00
|
|
|
graphInstance.graphBackend, route.Vertex{}, mcConfig,
|
2019-05-23 20:05:30 +02:00
|
|
|
)
|
2021-04-14 06:58:12 +02:00
|
|
|
require.NoError(t, err, "failed to create missioncontrol")
|
2024-08-13 19:19:06 +02:00
|
|
|
|
|
|
|
mc, err := mcController.GetNamespacedStore(
|
|
|
|
DefaultMissionControlNamespace,
|
|
|
|
)
|
|
|
|
require.NoError(t, err)
|
2019-06-18 18:30:56 +02:00
|
|
|
|
2021-10-21 13:55:22 +02:00
|
|
|
sourceNode, err := graphInstance.graph.SourceNode()
|
2021-09-21 19:18:24 +02:00
|
|
|
require.NoError(t, err)
|
2019-06-18 18:30:56 +02:00
|
|
|
sessionSource := &SessionSource{
|
2024-06-26 04:58:57 +02:00
|
|
|
GraphSessionFactory: newMockGraphSessionFactoryFromChanDB(
|
|
|
|
graphInstance.graph,
|
|
|
|
),
|
2021-10-21 13:55:22 +02:00
|
|
|
SourceNode: sourceNode,
|
2021-10-19 09:37:44 +02:00
|
|
|
GetLink: graphInstance.getLink,
|
2019-06-26 13:00:35 +02:00
|
|
|
PathFindingConfig: pathFindingConfig,
|
|
|
|
MissionControl: mc,
|
2019-06-18 18:30:56 +02:00
|
|
|
}
|
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
graphBuilder := newMockGraphBuilder(graphInstance.graph)
|
2021-03-18 03:32:26 +01:00
|
|
|
|
2017-03-21 02:25:54 +01:00
|
|
|
router, err := New(Config{
|
2024-06-17 01:30:01 +02:00
|
|
|
SelfNode: sourceNode.PubKeyBytes,
|
|
|
|
RoutingGraph: newMockGraphSessionChanDB(graphInstance.graph),
|
|
|
|
Chain: chain,
|
|
|
|
Payer: &mockPaymentAttemptDispatcherOld{},
|
|
|
|
Control: makeMockControlTower(),
|
|
|
|
MissionControl: mc,
|
|
|
|
SessionSource: sessionSource,
|
|
|
|
GetLink: graphInstance.getLink,
|
2019-05-16 15:27:28 +02:00
|
|
|
NextPaymentID: func() (uint64, error) {
|
|
|
|
next := atomic.AddUint64(&uniquePaymentID, 1)
|
|
|
|
return next, nil
|
|
|
|
},
|
2023-11-17 14:07:53 +01:00
|
|
|
PathFindingConfig: pathFindingConfig,
|
|
|
|
Clock: clock.NewTestClock(time.Unix(1, 0)),
|
|
|
|
ApplyChannelUpdate: graphBuilder.ApplyChannelUpdate,
|
|
|
|
ClosedSCIDs: mockClosedSCIDs,
|
2024-04-19 13:49:45 +02:00
|
|
|
TrafficShaper: fn.Some[TlvTrafficShaper](
|
|
|
|
&mockTrafficShaper{},
|
|
|
|
),
|
2017-03-21 02:25:54 +01:00
|
|
|
})
|
2021-04-14 06:58:12 +02:00
|
|
|
require.NoError(t, router.Start(), "unable to start router")
|
2017-03-21 02:25:54 +01:00
|
|
|
|
2017-11-07 01:00:51 +01:00
|
|
|
ctx := &testCtx{
|
2024-06-17 01:30:01 +02:00
|
|
|
router: router,
|
|
|
|
graphBuilder: graphBuilder,
|
|
|
|
graph: graphInstance.graph,
|
|
|
|
aliases: graphInstance.aliasMap,
|
|
|
|
privKeys: graphInstance.privKeyMap,
|
|
|
|
channelIDs: graphInstance.channelIDs,
|
2017-11-07 01:00:51 +01:00
|
|
|
}
|
|
|
|
|
2022-08-27 09:07:28 +02:00
|
|
|
t.Cleanup(func() {
|
2017-11-07 01:00:51 +01:00
|
|
|
ctx.router.Stop()
|
2022-08-27 09:07:28 +02:00
|
|
|
})
|
2017-11-07 01:00:51 +01:00
|
|
|
|
2022-08-27 09:07:28 +02:00
|
|
|
return ctx
|
2017-03-21 02:25:54 +01:00
|
|
|
}
|
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
func createTestNode() (*channeldb.LightningNode, error) {
|
|
|
|
updateTime := rand.Int63()
|
2018-08-16 21:35:38 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
priv, err := btcec.NewPrivateKey()
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Errorf("unable create private key: %v", err)
|
|
|
|
}
|
2018-08-16 21:35:38 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
pub := priv.PubKey().SerializeCompressed()
|
|
|
|
n := &channeldb.LightningNode{
|
|
|
|
HaveNodeAnnouncement: true,
|
|
|
|
LastUpdate: time.Unix(updateTime, 0),
|
|
|
|
Addresses: testAddrs,
|
|
|
|
Color: color.RGBA{1, 2, 3, 0},
|
2024-06-18 21:34:25 +02:00
|
|
|
Alias: "kek" + string(pub),
|
2024-06-17 01:30:01 +02:00
|
|
|
AuthSigBytes: testSig.Serialize(),
|
|
|
|
Features: testFeatures,
|
2018-08-16 21:35:38 +02:00
|
|
|
}
|
2024-06-17 01:30:01 +02:00
|
|
|
copy(n.PubKeyBytes[:], pub)
|
2018-08-16 21:35:38 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
return n, nil
|
2018-08-16 21:35:38 +02:00
|
|
|
}
|
|
|
|
|
2021-04-14 06:58:12 +02:00
|
|
|
func createTestCtxFromFile(t *testing.T,
|
2022-08-27 09:07:28 +02:00
|
|
|
startingHeight uint32, testGraph string) *testCtx {
|
2021-04-14 06:58:12 +02:00
|
|
|
|
2018-08-16 21:35:38 +02:00
|
|
|
// We'll attempt to locate and parse out the file
|
|
|
|
// that encodes the graph that our tests should be run against.
|
2022-08-15 15:08:16 +02:00
|
|
|
graphInstance, err := parseTestGraph(t, true, testGraph)
|
2021-04-14 06:58:12 +02:00
|
|
|
require.NoError(t, err, "unable to create test graph")
|
2018-08-16 21:35:38 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
return createTestCtxFromGraphInstance(t, startingHeight, graphInstance)
|
2018-08-16 21:35:38 +02:00
|
|
|
}
|
|
|
|
|
2018-12-10 13:42:57 +01:00
|
|
|
// Add valid signature to channel update simulated as error received from the
|
|
|
|
// network.
|
2021-04-14 06:58:12 +02:00
|
|
|
func signErrChanUpdate(t *testing.T, key *btcec.PrivateKey,
|
2024-08-21 08:39:37 +02:00
|
|
|
errChanUpdate *lnwire.ChannelUpdate1) {
|
2018-12-10 13:42:57 +01:00
|
|
|
|
|
|
|
chanUpdateMsg, err := errChanUpdate.DataToSign()
|
2021-04-14 06:58:12 +02:00
|
|
|
require.NoError(t, err, "failed to retrieve data to sign")
|
2018-12-10 13:42:57 +01:00
|
|
|
|
|
|
|
digest := chainhash.DoubleHashB(chanUpdateMsg)
|
2022-02-23 14:48:00 +01:00
|
|
|
sig := ecdsa.Sign(key, digest)
|
2018-12-10 13:42:57 +01:00
|
|
|
|
|
|
|
errChanUpdate.Signature, err = lnwire.NewSigFromSignature(sig)
|
2021-04-14 06:58:12 +02:00
|
|
|
require.NoError(t, err, "failed to create new signature")
|
2018-12-10 13:42:57 +01:00
|
|
|
}
|
|
|
|
|
2018-04-19 16:32:24 +02:00
|
|
|
// TestFindRoutesWithFeeLimit asserts that routes found by the FindRoutes method
|
|
|
|
// within the channel router contain a total fee less than or equal to the fee
|
|
|
|
// limit.
|
|
|
|
func TestFindRoutesWithFeeLimit(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
const startingBlockHeight = 101
|
2022-08-27 09:07:28 +02:00
|
|
|
ctx := createTestCtxFromFile(t, startingBlockHeight, basicGraphFilePath)
|
2018-04-19 16:32:24 +02:00
|
|
|
|
|
|
|
// This test will attempt to find routes from roasbeef to sophon for 100
|
|
|
|
// satoshis with a fee limit of 10 satoshis. There are two routes from
|
|
|
|
// roasbeef to sophon:
|
|
|
|
// 1. roasbeef -> songoku -> sophon
|
|
|
|
// 2. roasbeef -> phamnuwen -> sophon
|
|
|
|
// The second route violates our fee limit, so we should only expect to
|
|
|
|
// see the first route.
|
|
|
|
target := ctx.aliases["sophon"]
|
|
|
|
paymentAmt := lnwire.NewMSatFromSatoshis(100)
|
2019-03-05 12:42:29 +01:00
|
|
|
restrictions := &RestrictParams{
|
2019-03-19 11:45:10 +01:00
|
|
|
FeeLimit: lnwire.NewMSatFromSatoshis(10),
|
|
|
|
ProbabilitySource: noProbabilitySource,
|
2019-10-11 21:46:10 +02:00
|
|
|
CltvLimit: math.MaxUint32,
|
2019-03-05 12:42:29 +01:00
|
|
|
}
|
2018-04-19 16:32:24 +02:00
|
|
|
|
2022-12-19 23:10:22 +01:00
|
|
|
req, err := NewRouteRequest(
|
2024-06-26 05:08:57 +02:00
|
|
|
ctx.router.cfg.SelfNode, &target, paymentAmt, 0,
|
2022-12-15 22:48:56 +01:00
|
|
|
restrictions, nil, nil, nil, MinCLTVDelta,
|
2018-04-19 16:32:24 +02:00
|
|
|
)
|
2022-12-19 23:10:22 +01:00
|
|
|
require.NoError(t, err, "invalid route request")
|
|
|
|
|
|
|
|
route, _, err := ctx.router.FindRoute(req)
|
2021-04-14 06:58:12 +02:00
|
|
|
require.NoError(t, err, "unable to find any routes")
|
2018-04-19 16:32:24 +02:00
|
|
|
|
2021-04-14 06:58:12 +02:00
|
|
|
require.Falsef(t,
|
|
|
|
route.TotalFees() > restrictions.FeeLimit,
|
|
|
|
"route exceeded fee limit: %v", spew.Sdump(route),
|
|
|
|
)
|
2018-04-19 16:32:24 +02:00
|
|
|
|
2019-05-07 17:01:01 +02:00
|
|
|
hops := route.Hops
|
2021-04-14 06:58:12 +02:00
|
|
|
require.Equal(t, 2, len(hops), "expected 2 hops")
|
2018-04-19 16:32:24 +02:00
|
|
|
|
2021-04-14 06:58:12 +02:00
|
|
|
require.Equalf(t,
|
|
|
|
ctx.aliases["songoku"], hops[0].PubKeyBytes,
|
|
|
|
"expected first hop through songoku, got %s",
|
|
|
|
getAliasFromPubKey(hops[0].PubKeyBytes, ctx.aliases),
|
|
|
|
)
|
2018-04-19 16:32:24 +02:00
|
|
|
}
|
|
|
|
|
2017-03-21 02:58:21 +01:00
|
|
|
// TestSendPaymentRouteFailureFallback tests that when sending a payment, if
|
|
|
|
// one of the target routes is seen as unavailable, then the next route in the
|
|
|
|
// queue is used instead. This process should continue until either a payment
|
|
|
|
// succeeds, or all routes have been exhausted.
|
|
|
|
func TestSendPaymentRouteFailureFallback(t *testing.T) {
|
2017-06-17 00:59:20 +02:00
|
|
|
t.Parallel()
|
|
|
|
|
2017-03-21 02:58:21 +01:00
|
|
|
const startingBlockHeight = 101
|
2022-08-27 09:07:28 +02:00
|
|
|
ctx := createTestCtxFromFile(t, startingBlockHeight, basicGraphFilePath)
|
2017-03-21 02:58:21 +01:00
|
|
|
|
|
|
|
// Craft a LightningPayment struct that'll send a payment from roasbeef
|
2018-01-31 23:36:10 +01:00
|
|
|
// to luo ji for 1000 satoshis, with a maximum of 1000 satoshis in fees.
|
|
|
|
paymentAmt := lnwire.NewMSatFromSatoshis(1000)
|
2023-03-08 19:21:39 +01:00
|
|
|
payment := createDummyLightningPayment(
|
|
|
|
t, ctx.aliases["sophon"], paymentAmt,
|
|
|
|
)
|
2017-03-21 02:58:21 +01:00
|
|
|
|
|
|
|
var preImage [32]byte
|
|
|
|
copy(preImage[:], bytes.Repeat([]byte{9}, 32))
|
|
|
|
|
2021-04-10 13:27:58 +02:00
|
|
|
// Get the channel ID.
|
|
|
|
roasbeefSongoku := lnwire.NewShortChanIDFromInt(
|
|
|
|
ctx.getChannelIDFromAlias(t, "roasbeef", "songoku"),
|
|
|
|
)
|
|
|
|
|
2017-03-21 02:58:21 +01:00
|
|
|
// We'll modify the SendToSwitch method that's been set within the
|
2019-06-18 19:42:51 +02:00
|
|
|
// router's configuration to ignore the path that has son goku as the
|
2017-03-21 02:58:21 +01:00
|
|
|
// first hop. This should force the router to instead take the
|
2019-06-18 19:42:51 +02:00
|
|
|
// the more costly path (through pham nuwen).
|
2021-05-24 12:29:27 +02:00
|
|
|
ctx.router.cfg.Payer.(*mockPaymentAttemptDispatcherOld).setPaymentResult(
|
2019-05-16 15:27:28 +02:00
|
|
|
func(firstHop lnwire.ShortChannelID) ([32]byte, error) {
|
2019-06-18 19:42:51 +02:00
|
|
|
if firstHop == roasbeefSongoku {
|
2020-01-14 14:07:29 +01:00
|
|
|
return [32]byte{}, htlcswitch.NewForwardingError(
|
|
|
|
// TODO(roasbeef): temp node failure
|
|
|
|
// should be?
|
|
|
|
&lnwire.FailTemporaryChannelFailure{},
|
2020-01-14 14:07:42 +01:00
|
|
|
1,
|
2020-01-14 14:07:29 +01:00
|
|
|
)
|
2018-01-31 05:26:26 +01:00
|
|
|
}
|
2017-03-21 02:58:21 +01:00
|
|
|
|
2019-05-16 15:27:28 +02:00
|
|
|
return preImage, nil
|
|
|
|
})
|
2017-03-21 02:58:21 +01:00
|
|
|
|
2019-06-18 19:42:51 +02:00
|
|
|
// Send off the payment request to the router, route through pham nuwen
|
2017-03-21 02:58:21 +01:00
|
|
|
// should've been selected as a fall back and succeeded correctly.
|
2023-03-08 19:21:39 +01:00
|
|
|
paymentPreImage, route, err := ctx.router.SendPayment(payment)
|
|
|
|
require.NoErrorf(t, err, "unable to send payment: %v",
|
|
|
|
payment.paymentHash)
|
2017-03-21 02:58:21 +01:00
|
|
|
|
|
|
|
// The route selected should have two hops
|
2021-04-14 06:58:12 +02:00
|
|
|
require.Equal(t, 2, len(route.Hops), "incorrect route length")
|
2017-03-21 02:58:21 +01:00
|
|
|
|
|
|
|
// The preimage should match up with the once created above.
|
|
|
|
if !bytes.Equal(paymentPreImage[:], preImage[:]) {
|
|
|
|
t.Fatalf("incorrect preimage used: expected %x got %x",
|
|
|
|
preImage[:], paymentPreImage[:])
|
|
|
|
}
|
|
|
|
|
2019-06-18 19:42:51 +02:00
|
|
|
// The route should have pham nuwen as the first hop.
|
2021-04-14 06:58:12 +02:00
|
|
|
require.Equalf(t,
|
|
|
|
ctx.aliases["phamnuwen"], route.Hops[0].PubKeyBytes,
|
|
|
|
"route should go through phamnuwen as first hop, instead "+
|
|
|
|
"passes through: %v",
|
|
|
|
getAliasFromPubKey(route.Hops[0].PubKeyBytes, ctx.aliases),
|
|
|
|
)
|
2017-10-11 04:49:09 +02:00
|
|
|
}
|
|
|
|
|
2022-09-13 01:58:58 +02:00
|
|
|
// TestSendPaymentRouteInfiniteLoopWithBadHopHint tests that when sending
|
|
|
|
// a payment with a malformed hop hint in the first hop, the hint is ignored
|
|
|
|
// and the payment succeeds without an infinite loop of retries.
|
|
|
|
func TestSendPaymentRouteInfiniteLoopWithBadHopHint(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
const startingBlockHeight = 101
|
|
|
|
ctx := createTestCtxFromFile(t, startingBlockHeight, basicGraphFilePath)
|
|
|
|
|
|
|
|
source := ctx.aliases["roasbeef"]
|
|
|
|
sourceNodeID, err := btcec.ParsePubKey(source[:])
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
actualChannelID := ctx.getChannelIDFromAlias(t, "roasbeef", "songoku")
|
|
|
|
badChannelID := uint64(66666)
|
|
|
|
|
|
|
|
// Craft a LightningPayment struct that'll send a payment from roasbeef
|
|
|
|
// to songoku for 1000 satoshis.
|
|
|
|
paymentAmt := lnwire.NewMSatFromSatoshis(1000)
|
2023-03-08 19:21:39 +01:00
|
|
|
payment := createDummyLightningPayment(
|
|
|
|
t, ctx.aliases["songoku"], paymentAmt,
|
|
|
|
)
|
|
|
|
payment.RouteHints = [][]zpay32.HopHint{{
|
|
|
|
zpay32.HopHint{
|
|
|
|
NodeID: sourceNodeID,
|
|
|
|
ChannelID: badChannelID,
|
|
|
|
FeeBaseMSat: uint32(50),
|
|
|
|
CLTVExpiryDelta: uint16(200),
|
|
|
|
},
|
|
|
|
}}
|
2022-09-13 01:58:58 +02:00
|
|
|
|
|
|
|
var preImage [32]byte
|
|
|
|
copy(preImage[:], bytes.Repeat([]byte{9}, 32))
|
|
|
|
|
|
|
|
// Mock a payment result that always fails with FailUnknownNextPeer when
|
|
|
|
// the bad channel is the first hop.
|
|
|
|
badShortChanID := lnwire.NewShortChanIDFromInt(badChannelID)
|
|
|
|
newFwdError := htlcswitch.NewForwardingError(
|
|
|
|
&lnwire.FailUnknownNextPeer{}, 0,
|
|
|
|
)
|
|
|
|
|
|
|
|
payer, ok := ctx.router.cfg.Payer.(*mockPaymentAttemptDispatcherOld)
|
|
|
|
require.Equal(t, ok, true, "failed Payer cast")
|
|
|
|
|
|
|
|
payer.setPaymentResult(
|
|
|
|
func(firstHop lnwire.ShortChannelID) ([32]byte, error) {
|
|
|
|
// Returns a FailUnknownNextPeer if it's trying
|
|
|
|
// to pay an invalid channel.
|
|
|
|
if firstHop == badShortChanID {
|
|
|
|
return [32]byte{}, newFwdError
|
|
|
|
}
|
|
|
|
|
|
|
|
return preImage, nil
|
|
|
|
})
|
|
|
|
|
|
|
|
// Send off the payment request to the router, should succeed
|
|
|
|
// ignoring the bad channel id hint.
|
2023-03-08 19:21:39 +01:00
|
|
|
paymentPreImage, route, paymentErr := ctx.router.SendPayment(payment)
|
|
|
|
require.NoErrorf(t, paymentErr, "unable to send payment: %v",
|
|
|
|
payment.paymentHash)
|
2022-09-13 01:58:58 +02:00
|
|
|
|
|
|
|
// The preimage should match up with the one created above.
|
|
|
|
require.Equal(t, preImage[:], paymentPreImage[:], "incorrect preimage")
|
|
|
|
|
|
|
|
// The route should have songoku as the first hop.
|
|
|
|
require.Equal(t, actualChannelID, route.Hops[0].ChannelID,
|
|
|
|
"route should go through the correct channel id",
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2018-08-18 20:05:58 +02:00
|
|
|
// TestChannelUpdateValidation tests that a failed payment with an associated
|
|
|
|
// channel update will only be applied to the graph when the update contains a
|
|
|
|
// valid signature.
|
|
|
|
func TestChannelUpdateValidation(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
// Setup a three node network.
|
2018-12-09 03:42:46 +01:00
|
|
|
chanCapSat := btcutil.Amount(100000)
|
2021-04-14 06:58:12 +02:00
|
|
|
feeRate := lnwire.MilliSatoshi(400)
|
2018-08-18 20:05:58 +02:00
|
|
|
testChannels := []*testChannel{
|
2018-12-09 03:42:46 +01:00
|
|
|
symmetricTestChannel("a", "b", chanCapSat, &testChannelPolicy{
|
2018-08-18 20:05:58 +02:00
|
|
|
Expiry: 144,
|
2021-04-14 06:58:12 +02:00
|
|
|
FeeRate: feeRate,
|
2018-08-18 20:05:58 +02:00
|
|
|
MinHTLC: 1,
|
2018-12-09 03:42:46 +01:00
|
|
|
MaxHTLC: lnwire.NewMSatFromSatoshis(chanCapSat),
|
2018-08-18 20:05:58 +02:00
|
|
|
}, 1),
|
2018-12-09 03:42:46 +01:00
|
|
|
symmetricTestChannel("b", "c", chanCapSat, &testChannelPolicy{
|
2018-08-18 20:05:58 +02:00
|
|
|
Expiry: 144,
|
2021-04-14 06:58:12 +02:00
|
|
|
FeeRate: feeRate,
|
2018-08-18 20:05:58 +02:00
|
|
|
MinHTLC: 1,
|
2018-12-09 03:42:46 +01:00
|
|
|
MaxHTLC: lnwire.NewMSatFromSatoshis(chanCapSat),
|
2018-08-18 20:05:58 +02:00
|
|
|
}, 2),
|
|
|
|
}
|
|
|
|
|
2022-09-12 22:15:52 +02:00
|
|
|
testGraph, err := createTestGraphFromChannels(
|
|
|
|
t, true, testChannels, "a",
|
|
|
|
)
|
2021-04-14 06:58:12 +02:00
|
|
|
require.NoError(t, err, "unable to create graph")
|
2018-08-18 20:05:58 +02:00
|
|
|
|
|
|
|
const startingBlockHeight = 101
|
2024-06-17 01:30:01 +02:00
|
|
|
ctx := createTestCtxFromGraphInstance(t, startingBlockHeight, testGraph)
|
2018-08-18 20:05:58 +02:00
|
|
|
|
|
|
|
// Assert that the initially configured fee is retrieved correctly.
|
2024-06-17 01:30:01 +02:00
|
|
|
_, e1, e2, err := ctx.graph.FetchChannelEdgesByID(
|
|
|
|
lnwire.NewShortChanIDFromInt(1).ToUint64(),
|
2022-09-12 22:15:52 +02:00
|
|
|
)
|
2021-04-14 06:58:12 +02:00
|
|
|
require.NoError(t, err, "cannot retrieve channel")
|
2018-08-18 20:05:58 +02:00
|
|
|
|
2022-09-12 22:15:52 +02:00
|
|
|
require.Equal(t, feeRate, e1.FeeProportionalMillionths, "invalid fee")
|
|
|
|
require.Equal(t, feeRate, e2.FeeProportionalMillionths, "invalid fee")
|
2018-08-18 20:05:58 +02:00
|
|
|
|
|
|
|
// Setup a route from source a to destination c. The route will be used
|
|
|
|
// in a call to SendToRoute. SendToRoute also applies channel updates,
|
|
|
|
// but it saves us from including RequestRoute in the test scope too.
|
2019-03-05 16:55:19 +01:00
|
|
|
hop1 := ctx.aliases["b"]
|
|
|
|
hop2 := ctx.aliases["c"]
|
2019-04-05 17:36:11 +02:00
|
|
|
hops := []*route.Hop{
|
2018-08-18 20:05:58 +02:00
|
|
|
{
|
2019-07-31 06:41:58 +02:00
|
|
|
ChannelID: 1,
|
|
|
|
PubKeyBytes: hop1,
|
|
|
|
LegacyPayload: true,
|
2018-08-18 20:05:58 +02:00
|
|
|
},
|
|
|
|
{
|
2019-07-31 06:41:58 +02:00
|
|
|
ChannelID: 2,
|
|
|
|
PubKeyBytes: hop2,
|
|
|
|
LegacyPayload: true,
|
2018-08-18 20:05:58 +02:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2019-04-05 17:36:11 +02:00
|
|
|
rt, err := route.NewRouteFromHops(
|
2018-10-24 21:16:38 +02:00
|
|
|
lnwire.MilliSatoshi(10000), 100,
|
2019-03-05 16:55:19 +01:00
|
|
|
ctx.aliases["a"], hops,
|
2018-10-24 21:16:38 +02:00
|
|
|
)
|
2021-04-14 06:58:12 +02:00
|
|
|
require.NoError(t, err, "unable to create route")
|
2018-08-18 20:05:58 +02:00
|
|
|
|
|
|
|
// Set up a channel update message with an invalid signature to be
|
|
|
|
// returned to the sender.
|
2023-01-17 04:33:21 +01:00
|
|
|
var invalidSignature lnwire.Sig
|
2024-08-21 08:39:37 +02:00
|
|
|
errChanUpdate := lnwire.ChannelUpdate1{
|
2022-09-12 22:15:52 +02:00
|
|
|
Signature: invalidSignature,
|
|
|
|
FeeRate: 500,
|
|
|
|
ShortChannelID: lnwire.NewShortChanIDFromInt(1),
|
|
|
|
Timestamp: uint32(testTime.Add(time.Minute).Unix()),
|
|
|
|
MessageFlags: e2.MessageFlags,
|
|
|
|
ChannelFlags: e2.ChannelFlags,
|
|
|
|
HtlcMaximumMsat: e2.MaxHTLC,
|
2018-08-18 20:05:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// We'll modify the SendToSwitch method so that it simulates a failed
|
|
|
|
// payment with an error originating from the first hop of the route.
|
|
|
|
// The unsigned channel update is attached to the failure message.
|
2021-05-24 12:29:27 +02:00
|
|
|
ctx.router.cfg.Payer.(*mockPaymentAttemptDispatcherOld).setPaymentResult(
|
2019-05-16 15:27:28 +02:00
|
|
|
func(firstHop lnwire.ShortChannelID) ([32]byte, error) {
|
2020-01-14 14:07:29 +01:00
|
|
|
return [32]byte{}, htlcswitch.NewForwardingError(
|
|
|
|
&lnwire.FailFeeInsufficient{
|
2019-05-16 15:27:28 +02:00
|
|
|
Update: errChanUpdate,
|
|
|
|
},
|
2020-01-14 14:07:42 +01:00
|
|
|
1,
|
2020-01-14 14:07:29 +01:00
|
|
|
)
|
2019-05-16 15:27:28 +02:00
|
|
|
})
|
2018-08-18 20:05:58 +02:00
|
|
|
|
|
|
|
// The payment parameter is mostly redundant in SendToRoute. Can be left
|
|
|
|
// empty for this test.
|
2018-08-08 11:09:30 +02:00
|
|
|
var payment lntypes.Hash
|
2018-08-18 20:05:58 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
// Instruct the mock graph builder to reject the next update we send
|
|
|
|
// it.
|
|
|
|
ctx.graphBuilder.setNextReject(true)
|
|
|
|
|
2018-08-18 20:05:58 +02:00
|
|
|
// Send off the payment request to the router. The specified route
|
|
|
|
// should be attempted and the channel update should be received by
|
2024-06-17 01:30:01 +02:00
|
|
|
// graph and ignored because it is missing a valid signature.
|
2024-04-16 11:03:59 +02:00
|
|
|
_, err = ctx.router.SendToRoute(payment, rt, nil)
|
2021-04-14 06:58:12 +02:00
|
|
|
require.Error(t, err, "expected route to fail with channel update")
|
2018-08-18 20:05:58 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
_, e1, e2, err = ctx.graph.FetchChannelEdgesByID(
|
|
|
|
lnwire.NewShortChanIDFromInt(1).ToUint64(),
|
2022-09-12 22:15:52 +02:00
|
|
|
)
|
2021-04-14 06:58:12 +02:00
|
|
|
require.NoError(t, err, "cannot retrieve channel")
|
2018-08-18 20:05:58 +02:00
|
|
|
|
2022-09-12 22:15:52 +02:00
|
|
|
require.Equal(t, feeRate, e1.FeeProportionalMillionths,
|
|
|
|
"fee updated without valid signature")
|
|
|
|
require.Equal(t, feeRate, e2.FeeProportionalMillionths,
|
|
|
|
"fee updated without valid signature")
|
2018-08-18 20:05:58 +02:00
|
|
|
|
|
|
|
// Next, add a signature to the channel update.
|
2021-04-14 06:58:12 +02:00
|
|
|
signErrChanUpdate(t, testGraph.privKeyMap["b"], &errChanUpdate)
|
2018-08-18 20:05:58 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
// Let the graph builder accept the next update.
|
|
|
|
ctx.graphBuilder.setNextReject(false)
|
|
|
|
|
2018-08-18 20:05:58 +02:00
|
|
|
// Retry the payment using the same route as before.
|
2024-04-16 11:03:59 +02:00
|
|
|
_, err = ctx.router.SendToRoute(payment, rt, nil)
|
2022-09-12 22:15:52 +02:00
|
|
|
require.Error(t, err, "expected route to fail with channel update")
|
2018-08-18 20:05:58 +02:00
|
|
|
|
|
|
|
// This time a valid signature was supplied and the policy change should
|
|
|
|
// have been applied to the graph.
|
2024-06-17 01:30:01 +02:00
|
|
|
_, e1, e2, err = ctx.graph.FetchChannelEdgesByID(
|
|
|
|
lnwire.NewShortChanIDFromInt(1).ToUint64(),
|
2022-09-12 22:15:52 +02:00
|
|
|
)
|
2021-04-14 06:58:12 +02:00
|
|
|
require.NoError(t, err, "cannot retrieve channel")
|
2018-08-18 20:05:58 +02:00
|
|
|
|
2022-09-12 22:15:52 +02:00
|
|
|
require.Equal(t, feeRate, e1.FeeProportionalMillionths,
|
|
|
|
"fee should not be updated")
|
|
|
|
require.EqualValues(t, 500, int(e2.FeeProportionalMillionths),
|
|
|
|
"fee not updated even though signature is valid")
|
2018-08-18 20:05:58 +02:00
|
|
|
}
|
|
|
|
|
2018-03-20 03:08:55 +01:00
|
|
|
// TestSendPaymentErrorRepeatedFeeInsufficient tests that if we receive
|
|
|
|
// multiple fee related errors from a channel that we're attempting to route
|
|
|
|
// through, then we'll prune the channel after the second attempt.
|
|
|
|
func TestSendPaymentErrorRepeatedFeeInsufficient(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
const startingBlockHeight = 101
|
2022-08-27 09:07:28 +02:00
|
|
|
ctx := createTestCtxFromFile(t, startingBlockHeight, basicGraphFilePath)
|
2018-03-20 03:08:55 +01:00
|
|
|
|
2021-04-10 13:27:58 +02:00
|
|
|
// Get the channel ID.
|
|
|
|
roasbeefSongokuChanID := ctx.getChannelIDFromAlias(
|
|
|
|
t, "roasbeef", "songoku",
|
|
|
|
)
|
|
|
|
songokuSophonChanID := ctx.getChannelIDFromAlias(
|
|
|
|
t, "songoku", "sophon",
|
|
|
|
)
|
|
|
|
|
2018-03-20 03:08:55 +01:00
|
|
|
// Craft a LightningPayment struct that'll send a payment from roasbeef
|
2018-12-10 13:42:57 +01:00
|
|
|
// to sophon for 1000 satoshis.
|
2018-04-19 16:32:24 +02:00
|
|
|
amt := lnwire.NewMSatFromSatoshis(1000)
|
2023-03-08 19:21:39 +01:00
|
|
|
payment := createDummyLightningPayment(
|
|
|
|
t, ctx.aliases["sophon"], amt,
|
|
|
|
)
|
2018-03-20 03:08:55 +01:00
|
|
|
|
|
|
|
var preImage [32]byte
|
|
|
|
copy(preImage[:], bytes.Repeat([]byte{9}, 32))
|
|
|
|
|
2018-12-10 13:42:57 +01:00
|
|
|
// We'll also fetch the first outgoing channel edge from son goku
|
|
|
|
// to sophon. We'll obtain this as we'll need to to generate the
|
2018-03-20 03:08:55 +01:00
|
|
|
// FeeInsufficient error that we'll send back.
|
2018-12-10 13:42:57 +01:00
|
|
|
_, _, edgeUpdateToFail, err := ctx.graph.FetchChannelEdgesByID(
|
|
|
|
songokuSophonChanID,
|
|
|
|
)
|
2021-04-14 06:58:12 +02:00
|
|
|
require.NoError(t, err, "unable to fetch chan id")
|
2018-03-20 03:08:55 +01:00
|
|
|
|
2024-08-21 08:39:37 +02:00
|
|
|
errChanUpdate := lnwire.ChannelUpdate1{
|
2018-12-10 13:42:57 +01:00
|
|
|
ShortChannelID: lnwire.NewShortChanIDFromInt(
|
|
|
|
songokuSophonChanID,
|
|
|
|
),
|
2018-12-09 03:42:46 +01:00
|
|
|
Timestamp: uint32(edgeUpdateToFail.LastUpdate.Unix()),
|
|
|
|
MessageFlags: edgeUpdateToFail.MessageFlags,
|
|
|
|
ChannelFlags: edgeUpdateToFail.ChannelFlags,
|
|
|
|
TimeLockDelta: edgeUpdateToFail.TimeLockDelta,
|
|
|
|
HtlcMinimumMsat: edgeUpdateToFail.MinHTLC,
|
|
|
|
HtlcMaximumMsat: edgeUpdateToFail.MaxHTLC,
|
|
|
|
BaseFee: uint32(edgeUpdateToFail.FeeBaseMSat),
|
|
|
|
FeeRate: uint32(edgeUpdateToFail.FeeProportionalMillionths),
|
2018-03-20 03:08:55 +01:00
|
|
|
}
|
|
|
|
|
2021-04-14 06:58:12 +02:00
|
|
|
signErrChanUpdate(t, ctx.privKeys["songoku"], &errChanUpdate)
|
2018-12-10 13:42:57 +01:00
|
|
|
|
2018-03-20 03:08:55 +01:00
|
|
|
// We'll now modify the SendToSwitch method to return an error for the
|
2018-04-06 03:01:40 +02:00
|
|
|
// outgoing channel to Son goku. This will be a fee related error, so
|
|
|
|
// it should only cause the edge to be pruned after the second attempt.
|
2024-06-18 21:34:25 +02:00
|
|
|
dispatcher, ok := ctx.router.cfg.Payer.(*mockPaymentAttemptDispatcherOld) //nolint:lll
|
|
|
|
require.True(t, ok)
|
2019-05-16 15:27:28 +02:00
|
|
|
|
2024-06-18 21:34:25 +02:00
|
|
|
dispatcher.setPaymentResult(func(firstHop lnwire.ShortChannelID) (
|
|
|
|
[32]byte, error) {
|
|
|
|
|
|
|
|
roasbeefSongoku := lnwire.NewShortChanIDFromInt(
|
|
|
|
roasbeefSongokuChanID,
|
|
|
|
)
|
|
|
|
if firstHop == roasbeefSongoku {
|
|
|
|
return [32]byte{}, htlcswitch.NewForwardingError(
|
|
|
|
// Within our error, we'll add a
|
|
|
|
// channel update which is meant to
|
|
|
|
// reflect the new fee schedule for the
|
|
|
|
// node/channel.
|
|
|
|
&lnwire.FailFeeInsufficient{
|
|
|
|
Update: errChanUpdate,
|
|
|
|
}, 1,
|
2018-12-10 13:42:57 +01:00
|
|
|
)
|
2024-06-18 21:34:25 +02:00
|
|
|
}
|
2019-03-05 16:55:19 +01:00
|
|
|
|
2024-06-18 21:34:25 +02:00
|
|
|
return preImage, nil
|
|
|
|
})
|
2018-03-20 03:08:55 +01:00
|
|
|
|
2021-04-14 06:58:12 +02:00
|
|
|
// Send off the payment request to the router, route through phamnuwen
|
2018-03-20 03:08:55 +01:00
|
|
|
// should've been selected as a fall back and succeeded correctly.
|
2023-03-08 19:21:39 +01:00
|
|
|
paymentPreImage, route, err := ctx.router.SendPayment(payment)
|
|
|
|
require.NoErrorf(t, err, "unable to send payment: %v",
|
|
|
|
payment.paymentHash)
|
2018-03-20 03:08:55 +01:00
|
|
|
|
|
|
|
// The route selected should have two hops
|
2021-04-14 06:58:12 +02:00
|
|
|
require.Equal(t, 2, len(route.Hops), "incorrect route length")
|
2018-03-20 03:08:55 +01:00
|
|
|
|
|
|
|
// The preimage should match up with the once created above.
|
2021-04-14 06:58:12 +02:00
|
|
|
require.Equal(t, preImage[:], paymentPreImage[:], "incorrect preimage")
|
2018-03-20 03:08:55 +01:00
|
|
|
|
2018-04-06 03:01:40 +02:00
|
|
|
// The route should have pham nuwen as the first hop.
|
2021-04-14 06:58:12 +02:00
|
|
|
require.Equalf(t,
|
|
|
|
ctx.aliases["phamnuwen"], route.Hops[0].PubKeyBytes,
|
|
|
|
"route should go through pham nuwen as first hop, "+
|
2018-03-20 03:08:55 +01:00
|
|
|
"instead passes through: %v",
|
2021-04-14 06:58:12 +02:00
|
|
|
getAliasFromPubKey(route.Hops[0].PubKeyBytes, ctx.aliases),
|
|
|
|
)
|
2018-03-20 03:08:55 +01:00
|
|
|
}
|
|
|
|
|
2018-12-22 20:38:03 +01:00
|
|
|
// TestSendPaymentErrorFeeInsufficientPrivateEdge tests that if we receive
|
|
|
|
// a fee related error from a private channel that we're attempting to route
|
|
|
|
// through, then we'll update the fees in the route hints and successfully
|
|
|
|
// route through the private channel in the second attempt.
|
2021-04-14 14:01:27 +02:00
|
|
|
//
|
|
|
|
// The test will send a payment from roasbeef to elst, available paths are,
|
|
|
|
// path1: roasbeef -> songoku -> sophon -> elst, total fee: 210k
|
|
|
|
// path2: roasbeef -> phamnuwen -> sophon -> elst, total fee: 220k
|
|
|
|
// path3: roasbeef -> songoku ->(private channel) elst
|
|
|
|
// We will setup the path3 to have the lowest fee so it's always the preferred
|
|
|
|
// path.
|
2018-12-22 20:38:03 +01:00
|
|
|
func TestSendPaymentErrorFeeInsufficientPrivateEdge(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
const startingBlockHeight = 101
|
2022-08-27 09:07:28 +02:00
|
|
|
ctx := createTestCtxFromFile(t, startingBlockHeight, basicGraphFilePath)
|
2018-12-22 20:38:03 +01:00
|
|
|
|
2021-04-10 13:27:58 +02:00
|
|
|
// Get the channel ID.
|
|
|
|
roasbeefSongoku := lnwire.NewShortChanIDFromInt(
|
|
|
|
ctx.getChannelIDFromAlias(t, "roasbeef", "songoku"),
|
|
|
|
)
|
|
|
|
|
2021-04-14 14:01:27 +02:00
|
|
|
var (
|
|
|
|
preImage [32]byte
|
|
|
|
amt = lnwire.NewMSatFromSatoshis(1000)
|
|
|
|
privateChannelID = uint64(55555)
|
|
|
|
feeBaseMSat = uint32(15)
|
|
|
|
expiryDelta = uint16(32)
|
|
|
|
sgNode = ctx.aliases["songoku"]
|
|
|
|
)
|
|
|
|
|
2022-02-23 14:48:00 +01:00
|
|
|
sgNodeID, err := btcec.ParsePubKey(sgNode[:])
|
2018-12-22 20:38:03 +01:00
|
|
|
require.NoError(t, err)
|
2021-04-14 14:01:27 +02:00
|
|
|
|
|
|
|
// Craft a LightningPayment struct that'll send a payment from roasbeef
|
|
|
|
// to elst, through a private channel between songoku and elst for
|
|
|
|
// 1000 satoshis. This route has lowest fees compared with the rest.
|
|
|
|
// This also holds when the private channel fee is updated to a higher
|
|
|
|
// value.
|
2023-03-08 19:21:39 +01:00
|
|
|
payment := createDummyLightningPayment(
|
|
|
|
t, ctx.aliases["elst"], amt,
|
|
|
|
)
|
|
|
|
payment.RouteHints = [][]zpay32.HopHint{{
|
|
|
|
// Add a private channel between songoku and elst.
|
|
|
|
zpay32.HopHint{
|
|
|
|
NodeID: sgNodeID,
|
|
|
|
ChannelID: privateChannelID,
|
|
|
|
FeeBaseMSat: feeBaseMSat,
|
|
|
|
CLTVExpiryDelta: expiryDelta,
|
|
|
|
},
|
|
|
|
}}
|
2018-12-22 20:38:03 +01:00
|
|
|
|
|
|
|
// Prepare an error update for the private channel, with twice the
|
|
|
|
// original fee.
|
|
|
|
updatedFeeBaseMSat := feeBaseMSat * 2
|
2024-08-21 08:39:37 +02:00
|
|
|
errChanUpdate := lnwire.ChannelUpdate1{
|
2018-12-22 20:38:03 +01:00
|
|
|
ShortChannelID: lnwire.NewShortChanIDFromInt(privateChannelID),
|
|
|
|
Timestamp: uint32(testTime.Add(time.Minute).Unix()),
|
|
|
|
BaseFee: updatedFeeBaseMSat,
|
|
|
|
TimeLockDelta: expiryDelta,
|
|
|
|
}
|
2021-04-14 06:58:12 +02:00
|
|
|
signErrChanUpdate(t, ctx.privKeys["songoku"], &errChanUpdate)
|
2018-12-22 20:38:03 +01:00
|
|
|
|
2021-04-14 14:01:27 +02:00
|
|
|
// We'll now modify the SendHTLC method to return an error for the
|
|
|
|
// outgoing channel to songoku.
|
2018-12-22 20:38:03 +01:00
|
|
|
errorReturned := false
|
2021-04-14 14:01:27 +02:00
|
|
|
copy(preImage[:], bytes.Repeat([]byte{9}, 32))
|
2021-05-24 12:29:27 +02:00
|
|
|
ctx.router.cfg.Payer.(*mockPaymentAttemptDispatcherOld).setPaymentResult(
|
2018-12-22 20:38:03 +01:00
|
|
|
func(firstHop lnwire.ShortChannelID) ([32]byte, error) {
|
2021-04-10 13:27:58 +02:00
|
|
|
if firstHop != roasbeefSongoku || errorReturned {
|
2018-12-22 20:38:03 +01:00
|
|
|
return preImage, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
errorReturned = true
|
|
|
|
return [32]byte{}, htlcswitch.NewForwardingError(
|
|
|
|
// Within our error, we'll add a
|
|
|
|
// channel update which is meant to
|
|
|
|
// reflect the new fee schedule for the
|
|
|
|
// node/channel.
|
|
|
|
&lnwire.FailFeeInsufficient{
|
|
|
|
Update: errChanUpdate,
|
|
|
|
}, 1,
|
|
|
|
)
|
2022-02-15 16:25:54 +01:00
|
|
|
},
|
|
|
|
)
|
2018-12-22 20:38:03 +01:00
|
|
|
|
|
|
|
// Send off the payment request to the router, route through son
|
|
|
|
// goku and then across the private channel to elst.
|
2023-03-08 19:21:39 +01:00
|
|
|
paymentPreImage, route, err := ctx.router.SendPayment(payment)
|
|
|
|
require.NoErrorf(t, err, "unable to send payment: %v",
|
|
|
|
payment.paymentHash)
|
2018-12-22 20:38:03 +01:00
|
|
|
|
|
|
|
require.True(t, errorReturned,
|
|
|
|
"failed to simulate error in the first payment attempt",
|
|
|
|
)
|
|
|
|
|
|
|
|
// The route selected should have two hops. Make sure that,
|
2021-04-14 14:01:27 +02:00
|
|
|
// path: roasbeef -> son goku -> sophon -> elst
|
|
|
|
// path: roasbeef -> pham nuwen -> sophon -> elst
|
2018-12-22 20:38:03 +01:00
|
|
|
// are not selected instead.
|
|
|
|
require.Equal(t, 2, len(route.Hops), "incorrect route length")
|
|
|
|
|
|
|
|
// The preimage should match up with the one created above.
|
|
|
|
require.Equal(t,
|
|
|
|
paymentPreImage[:], preImage[:], "incorrect preimage used",
|
|
|
|
)
|
|
|
|
|
|
|
|
// The route should have son goku as the first hop.
|
|
|
|
require.Equal(t, route.Hops[0].PubKeyBytes, ctx.aliases["songoku"],
|
|
|
|
"route should go through son goku as first hop",
|
|
|
|
)
|
|
|
|
|
|
|
|
// The route should pass via the private channel.
|
|
|
|
require.Equal(t,
|
|
|
|
privateChannelID, route.FinalHop().ChannelID,
|
|
|
|
"route did not pass through private channel "+
|
|
|
|
"between pham nuwen and elst",
|
|
|
|
)
|
|
|
|
|
|
|
|
// The route should have the updated fee.
|
2021-04-14 14:01:27 +02:00
|
|
|
require.Equal(t,
|
|
|
|
lnwire.MilliSatoshi(updatedFeeBaseMSat).String(),
|
|
|
|
route.HopFee(0).String(),
|
2018-12-22 20:38:03 +01:00
|
|
|
"fee to forward to the private channel not matched",
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2021-04-15 18:40:22 +02:00
|
|
|
// TestSendPaymentPrivateEdgeUpdateFeeExceedsLimit tests that upon receiving a
|
|
|
|
// ChannelUpdate in a fee related error from the private channel, we won't
|
|
|
|
// choose the route in our second attempt if the updated fee exceeds our fee
|
|
|
|
// limit specified in the payment.
|
|
|
|
//
|
|
|
|
// The test will send a payment from roasbeef to elst, available paths are,
|
|
|
|
// path1: roasbeef -> songoku -> sophon -> elst, total fee: 210k
|
|
|
|
// path2: roasbeef -> phamnuwen -> sophon -> elst, total fee: 220k
|
|
|
|
// path3: roasbeef -> songoku ->(private channel) elst
|
|
|
|
// We will setup the path3 to have the lowest fee and then update it with a fee
|
|
|
|
// exceeds our fee limit, thus this route won't be chosen.
|
|
|
|
func TestSendPaymentPrivateEdgeUpdateFeeExceedsLimit(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
const startingBlockHeight = 101
|
2022-08-27 09:07:28 +02:00
|
|
|
ctx := createTestCtxFromFile(t, startingBlockHeight, basicGraphFilePath)
|
2021-04-15 18:40:22 +02:00
|
|
|
|
|
|
|
// Get the channel ID.
|
|
|
|
roasbeefSongoku := lnwire.NewShortChanIDFromInt(
|
|
|
|
ctx.getChannelIDFromAlias(t, "roasbeef", "songoku"),
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
preImage [32]byte
|
|
|
|
amt = lnwire.NewMSatFromSatoshis(1000)
|
|
|
|
privateChannelID = uint64(55555)
|
|
|
|
feeBaseMSat = uint32(15)
|
|
|
|
expiryDelta = uint16(32)
|
|
|
|
sgNode = ctx.aliases["songoku"]
|
|
|
|
feeLimit = lnwire.MilliSatoshi(500000)
|
|
|
|
)
|
|
|
|
|
2022-02-23 14:48:00 +01:00
|
|
|
sgNodeID, err := btcec.ParsePubKey(sgNode[:])
|
2021-04-15 18:40:22 +02:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
// Craft a LightningPayment struct that'll send a payment from roasbeef
|
|
|
|
// to elst, through a private channel between songoku and elst for
|
|
|
|
// 1000 satoshis. This route has lowest fees compared with the rest.
|
2023-03-08 19:21:39 +01:00
|
|
|
payment := createDummyLightningPayment(
|
|
|
|
t, ctx.aliases["elst"], amt,
|
|
|
|
)
|
|
|
|
payment.RouteHints = [][]zpay32.HopHint{{
|
|
|
|
// Add a private channel between songoku and elst.
|
|
|
|
zpay32.HopHint{
|
|
|
|
NodeID: sgNodeID,
|
|
|
|
ChannelID: privateChannelID,
|
|
|
|
FeeBaseMSat: feeBaseMSat,
|
|
|
|
CLTVExpiryDelta: expiryDelta,
|
|
|
|
},
|
|
|
|
}}
|
2021-04-15 18:40:22 +02:00
|
|
|
|
|
|
|
// Prepare an error update for the private channel. The updated fee
|
|
|
|
// will exceeds the feeLimit.
|
|
|
|
updatedFeeBaseMSat := feeBaseMSat + uint32(feeLimit)
|
2024-08-21 08:39:37 +02:00
|
|
|
errChanUpdate := lnwire.ChannelUpdate1{
|
2021-04-15 18:40:22 +02:00
|
|
|
ShortChannelID: lnwire.NewShortChanIDFromInt(privateChannelID),
|
|
|
|
Timestamp: uint32(testTime.Add(time.Minute).Unix()),
|
|
|
|
BaseFee: updatedFeeBaseMSat,
|
|
|
|
TimeLockDelta: expiryDelta,
|
|
|
|
}
|
|
|
|
signErrChanUpdate(t, ctx.privKeys["songoku"], &errChanUpdate)
|
|
|
|
|
|
|
|
// We'll now modify the SendHTLC method to return an error for the
|
|
|
|
// outgoing channel to songoku.
|
|
|
|
errorReturned := false
|
|
|
|
copy(preImage[:], bytes.Repeat([]byte{9}, 32))
|
2021-05-24 12:29:27 +02:00
|
|
|
ctx.router.cfg.Payer.(*mockPaymentAttemptDispatcherOld).setPaymentResult(
|
2021-04-15 18:40:22 +02:00
|
|
|
func(firstHop lnwire.ShortChannelID) ([32]byte, error) {
|
|
|
|
if firstHop != roasbeefSongoku || errorReturned {
|
|
|
|
return preImage, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
errorReturned = true
|
|
|
|
return [32]byte{}, htlcswitch.NewForwardingError(
|
|
|
|
// Within our error, we'll add a
|
|
|
|
// channel update which is meant to
|
|
|
|
// reflect the new fee schedule for the
|
|
|
|
// node/channel.
|
|
|
|
&lnwire.FailFeeInsufficient{
|
|
|
|
Update: errChanUpdate,
|
|
|
|
}, 1,
|
|
|
|
)
|
2022-02-15 16:25:54 +01:00
|
|
|
},
|
|
|
|
)
|
2021-04-15 18:40:22 +02:00
|
|
|
|
|
|
|
// Send off the payment request to the router, route through son
|
|
|
|
// goku and then across the private channel to elst.
|
2023-03-08 19:21:39 +01:00
|
|
|
paymentPreImage, route, err := ctx.router.SendPayment(payment)
|
|
|
|
require.NoErrorf(t, err, "unable to send payment: %v",
|
|
|
|
payment.paymentHash)
|
2021-04-15 18:40:22 +02:00
|
|
|
|
|
|
|
require.True(t, errorReturned,
|
|
|
|
"failed to simulate error in the first payment attempt",
|
|
|
|
)
|
|
|
|
|
|
|
|
// The route selected should have three hops. Make sure that,
|
|
|
|
// path1: roasbeef -> son goku -> sophon -> elst
|
|
|
|
// path2: roasbeef -> pham nuwen -> sophon -> elst
|
|
|
|
// path3: roasbeef -> sophon -> (private channel) else
|
|
|
|
// path1 is selected.
|
|
|
|
require.Equal(t, 3, len(route.Hops), "incorrect route length")
|
|
|
|
|
|
|
|
// The preimage should match up with the one created above.
|
|
|
|
require.Equal(t,
|
|
|
|
paymentPreImage[:], preImage[:], "incorrect preimage used",
|
|
|
|
)
|
|
|
|
|
|
|
|
// The route should have son goku as the first hop.
|
|
|
|
require.Equal(t, route.Hops[0].PubKeyBytes, ctx.aliases["songoku"],
|
|
|
|
"route should go through son goku as the first hop",
|
|
|
|
)
|
|
|
|
|
|
|
|
// The route should have sophon as the first hop.
|
|
|
|
require.Equal(t, route.Hops[1].PubKeyBytes, ctx.aliases["sophon"],
|
|
|
|
"route should go through sophon as the second hop",
|
|
|
|
)
|
|
|
|
// The route should pass via the public channel.
|
|
|
|
require.Equal(t, route.FinalHop().PubKeyBytes, ctx.aliases["elst"],
|
|
|
|
"route should go through elst as the final hop",
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2018-03-23 01:40:07 +01:00
|
|
|
// TestSendPaymentErrorNonFinalTimeLockErrors tests that if we receive either
|
|
|
|
// an ExpiryTooSoon or a IncorrectCltvExpiry error from a node, then we prune
|
2022-01-13 17:29:43 +01:00
|
|
|
// that node from the available graph within a mission control session. This
|
2018-03-23 01:40:07 +01:00
|
|
|
// test ensures that we'll route around errors due to nodes not knowing the
|
|
|
|
// current block height.
|
|
|
|
func TestSendPaymentErrorNonFinalTimeLockErrors(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
const startingBlockHeight = 101
|
2022-08-27 09:07:28 +02:00
|
|
|
ctx := createTestCtxFromFile(
|
2021-04-14 06:58:12 +02:00
|
|
|
t, startingBlockHeight, basicGraphFilePath,
|
|
|
|
)
|
2018-03-23 01:40:07 +01:00
|
|
|
|
|
|
|
// Craft a LightningPayment struct that'll send a payment from roasbeef
|
|
|
|
// to sophon for 1k satoshis.
|
2018-04-19 16:32:24 +02:00
|
|
|
amt := lnwire.NewMSatFromSatoshis(1000)
|
2023-03-08 19:21:39 +01:00
|
|
|
payment := createDummyLightningPayment(
|
|
|
|
t, ctx.aliases["sophon"], amt,
|
|
|
|
)
|
2018-03-23 01:40:07 +01:00
|
|
|
|
|
|
|
var preImage [32]byte
|
|
|
|
copy(preImage[:], bytes.Repeat([]byte{9}, 32))
|
|
|
|
|
|
|
|
// We'll also fetch the first outgoing channel edge from roasbeef to
|
|
|
|
// son goku. This edge will be included in the time lock related expiry
|
|
|
|
// errors that we'll get back due to disagrements in what the current
|
|
|
|
// block height is.
|
2021-04-10 13:27:58 +02:00
|
|
|
chanID := ctx.getChannelIDFromAlias(t, "roasbeef", "songoku")
|
2018-07-10 03:12:34 +02:00
|
|
|
roasbeefSongoku := lnwire.NewShortChanIDFromInt(chanID)
|
2021-04-10 13:27:58 +02:00
|
|
|
|
2018-12-09 03:42:46 +01:00
|
|
|
_, _, edgeUpdateToFail, err := ctx.graph.FetchChannelEdgesByID(chanID)
|
2021-04-14 06:58:12 +02:00
|
|
|
require.NoError(t, err, "unable to fetch chan id")
|
2018-03-23 01:40:07 +01:00
|
|
|
|
2024-08-21 08:39:37 +02:00
|
|
|
errChanUpdate := lnwire.ChannelUpdate1{
|
2018-03-23 01:40:07 +01:00
|
|
|
ShortChannelID: lnwire.NewShortChanIDFromInt(chanID),
|
2018-12-09 03:42:46 +01:00
|
|
|
Timestamp: uint32(edgeUpdateToFail.LastUpdate.Unix()),
|
|
|
|
MessageFlags: edgeUpdateToFail.MessageFlags,
|
|
|
|
ChannelFlags: edgeUpdateToFail.ChannelFlags,
|
|
|
|
TimeLockDelta: edgeUpdateToFail.TimeLockDelta,
|
|
|
|
HtlcMinimumMsat: edgeUpdateToFail.MinHTLC,
|
|
|
|
HtlcMaximumMsat: edgeUpdateToFail.MaxHTLC,
|
|
|
|
BaseFee: uint32(edgeUpdateToFail.FeeBaseMSat),
|
|
|
|
FeeRate: uint32(edgeUpdateToFail.FeeProportionalMillionths),
|
2018-03-23 01:40:07 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// We'll now modify the SendToSwitch method to return an error for the
|
|
|
|
// outgoing channel to son goku. Since this is a time lock related
|
|
|
|
// error, we should fail the payment flow all together, as Goku is the
|
|
|
|
// only channel to Sophon.
|
2021-05-24 12:29:27 +02:00
|
|
|
ctx.router.cfg.Payer.(*mockPaymentAttemptDispatcherOld).setPaymentResult(
|
2019-05-16 15:27:28 +02:00
|
|
|
func(firstHop lnwire.ShortChannelID) ([32]byte, error) {
|
|
|
|
if firstHop == roasbeefSongoku {
|
2020-01-14 14:07:29 +01:00
|
|
|
return [32]byte{}, htlcswitch.NewForwardingError(
|
|
|
|
&lnwire.FailExpiryTooSoon{
|
2019-05-16 15:27:28 +02:00
|
|
|
Update: errChanUpdate,
|
2020-01-14 14:07:42 +01:00
|
|
|
}, 1,
|
2020-01-14 14:07:29 +01:00
|
|
|
)
|
2019-03-05 16:55:19 +01:00
|
|
|
}
|
|
|
|
|
2019-05-16 15:27:28 +02:00
|
|
|
return preImage, nil
|
|
|
|
})
|
2018-03-23 01:40:07 +01:00
|
|
|
|
|
|
|
// assertExpectedPath is a helper function that asserts the returned
|
|
|
|
// route properly routes around the failure we've introduced in the
|
|
|
|
// graph.
|
2019-04-05 17:36:11 +02:00
|
|
|
assertExpectedPath := func(retPreImage [32]byte, route *route.Route) {
|
2018-03-23 01:40:07 +01:00
|
|
|
// The route selected should have two hops
|
2021-04-14 06:58:12 +02:00
|
|
|
require.Equal(t, 2, len(route.Hops), "incorrect route length")
|
2018-03-23 01:40:07 +01:00
|
|
|
|
|
|
|
// The preimage should match up with the once created above.
|
2021-04-14 06:58:12 +02:00
|
|
|
require.Equal(t,
|
|
|
|
preImage[:], retPreImage[:], "incorrect preimage used",
|
|
|
|
)
|
2018-03-23 01:40:07 +01:00
|
|
|
|
|
|
|
// The route should have satoshi as the first hop.
|
2021-04-14 06:58:12 +02:00
|
|
|
require.Equalf(t,
|
|
|
|
ctx.aliases["phamnuwen"], route.Hops[0].PubKeyBytes,
|
|
|
|
"route should go through phamnuwen as first hop, "+
|
2018-03-23 01:40:07 +01:00
|
|
|
"instead passes through: %v",
|
2021-04-14 06:58:12 +02:00
|
|
|
getAliasFromPubKey(
|
|
|
|
route.Hops[0].PubKeyBytes, ctx.aliases,
|
|
|
|
),
|
|
|
|
)
|
2018-03-23 01:40:07 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Send off the payment request to the router, this payment should
|
2018-09-06 10:48:46 +02:00
|
|
|
// succeed as we should actually go through Pham Nuwen in order to get
|
2018-03-23 01:40:07 +01:00
|
|
|
// to Sophon, even though he has higher fees.
|
2023-03-08 19:21:39 +01:00
|
|
|
paymentPreImage, rt, err := ctx.router.SendPayment(payment)
|
|
|
|
require.NoErrorf(t, err, "unable to send payment: %v",
|
|
|
|
payment.paymentHash)
|
2018-03-23 01:40:07 +01:00
|
|
|
|
2019-04-05 17:36:11 +02:00
|
|
|
assertExpectedPath(paymentPreImage, rt)
|
2018-03-23 01:40:07 +01:00
|
|
|
|
|
|
|
// We'll now modify the error return an IncorrectCltvExpiry error
|
|
|
|
// instead, this should result in the same behavior of roasbeef routing
|
|
|
|
// around the faulty Son Goku node.
|
2021-05-24 12:29:27 +02:00
|
|
|
ctx.router.cfg.Payer.(*mockPaymentAttemptDispatcherOld).setPaymentResult(
|
2019-05-16 15:27:28 +02:00
|
|
|
func(firstHop lnwire.ShortChannelID) ([32]byte, error) {
|
|
|
|
if firstHop == roasbeefSongoku {
|
2020-01-14 14:07:29 +01:00
|
|
|
return [32]byte{}, htlcswitch.NewForwardingError(
|
|
|
|
&lnwire.FailIncorrectCltvExpiry{
|
2019-05-16 15:27:28 +02:00
|
|
|
Update: errChanUpdate,
|
2020-01-14 14:07:42 +01:00
|
|
|
}, 1,
|
2020-01-14 14:07:29 +01:00
|
|
|
)
|
2019-03-05 16:55:19 +01:00
|
|
|
}
|
|
|
|
|
2019-05-16 15:27:28 +02:00
|
|
|
return preImage, nil
|
|
|
|
})
|
2018-03-23 01:40:07 +01:00
|
|
|
|
|
|
|
// Once again, Roasbeef should route around Goku since they disagree
|
2019-05-23 20:05:30 +02:00
|
|
|
// w.r.t to the block height, and instead go through Pham Nuwen. We
|
|
|
|
// flip a bit in the payment hash to allow resending this payment.
|
2021-03-31 12:23:08 +02:00
|
|
|
payment.paymentHash[1] ^= 1
|
2023-03-08 19:21:39 +01:00
|
|
|
paymentPreImage, rt, err = ctx.router.SendPayment(payment)
|
|
|
|
require.NoErrorf(t, err, "unable to send payment: %v",
|
|
|
|
payment.paymentHash)
|
2018-03-23 01:40:07 +01:00
|
|
|
|
2019-04-05 17:36:11 +02:00
|
|
|
assertExpectedPath(paymentPreImage, rt)
|
2018-03-23 01:40:07 +01:00
|
|
|
}
|
|
|
|
|
2017-10-11 04:49:09 +02:00
|
|
|
// TestSendPaymentErrorPathPruning tests that the send of candidate routes
|
|
|
|
// properly gets pruned in response to ForwardingError response from the
|
|
|
|
// underlying SendToSwitch function.
|
|
|
|
func TestSendPaymentErrorPathPruning(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
const startingBlockHeight = 101
|
2022-08-27 09:07:28 +02:00
|
|
|
ctx := createTestCtxFromFile(t, startingBlockHeight, basicGraphFilePath)
|
2017-10-11 04:49:09 +02:00
|
|
|
|
|
|
|
// Craft a LightningPayment struct that'll send a payment from roasbeef
|
2018-01-31 23:36:10 +01:00
|
|
|
// to luo ji for 1000 satoshis, with a maximum of 1000 satoshis in fees.
|
|
|
|
paymentAmt := lnwire.NewMSatFromSatoshis(1000)
|
2023-03-08 19:21:39 +01:00
|
|
|
payment := createDummyLightningPayment(
|
|
|
|
t, ctx.aliases["sophon"], paymentAmt,
|
|
|
|
)
|
2017-10-11 04:49:09 +02:00
|
|
|
|
|
|
|
var preImage [32]byte
|
|
|
|
copy(preImage[:], bytes.Repeat([]byte{9}, 32))
|
|
|
|
|
2021-04-10 13:27:58 +02:00
|
|
|
roasbeefSongoku := lnwire.NewShortChanIDFromInt(
|
|
|
|
ctx.getChannelIDFromAlias(t, "roasbeef", "songoku"),
|
|
|
|
)
|
|
|
|
roasbeefPhanNuwen := lnwire.NewShortChanIDFromInt(
|
|
|
|
ctx.getChannelIDFromAlias(t, "roasbeef", "phamnuwen"),
|
|
|
|
)
|
2018-07-10 03:12:34 +02:00
|
|
|
|
2017-10-11 04:49:09 +02:00
|
|
|
// First, we'll modify the SendToSwitch method to return an error
|
2019-06-18 19:42:51 +02:00
|
|
|
// indicating that the channel from roasbeef to son goku is not operable
|
2017-10-11 04:49:09 +02:00
|
|
|
// with an UnknownNextPeer.
|
2021-05-24 12:29:27 +02:00
|
|
|
ctx.router.cfg.Payer.(*mockPaymentAttemptDispatcherOld).setPaymentResult(
|
2019-05-16 15:27:28 +02:00
|
|
|
func(firstHop lnwire.ShortChannelID) ([32]byte, error) {
|
|
|
|
|
2019-06-18 19:42:51 +02:00
|
|
|
if firstHop == roasbeefSongoku {
|
2019-05-16 15:27:28 +02:00
|
|
|
// We'll first simulate an error from the first
|
2019-06-18 19:42:51 +02:00
|
|
|
// hop to simulate the channel from songoku to
|
|
|
|
// sophon not having enough capacity.
|
2020-01-14 14:07:29 +01:00
|
|
|
return [32]byte{}, htlcswitch.NewForwardingError(
|
|
|
|
&lnwire.FailTemporaryChannelFailure{},
|
2020-01-14 14:07:42 +01:00
|
|
|
1,
|
2020-01-14 14:07:29 +01:00
|
|
|
)
|
2019-03-05 16:55:19 +01:00
|
|
|
}
|
|
|
|
|
2019-06-18 19:42:51 +02:00
|
|
|
// Next, we'll create an error from phan nuwen to
|
|
|
|
// indicate that the sophon node is not longer online,
|
|
|
|
// which should prune out the rest of the routes.
|
|
|
|
if firstHop == roasbeefPhanNuwen {
|
2020-01-14 14:07:29 +01:00
|
|
|
return [32]byte{}, htlcswitch.NewForwardingError(
|
2020-01-14 14:07:42 +01:00
|
|
|
&lnwire.FailUnknownNextPeer{}, 1,
|
2020-01-14 14:07:29 +01:00
|
|
|
)
|
2017-10-11 04:49:09 +02:00
|
|
|
}
|
|
|
|
|
2019-05-16 15:27:28 +02:00
|
|
|
return preImage, nil
|
|
|
|
})
|
2017-10-11 04:49:09 +02:00
|
|
|
|
2024-08-13 18:52:16 +02:00
|
|
|
require.IsType(t, ctx.router.cfg.MissionControl, &MissionControl{})
|
|
|
|
mc, _ := ctx.router.cfg.MissionControl.(*MissionControl)
|
|
|
|
|
|
|
|
err := mc.ResetHistory()
|
|
|
|
require.NoError(t, err)
|
2017-10-17 04:07:31 +02:00
|
|
|
|
2017-10-11 04:49:09 +02:00
|
|
|
// When we try to dispatch that payment, we should receive an error as
|
|
|
|
// both attempts should fail and cause both routes to be pruned.
|
2024-08-13 18:52:16 +02:00
|
|
|
_, _, err = ctx.router.SendPayment(payment)
|
2021-04-14 06:58:12 +02:00
|
|
|
require.Error(t, err, "payment didn't return error")
|
2017-10-11 04:49:09 +02:00
|
|
|
|
|
|
|
// The final error returned should also indicate that the peer wasn't
|
|
|
|
// online (the last error we returned).
|
2021-04-14 06:58:12 +02:00
|
|
|
require.Equal(t, channeldb.FailureReasonNoRoute, err)
|
2017-10-11 04:49:09 +02:00
|
|
|
|
2020-04-01 00:13:24 +02:00
|
|
|
// Inspect the two attempts that were made before the payment failed.
|
2023-03-08 19:21:39 +01:00
|
|
|
p, err := ctx.router.cfg.Control.FetchPayment(*payment.paymentHash)
|
2021-04-14 06:58:12 +02:00
|
|
|
require.NoError(t, err)
|
2020-04-01 00:13:24 +02:00
|
|
|
|
2023-02-09 05:51:43 +01:00
|
|
|
htlcs := p.GetHTLCs()
|
|
|
|
require.Equal(t, 2, len(htlcs), "expected two attempts")
|
2020-04-01 00:13:24 +02:00
|
|
|
|
|
|
|
// We expect the first attempt to have failed with a
|
|
|
|
// TemporaryChannelFailure, the second with UnknownNextPeer.
|
2023-02-09 05:51:43 +01:00
|
|
|
msg := htlcs[0].Failure.Message
|
2024-08-13 18:52:16 +02:00
|
|
|
require.IsType(t, msg, &lnwire.FailTemporaryChannelFailure{})
|
2020-04-01 00:13:24 +02:00
|
|
|
|
2023-02-09 05:51:43 +01:00
|
|
|
msg = htlcs[1].Failure.Message
|
2024-08-13 18:52:16 +02:00
|
|
|
require.IsType(t, msg, &lnwire.FailUnknownNextPeer{})
|
2020-04-01 00:13:24 +02:00
|
|
|
|
2021-05-21 13:02:49 +02:00
|
|
|
err = ctx.router.cfg.MissionControl.(*MissionControl).ResetHistory()
|
|
|
|
require.NoError(t, err, "reset history failed")
|
2017-10-17 04:07:31 +02:00
|
|
|
|
2019-06-18 19:42:51 +02:00
|
|
|
// Next, we'll modify the SendToSwitch method to indicate that the
|
|
|
|
// connection between songoku and isn't up.
|
2021-05-24 12:29:27 +02:00
|
|
|
ctx.router.cfg.Payer.(*mockPaymentAttemptDispatcherOld).setPaymentResult(
|
2019-05-16 15:27:28 +02:00
|
|
|
func(firstHop lnwire.ShortChannelID) ([32]byte, error) {
|
|
|
|
|
2019-06-18 19:42:51 +02:00
|
|
|
if firstHop == roasbeefSongoku {
|
2020-01-14 14:07:29 +01:00
|
|
|
failure := htlcswitch.NewForwardingError(
|
2020-01-14 14:07:42 +01:00
|
|
|
&lnwire.FailUnknownNextPeer{}, 1,
|
2020-01-14 14:07:29 +01:00
|
|
|
)
|
|
|
|
return [32]byte{}, failure
|
2017-10-11 04:49:09 +02:00
|
|
|
}
|
|
|
|
|
2019-05-16 15:27:28 +02:00
|
|
|
return preImage, nil
|
|
|
|
})
|
2017-10-11 04:49:09 +02:00
|
|
|
|
2018-04-24 02:53:41 +02:00
|
|
|
// This shouldn't return an error, as we'll make a payment attempt via
|
2019-06-18 19:42:51 +02:00
|
|
|
// the pham nuwen channel based on the assumption that there might be an
|
|
|
|
// intermittent issue with the songoku <-> sophon channel.
|
2023-03-08 19:21:39 +01:00
|
|
|
paymentPreImage, rt, err := ctx.router.SendPayment(payment)
|
|
|
|
require.NoErrorf(t, err, "unable to send payment: %v",
|
|
|
|
payment.paymentHash)
|
2018-04-24 02:53:41 +02:00
|
|
|
|
2019-06-18 19:42:51 +02:00
|
|
|
// This path should go: roasbeef -> pham nuwen -> sophon
|
2021-04-14 06:58:12 +02:00
|
|
|
require.Equal(t, 2, len(rt.Hops), "incorrect route length")
|
|
|
|
require.Equal(t, preImage[:], paymentPreImage[:], "incorrect preimage")
|
|
|
|
require.Equalf(t,
|
|
|
|
ctx.aliases["phamnuwen"], rt.Hops[0].PubKeyBytes,
|
|
|
|
"route should go through phamnuwen as first hop, "+
|
2018-04-24 02:53:41 +02:00
|
|
|
"instead passes through: %v",
|
2021-04-14 06:58:12 +02:00
|
|
|
getAliasFromPubKey(rt.Hops[0].PubKeyBytes, ctx.aliases),
|
|
|
|
)
|
2017-10-11 04:49:09 +02:00
|
|
|
|
2024-08-13 18:52:16 +02:00
|
|
|
require.IsType(t, ctx.router.cfg.MissionControl, &MissionControl{})
|
|
|
|
mc, _ = ctx.router.cfg.MissionControl.(*MissionControl)
|
|
|
|
|
|
|
|
err = mc.ResetHistory()
|
|
|
|
require.NoError(t, err)
|
2017-10-17 04:07:31 +02:00
|
|
|
|
2017-10-11 04:49:09 +02:00
|
|
|
// Finally, we'll modify the SendToSwitch function to indicate that the
|
2018-04-24 02:53:41 +02:00
|
|
|
// roasbeef -> luoji channel has insufficient capacity. This should
|
|
|
|
// again cause us to instead go via the satoshi route.
|
2021-05-24 12:29:27 +02:00
|
|
|
ctx.router.cfg.Payer.(*mockPaymentAttemptDispatcherOld).setPaymentResult(
|
2019-05-16 15:27:28 +02:00
|
|
|
func(firstHop lnwire.ShortChannelID) ([32]byte, error) {
|
|
|
|
|
2019-06-18 19:42:51 +02:00
|
|
|
if firstHop == roasbeefSongoku {
|
2019-05-16 15:27:28 +02:00
|
|
|
// We'll first simulate an error from the first
|
|
|
|
// outgoing link to simulate the channel from luo ji to
|
|
|
|
// roasbeef not having enough capacity.
|
2020-01-14 14:07:29 +01:00
|
|
|
return [32]byte{}, htlcswitch.NewForwardingError(
|
|
|
|
&lnwire.FailTemporaryChannelFailure{},
|
2020-01-14 14:07:42 +01:00
|
|
|
1,
|
2020-01-14 14:07:29 +01:00
|
|
|
)
|
2017-10-11 04:49:09 +02:00
|
|
|
}
|
2019-05-16 15:27:28 +02:00
|
|
|
return preImage, nil
|
|
|
|
})
|
2017-10-11 04:49:09 +02:00
|
|
|
|
2019-05-23 20:05:30 +02:00
|
|
|
// We flip a bit in the payment hash to allow resending this payment.
|
2021-03-31 12:23:08 +02:00
|
|
|
payment.paymentHash[1] ^= 1
|
2023-03-08 19:21:39 +01:00
|
|
|
paymentPreImage, rt, err = ctx.router.SendPayment(payment)
|
|
|
|
require.NoErrorf(t, err, "unable to send payment: %v",
|
|
|
|
payment.paymentHash)
|
2017-10-11 04:49:09 +02:00
|
|
|
|
2017-10-17 04:07:31 +02:00
|
|
|
// This should succeed finally. The route selected should have two
|
|
|
|
// hops.
|
2021-04-14 06:58:12 +02:00
|
|
|
require.Equal(t, 2, len(rt.Hops), "incorrect route length")
|
2017-10-11 04:49:09 +02:00
|
|
|
|
|
|
|
// The preimage should match up with the once created above.
|
2021-04-14 06:58:12 +02:00
|
|
|
require.Equal(t, preImage[:], paymentPreImage[:], "incorrect preimage")
|
2017-10-11 04:49:09 +02:00
|
|
|
|
|
|
|
// The route should have satoshi as the first hop.
|
2021-04-14 06:58:12 +02:00
|
|
|
require.Equalf(t,
|
|
|
|
ctx.aliases["phamnuwen"], rt.Hops[0].PubKeyBytes,
|
|
|
|
"route should go through phamnuwen as first hop, "+
|
2017-03-21 02:58:21 +01:00
|
|
|
"instead passes through: %v",
|
2021-04-14 06:58:12 +02:00
|
|
|
getAliasFromPubKey(rt.Hops[0].PubKeyBytes, ctx.aliases),
|
|
|
|
)
|
2017-03-21 02:58:21 +01:00
|
|
|
}
|
2017-03-27 19:00:38 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
// TestFindPathFeeWeighting tests that the findPath method will properly prefer
|
|
|
|
// routes with lower fees over routes with lower time lock values. This is
|
|
|
|
// meant to exercise the fact that the internal findPath method ranks edges
|
|
|
|
// with the square of the total fee in order bias towards lower fees.
|
|
|
|
func TestFindPathFeeWeighting(t *testing.T) {
|
2017-06-17 00:59:20 +02:00
|
|
|
t.Parallel()
|
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
const startingBlockHeight = 101
|
|
|
|
ctx := createTestCtxFromFile(t, startingBlockHeight, basicGraphFilePath)
|
2017-03-27 19:00:38 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
var preImage [32]byte
|
|
|
|
copy(preImage[:], bytes.Repeat([]byte{9}, 32))
|
2017-05-15 04:27:14 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
sourceNode, err := ctx.graph.SourceNode()
|
|
|
|
require.NoError(t, err, "unable to fetch source node")
|
2017-03-27 19:00:38 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
amt := lnwire.MilliSatoshi(100)
|
2017-03-27 19:00:38 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
target := ctx.aliases["luoji"]
|
2017-03-27 19:00:38 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
// We'll now attempt a path finding attempt using this set up. Due to
|
|
|
|
// the edge weighting, we should select the direct path over the 2 hop
|
|
|
|
// path even though the direct path has a higher potential time lock.
|
|
|
|
path, err := dbFindPath(
|
|
|
|
ctx.graph, nil, &mockBandwidthHints{},
|
|
|
|
noRestrictions,
|
|
|
|
testPathFindingConfig,
|
|
|
|
sourceNode.PubKeyBytes, target, amt, 0, 0,
|
|
|
|
)
|
|
|
|
require.NoError(t, err, "unable to find path")
|
2017-03-27 19:00:38 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
// The route that was chosen should be exactly one hop, and should be
|
|
|
|
// directly to luoji.
|
2024-06-18 21:34:25 +02:00
|
|
|
require.Len(t, path, 1)
|
|
|
|
require.Equal(t, ctx.aliases["luoji"], path[0].policy.ToNodePubKey())
|
2017-03-27 19:00:38 +02:00
|
|
|
}
|
2017-05-15 04:27:14 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
// TestEmptyRoutesGenerateSphinxPacket tests that the generateSphinxPacket
|
|
|
|
// function is able to gracefully handle being passed a nil set of hops for the
|
|
|
|
// route by the caller.
|
|
|
|
func TestEmptyRoutesGenerateSphinxPacket(t *testing.T) {
|
2017-07-14 21:32:00 +02:00
|
|
|
t.Parallel()
|
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
sessionKey, _ := btcec.NewPrivateKey()
|
|
|
|
emptyRoute := &route.Route{}
|
|
|
|
_, _, err := generateSphinxPacket(emptyRoute, testHash[:], sessionKey)
|
2024-06-18 21:34:25 +02:00
|
|
|
require.ErrorIs(t, err, route.ErrNoRouteHopsProvided)
|
2017-07-14 21:32:00 +02:00
|
|
|
}
|
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
// TestUnknownErrorSource tests that if the source of an error is unknown, all
|
|
|
|
// edges along the route will be pruned.
|
|
|
|
func TestUnknownErrorSource(t *testing.T) {
|
2018-12-04 15:33:01 +01:00
|
|
|
t.Parallel()
|
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
// Setup a network. It contains two paths to c: a->b->c and an
|
|
|
|
// alternative a->d->c.
|
|
|
|
chanCapSat := btcutil.Amount(100000)
|
|
|
|
testChannels := []*testChannel{
|
|
|
|
symmetricTestChannel("a", "b", chanCapSat, &testChannelPolicy{
|
|
|
|
Expiry: 144,
|
|
|
|
FeeRate: 400,
|
|
|
|
MinHTLC: 1,
|
|
|
|
MaxHTLC: lnwire.NewMSatFromSatoshis(chanCapSat),
|
|
|
|
}, 1),
|
|
|
|
symmetricTestChannel("b", "c", chanCapSat, &testChannelPolicy{
|
|
|
|
Expiry: 144,
|
|
|
|
FeeRate: 400,
|
|
|
|
MinHTLC: 1,
|
|
|
|
MaxHTLC: lnwire.NewMSatFromSatoshis(chanCapSat),
|
|
|
|
}, 3),
|
|
|
|
symmetricTestChannel("a", "d", chanCapSat, &testChannelPolicy{
|
|
|
|
Expiry: 144,
|
|
|
|
FeeRate: 400,
|
|
|
|
FeeBaseMsat: 100000,
|
|
|
|
MinHTLC: 1,
|
|
|
|
MaxHTLC: lnwire.NewMSatFromSatoshis(chanCapSat),
|
|
|
|
}, 2),
|
|
|
|
symmetricTestChannel("d", "c", chanCapSat, &testChannelPolicy{
|
|
|
|
Expiry: 144,
|
|
|
|
FeeRate: 400,
|
|
|
|
FeeBaseMsat: 100000,
|
|
|
|
MinHTLC: 1,
|
|
|
|
MaxHTLC: lnwire.NewMSatFromSatoshis(chanCapSat),
|
|
|
|
}, 4),
|
|
|
|
}
|
2018-12-04 15:33:01 +01:00
|
|
|
|
2024-06-18 21:34:25 +02:00
|
|
|
testGraph, err := createTestGraphFromChannels(
|
|
|
|
t, true, testChannels, "a",
|
|
|
|
)
|
2022-05-05 22:11:50 +02:00
|
|
|
require.NoError(t, err, "unable to create graph")
|
2018-12-04 15:33:01 +01:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
const startingBlockHeight = 101
|
|
|
|
ctx := createTestCtxFromGraphInstance(t, startingBlockHeight, testGraph)
|
|
|
|
|
|
|
|
// Create a payment to node c.
|
|
|
|
payment := createDummyLightningPayment(
|
|
|
|
t, ctx.aliases["c"], lnwire.NewMSatFromSatoshis(1000),
|
2018-12-04 15:33:01 +01:00
|
|
|
)
|
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
// We'll modify the SendToSwitch method so that it simulates hop b as a
|
|
|
|
// node that returns an unparsable failure if approached via the a->b
|
|
|
|
// channel.
|
2024-06-18 21:34:25 +02:00
|
|
|
dispatcher, ok := ctx.router.cfg.Payer.(*mockPaymentAttemptDispatcherOld) //nolint:lll
|
|
|
|
require.True(t, ok)
|
|
|
|
|
|
|
|
dispatcher.setPaymentResult(func(firstHop lnwire.ShortChannelID) (
|
|
|
|
[32]byte, error) {
|
|
|
|
|
|
|
|
// If channel a->b is used, return an error without
|
|
|
|
// source and message. The sender won't know the origin
|
|
|
|
// of the error.
|
|
|
|
if firstHop.ToUint64() == 1 {
|
|
|
|
return [32]byte{},
|
|
|
|
htlcswitch.ErrUnreadableFailureMessage
|
|
|
|
}
|
2018-12-04 15:33:01 +01:00
|
|
|
|
2024-06-18 21:34:25 +02:00
|
|
|
// Otherwise the payment succeeds.
|
|
|
|
return lntypes.Preimage{}, nil
|
|
|
|
})
|
2018-12-04 15:33:01 +01:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
// Send off the payment request to the router. The expectation is that
|
|
|
|
// the route a->b->c is tried first. An unreadable faiure is returned
|
|
|
|
// which should pruning the channel a->b. We expect the payment to
|
|
|
|
// succeed via a->d.
|
|
|
|
_, _, err = ctx.router.SendPayment(payment)
|
|
|
|
require.NoErrorf(t, err, "unable to send payment: %v",
|
|
|
|
payment.paymentHash)
|
2018-12-04 15:33:01 +01:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
// Next we modify payment result to return an unknown failure.
|
2024-06-18 21:34:25 +02:00
|
|
|
dispatcher, ok = ctx.router.cfg.Payer.(*mockPaymentAttemptDispatcherOld) //nolint:lll
|
|
|
|
require.True(t, ok)
|
2018-12-04 15:33:01 +01:00
|
|
|
|
2024-06-18 21:34:25 +02:00
|
|
|
dispatcher.setPaymentResult(func(firstHop lnwire.ShortChannelID) (
|
|
|
|
[32]byte, error) {
|
2024-06-17 01:30:01 +02:00
|
|
|
|
2024-06-18 21:34:25 +02:00
|
|
|
// If channel a->b is used, simulate that the failure
|
|
|
|
// couldn't be decoded (FailureMessage is nil).
|
|
|
|
if firstHop.ToUint64() == 2 {
|
|
|
|
return [32]byte{},
|
|
|
|
htlcswitch.NewUnknownForwardingError(1)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Otherwise the payment succeeds.
|
|
|
|
return lntypes.Preimage{}, nil
|
|
|
|
})
|
2018-12-04 15:33:01 +01:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
// Send off the payment request to the router. We expect the payment to
|
|
|
|
// fail because both routes have been pruned.
|
|
|
|
payment.paymentHash[1] ^= 1
|
|
|
|
_, _, err = ctx.router.SendPayment(payment)
|
|
|
|
if err == nil {
|
|
|
|
t.Fatalf("expected payment to fail")
|
2018-12-04 15:33:01 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
// TestSendToRouteStructuredError asserts that SendToRoute returns a structured
|
|
|
|
// error.
|
|
|
|
func TestSendToRouteStructuredError(t *testing.T) {
|
2017-06-17 00:59:20 +02:00
|
|
|
t.Parallel()
|
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
// Setup a three node network.
|
|
|
|
chanCapSat := btcutil.Amount(100000)
|
|
|
|
testChannels := []*testChannel{
|
|
|
|
symmetricTestChannel("a", "b", chanCapSat, &testChannelPolicy{
|
|
|
|
Expiry: 144,
|
|
|
|
FeeRate: 400,
|
|
|
|
MinHTLC: 1,
|
|
|
|
MaxHTLC: lnwire.NewMSatFromSatoshis(chanCapSat),
|
|
|
|
}, 1),
|
|
|
|
symmetricTestChannel("b", "c", chanCapSat, &testChannelPolicy{
|
|
|
|
Expiry: 144,
|
|
|
|
FeeRate: 400,
|
|
|
|
MinHTLC: 1,
|
|
|
|
MaxHTLC: lnwire.NewMSatFromSatoshis(chanCapSat),
|
|
|
|
}, 2),
|
|
|
|
}
|
2018-01-31 05:26:26 +01:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
testGraph, err := createTestGraphFromChannels(t, true, testChannels, "a")
|
|
|
|
require.NoError(t, err, "unable to create graph")
|
2018-01-31 05:26:26 +01:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
const startingBlockHeight = 101
|
|
|
|
ctx := createTestCtxFromGraphInstance(t, startingBlockHeight, testGraph)
|
2019-05-23 21:17:16 +02:00
|
|
|
|
2019-06-04 10:34:59 +02:00
|
|
|
// Set up an init channel for the control tower, such that we can make
|
|
|
|
// sure the payment is initiated correctly.
|
|
|
|
init := make(chan initArgs, 1)
|
2021-05-24 12:29:27 +02:00
|
|
|
ctx.router.cfg.Control.(*mockControlTowerOld).init = init
|
2019-06-04 10:34:59 +02:00
|
|
|
|
2019-05-23 21:17:16 +02:00
|
|
|
// Setup a route from source a to destination c. The route will be used
|
|
|
|
// in a call to SendToRoute. SendToRoute also applies channel updates,
|
|
|
|
// but it saves us from including RequestRoute in the test scope too.
|
2019-06-04 10:34:59 +02:00
|
|
|
const payAmt = lnwire.MilliSatoshi(10000)
|
2019-05-23 21:17:16 +02:00
|
|
|
hop1 := ctx.aliases["b"]
|
|
|
|
hop2 := ctx.aliases["c"]
|
|
|
|
hops := []*route.Hop{
|
|
|
|
{
|
2019-07-31 06:41:58 +02:00
|
|
|
ChannelID: 1,
|
|
|
|
PubKeyBytes: hop1,
|
|
|
|
AmtToForward: payAmt,
|
|
|
|
LegacyPayload: true,
|
2019-05-23 21:17:16 +02:00
|
|
|
},
|
|
|
|
{
|
2019-07-31 06:41:58 +02:00
|
|
|
ChannelID: 2,
|
|
|
|
PubKeyBytes: hop2,
|
|
|
|
AmtToForward: payAmt,
|
|
|
|
LegacyPayload: true,
|
2019-05-23 21:17:16 +02:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2019-06-04 10:34:59 +02:00
|
|
|
rt, err := route.NewRouteFromHops(payAmt, 100, ctx.aliases["a"], hops)
|
2022-05-05 22:11:50 +02:00
|
|
|
require.NoError(t, err, "unable to create route")
|
2019-05-23 21:17:16 +02:00
|
|
|
|
2021-07-07 03:35:20 +02:00
|
|
|
finalHopIndex := len(hops)
|
|
|
|
testCases := map[int]lnwire.FailureMessage{
|
|
|
|
finalHopIndex: lnwire.NewFailIncorrectDetails(payAmt, 100),
|
|
|
|
1: &lnwire.FailFeeInsufficient{
|
2024-08-21 08:39:37 +02:00
|
|
|
Update: lnwire.ChannelUpdate1{},
|
2021-07-07 03:35:20 +02:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for failIndex, errorType := range testCases {
|
|
|
|
failIndex := failIndex
|
|
|
|
errorType := errorType
|
|
|
|
|
|
|
|
t.Run(fmt.Sprintf("%T", errorType), func(t *testing.T) {
|
|
|
|
// We'll modify the SendToSwitch method so that it
|
|
|
|
// simulates a failed payment with an error originating
|
|
|
|
// from the final hop in the route.
|
|
|
|
ctx.router.cfg.Payer.(*mockPaymentAttemptDispatcherOld).setPaymentResult(
|
|
|
|
func(firstHop lnwire.ShortChannelID) ([32]byte, error) {
|
|
|
|
return [32]byte{}, htlcswitch.NewForwardingError(
|
|
|
|
errorType, failIndex,
|
|
|
|
)
|
|
|
|
},
|
2020-01-14 14:07:29 +01:00
|
|
|
)
|
2019-05-23 21:17:16 +02:00
|
|
|
|
2021-07-07 03:35:20 +02:00
|
|
|
// The payment parameter is mostly redundant in
|
|
|
|
// SendToRoute. Can be left empty for this test.
|
|
|
|
var payment lntypes.Hash
|
2019-05-23 21:17:16 +02:00
|
|
|
|
2021-07-07 03:35:20 +02:00
|
|
|
// Send off the payment request to the router. The
|
|
|
|
// specified route should be attempted and the channel
|
|
|
|
// update should be received by router and ignored
|
|
|
|
// because it is missing a valid
|
|
|
|
// signature.
|
2024-04-16 11:03:59 +02:00
|
|
|
_, err = ctx.router.SendToRoute(payment, rt, nil)
|
2019-05-23 21:17:16 +02:00
|
|
|
|
2021-07-07 03:35:20 +02:00
|
|
|
fErr, ok := err.(*htlcswitch.ForwardingError)
|
|
|
|
require.True(
|
|
|
|
t, ok, "expected forwarding error, got: %T", err,
|
|
|
|
)
|
2019-05-23 21:17:16 +02:00
|
|
|
|
2021-07-07 03:35:20 +02:00
|
|
|
require.IsType(
|
|
|
|
t, errorType, fErr.WireMessage(),
|
|
|
|
"expected type %T got %T", errorType,
|
|
|
|
fErr.WireMessage(),
|
|
|
|
)
|
2019-06-04 10:34:59 +02:00
|
|
|
|
2021-07-07 03:35:20 +02:00
|
|
|
// Check that the correct values were used when
|
|
|
|
// initiating the payment.
|
|
|
|
select {
|
|
|
|
case initVal := <-init:
|
|
|
|
if initVal.c.Value != payAmt {
|
|
|
|
t.Fatalf("expected %v, got %v", payAmt,
|
|
|
|
initVal.c.Value)
|
|
|
|
}
|
|
|
|
case <-time.After(100 * time.Millisecond):
|
|
|
|
t.Fatalf("initPayment not called")
|
|
|
|
}
|
|
|
|
})
|
2019-06-04 10:34:59 +02:00
|
|
|
}
|
2019-05-23 21:17:16 +02:00
|
|
|
}
|
2019-08-29 13:03:37 +02:00
|
|
|
|
2019-12-11 08:56:41 +01:00
|
|
|
// TestSendToRouteMaxHops asserts that SendToRoute fails when using a route that
|
|
|
|
// exceeds the maximum number of hops.
|
|
|
|
func TestSendToRouteMaxHops(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
// Setup a two node network.
|
|
|
|
chanCapSat := btcutil.Amount(100000)
|
|
|
|
testChannels := []*testChannel{
|
|
|
|
symmetricTestChannel("a", "b", chanCapSat, &testChannelPolicy{
|
|
|
|
Expiry: 144,
|
|
|
|
FeeRate: 400,
|
|
|
|
MinHTLC: 1,
|
|
|
|
MaxHTLC: lnwire.NewMSatFromSatoshis(chanCapSat),
|
|
|
|
}, 1),
|
|
|
|
}
|
|
|
|
|
2022-08-15 15:08:16 +02:00
|
|
|
testGraph, err := createTestGraphFromChannels(t, true, testChannels, "a")
|
2022-05-05 22:11:50 +02:00
|
|
|
require.NoError(t, err, "unable to create graph")
|
2019-12-11 08:56:41 +01:00
|
|
|
|
|
|
|
const startingBlockHeight = 101
|
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
ctx := createTestCtxFromGraphInstance(t, startingBlockHeight, testGraph)
|
2019-12-11 08:56:41 +01:00
|
|
|
|
|
|
|
// Create a 30 hop route that exceeds the maximum hop limit.
|
|
|
|
const payAmt = lnwire.MilliSatoshi(10000)
|
|
|
|
hopA := ctx.aliases["a"]
|
|
|
|
hopB := ctx.aliases["b"]
|
|
|
|
|
|
|
|
var hops []*route.Hop
|
|
|
|
for i := 0; i < 15; i++ {
|
|
|
|
hops = append(hops, &route.Hop{
|
|
|
|
ChannelID: 1,
|
|
|
|
PubKeyBytes: hopB,
|
|
|
|
AmtToForward: payAmt,
|
|
|
|
LegacyPayload: true,
|
|
|
|
})
|
|
|
|
|
|
|
|
hops = append(hops, &route.Hop{
|
|
|
|
ChannelID: 1,
|
|
|
|
PubKeyBytes: hopA,
|
|
|
|
AmtToForward: payAmt,
|
|
|
|
LegacyPayload: true,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
rt, err := route.NewRouteFromHops(payAmt, 100, ctx.aliases["a"], hops)
|
2022-05-05 22:11:50 +02:00
|
|
|
require.NoError(t, err, "unable to create route")
|
2019-12-11 08:56:41 +01:00
|
|
|
|
|
|
|
// Send off the payment request to the router. We expect an error back
|
|
|
|
// indicating that the route is too long.
|
2023-03-07 11:43:14 +01:00
|
|
|
var payHash lntypes.Hash
|
2024-04-16 11:03:59 +02:00
|
|
|
_, err = ctx.router.SendToRoute(payHash, rt, nil)
|
2019-12-11 08:56:41 +01:00
|
|
|
if err != route.ErrMaxRouteHopsExceeded {
|
|
|
|
t.Fatalf("expected ErrMaxRouteHopsExceeded, but got %v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-29 13:03:37 +02:00
|
|
|
// TestBuildRoute tests whether correct routes are built.
|
|
|
|
func TestBuildRoute(t *testing.T) {
|
|
|
|
// Setup a three node network.
|
|
|
|
chanCapSat := btcutil.Amount(100000)
|
2020-11-24 05:17:16 +01:00
|
|
|
paymentAddrFeatures := lnwire.NewFeatureVector(
|
|
|
|
lnwire.NewRawFeatureVector(lnwire.PaymentAddrOptional),
|
|
|
|
lnwire.Features,
|
|
|
|
)
|
2019-08-29 13:03:37 +02:00
|
|
|
testChannels := []*testChannel{
|
|
|
|
// Create two local channels from a. The bandwidth is estimated
|
|
|
|
// in this test as the channel capacity. For building routes, we
|
|
|
|
// expected the channel with the largest estimated bandwidth to
|
|
|
|
// be selected.
|
|
|
|
symmetricTestChannel("a", "b", chanCapSat, &testChannelPolicy{
|
|
|
|
Expiry: 144,
|
|
|
|
FeeRate: 20000,
|
|
|
|
MinHTLC: lnwire.NewMSatFromSatoshis(5),
|
|
|
|
MaxHTLC: lnwire.NewMSatFromSatoshis(chanCapSat),
|
|
|
|
}, 1),
|
|
|
|
symmetricTestChannel("a", "b", chanCapSat/2, &testChannelPolicy{
|
|
|
|
Expiry: 144,
|
|
|
|
FeeRate: 20000,
|
|
|
|
MinHTLC: lnwire.NewMSatFromSatoshis(5),
|
|
|
|
MaxHTLC: lnwire.NewMSatFromSatoshis(chanCapSat / 2),
|
|
|
|
}, 6),
|
|
|
|
|
|
|
|
// Create two channels from b to c. For building routes, we
|
2024-06-25 11:11:18 +02:00
|
|
|
// expect the highest cost channel to be selected. Note that
|
|
|
|
// this isn't a situation that we are expecting in reality.
|
|
|
|
// Routing nodes are recommended to keep their channel policies
|
|
|
|
// towards the same peer identical.
|
2019-08-29 13:03:37 +02:00
|
|
|
symmetricTestChannel("b", "c", chanCapSat, &testChannelPolicy{
|
2020-11-24 05:17:16 +01:00
|
|
|
Expiry: 144,
|
|
|
|
FeeRate: 50000,
|
|
|
|
MinHTLC: lnwire.NewMSatFromSatoshis(20),
|
|
|
|
MaxHTLC: lnwire.NewMSatFromSatoshis(120),
|
|
|
|
Features: paymentAddrFeatures,
|
2019-08-29 13:03:37 +02:00
|
|
|
}, 2),
|
|
|
|
symmetricTestChannel("b", "c", chanCapSat, &testChannelPolicy{
|
2020-11-24 05:17:16 +01:00
|
|
|
Expiry: 144,
|
|
|
|
FeeRate: 60000,
|
|
|
|
MinHTLC: lnwire.NewMSatFromSatoshis(20),
|
|
|
|
MaxHTLC: lnwire.NewMSatFromSatoshis(120),
|
|
|
|
Features: paymentAddrFeatures,
|
2019-08-29 13:03:37 +02:00
|
|
|
}, 7),
|
|
|
|
|
2024-06-25 11:11:18 +02:00
|
|
|
// Create some channels that have conflicting min/max
|
|
|
|
// constraints.
|
2019-08-29 13:03:37 +02:00
|
|
|
symmetricTestChannel("a", "e", chanCapSat, &testChannelPolicy{
|
2020-11-24 05:17:16 +01:00
|
|
|
Expiry: 144,
|
|
|
|
FeeRate: 80000,
|
|
|
|
MinHTLC: lnwire.NewMSatFromSatoshis(5),
|
|
|
|
MaxHTLC: lnwire.NewMSatFromSatoshis(10),
|
|
|
|
Features: paymentAddrFeatures,
|
2019-08-29 13:03:37 +02:00
|
|
|
}, 5),
|
|
|
|
symmetricTestChannel("e", "c", chanCapSat, &testChannelPolicy{
|
2020-11-24 05:17:16 +01:00
|
|
|
Expiry: 144,
|
|
|
|
FeeRate: 100000,
|
|
|
|
MinHTLC: lnwire.NewMSatFromSatoshis(20),
|
|
|
|
MaxHTLC: lnwire.NewMSatFromSatoshis(chanCapSat),
|
|
|
|
Features: paymentAddrFeatures,
|
2019-08-29 13:03:37 +02:00
|
|
|
}, 4),
|
2024-06-25 11:11:18 +02:00
|
|
|
|
|
|
|
// Create some channels that have a conflicting max HTLC
|
|
|
|
// constraint for one node pair, similar to the b->c channels.
|
|
|
|
symmetricTestChannel("b", "z", chanCapSat, &testChannelPolicy{
|
|
|
|
Expiry: 144,
|
|
|
|
FeeRate: 50000,
|
|
|
|
MinHTLC: lnwire.NewMSatFromSatoshis(20),
|
|
|
|
MaxHTLC: lnwire.NewMSatFromSatoshis(25),
|
|
|
|
Features: paymentAddrFeatures,
|
|
|
|
}, 3),
|
|
|
|
symmetricTestChannel("b", "z", chanCapSat, &testChannelPolicy{
|
|
|
|
Expiry: 144,
|
|
|
|
FeeRate: 60000,
|
|
|
|
MinHTLC: lnwire.NewMSatFromSatoshis(20),
|
|
|
|
MaxHTLC: lnwire.MilliSatoshi(20100),
|
|
|
|
Features: paymentAddrFeatures,
|
|
|
|
}, 8),
|
2024-07-30 14:17:47 +02:00
|
|
|
|
|
|
|
// Create a route with inbound fees.
|
|
|
|
symmetricTestChannel("a", "d", chanCapSat, &testChannelPolicy{
|
|
|
|
Expiry: 144,
|
|
|
|
FeeRate: 20000,
|
|
|
|
MinHTLC: lnwire.NewMSatFromSatoshis(5),
|
|
|
|
MaxHTLC: lnwire.NewMSatFromSatoshis(
|
|
|
|
chanCapSat,
|
|
|
|
),
|
|
|
|
InboundFeeBaseMsat: -1000,
|
|
|
|
InboundFeeRate: -1000,
|
|
|
|
}, 9),
|
|
|
|
symmetricTestChannel("d", "f", chanCapSat, &testChannelPolicy{
|
|
|
|
Expiry: 144,
|
|
|
|
FeeRate: 60000,
|
|
|
|
MinHTLC: lnwire.NewMSatFromSatoshis(20),
|
|
|
|
MaxHTLC: lnwire.NewMSatFromSatoshis(120),
|
|
|
|
Features: paymentAddrFeatures,
|
|
|
|
// The inbound fee will not be active for the last hop.
|
|
|
|
InboundFeeBaseMsat: 2000,
|
|
|
|
InboundFeeRate: 2000,
|
|
|
|
}, 10),
|
2019-08-29 13:03:37 +02:00
|
|
|
}
|
|
|
|
|
2024-06-25 11:11:18 +02:00
|
|
|
testGraph, err := createTestGraphFromChannels(
|
|
|
|
t, true, testChannels, "a",
|
|
|
|
)
|
2022-05-05 22:11:50 +02:00
|
|
|
require.NoError(t, err, "unable to create graph")
|
2019-08-29 13:03:37 +02:00
|
|
|
|
|
|
|
const startingBlockHeight = 101
|
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
ctx := createTestCtxFromGraphInstance(t, startingBlockHeight, testGraph)
|
2019-08-29 13:03:37 +02:00
|
|
|
|
2020-11-24 05:17:16 +01:00
|
|
|
checkHops := func(rt *route.Route, expected []uint64,
|
|
|
|
payAddr [32]byte) {
|
|
|
|
|
2019-10-03 12:52:38 +02:00
|
|
|
t.Helper()
|
|
|
|
|
2024-06-25 11:11:18 +02:00
|
|
|
require.Len(t, rt.Hops, len(expected))
|
|
|
|
|
2019-08-29 13:03:37 +02:00
|
|
|
for i, hop := range rt.Hops {
|
2024-06-25 11:11:18 +02:00
|
|
|
require.Equal(t, expected[i], hop.ChannelID)
|
2019-08-29 13:03:37 +02:00
|
|
|
}
|
2020-11-24 05:17:16 +01:00
|
|
|
|
|
|
|
lastHop := rt.Hops[len(rt.Hops)-1]
|
|
|
|
require.NotNil(t, lastHop.MPP)
|
|
|
|
require.Equal(t, lastHop.MPP.PaymentAddr(), payAddr)
|
2019-08-29 13:03:37 +02:00
|
|
|
}
|
|
|
|
|
2020-11-24 05:17:16 +01:00
|
|
|
var payAddr [32]byte
|
|
|
|
_, err = rand.Read(payAddr[:])
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
2024-07-31 14:32:48 +02:00
|
|
|
noAmt := fn.None[lnwire.MilliSatoshi]()
|
|
|
|
|
2024-06-25 19:32:55 +02:00
|
|
|
// Test that we can't build a route when no hops are given.
|
|
|
|
hops = []route.Vertex{}
|
2024-04-16 11:03:59 +02:00
|
|
|
_, err = ctx.router.BuildRoute(
|
2024-09-24 05:36:15 +02:00
|
|
|
noAmt, hops, nil, 40, fn.None[[32]byte](), fn.None[[]byte](),
|
2024-04-16 11:03:59 +02:00
|
|
|
)
|
2024-06-25 19:32:55 +02:00
|
|
|
require.Error(t, err)
|
|
|
|
|
2024-06-25 11:11:18 +02:00
|
|
|
// Create hop list for an unknown destination.
|
|
|
|
hops := []route.Vertex{ctx.aliases["b"], ctx.aliases["y"]}
|
2024-04-16 11:03:59 +02:00
|
|
|
_, err = ctx.router.BuildRoute(
|
2024-09-24 05:36:15 +02:00
|
|
|
noAmt, hops, nil, 40, fn.Some(payAddr), fn.None[[]byte](),
|
2024-04-16 11:03:59 +02:00
|
|
|
)
|
2024-06-25 11:11:18 +02:00
|
|
|
noChanErr := ErrNoChannel{}
|
|
|
|
require.ErrorAs(t, err, &noChanErr)
|
|
|
|
require.Equal(t, 1, noChanErr.position)
|
|
|
|
|
2019-08-29 13:03:37 +02:00
|
|
|
// Create hop list from the route node pubkeys.
|
2024-06-25 11:11:18 +02:00
|
|
|
hops = []route.Vertex{ctx.aliases["b"], ctx.aliases["c"]}
|
2019-08-29 13:03:37 +02:00
|
|
|
amt := lnwire.NewMSatFromSatoshis(100)
|
|
|
|
|
|
|
|
// Build the route for the given amount.
|
2024-04-16 11:03:59 +02:00
|
|
|
rt, err := ctx.router.BuildRoute(
|
2024-09-24 05:36:15 +02:00
|
|
|
fn.Some(amt), hops, nil, 40, fn.Some(payAddr),
|
|
|
|
fn.None[[]byte](),
|
2024-04-16 11:03:59 +02:00
|
|
|
)
|
2024-06-25 11:11:18 +02:00
|
|
|
require.NoError(t, err)
|
2019-08-29 13:03:37 +02:00
|
|
|
|
|
|
|
// Check that we get the expected route back. The total amount should be
|
2019-09-30 15:45:16 +02:00
|
|
|
// the amount to deliver to hop c (100 sats) plus the max fee for the
|
|
|
|
// connection b->c (6 sats).
|
2020-11-24 05:17:16 +01:00
|
|
|
checkHops(rt, []uint64{1, 7}, payAddr)
|
2024-06-25 11:11:18 +02:00
|
|
|
require.Equal(t, lnwire.MilliSatoshi(106000), rt.TotalAmount)
|
2019-08-29 13:03:37 +02:00
|
|
|
|
|
|
|
// Build the route for the minimum amount.
|
2024-04-16 11:03:59 +02:00
|
|
|
rt, err = ctx.router.BuildRoute(
|
2024-09-24 05:36:15 +02:00
|
|
|
noAmt, hops, nil, 40, fn.Some(payAddr), fn.None[[]byte](),
|
2024-04-16 11:03:59 +02:00
|
|
|
)
|
2024-06-25 11:11:18 +02:00
|
|
|
require.NoError(t, err)
|
2019-08-29 13:03:37 +02:00
|
|
|
|
|
|
|
// Check that we get the expected route back. The minimum that we can
|
2019-09-30 15:45:16 +02:00
|
|
|
// send from b to c is 20 sats. Hop b charges 1200 msat for the
|
|
|
|
// forwarding. The channel between hop a and b can carry amounts in the
|
|
|
|
// range [5, 100], so 21200 msats is the minimum amount for this route.
|
2020-11-24 05:17:16 +01:00
|
|
|
checkHops(rt, []uint64{1, 7}, payAddr)
|
2024-06-25 11:11:18 +02:00
|
|
|
require.Equal(t, lnwire.MilliSatoshi(21200), rt.TotalAmount)
|
|
|
|
|
|
|
|
// The receiver gets sent the minimal HTLC amount.
|
|
|
|
require.Equal(t, lnwire.MilliSatoshi(20000), rt.Hops[1].AmtToForward)
|
2019-08-29 13:03:37 +02:00
|
|
|
|
|
|
|
// Test a route that contains incompatible channel htlc constraints.
|
|
|
|
// There is no amount that can pass through both channel 5 and 4.
|
2024-06-25 11:11:18 +02:00
|
|
|
hops = []route.Vertex{ctx.aliases["e"], ctx.aliases["c"]}
|
2024-04-16 11:03:59 +02:00
|
|
|
_, err = ctx.router.BuildRoute(
|
2024-09-24 05:36:15 +02:00
|
|
|
noAmt, hops, nil, 40, fn.None[[32]byte](), fn.None[[]byte](),
|
2024-04-16 11:03:59 +02:00
|
|
|
)
|
2024-06-25 11:11:18 +02:00
|
|
|
require.Error(t, err)
|
|
|
|
noChanErr = ErrNoChannel{}
|
|
|
|
require.ErrorAs(t, err, &noChanErr)
|
|
|
|
require.Equal(t, 0, noChanErr.position)
|
|
|
|
|
|
|
|
// Test a route that contains channel constraints that lead to a
|
|
|
|
// different selection of a unified edge, when the amount is rescaled
|
|
|
|
// for the final edge. From a backward pass we expect the policy of
|
|
|
|
// channel 8 to be used, because its policy has the highest fee rate,
|
|
|
|
// bumping the amount to 20000 msat leading to a sender amount of 21200
|
|
|
|
// msat including the fees for hop over channel 8. In the forward pass
|
2024-06-25 11:45:36 +02:00
|
|
|
// however, we subtract that fee again, resulting in the min HTLC
|
|
|
|
// amount. The forward pass doesn't check for a different policy that
|
|
|
|
// could me more applicable, which is why we don't get back the highest
|
|
|
|
// amount that could be delivered to the receiver of 21819 msat, using
|
|
|
|
// policy of channel 3.
|
2024-06-25 11:11:18 +02:00
|
|
|
hops = []route.Vertex{ctx.aliases["b"], ctx.aliases["z"]}
|
2024-04-16 11:03:59 +02:00
|
|
|
rt, err = ctx.router.BuildRoute(
|
2024-09-24 05:36:15 +02:00
|
|
|
noAmt, hops, nil, 40, fn.Some(payAddr), fn.None[[]byte](),
|
2024-04-16 11:03:59 +02:00
|
|
|
)
|
2024-06-25 11:11:18 +02:00
|
|
|
require.NoError(t, err)
|
2024-06-25 11:45:36 +02:00
|
|
|
checkHops(rt, []uint64{1, 8}, payAddr)
|
2024-06-25 11:11:18 +02:00
|
|
|
require.Equal(t, lnwire.MilliSatoshi(21200), rt.TotalAmount)
|
2024-06-25 11:45:36 +02:00
|
|
|
require.Equal(t, lnwire.MilliSatoshi(20000), rt.Hops[1].AmtToForward)
|
|
|
|
|
2024-07-30 14:17:47 +02:00
|
|
|
// Check that we compute a correct forwarding amount that involves
|
|
|
|
// inbound fees. We expect a similar amount as for the above case of
|
|
|
|
// b->c, but reduced by the inbound discount on the channel a->d.
|
|
|
|
// We get 106000 - 1000 (base in) - 0.001 * 106000 (rate in) = 104894.
|
|
|
|
hops = []route.Vertex{ctx.aliases["d"], ctx.aliases["f"]}
|
|
|
|
amt = lnwire.NewMSatFromSatoshis(100)
|
2024-04-16 11:03:59 +02:00
|
|
|
rt, err = ctx.router.BuildRoute(
|
2024-09-24 05:36:15 +02:00
|
|
|
fn.Some(amt), hops, nil, 40, fn.Some(payAddr),
|
|
|
|
fn.None[[]byte](),
|
2024-04-16 11:03:59 +02:00
|
|
|
)
|
2024-07-30 14:17:47 +02:00
|
|
|
require.NoError(t, err)
|
|
|
|
checkHops(rt, []uint64{9, 10}, payAddr)
|
|
|
|
require.EqualValues(t, 104894, rt.TotalAmount)
|
|
|
|
|
|
|
|
// Also check the min amount with inbound fees. The min amount bumps
|
|
|
|
// this to 20000 msat for the last hop. The outbound fee is 1200 msat,
|
|
|
|
// the inbound fee is -1021.2 msat (rounded down). This results in a
|
|
|
|
// total fee of 179 msat, giving a sender amount of 20179 msat. The
|
|
|
|
// determined receiver amount however reduces this to 20001 msat again
|
|
|
|
// due to rounding. This would not be compatible with the sender amount
|
|
|
|
// of 20179 msat, which results in underpayment of 1 msat in fee. There
|
|
|
|
// is a third pass through newRoute in which this gets corrected to end
|
|
|
|
hops = []route.Vertex{ctx.aliases["d"], ctx.aliases["f"]}
|
2024-04-16 11:03:59 +02:00
|
|
|
rt, err = ctx.router.BuildRoute(
|
2024-09-24 05:36:15 +02:00
|
|
|
noAmt, hops, nil, 40, fn.Some(payAddr), fn.None[[]byte](),
|
2024-04-16 11:03:59 +02:00
|
|
|
)
|
2024-07-30 14:17:47 +02:00
|
|
|
require.NoError(t, err)
|
|
|
|
checkHops(rt, []uint64{9, 10}, payAddr)
|
|
|
|
require.EqualValues(t, 20180, rt.TotalAmount, "%v", rt.TotalAmount)
|
2019-08-29 13:03:37 +02:00
|
|
|
}
|
2021-04-21 01:24:34 +02:00
|
|
|
|
2024-06-25 11:45:36 +02:00
|
|
|
// TestReceiverAmtForwardPass tests that the forward pass returns the expected
|
|
|
|
// receiver amount when given a set of edges and does not panic.
|
|
|
|
func TestReceiverAmtForwardPass(t *testing.T) {
|
2023-06-30 14:32:04 +02:00
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
testCases := []struct {
|
2024-06-25 11:45:36 +02:00
|
|
|
name string
|
|
|
|
amt lnwire.MilliSatoshi
|
|
|
|
unifiedEdges []*unifiedEdge
|
|
|
|
hops []route.Vertex
|
|
|
|
|
|
|
|
expectedAmt lnwire.MilliSatoshi
|
|
|
|
expectedErr string
|
|
|
|
}{
|
|
|
|
{
|
2024-06-25 19:32:55 +02:00
|
|
|
name: "empty",
|
|
|
|
expectedErr: "no edges to forward through",
|
2024-06-25 11:45:36 +02:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "single edge, no valid policy",
|
|
|
|
amt: 1000,
|
|
|
|
unifiedEdges: []*unifiedEdge{
|
|
|
|
{
|
|
|
|
policy: &models.CachedEdgePolicy{
|
|
|
|
MinHTLC: 1001,
|
|
|
|
},
|
|
|
|
},
|
2023-06-30 14:32:04 +02:00
|
|
|
},
|
2024-06-25 11:45:36 +02:00
|
|
|
expectedErr: fmt.Sprintf("no matching outgoing " +
|
|
|
|
"channel available for node index 0"),
|
2023-06-30 14:32:04 +02:00
|
|
|
},
|
2024-06-25 11:45:36 +02:00
|
|
|
{
|
|
|
|
name: "single edge",
|
|
|
|
amt: 1000,
|
|
|
|
unifiedEdges: []*unifiedEdge{
|
|
|
|
{
|
|
|
|
policy: &models.CachedEdgePolicy{
|
|
|
|
MinHTLC: 1000,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
expectedAmt: 1000,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "outbound fee, no rounding",
|
|
|
|
amt: 1e9,
|
|
|
|
unifiedEdges: []*unifiedEdge{
|
|
|
|
{
|
|
|
|
// The first hop's outbound fee is
|
|
|
|
// irrelevant in fee calculation.
|
|
|
|
policy: &models.CachedEdgePolicy{
|
|
|
|
FeeBaseMSat: 1234,
|
|
|
|
FeeProportionalMillionths: 1234,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
// No rounding is done here.
|
|
|
|
policy: &models.CachedEdgePolicy{
|
|
|
|
FeeBaseMSat: 1000,
|
|
|
|
FeeProportionalMillionths: 1000,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
// From an outgoing amount of 999000000 msat, we get
|
|
|
|
// in = out + base + out * rate = 1000000000.0
|
|
|
|
//
|
|
|
|
// The inverse outgoing amount for this is
|
|
|
|
// out = (in - base) / (1 + rate) =
|
|
|
|
// (1e9 - 1000) / (1 + 1e-3) = 999000000.0000001,
|
|
|
|
// which is rounded down.
|
|
|
|
expectedAmt: 999000000,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "outbound fee, rounding",
|
|
|
|
amt: 1e9,
|
|
|
|
unifiedEdges: []*unifiedEdge{
|
|
|
|
{
|
|
|
|
// The first hop's outbound fee is
|
|
|
|
// irrelevant in fee calculation.
|
|
|
|
policy: &models.CachedEdgePolicy{
|
|
|
|
FeeBaseMSat: 1234,
|
|
|
|
FeeProportionalMillionths: 1234,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
// This policy is chosen such that we
|
|
|
|
// round down.
|
|
|
|
policy: &models.CachedEdgePolicy{
|
|
|
|
FeeBaseMSat: 1000,
|
|
|
|
FeeProportionalMillionths: 999,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
// The float amount for this is
|
|
|
|
// out = (in - base) / (1 + rate) =
|
|
|
|
// (1e9 - 1000) / (1 + 999e-6) = 999000998.002995,
|
|
|
|
// which is rounded up.
|
|
|
|
expectedAmt: 999000999,
|
|
|
|
},
|
|
|
|
}
|
2023-06-30 14:32:04 +02:00
|
|
|
|
|
|
|
for _, tc := range testCases {
|
2024-06-25 11:45:36 +02:00
|
|
|
amt, err := receiverAmtForwardPass(tc.amt, tc.unifiedEdges)
|
2023-06-30 14:32:04 +02:00
|
|
|
|
|
|
|
if tc.expectedErr != "" {
|
|
|
|
require.Error(t, err)
|
|
|
|
require.ErrorContains(t, err, tc.expectedErr)
|
|
|
|
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, amt, tc.expectedAmt)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-06-25 11:45:36 +02:00
|
|
|
// TestSenderAmtBackwardPass tests that the computation of the sender amount is
|
|
|
|
// done correctly for route building.
|
|
|
|
func TestSenderAmtBackwardPass(t *testing.T) {
|
|
|
|
bandwidthHints := bandwidthManager{
|
|
|
|
getLink: func(chanId lnwire.ShortChannelID) (
|
|
|
|
htlcswitch.ChannelLink, error) {
|
|
|
|
|
|
|
|
return nil, nil
|
|
|
|
},
|
|
|
|
localChans: make(map[lnwire.ShortChannelID]struct{}),
|
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
|
|
|
capacity btcutil.Amount = 1_000_000
|
|
|
|
testReceiverAmt lnwire.MilliSatoshi = 1_000_000
|
|
|
|
minHTLC lnwire.MilliSatoshi = 1_000
|
|
|
|
)
|
|
|
|
|
|
|
|
edgeUnifiers := []*edgeUnifier{
|
|
|
|
{
|
|
|
|
edges: []*unifiedEdge{
|
|
|
|
{
|
|
|
|
// This outbound fee doesn't have an
|
|
|
|
// effect (sender doesn't pay outbound).
|
|
|
|
policy: &models.CachedEdgePolicy{
|
|
|
|
FeeBaseMSat: 112,
|
|
|
|
},
|
2024-07-30 14:17:47 +02:00
|
|
|
inboundFees: models.InboundFee{
|
|
|
|
Base: 111,
|
|
|
|
},
|
2024-06-25 11:45:36 +02:00
|
|
|
capacity: capacity,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
edges: []*unifiedEdge{
|
|
|
|
{
|
|
|
|
policy: &models.CachedEdgePolicy{
|
|
|
|
FeeBaseMSat: 222,
|
|
|
|
},
|
2024-07-30 14:17:47 +02:00
|
|
|
inboundFees: models.InboundFee{
|
|
|
|
Base: 222,
|
|
|
|
},
|
2024-06-25 11:45:36 +02:00
|
|
|
capacity: capacity,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
edges: []*unifiedEdge{
|
|
|
|
{
|
|
|
|
policy: &models.CachedEdgePolicy{
|
|
|
|
FeeBaseMSat: 333,
|
|
|
|
MinHTLC: minHTLC,
|
|
|
|
},
|
2024-07-30 14:17:47 +02:00
|
|
|
// In pathfinding, inbound fees are not
|
|
|
|
// populated for exit hops because the
|
|
|
|
// newNodeEdgeUnifier enforces this.
|
|
|
|
// This is important as otherwise we
|
|
|
|
// would not fail the min HTLC check in
|
|
|
|
// getEdge.
|
2024-06-25 11:45:36 +02:00
|
|
|
capacity: capacity,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
// A search for an amount that is below the minimum HTLC amount should
|
|
|
|
// fail.
|
|
|
|
_, _, err := senderAmtBackwardPass(
|
2024-07-31 14:32:48 +02:00
|
|
|
edgeUnifiers, fn.Some(minHTLC-1), &bandwidthHints,
|
2024-06-25 11:45:36 +02:00
|
|
|
)
|
|
|
|
require.Error(t, err)
|
|
|
|
|
|
|
|
// Do a min amount search.
|
2024-07-31 14:32:48 +02:00
|
|
|
_, senderAmount, err := senderAmtBackwardPass(
|
|
|
|
edgeUnifiers, fn.None[lnwire.MilliSatoshi](), &bandwidthHints,
|
2024-06-25 11:45:36 +02:00
|
|
|
)
|
|
|
|
require.NoError(t, err)
|
2024-07-30 14:17:47 +02:00
|
|
|
require.Equal(t, minHTLC+333+222+222+111, senderAmount)
|
2024-06-25 11:45:36 +02:00
|
|
|
|
|
|
|
// Do a search for a specific amount.
|
2024-07-31 14:32:48 +02:00
|
|
|
unifiedEdges, senderAmount, err := senderAmtBackwardPass(
|
|
|
|
edgeUnifiers, fn.Some(testReceiverAmt), &bandwidthHints,
|
2024-06-25 11:45:36 +02:00
|
|
|
)
|
|
|
|
require.NoError(t, err)
|
2024-07-30 14:17:47 +02:00
|
|
|
require.Equal(t, testReceiverAmt+333+222+222+111, senderAmount)
|
2024-06-25 11:45:36 +02:00
|
|
|
|
|
|
|
// Check that we arrive at the same receiver amount by doing a forward
|
|
|
|
// pass.
|
|
|
|
receiverAmt, err := receiverAmtForwardPass(senderAmount, unifiedEdges)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, testReceiverAmt, receiverAmt)
|
2024-07-30 14:17:47 +02:00
|
|
|
|
|
|
|
// Insert a policy that leads to rounding.
|
|
|
|
edgeUnifiers[1] = &edgeUnifier{
|
|
|
|
edges: []*unifiedEdge{
|
|
|
|
{
|
|
|
|
policy: &models.CachedEdgePolicy{
|
|
|
|
FeeBaseMSat: 20,
|
|
|
|
FeeProportionalMillionths: 100,
|
|
|
|
},
|
|
|
|
inboundFees: models.InboundFee{
|
|
|
|
Base: -10,
|
|
|
|
Rate: -50,
|
|
|
|
},
|
|
|
|
capacity: capacity,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
unifiedEdges, senderAmount, err = senderAmtBackwardPass(
|
2024-07-31 14:32:48 +02:00
|
|
|
edgeUnifiers, fn.Some(testReceiverAmt), &bandwidthHints,
|
2024-07-30 14:17:47 +02:00
|
|
|
)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
// For this route, we have some rounding errors, so we can't expect the
|
|
|
|
// exact amount, but it should be higher than the exact amount, to not
|
|
|
|
// end up below a min HTLC constraint.
|
|
|
|
receiverAmt, err = receiverAmtForwardPass(senderAmount, unifiedEdges)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.NotEqual(t, testReceiverAmt, receiverAmt)
|
|
|
|
require.InDelta(t, int64(testReceiverAmt), int64(receiverAmt), 1)
|
|
|
|
require.GreaterOrEqual(t, int64(receiverAmt), int64(testReceiverAmt))
|
2024-06-25 11:45:36 +02:00
|
|
|
}
|
|
|
|
|
2024-07-01 13:34:45 +02:00
|
|
|
// TestInboundOutbound tests the functions that computes the incoming and
|
|
|
|
// outgoing amounts based on the fees of the incoming and outgoing channels.
|
|
|
|
func TestInboundOutbound(t *testing.T) {
|
|
|
|
var outgoingAmt uint64 = 10_000_000
|
|
|
|
|
|
|
|
tests := []struct {
|
|
|
|
name string
|
|
|
|
incomingBase int32
|
|
|
|
incomingRate int32
|
|
|
|
outgoingBase uint64
|
|
|
|
outgoingRate uint64
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "only outbound fee",
|
|
|
|
incomingBase: 0,
|
|
|
|
incomingRate: 0,
|
|
|
|
outgoingBase: 20,
|
|
|
|
outgoingRate: 100,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "positive inbound and outbound fee",
|
|
|
|
incomingBase: 20,
|
|
|
|
incomingRate: 100,
|
|
|
|
outgoingBase: 20,
|
|
|
|
outgoingRate: 100,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "small negative inbound and outbound fee",
|
|
|
|
incomingBase: -10,
|
|
|
|
incomingRate: -50,
|
|
|
|
outgoingBase: 20,
|
|
|
|
outgoingRate: 100,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "equal negative inbound and outbound fee",
|
|
|
|
incomingBase: -20,
|
|
|
|
incomingRate: -100,
|
|
|
|
outgoingBase: 20,
|
|
|
|
outgoingRate: 100,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "large negative inbound and outbound fee",
|
|
|
|
incomingBase: -30,
|
|
|
|
incomingRate: -200,
|
|
|
|
outgoingBase: 20,
|
|
|
|
outgoingRate: 100,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "order of PPM negative inbound and " +
|
|
|
|
"outbound fee (m=0)",
|
|
|
|
incomingBase: -30,
|
|
|
|
incomingRate: -1_000_000,
|
|
|
|
outgoingBase: 20,
|
|
|
|
outgoingRate: 100,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "huge negative inbound and " +
|
|
|
|
"outbound fee (m<0)",
|
|
|
|
incomingBase: -30,
|
|
|
|
incomingRate: -2_000_000,
|
|
|
|
outgoingBase: 20,
|
|
|
|
outgoingRate: 100,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tc := range tests {
|
|
|
|
tc := tc
|
|
|
|
|
|
|
|
t.Run(tc.name, func(tt *testing.T) {
|
|
|
|
testInboundOutboundFee(
|
|
|
|
tt, outgoingAmt, tc.incomingBase,
|
|
|
|
tc.incomingRate, tc.outgoingBase,
|
|
|
|
tc.outgoingRate,
|
|
|
|
)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// testInboundOutboundFee is a helper function that tests the outgoing and
|
|
|
|
// incoming amount relationship.
|
|
|
|
func testInboundOutboundFee(t *testing.T, outgoingAmt uint64, inBase,
|
|
|
|
inRate int32, outBase, outRate uint64) {
|
|
|
|
|
|
|
|
debugStr := fmt.Sprintf(
|
|
|
|
"outAmt=%d, inBase=%d, inRate=%d, outBase=%d, outRate=%d",
|
|
|
|
outgoingAmt, inBase, inRate, outBase, outRate,
|
|
|
|
)
|
|
|
|
|
|
|
|
incomingEdge := &unifiedEdge{
|
|
|
|
policy: &models.CachedEdgePolicy{},
|
|
|
|
inboundFees: models.InboundFee{
|
|
|
|
Base: inBase,
|
|
|
|
Rate: inRate,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
outgoingEdge := &unifiedEdge{
|
|
|
|
policy: &models.CachedEdgePolicy{
|
|
|
|
FeeBaseMSat: lnwire.MilliSatoshi(
|
|
|
|
outBase,
|
|
|
|
),
|
|
|
|
FeeProportionalMillionths: lnwire.MilliSatoshi(
|
|
|
|
outRate,
|
|
|
|
),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
// We compute the incoming amount based on the outgoing amount, which
|
|
|
|
// mimicks the path finding process.
|
|
|
|
incomingAmt := incomingFromOutgoing(
|
|
|
|
lnwire.MilliSatoshi(outgoingAmt), incomingEdge,
|
|
|
|
outgoingEdge,
|
|
|
|
)
|
|
|
|
|
|
|
|
// We do the reverse and compute the outgoing amount based on the
|
|
|
|
// incoming amount.
|
|
|
|
outgoingAmtNew := outgoingFromIncoming(
|
|
|
|
incomingAmt, incomingEdge, outgoingEdge,
|
|
|
|
)
|
|
|
|
|
|
|
|
// We require that the incoming amount is always larger than or equal to
|
|
|
|
// the outgoing amount, because total fees (=incoming-outgoing) should
|
|
|
|
// not become negative.
|
|
|
|
require.GreaterOrEqual(
|
|
|
|
t, int64(incomingAmt), int64(outgoingAmtNew), debugStr,
|
|
|
|
"expected incomingAmt >= outgoingAmtNew",
|
|
|
|
)
|
|
|
|
|
|
|
|
// We check that up to rounding the amounts are equal.
|
|
|
|
require.InDelta(
|
|
|
|
t, int64(outgoingAmt), int64(outgoingAmtNew), 1.0, debugStr,
|
|
|
|
"expected |outgoingAmt - outgoingAmtNew | <= 1",
|
|
|
|
)
|
|
|
|
|
|
|
|
// If we round, the computed outgoing amount should be larger than the
|
|
|
|
// exact outgoing amount, to not hit any min HTLC limits.
|
|
|
|
require.GreaterOrEqual(
|
|
|
|
t, int64(outgoingAmtNew), int64(outgoingAmt), debugStr,
|
|
|
|
"expected outgoingAmtNew >= outgoingAmt",
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
// FuzzInboundOutbound tests the incoming and outgoing amount calculation
|
|
|
|
// functions with fuzzing.
|
|
|
|
func FuzzInboundOutboundFee(f *testing.F) {
|
|
|
|
f.Add(uint64(0), int32(0), int32(0), uint64(0), uint64(0))
|
|
|
|
|
|
|
|
f.Fuzz(testInboundOutboundFee)
|
|
|
|
}
|
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
// TestSendToRouteSkipTempErrSuccess validates a successful payment send.
|
|
|
|
func TestSendToRouteSkipTempErrSuccess(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
var (
|
|
|
|
payHash lntypes.Hash
|
|
|
|
payAmt = lnwire.MilliSatoshi(10000)
|
|
|
|
)
|
|
|
|
|
|
|
|
preimage := lntypes.Preimage{1}
|
|
|
|
testAttempt := makeSettledAttempt(t, int(payAmt), preimage)
|
|
|
|
|
|
|
|
node, err := createTestNode()
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
// Create a simple 1-hop route.
|
|
|
|
hops := []*route.Hop{
|
|
|
|
{
|
|
|
|
ChannelID: 1,
|
|
|
|
PubKeyBytes: node.PubKeyBytes,
|
|
|
|
AmtToForward: payAmt,
|
|
|
|
OutgoingTimeLock: 120,
|
|
|
|
MPP: record.NewMPP(payAmt, [32]byte{}),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
rt, err := route.NewRouteFromHops(payAmt, 100, node.PubKeyBytes, hops)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
// Create mockers.
|
|
|
|
controlTower := &mockControlTower{}
|
|
|
|
payer := &mockPaymentAttemptDispatcher{}
|
|
|
|
missionControl := &mockMissionControl{}
|
|
|
|
|
|
|
|
// Create the router.
|
|
|
|
router := &ChannelRouter{cfg: &Config{
|
|
|
|
Control: controlTower,
|
|
|
|
Payer: payer,
|
|
|
|
MissionControl: missionControl,
|
|
|
|
Clock: clock.NewTestClock(time.Unix(1, 0)),
|
|
|
|
NextPaymentID: func() (uint64, error) {
|
|
|
|
return 0, nil
|
|
|
|
},
|
2024-04-19 13:49:45 +02:00
|
|
|
ClosedSCIDs: mockClosedSCIDs,
|
|
|
|
TrafficShaper: fn.Some[TlvTrafficShaper](&mockTrafficShaper{}),
|
2024-06-17 01:30:01 +02:00
|
|
|
}}
|
|
|
|
|
|
|
|
// Register mockers with the expected method calls.
|
|
|
|
controlTower.On("InitPayment", payHash, mock.Anything).Return(nil)
|
|
|
|
controlTower.On("RegisterAttempt", payHash, mock.Anything).Return(nil)
|
|
|
|
controlTower.On("SettleAttempt",
|
|
|
|
payHash, mock.Anything, mock.Anything,
|
|
|
|
).Return(testAttempt, nil)
|
|
|
|
|
|
|
|
payer.On("SendHTLC",
|
|
|
|
mock.Anything, mock.Anything, mock.Anything,
|
|
|
|
).Return(nil)
|
2023-06-30 14:32:04 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
// Create a buffered chan and it will be returned by GetAttemptResult.
|
|
|
|
resultChan := make(chan *htlcswitch.PaymentResult, 1)
|
|
|
|
payer.On("GetAttemptResult",
|
|
|
|
mock.Anything, mock.Anything, mock.Anything,
|
|
|
|
).Return(resultChan, nil).Run(func(_ mock.Arguments) {
|
|
|
|
// Send a successful payment result.
|
|
|
|
resultChan <- &htlcswitch.PaymentResult{}
|
|
|
|
})
|
2021-04-21 01:24:34 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
missionControl.On("ReportPaymentSuccess",
|
|
|
|
mock.Anything, rt,
|
|
|
|
).Return(nil)
|
2021-04-21 01:24:34 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
// Mock the control tower to return the mocked payment.
|
|
|
|
payment := &mockMPPayment{}
|
|
|
|
controlTower.On("FetchPayment", payHash).Return(payment, nil).Once()
|
2021-04-23 01:41:15 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
// Mock the payment to return nil failure reason.
|
|
|
|
payment.On("TerminalInfo").Return(nil, nil)
|
2021-04-21 01:24:34 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
// Expect a successful send to route.
|
2024-04-16 11:03:59 +02:00
|
|
|
attempt, err := router.SendToRouteSkipTempErr(payHash, rt, nil)
|
2024-06-17 01:30:01 +02:00
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, testAttempt, attempt)
|
2021-04-21 01:24:34 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
// Assert the above methods are called as expected.
|
|
|
|
controlTower.AssertExpectations(t)
|
|
|
|
payer.AssertExpectations(t)
|
|
|
|
missionControl.AssertExpectations(t)
|
|
|
|
payment.AssertExpectations(t)
|
|
|
|
}
|
|
|
|
|
|
|
|
// TestSendToRouteSkipTempErrNonMPP checks that an error is return when
|
|
|
|
// skipping temp error for non-MPP.
|
|
|
|
func TestSendToRouteSkipTempErrNonMPP(t *testing.T) {
|
|
|
|
t.Parallel()
|
2021-04-21 01:24:34 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
var (
|
|
|
|
payHash lntypes.Hash
|
|
|
|
payAmt = lnwire.MilliSatoshi(10000)
|
2021-04-21 01:24:34 +02:00
|
|
|
)
|
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
node, err := createTestNode()
|
|
|
|
require.NoError(t, err)
|
2021-04-21 01:24:34 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
// Create a simple 1-hop route without the MPP field.
|
|
|
|
hops := []*route.Hop{
|
|
|
|
{
|
|
|
|
ChannelID: 1,
|
|
|
|
PubKeyBytes: node.PubKeyBytes,
|
|
|
|
AmtToForward: payAmt,
|
|
|
|
},
|
2021-04-21 01:24:34 +02:00
|
|
|
}
|
2024-06-17 01:30:01 +02:00
|
|
|
rt, err := route.NewRouteFromHops(payAmt, 100, node.PubKeyBytes, hops)
|
|
|
|
require.NoError(t, err)
|
2021-04-21 01:24:34 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
// Create mockers.
|
|
|
|
controlTower := &mockControlTower{}
|
|
|
|
payer := &mockPaymentAttemptDispatcher{}
|
|
|
|
missionControl := &mockMissionControl{}
|
2021-04-21 01:24:34 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
// Create the router.
|
|
|
|
router := &ChannelRouter{cfg: &Config{
|
|
|
|
Control: controlTower,
|
|
|
|
Payer: payer,
|
|
|
|
MissionControl: missionControl,
|
|
|
|
Clock: clock.NewTestClock(time.Unix(1, 0)),
|
|
|
|
NextPaymentID: func() (uint64, error) {
|
|
|
|
return 0, nil
|
|
|
|
},
|
2024-04-19 13:49:45 +02:00
|
|
|
ClosedSCIDs: mockClosedSCIDs,
|
|
|
|
TrafficShaper: fn.Some[TlvTrafficShaper](&mockTrafficShaper{}),
|
2024-06-17 01:30:01 +02:00
|
|
|
}}
|
2021-04-21 01:24:34 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
// Expect an error to be returned.
|
2024-04-16 11:03:59 +02:00
|
|
|
attempt, err := router.SendToRouteSkipTempErr(payHash, rt, nil)
|
2024-06-17 01:30:01 +02:00
|
|
|
require.ErrorIs(t, ErrSkipTempErr, err)
|
|
|
|
require.Nil(t, attempt)
|
2021-04-23 01:41:15 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
// Assert the above methods are not called.
|
|
|
|
controlTower.AssertExpectations(t)
|
|
|
|
payer.AssertExpectations(t)
|
|
|
|
missionControl.AssertExpectations(t)
|
2021-04-21 01:24:34 +02:00
|
|
|
}
|
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
// TestSendToRouteSkipTempErrTempFailure validates a temporary failure won't
|
|
|
|
// cause the payment to be failed.
|
|
|
|
func TestSendToRouteSkipTempErrTempFailure(t *testing.T) {
|
|
|
|
t.Parallel()
|
2021-04-21 01:24:34 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
var (
|
|
|
|
payHash lntypes.Hash
|
|
|
|
payAmt = lnwire.MilliSatoshi(10000)
|
2021-04-21 01:24:34 +02:00
|
|
|
)
|
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
testAttempt := makeFailedAttempt(t, int(payAmt))
|
|
|
|
node, err := createTestNode()
|
|
|
|
require.NoError(t, err)
|
2021-04-21 01:24:34 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
// Create a simple 1-hop route.
|
|
|
|
hops := []*route.Hop{
|
|
|
|
{
|
|
|
|
ChannelID: 1,
|
|
|
|
PubKeyBytes: node.PubKeyBytes,
|
|
|
|
AmtToForward: payAmt,
|
|
|
|
OutgoingTimeLock: 120,
|
|
|
|
MPP: record.NewMPP(payAmt, [32]byte{}),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
rt, err := route.NewRouteFromHops(payAmt, 100, node.PubKeyBytes, hops)
|
|
|
|
require.NoError(t, err)
|
2021-04-21 01:24:34 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
// Create mockers.
|
|
|
|
controlTower := &mockControlTower{}
|
|
|
|
payer := &mockPaymentAttemptDispatcher{}
|
|
|
|
missionControl := &mockMissionControl{}
|
2021-04-21 01:24:34 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
// Create the router.
|
|
|
|
router := &ChannelRouter{cfg: &Config{
|
|
|
|
Control: controlTower,
|
|
|
|
Payer: payer,
|
|
|
|
MissionControl: missionControl,
|
|
|
|
Clock: clock.NewTestClock(time.Unix(1, 0)),
|
|
|
|
NextPaymentID: func() (uint64, error) {
|
|
|
|
return 0, nil
|
|
|
|
},
|
2024-04-19 13:49:45 +02:00
|
|
|
ClosedSCIDs: mockClosedSCIDs,
|
|
|
|
TrafficShaper: fn.Some[TlvTrafficShaper](&mockTrafficShaper{}),
|
2024-06-17 01:30:01 +02:00
|
|
|
}}
|
2021-04-21 01:24:34 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
// Create the error to be returned.
|
|
|
|
tempErr := htlcswitch.NewForwardingError(
|
|
|
|
&lnwire.FailTemporaryChannelFailure{}, 1,
|
|
|
|
)
|
2021-04-23 01:41:15 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
// Register mockers with the expected method calls.
|
|
|
|
controlTower.On("InitPayment", payHash, mock.Anything).Return(nil)
|
|
|
|
controlTower.On("RegisterAttempt", payHash, mock.Anything).Return(nil)
|
|
|
|
controlTower.On("FailAttempt",
|
|
|
|
payHash, mock.Anything, mock.Anything,
|
|
|
|
).Return(testAttempt, nil)
|
2021-05-21 13:02:49 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
payer.On("SendHTLC",
|
|
|
|
mock.Anything, mock.Anything, mock.Anything,
|
|
|
|
).Return(tempErr)
|
2021-05-21 13:02:49 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
// Mock the control tower to return the mocked payment.
|
|
|
|
payment := &mockMPPayment{}
|
|
|
|
controlTower.On("FetchPayment", payHash).Return(payment, nil).Once()
|
2021-05-21 13:02:49 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
// Mock the mission control to return a nil reason from reporting the
|
|
|
|
// attempt failure.
|
|
|
|
missionControl.On("ReportPaymentFail",
|
|
|
|
mock.Anything, rt, mock.Anything, mock.Anything,
|
|
|
|
).Return(nil, nil)
|
2021-05-21 13:02:49 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
// Mock the payment to return nil failure reason.
|
|
|
|
payment.On("TerminalInfo").Return(nil, nil)
|
2021-05-21 13:02:49 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
// Expect a failed send to route.
|
2024-04-16 11:03:59 +02:00
|
|
|
attempt, err := router.SendToRouteSkipTempErr(payHash, rt, nil)
|
2024-06-17 01:30:01 +02:00
|
|
|
require.Equal(t, tempErr, err)
|
|
|
|
require.Equal(t, testAttempt, attempt)
|
2021-05-21 13:02:49 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
// Assert the above methods are called as expected.
|
|
|
|
controlTower.AssertExpectations(t)
|
|
|
|
payer.AssertExpectations(t)
|
|
|
|
missionControl.AssertExpectations(t)
|
|
|
|
payment.AssertExpectations(t)
|
2021-05-21 13:02:49 +02:00
|
|
|
}
|
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
// TestSendToRouteSkipTempErrPermanentFailure validates a permanent failure
|
|
|
|
// will fail the payment.
|
|
|
|
func TestSendToRouteSkipTempErrPermanentFailure(t *testing.T) {
|
|
|
|
var (
|
|
|
|
payHash lntypes.Hash
|
|
|
|
payAmt = lnwire.MilliSatoshi(10000)
|
|
|
|
)
|
2021-03-18 03:32:26 +01:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
testAttempt := makeFailedAttempt(t, int(payAmt))
|
|
|
|
node, err := createTestNode()
|
|
|
|
require.NoError(t, err)
|
2021-03-18 03:32:26 +01:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
// Create a simple 1-hop route.
|
|
|
|
hops := []*route.Hop{
|
|
|
|
{
|
|
|
|
ChannelID: 1,
|
|
|
|
PubKeyBytes: node.PubKeyBytes,
|
|
|
|
AmtToForward: payAmt,
|
|
|
|
OutgoingTimeLock: 120,
|
|
|
|
MPP: record.NewMPP(payAmt, [32]byte{}),
|
|
|
|
},
|
2021-03-18 03:32:26 +01:00
|
|
|
}
|
2024-06-17 01:30:01 +02:00
|
|
|
rt, err := route.NewRouteFromHops(payAmt, 100, node.PubKeyBytes, hops)
|
|
|
|
require.NoError(t, err)
|
2021-03-18 03:32:26 +01:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
// Create mockers.
|
|
|
|
controlTower := &mockControlTower{}
|
|
|
|
payer := &mockPaymentAttemptDispatcher{}
|
|
|
|
missionControl := &mockMissionControl{}
|
2021-03-18 03:32:26 +01:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
// Create the router.
|
|
|
|
router := &ChannelRouter{cfg: &Config{
|
|
|
|
Control: controlTower,
|
|
|
|
Payer: payer,
|
|
|
|
MissionControl: missionControl,
|
|
|
|
Clock: clock.NewTestClock(time.Unix(1, 0)),
|
|
|
|
NextPaymentID: func() (uint64, error) {
|
|
|
|
return 0, nil
|
|
|
|
},
|
2024-04-19 13:49:45 +02:00
|
|
|
ClosedSCIDs: mockClosedSCIDs,
|
|
|
|
TrafficShaper: fn.Some[TlvTrafficShaper](&mockTrafficShaper{}),
|
2024-06-17 01:30:01 +02:00
|
|
|
}}
|
2021-03-18 03:32:26 +01:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
// Create the error to be returned.
|
|
|
|
permErr := htlcswitch.NewForwardingError(
|
|
|
|
&lnwire.FailIncorrectDetails{}, 1,
|
|
|
|
)
|
2021-03-18 03:32:26 +01:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
// Register mockers with the expected method calls.
|
|
|
|
controlTower.On("InitPayment", payHash, mock.Anything).Return(nil)
|
|
|
|
controlTower.On("RegisterAttempt", payHash, mock.Anything).Return(nil)
|
2021-03-18 03:32:26 +01:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
controlTower.On("FailAttempt",
|
|
|
|
payHash, mock.Anything, mock.Anything,
|
|
|
|
).Return(testAttempt, nil)
|
2021-03-18 03:32:26 +01:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
// Expect the payment to be failed.
|
|
|
|
controlTower.On("FailPayment", payHash, mock.Anything).Return(nil)
|
2021-03-18 03:32:26 +01:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
// Mock an error to be returned from sending the htlc.
|
|
|
|
payer.On("SendHTLC",
|
|
|
|
mock.Anything, mock.Anything, mock.Anything,
|
|
|
|
).Return(permErr)
|
2021-03-18 03:32:26 +01:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
failureReason := channeldb.FailureReasonPaymentDetails
|
|
|
|
missionControl.On("ReportPaymentFail",
|
|
|
|
mock.Anything, rt, mock.Anything, mock.Anything,
|
|
|
|
).Return(&failureReason, nil)
|
2021-03-18 03:32:26 +01:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
// Mock the control tower to return the mocked payment.
|
|
|
|
payment := &mockMPPayment{}
|
|
|
|
controlTower.On("FetchPayment", payHash).Return(payment, nil).Once()
|
2021-03-18 03:32:26 +01:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
// Mock the payment to return a failure reason.
|
|
|
|
payment.On("TerminalInfo").Return(nil, &failureReason)
|
2021-03-18 03:32:26 +01:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
// Expect a failed send to route.
|
2024-04-16 11:03:59 +02:00
|
|
|
attempt, err := router.SendToRouteSkipTempErr(payHash, rt, nil)
|
2024-06-17 01:30:01 +02:00
|
|
|
require.Equal(t, permErr, err)
|
|
|
|
require.Equal(t, testAttempt, attempt)
|
2021-09-17 00:21:00 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
// Assert the above methods are called as expected.
|
|
|
|
controlTower.AssertExpectations(t)
|
|
|
|
payer.AssertExpectations(t)
|
|
|
|
missionControl.AssertExpectations(t)
|
|
|
|
payment.AssertExpectations(t)
|
2021-03-18 03:32:26 +01:00
|
|
|
}
|
2022-05-20 08:54:10 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
// TestSendToRouteTempFailure validates a temporary failure will cause the
|
|
|
|
// payment to be failed.
|
|
|
|
func TestSendToRouteTempFailure(t *testing.T) {
|
2022-05-20 08:54:10 +02:00
|
|
|
var (
|
2023-03-07 11:43:14 +01:00
|
|
|
payHash lntypes.Hash
|
|
|
|
payAmt = lnwire.MilliSatoshi(10000)
|
2022-05-20 08:54:10 +02:00
|
|
|
)
|
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
testAttempt := makeFailedAttempt(t, int(payAmt))
|
2022-05-20 08:54:10 +02:00
|
|
|
node, err := createTestNode()
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
// Create a simple 1-hop route.
|
|
|
|
hops := []*route.Hop{
|
|
|
|
{
|
2023-11-02 19:34:47 +01:00
|
|
|
ChannelID: 1,
|
|
|
|
PubKeyBytes: node.PubKeyBytes,
|
|
|
|
AmtToForward: payAmt,
|
|
|
|
OutgoingTimeLock: 120,
|
|
|
|
MPP: record.NewMPP(payAmt, [32]byte{}),
|
2022-05-20 08:54:10 +02:00
|
|
|
},
|
|
|
|
}
|
|
|
|
rt, err := route.NewRouteFromHops(payAmt, 100, node.PubKeyBytes, hops)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
// Create mockers.
|
|
|
|
controlTower := &mockControlTower{}
|
|
|
|
payer := &mockPaymentAttemptDispatcher{}
|
|
|
|
missionControl := &mockMissionControl{}
|
|
|
|
|
|
|
|
// Create the router.
|
|
|
|
router := &ChannelRouter{cfg: &Config{
|
|
|
|
Control: controlTower,
|
|
|
|
Payer: payer,
|
|
|
|
MissionControl: missionControl,
|
|
|
|
Clock: clock.NewTestClock(time.Unix(1, 0)),
|
|
|
|
NextPaymentID: func() (uint64, error) {
|
|
|
|
return 0, nil
|
|
|
|
},
|
2024-04-19 13:49:45 +02:00
|
|
|
ClosedSCIDs: mockClosedSCIDs,
|
|
|
|
TrafficShaper: fn.Some[TlvTrafficShaper](&mockTrafficShaper{}),
|
2022-05-20 08:54:10 +02:00
|
|
|
}}
|
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
// Create the error to be returned.
|
|
|
|
tempErr := htlcswitch.NewForwardingError(
|
|
|
|
&lnwire.FailTemporaryChannelFailure{}, 1,
|
|
|
|
)
|
|
|
|
|
2022-05-20 08:54:10 +02:00
|
|
|
// Register mockers with the expected method calls.
|
|
|
|
controlTower.On("InitPayment", payHash, mock.Anything).Return(nil)
|
|
|
|
controlTower.On("RegisterAttempt", payHash, mock.Anything).Return(nil)
|
2024-06-17 01:30:01 +02:00
|
|
|
controlTower.On("FailAttempt",
|
2022-05-20 08:54:10 +02:00
|
|
|
payHash, mock.Anything, mock.Anything,
|
2023-03-07 18:14:32 +01:00
|
|
|
).Return(testAttempt, nil)
|
2022-05-20 08:54:10 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
// Expect the payment to be failed.
|
|
|
|
controlTower.On("FailPayment", payHash, mock.Anything).Return(nil)
|
2022-05-20 08:54:10 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
payer.On("SendHTLC",
|
2022-05-20 08:54:10 +02:00
|
|
|
mock.Anything, mock.Anything, mock.Anything,
|
2024-06-17 01:30:01 +02:00
|
|
|
).Return(tempErr)
|
2022-05-20 08:54:10 +02:00
|
|
|
|
2023-03-07 11:43:14 +01:00
|
|
|
// Mock the control tower to return the mocked payment.
|
|
|
|
payment := &mockMPPayment{}
|
|
|
|
controlTower.On("FetchPayment", payHash).Return(payment, nil).Once()
|
|
|
|
|
2023-10-20 00:30:54 +02:00
|
|
|
// Mock the payment to return nil failure reason.
|
2023-02-13 06:58:52 +01:00
|
|
|
payment.On("TerminalInfo").Return(nil, nil)
|
2023-03-07 11:43:14 +01:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
// Return a nil reason to mock a temporary failure.
|
|
|
|
missionControl.On("ReportPaymentFail",
|
|
|
|
mock.Anything, rt, mock.Anything, mock.Anything,
|
|
|
|
).Return(nil, nil)
|
|
|
|
|
|
|
|
// Expect a failed send to route.
|
2024-04-16 11:03:59 +02:00
|
|
|
attempt, err := router.SendToRoute(payHash, rt, nil)
|
2024-06-17 01:30:01 +02:00
|
|
|
require.Equal(t, tempErr, err)
|
2023-03-07 18:14:32 +01:00
|
|
|
require.Equal(t, testAttempt, attempt)
|
2022-05-20 08:54:10 +02:00
|
|
|
|
|
|
|
// Assert the above methods are called as expected.
|
|
|
|
controlTower.AssertExpectations(t)
|
|
|
|
payer.AssertExpectations(t)
|
|
|
|
missionControl.AssertExpectations(t)
|
2023-03-07 11:43:14 +01:00
|
|
|
payment.AssertExpectations(t)
|
2022-05-20 08:54:10 +02:00
|
|
|
}
|
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
// TestNewRouteRequest tests creation of route requests for blinded and
|
|
|
|
// unblinded routes.
|
|
|
|
func TestNewRouteRequest(t *testing.T) {
|
2023-10-24 08:53:39 +02:00
|
|
|
t.Parallel()
|
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
//nolint:lll
|
|
|
|
source, err := route.NewVertexFromStr("0367cec75158a4129177bfb8b269cb586efe93d751b43800d456485e81c2620ca6")
|
|
|
|
require.NoError(t, err)
|
|
|
|
sourcePubkey, err := btcec.ParsePubKey(source[:])
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
//nolint:lll
|
|
|
|
v1, err := route.NewVertexFromStr("026c43a8ac1cd8519985766e90748e1e06871dab0ff6b8af27e8c1a61640481318")
|
|
|
|
require.NoError(t, err)
|
|
|
|
pubkey1, err := btcec.ParsePubKey(v1[:])
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
//nolint:lll
|
|
|
|
v2, err := route.NewVertexFromStr("03c19f0027ffbb0ae0e14a4d958788793f9d74e107462473ec0c3891e4feb12e99")
|
|
|
|
require.NoError(t, err)
|
|
|
|
pubkey2, err := btcec.ParsePubKey(v2[:])
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
2023-10-24 08:53:39 +02:00
|
|
|
var (
|
2024-06-17 01:30:01 +02:00
|
|
|
unblindedCltv uint16 = 500
|
|
|
|
blindedCltv uint16 = 1000
|
2023-10-24 08:53:39 +02:00
|
|
|
)
|
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
blindedSelfIntro := &BlindedPayment{
|
|
|
|
CltvExpiryDelta: blindedCltv,
|
|
|
|
BlindedPath: &sphinx.BlindedPath{
|
|
|
|
IntroductionPoint: sourcePubkey,
|
|
|
|
BlindedHops: []*sphinx.BlindedHopInfo{{}},
|
|
|
|
},
|
|
|
|
}
|
2023-10-24 08:53:39 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
blindedOtherIntro := &BlindedPayment{
|
|
|
|
CltvExpiryDelta: blindedCltv,
|
|
|
|
BlindedPath: &sphinx.BlindedPath{
|
|
|
|
IntroductionPoint: pubkey1,
|
|
|
|
BlindedHops: []*sphinx.BlindedHopInfo{
|
|
|
|
{},
|
|
|
|
},
|
2023-10-24 08:53:39 +02:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
blindedMultiHop := &BlindedPayment{
|
|
|
|
CltvExpiryDelta: blindedCltv,
|
|
|
|
BlindedPath: &sphinx.BlindedPath{
|
|
|
|
IntroductionPoint: pubkey1,
|
|
|
|
BlindedHops: []*sphinx.BlindedHopInfo{
|
|
|
|
{},
|
|
|
|
{
|
|
|
|
BlindedNodePub: pubkey2,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
2023-10-24 08:53:39 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
testCases := []struct {
|
|
|
|
name string
|
|
|
|
target *route.Vertex
|
|
|
|
routeHints RouteHints
|
|
|
|
blindedPayment *BlindedPayment
|
|
|
|
finalExpiry uint16
|
|
|
|
|
|
|
|
expectedTarget route.Vertex
|
|
|
|
expectedCltv uint16
|
|
|
|
err error
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "blinded and target",
|
|
|
|
target: &v1,
|
|
|
|
blindedPayment: blindedOtherIntro,
|
|
|
|
err: ErrTargetAndBlinded,
|
2023-10-24 08:53:39 +02:00
|
|
|
},
|
2024-06-17 01:30:01 +02:00
|
|
|
{
|
|
|
|
// For single-hop blinded we have a final cltv.
|
|
|
|
name: "blinded intro node only",
|
|
|
|
blindedPayment: blindedOtherIntro,
|
|
|
|
expectedTarget: v1,
|
|
|
|
expectedCltv: blindedCltv,
|
|
|
|
err: nil,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
// For multi-hop blinded, we have no final cltv.
|
|
|
|
name: "blinded multi-hop",
|
|
|
|
blindedPayment: blindedMultiHop,
|
|
|
|
expectedTarget: v2,
|
|
|
|
expectedCltv: 0,
|
|
|
|
err: nil,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "unblinded",
|
|
|
|
target: &v2,
|
|
|
|
finalExpiry: unblindedCltv,
|
|
|
|
expectedTarget: v2,
|
|
|
|
expectedCltv: unblindedCltv,
|
|
|
|
err: nil,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "source node intro",
|
|
|
|
blindedPayment: blindedSelfIntro,
|
|
|
|
err: ErrSelfIntro,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "hints and blinded",
|
|
|
|
blindedPayment: blindedMultiHop,
|
|
|
|
routeHints: make(
|
|
|
|
map[route.Vertex][]AdditionalEdge,
|
|
|
|
),
|
|
|
|
err: ErrHintsAndBlinded,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "expiry and blinded",
|
|
|
|
blindedPayment: blindedMultiHop,
|
|
|
|
finalExpiry: unblindedCltv,
|
|
|
|
err: ErrExpiryAndBlinded,
|
|
|
|
},
|
|
|
|
}
|
2023-10-24 08:53:39 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
for _, testCase := range testCases {
|
|
|
|
testCase := testCase
|
2023-10-24 08:53:39 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
t.Run(testCase.name, func(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
2024-05-15 15:52:17 +02:00
|
|
|
var (
|
|
|
|
blindedPathInfo *BlindedPaymentPathSet
|
|
|
|
expectedTarget = testCase.expectedTarget
|
2024-08-05 15:08:31 +02:00
|
|
|
err error
|
2024-05-15 15:52:17 +02:00
|
|
|
)
|
2024-05-15 15:08:19 +02:00
|
|
|
if testCase.blindedPayment != nil {
|
|
|
|
blindedPathInfo, err = NewBlindedPaymentPathSet(
|
|
|
|
[]*BlindedPayment{
|
|
|
|
testCase.blindedPayment,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
require.NoError(t, err)
|
2024-05-15 15:52:17 +02:00
|
|
|
|
|
|
|
expectedTarget = route.NewVertex(
|
|
|
|
blindedPathInfo.TargetPubKey(),
|
|
|
|
)
|
2024-05-15 15:08:19 +02:00
|
|
|
}
|
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
req, err := NewRouteRequest(
|
|
|
|
source, testCase.target, 1000, 0, nil, nil,
|
2024-05-15 15:08:19 +02:00
|
|
|
testCase.routeHints, blindedPathInfo,
|
2024-06-17 01:30:01 +02:00
|
|
|
testCase.finalExpiry,
|
|
|
|
)
|
|
|
|
require.ErrorIs(t, err, testCase.err)
|
|
|
|
|
|
|
|
// Skip request validation if we got a non-nil error.
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-05-15 15:52:17 +02:00
|
|
|
require.Equal(t, req.Target, expectedTarget)
|
2024-06-17 01:30:01 +02:00
|
|
|
require.Equal(
|
|
|
|
t, req.FinalExpiry, testCase.expectedCltv,
|
|
|
|
)
|
|
|
|
})
|
|
|
|
}
|
2023-10-24 08:53:39 +02:00
|
|
|
}
|
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
// TestAddEdgeUnknownVertexes tests that if an edge is added that contains two
|
|
|
|
// vertexes which we don't know of, the edge should be available for use
|
|
|
|
// regardless. This is due to the fact that we don't actually need node
|
|
|
|
// announcements for the channel vertexes to be able to use the channel.
|
|
|
|
func TestAddEdgeUnknownVertexes(t *testing.T) {
|
2023-10-24 08:53:39 +02:00
|
|
|
t.Parallel()
|
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
const startingBlockHeight = 101
|
|
|
|
ctx := createTestCtxFromFile(t, startingBlockHeight, basicGraphFilePath)
|
|
|
|
|
|
|
|
var pub1 [33]byte
|
|
|
|
copy(pub1[:], priv1.PubKey().SerializeCompressed())
|
|
|
|
|
|
|
|
var pub2 [33]byte
|
|
|
|
copy(pub2[:], priv2.PubKey().SerializeCompressed())
|
|
|
|
|
|
|
|
// The two nodes we are about to add should not exist yet.
|
|
|
|
_, exists1, err := ctx.graph.HasLightningNode(pub1)
|
|
|
|
require.NoError(t, err, "unable to query graph")
|
|
|
|
require.False(t, exists1)
|
|
|
|
|
|
|
|
_, exists2, err := ctx.graph.HasLightningNode(pub2)
|
|
|
|
require.NoError(t, err, "unable to query graph")
|
|
|
|
require.False(t, exists2)
|
|
|
|
|
|
|
|
// Add the edge between the two unknown nodes to the graph, and check
|
|
|
|
// that the nodes are found after the fact.
|
|
|
|
_, _, chanID, err := createChannelEdge(
|
|
|
|
bitcoinKey1.SerializeCompressed(),
|
|
|
|
bitcoinKey2.SerializeCompressed(),
|
|
|
|
10000, 500,
|
2022-05-20 08:54:10 +02:00
|
|
|
)
|
2024-06-17 01:30:01 +02:00
|
|
|
require.NoError(t, err, "unable to create channel edge")
|
2022-05-20 08:54:10 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
edge := &models.ChannelEdgeInfo{
|
|
|
|
ChannelID: chanID.ToUint64(),
|
|
|
|
NodeKey1Bytes: pub1,
|
|
|
|
NodeKey2Bytes: pub2,
|
|
|
|
BitcoinKey1Bytes: pub1,
|
|
|
|
BitcoinKey2Bytes: pub2,
|
|
|
|
AuthProof: nil,
|
|
|
|
}
|
|
|
|
require.NoError(t, ctx.graph.AddChannelEdge(edge))
|
2022-05-20 08:54:10 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
// We must add the edge policy to be able to use the edge for route
|
|
|
|
// finding.
|
|
|
|
edgePolicy := &models.ChannelEdgePolicy{
|
|
|
|
SigBytes: testSig.Serialize(),
|
|
|
|
ChannelID: edge.ChannelID,
|
|
|
|
LastUpdate: testTime,
|
|
|
|
TimeLockDelta: 10,
|
|
|
|
MinHTLC: 1,
|
|
|
|
FeeBaseMSat: 10,
|
|
|
|
FeeProportionalMillionths: 10000,
|
|
|
|
ToNode: edge.NodeKey2Bytes,
|
2022-05-20 08:54:10 +02:00
|
|
|
}
|
2024-06-17 01:30:01 +02:00
|
|
|
edgePolicy.ChannelFlags = 0
|
2022-05-20 08:54:10 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
require.NoError(t, ctx.graph.UpdateEdgePolicy(edgePolicy))
|
2022-05-20 08:54:10 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
// Create edge in the other direction as well.
|
|
|
|
edgePolicy = &models.ChannelEdgePolicy{
|
|
|
|
SigBytes: testSig.Serialize(),
|
|
|
|
ChannelID: edge.ChannelID,
|
|
|
|
LastUpdate: testTime,
|
|
|
|
TimeLockDelta: 10,
|
|
|
|
MinHTLC: 1,
|
|
|
|
FeeBaseMSat: 10,
|
|
|
|
FeeProportionalMillionths: 10000,
|
|
|
|
ToNode: edge.NodeKey1Bytes,
|
|
|
|
}
|
|
|
|
edgePolicy.ChannelFlags = 1
|
2022-05-20 08:54:10 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
require.NoError(t, ctx.graph.UpdateEdgePolicy(edgePolicy))
|
2023-02-13 13:57:18 +01:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
// After adding the edge between the two previously unknown nodes, they
|
|
|
|
// should have been added to the graph.
|
|
|
|
_, exists1, err = ctx.graph.HasLightningNode(pub1)
|
|
|
|
require.NoError(t, err, "unable to query graph")
|
|
|
|
require.True(t, exists1)
|
2022-05-20 08:54:10 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
_, exists2, err = ctx.graph.HasLightningNode(pub2)
|
|
|
|
require.NoError(t, err, "unable to query graph")
|
|
|
|
require.True(t, exists2)
|
2022-05-20 08:54:10 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
// We will connect node1 to the rest of the test graph, and make sure
|
|
|
|
// we can find a route to node2, which will use the just added channel
|
|
|
|
// edge.
|
2023-03-07 11:43:14 +01:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
// We will connect node 1 to "sophon"
|
|
|
|
connectNode := ctx.aliases["sophon"]
|
|
|
|
connectNodeKey, err := btcec.ParsePubKey(connectNode[:])
|
|
|
|
require.NoError(t, err)
|
2022-05-20 08:54:10 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
var (
|
|
|
|
pubKey1 *btcec.PublicKey
|
|
|
|
pubKey2 *btcec.PublicKey
|
|
|
|
)
|
|
|
|
node1Bytes := priv1.PubKey().SerializeCompressed()
|
|
|
|
node2Bytes := connectNode
|
2024-06-18 21:34:25 +02:00
|
|
|
if bytes.Compare(node1Bytes, node2Bytes[:]) == -1 {
|
2024-06-17 01:30:01 +02:00
|
|
|
pubKey1 = priv1.PubKey()
|
|
|
|
pubKey2 = connectNodeKey
|
|
|
|
} else {
|
|
|
|
pubKey1 = connectNodeKey
|
|
|
|
pubKey2 = priv1.PubKey()
|
|
|
|
}
|
2023-03-07 11:43:14 +01:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
_, _, chanID, err = createChannelEdge(
|
|
|
|
pubKey1.SerializeCompressed(), pubKey2.SerializeCompressed(),
|
|
|
|
10000, 510)
|
|
|
|
require.NoError(t, err, "unable to create channel edge")
|
2022-05-20 08:54:10 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
edge = &models.ChannelEdgeInfo{
|
|
|
|
ChannelID: chanID.ToUint64(),
|
|
|
|
AuthProof: nil,
|
|
|
|
}
|
|
|
|
copy(edge.NodeKey1Bytes[:], node1Bytes)
|
|
|
|
edge.NodeKey2Bytes = node2Bytes
|
|
|
|
copy(edge.BitcoinKey1Bytes[:], node1Bytes)
|
|
|
|
edge.BitcoinKey2Bytes = node2Bytes
|
2022-05-20 08:54:10 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
require.NoError(t, ctx.graph.AddChannelEdge(edge))
|
2022-05-20 08:54:10 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
edgePolicy = &models.ChannelEdgePolicy{
|
|
|
|
SigBytes: testSig.Serialize(),
|
|
|
|
ChannelID: edge.ChannelID,
|
|
|
|
LastUpdate: testTime,
|
|
|
|
TimeLockDelta: 10,
|
|
|
|
MinHTLC: 1,
|
|
|
|
FeeBaseMSat: 10,
|
|
|
|
FeeProportionalMillionths: 10000,
|
|
|
|
ToNode: edge.NodeKey2Bytes,
|
|
|
|
}
|
|
|
|
edgePolicy.ChannelFlags = 0
|
2022-05-20 08:54:10 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
require.NoError(t, ctx.graph.UpdateEdgePolicy(edgePolicy))
|
|
|
|
|
|
|
|
edgePolicy = &models.ChannelEdgePolicy{
|
|
|
|
SigBytes: testSig.Serialize(),
|
|
|
|
ChannelID: edge.ChannelID,
|
|
|
|
LastUpdate: testTime,
|
|
|
|
TimeLockDelta: 10,
|
|
|
|
MinHTLC: 1,
|
|
|
|
FeeBaseMSat: 10,
|
|
|
|
FeeProportionalMillionths: 10000,
|
|
|
|
ToNode: edge.NodeKey1Bytes,
|
2022-05-20 08:54:10 +02:00
|
|
|
}
|
2024-06-17 01:30:01 +02:00
|
|
|
edgePolicy.ChannelFlags = 1
|
2022-05-20 08:54:10 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
require.NoError(t, ctx.graph.UpdateEdgePolicy(edgePolicy))
|
2022-05-20 08:54:10 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
// We should now be able to find a route to node 2.
|
|
|
|
paymentAmt := lnwire.NewMSatFromSatoshis(100)
|
|
|
|
targetNode := priv2.PubKey()
|
|
|
|
var targetPubKeyBytes route.Vertex
|
|
|
|
copy(targetPubKeyBytes[:], targetNode.SerializeCompressed())
|
2022-05-20 08:54:10 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
req, err := NewRouteRequest(
|
|
|
|
ctx.router.cfg.SelfNode, &targetPubKeyBytes,
|
|
|
|
paymentAmt, 0, noRestrictions, nil, nil, nil, MinCLTVDelta,
|
2023-03-07 11:43:14 +01:00
|
|
|
)
|
2024-06-17 01:30:01 +02:00
|
|
|
require.NoError(t, err, "invalid route request")
|
|
|
|
_, _, err = ctx.router.FindRoute(req)
|
|
|
|
require.NoError(t, err, "unable to find any routes")
|
2023-03-07 11:43:14 +01:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
// Now check that we can update the node info for the partial node
|
|
|
|
// without messing up the channel graph.
|
|
|
|
n1 := &channeldb.LightningNode{
|
|
|
|
HaveNodeAnnouncement: true,
|
|
|
|
LastUpdate: time.Unix(123, 0),
|
|
|
|
Addresses: testAddrs,
|
|
|
|
Color: color.RGBA{1, 2, 3, 0},
|
|
|
|
Alias: "node11",
|
|
|
|
AuthSigBytes: testSig.Serialize(),
|
|
|
|
Features: testFeatures,
|
|
|
|
}
|
|
|
|
copy(n1.PubKeyBytes[:], priv1.PubKey().SerializeCompressed())
|
2023-03-07 11:43:14 +01:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
require.NoError(t, ctx.graph.AddLightningNode(n1))
|
2022-05-20 08:54:10 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
n2 := &channeldb.LightningNode{
|
|
|
|
HaveNodeAnnouncement: true,
|
|
|
|
LastUpdate: time.Unix(123, 0),
|
|
|
|
Addresses: testAddrs,
|
|
|
|
Color: color.RGBA{1, 2, 3, 0},
|
|
|
|
Alias: "node22",
|
|
|
|
AuthSigBytes: testSig.Serialize(),
|
|
|
|
Features: testFeatures,
|
|
|
|
}
|
|
|
|
copy(n2.PubKeyBytes[:], priv2.PubKey().SerializeCompressed())
|
2022-05-20 08:54:10 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
require.NoError(t, ctx.graph.AddLightningNode(n2))
|
2022-05-20 08:54:10 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
// Should still be able to find the route, and the info should be
|
|
|
|
// updated.
|
|
|
|
req, err = NewRouteRequest(
|
|
|
|
ctx.router.cfg.SelfNode, &targetPubKeyBytes,
|
|
|
|
paymentAmt, 0, noRestrictions, nil, nil, nil, MinCLTVDelta,
|
|
|
|
)
|
|
|
|
require.NoError(t, err, "invalid route request")
|
2022-05-20 08:54:10 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
_, _, err = ctx.router.FindRoute(req)
|
|
|
|
require.NoError(t, err, "unable to find any routes")
|
2023-03-07 11:43:14 +01:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
copy1, err := ctx.graph.FetchLightningNode(pub1)
|
|
|
|
require.NoError(t, err, "unable to fetch node")
|
2023-03-07 11:43:14 +01:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
require.Equal(t, n1.Alias, copy1.Alias)
|
2022-05-20 08:54:10 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
copy2, err := ctx.graph.FetchLightningNode(pub2)
|
|
|
|
require.NoError(t, err, "unable to fetch node")
|
|
|
|
|
|
|
|
require.Equal(t, n2.Alias, copy2.Alias)
|
2022-05-20 08:54:10 +02:00
|
|
|
}
|
2022-05-20 08:54:59 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
func createDummyLightningPayment(t *testing.T,
|
|
|
|
target route.Vertex, amt lnwire.MilliSatoshi) *LightningPayment {
|
2022-05-20 08:54:59 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
var preImage lntypes.Preimage
|
|
|
|
_, err := rand.Read(preImage[:])
|
|
|
|
require.NoError(t, err, "unable to generate preimage")
|
2022-05-20 08:54:59 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
payHash := preImage.Hash()
|
|
|
|
|
|
|
|
return &LightningPayment{
|
|
|
|
Target: target,
|
|
|
|
Amount: amt,
|
|
|
|
FeeLimit: noFeeLimit,
|
|
|
|
paymentHash: &payHash,
|
2022-05-20 08:54:59 +02:00
|
|
|
}
|
2024-06-17 01:30:01 +02:00
|
|
|
}
|
2022-05-20 08:54:59 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
type mockGraphBuilder struct {
|
|
|
|
rejectUpdate bool
|
|
|
|
updateEdge func(update *models.ChannelEdgePolicy) error
|
|
|
|
}
|
2022-05-20 08:54:59 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
func newMockGraphBuilder(graph graph.DB) *mockGraphBuilder {
|
|
|
|
return &mockGraphBuilder{
|
|
|
|
updateEdge: func(update *models.ChannelEdgePolicy) error {
|
|
|
|
return graph.UpdateEdgePolicy(update)
|
2022-05-20 08:54:59 +02:00
|
|
|
},
|
2024-06-17 01:30:01 +02:00
|
|
|
}
|
|
|
|
}
|
2022-05-20 08:54:59 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
func (m *mockGraphBuilder) setNextReject(reject bool) {
|
|
|
|
m.rejectUpdate = reject
|
|
|
|
}
|
2023-03-07 11:43:14 +01:00
|
|
|
|
2024-08-21 08:39:37 +02:00
|
|
|
func (m *mockGraphBuilder) ApplyChannelUpdate(msg *lnwire.ChannelUpdate1) bool {
|
2024-06-17 01:30:01 +02:00
|
|
|
if m.rejectUpdate {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
err := m.updateEdge(&models.ChannelEdgePolicy{
|
|
|
|
SigBytes: msg.Signature.ToSignatureBytes(),
|
|
|
|
ChannelID: msg.ShortChannelID.ToUint64(),
|
|
|
|
LastUpdate: time.Unix(int64(msg.Timestamp), 0),
|
|
|
|
MessageFlags: msg.MessageFlags,
|
|
|
|
ChannelFlags: msg.ChannelFlags,
|
|
|
|
TimeLockDelta: msg.TimeLockDelta,
|
|
|
|
MinHTLC: msg.HtlcMinimumMsat,
|
|
|
|
MaxHTLC: msg.HtlcMaximumMsat,
|
|
|
|
FeeBaseMSat: lnwire.MilliSatoshi(msg.BaseFee),
|
|
|
|
FeeProportionalMillionths: lnwire.MilliSatoshi(msg.FeeRate),
|
|
|
|
ExtraOpaqueData: msg.ExtraOpaqueData,
|
|
|
|
})
|
2022-05-20 08:54:59 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
return err == nil
|
|
|
|
}
|
2022-05-20 08:54:59 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
type mockChain struct {
|
|
|
|
lnwallet.BlockChainIO
|
2022-05-20 08:54:59 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
blocks map[chainhash.Hash]*wire.MsgBlock
|
|
|
|
blockIndex map[uint32]chainhash.Hash
|
|
|
|
blockHeightIndex map[chainhash.Hash]uint32
|
2022-05-20 08:54:59 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
utxos map[wire.OutPoint]wire.TxOut
|
2022-05-20 08:54:59 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
bestHeight int32
|
2022-05-20 08:54:59 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
sync.RWMutex
|
|
|
|
}
|
2022-05-20 08:54:59 +02:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
func newMockChain(currentHeight uint32) *mockChain {
|
|
|
|
return &mockChain{
|
|
|
|
bestHeight: int32(currentHeight),
|
|
|
|
blocks: make(map[chainhash.Hash]*wire.MsgBlock),
|
|
|
|
utxos: make(map[wire.OutPoint]wire.TxOut),
|
|
|
|
blockIndex: make(map[uint32]chainhash.Hash),
|
|
|
|
blockHeightIndex: make(map[chainhash.Hash]uint32),
|
|
|
|
}
|
2022-05-20 08:54:59 +02:00
|
|
|
}
|
2022-12-15 22:48:56 +01:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
func (m *mockChain) GetBestBlock() (*chainhash.Hash, int32, error) {
|
|
|
|
m.RLock()
|
|
|
|
defer m.RUnlock()
|
2022-12-15 22:48:56 +01:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
blockHash := m.blockIndex[uint32(m.bestHeight)]
|
2022-12-15 22:48:56 +01:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
return &blockHash, m.bestHeight, nil
|
|
|
|
}
|
2022-12-15 22:48:56 +01:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
func createChannelEdge(bitcoinKey1, bitcoinKey2 []byte,
|
|
|
|
chanValue btcutil.Amount, fundingHeight uint32) (*wire.MsgTx,
|
|
|
|
*wire.OutPoint, *lnwire.ShortChannelID, error) {
|
2022-12-15 22:48:56 +01:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
fundingTx := wire.NewMsgTx(2)
|
|
|
|
_, tx, err := input.GenFundingPkScript(
|
|
|
|
bitcoinKey1,
|
|
|
|
bitcoinKey2,
|
|
|
|
int64(chanValue),
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, nil, err
|
2022-12-15 22:48:56 +01:00
|
|
|
}
|
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
fundingTx.TxOut = append(fundingTx.TxOut, tx)
|
|
|
|
chanUtxo := wire.OutPoint{
|
|
|
|
Hash: fundingTx.TxHash(),
|
|
|
|
Index: 0,
|
|
|
|
}
|
2022-12-15 22:48:56 +01:00
|
|
|
|
2024-06-17 01:30:01 +02:00
|
|
|
// Our fake channel will be "confirmed" at height 101.
|
|
|
|
chanID := &lnwire.ShortChannelID{
|
|
|
|
BlockHeight: fundingHeight,
|
|
|
|
TxIndex: 0,
|
|
|
|
TxPosition: 0,
|
2022-12-15 22:48:56 +01:00
|
|
|
}
|
2024-06-17 01:30:01 +02:00
|
|
|
|
|
|
|
return fundingTx, &chanUtxo, chanID, nil
|
2022-12-15 22:48:56 +01:00
|
|
|
}
|
2024-05-05 13:36:26 +02:00
|
|
|
|
|
|
|
// TestFindBlindedPathsWithMC tests that the FindBlindedPaths method correctly
|
|
|
|
// selects a set of blinded paths by using mission control data to select the
|
|
|
|
// paths with the highest success probability.
|
|
|
|
func TestFindBlindedPathsWithMC(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
rbFeatureBits := []lnwire.FeatureBit{
|
|
|
|
lnwire.RouteBlindingOptional,
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create the following graph and let all the nodes advertise support
|
|
|
|
// for blinded paths.
|
|
|
|
//
|
|
|
|
// C
|
|
|
|
// / \
|
|
|
|
// / \
|
|
|
|
// E -- A -- F -- D
|
|
|
|
// \ /
|
|
|
|
// \ /
|
|
|
|
// B
|
|
|
|
//
|
|
|
|
featuresWithRouteBlinding := lnwire.NewFeatureVector(
|
|
|
|
lnwire.NewRawFeatureVector(rbFeatureBits...), lnwire.Features,
|
|
|
|
)
|
|
|
|
|
|
|
|
policyWithRouteBlinding := &testChannelPolicy{
|
|
|
|
Expiry: 144,
|
|
|
|
FeeRate: 400,
|
|
|
|
MinHTLC: 1,
|
|
|
|
MaxHTLC: 100000000,
|
|
|
|
Features: featuresWithRouteBlinding,
|
|
|
|
}
|
|
|
|
|
|
|
|
testChannels := []*testChannel{
|
|
|
|
symmetricTestChannel(
|
|
|
|
"eve", "alice", 100000, policyWithRouteBlinding, 1,
|
|
|
|
),
|
|
|
|
symmetricTestChannel(
|
|
|
|
"alice", "charlie", 100000, policyWithRouteBlinding, 2,
|
|
|
|
),
|
|
|
|
symmetricTestChannel(
|
|
|
|
"alice", "bob", 100000, policyWithRouteBlinding, 3,
|
|
|
|
),
|
|
|
|
symmetricTestChannel(
|
|
|
|
"charlie", "dave", 100000, policyWithRouteBlinding, 4,
|
|
|
|
),
|
|
|
|
symmetricTestChannel(
|
|
|
|
"bob", "dave", 100000, policyWithRouteBlinding, 5,
|
|
|
|
),
|
|
|
|
symmetricTestChannel(
|
|
|
|
"alice", "frank", 100000, policyWithRouteBlinding, 6,
|
|
|
|
),
|
|
|
|
symmetricTestChannel(
|
|
|
|
"frank", "dave", 100000, policyWithRouteBlinding, 7,
|
|
|
|
),
|
|
|
|
}
|
|
|
|
|
|
|
|
testGraph, err := createTestGraphFromChannels(
|
|
|
|
t, true, testChannels, "dave", rbFeatureBits...,
|
|
|
|
)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
ctx := createTestCtxFromGraphInstance(t, 101, testGraph)
|
|
|
|
|
|
|
|
var (
|
|
|
|
alice = ctx.aliases["alice"]
|
|
|
|
bob = ctx.aliases["bob"]
|
|
|
|
charlie = ctx.aliases["charlie"]
|
|
|
|
dave = ctx.aliases["dave"]
|
|
|
|
eve = ctx.aliases["eve"]
|
|
|
|
frank = ctx.aliases["frank"]
|
|
|
|
)
|
|
|
|
|
|
|
|
// Create a mission control store which initially sets the success
|
|
|
|
// probability of each node pair to 1.
|
|
|
|
missionControl := map[route.Vertex]map[route.Vertex]float64{
|
|
|
|
eve: {alice: 1},
|
|
|
|
alice: {
|
|
|
|
charlie: 1,
|
|
|
|
bob: 1,
|
|
|
|
frank: 1,
|
|
|
|
},
|
|
|
|
charlie: {dave: 1},
|
|
|
|
bob: {dave: 1},
|
|
|
|
frank: {dave: 1},
|
|
|
|
}
|
|
|
|
|
|
|
|
// probabilitySrc is a helper that returns the mission control success
|
|
|
|
// probability of a forward between two vertices.
|
|
|
|
probabilitySrc := func(from route.Vertex, to route.Vertex,
|
|
|
|
amt lnwire.MilliSatoshi, capacity btcutil.Amount) float64 {
|
|
|
|
|
|
|
|
return missionControl[from][to]
|
|
|
|
}
|
|
|
|
|
|
|
|
// All the probabilities are set to 1. So if we restrict the path length
|
|
|
|
// to 2 and allow a max of 3 routes, then we expect three paths here.
|
|
|
|
routes, err := ctx.router.FindBlindedPaths(
|
|
|
|
dave, 1000, probabilitySrc, &BlindedPathRestrictions{
|
|
|
|
MinDistanceFromIntroNode: 2,
|
|
|
|
NumHops: 2,
|
|
|
|
MaxNumPaths: 3,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Len(t, routes, 3)
|
|
|
|
|
|
|
|
// assertPaths checks that the resulting set of paths is equal to the
|
|
|
|
// expected set and that the order of the paths is correct.
|
|
|
|
assertPaths := func(paths []*route.Route, expectedPaths []string) {
|
|
|
|
require.Len(t, paths, len(expectedPaths))
|
|
|
|
|
|
|
|
var actualPaths []string
|
|
|
|
for _, path := range paths {
|
|
|
|
label := getAliasFromPubKey(
|
|
|
|
path.SourcePubKey, ctx.aliases,
|
|
|
|
) + ","
|
|
|
|
|
|
|
|
for _, hop := range path.Hops {
|
|
|
|
label += getAliasFromPubKey(
|
|
|
|
hop.PubKeyBytes, ctx.aliases,
|
|
|
|
) + ","
|
|
|
|
}
|
|
|
|
|
|
|
|
actualPaths = append(
|
|
|
|
actualPaths, strings.TrimRight(label, ","),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
for i, path := range expectedPaths {
|
2024-08-07 16:51:09 +02:00
|
|
|
require.Equal(t, path, actualPaths[i])
|
2024-05-05 13:36:26 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now, let's lower the MC probability of the B-D to 0.5 and F-D link to
|
|
|
|
// 0.25. We will leave the MaxNumPaths as 3 and so all paths should
|
|
|
|
// still be returned but the order should be:
|
|
|
|
// 1) A -> C -> D
|
|
|
|
// 2) A -> B -> D
|
|
|
|
// 3) A -> F -> D
|
|
|
|
missionControl[bob][dave] = 0.5
|
|
|
|
missionControl[frank][dave] = 0.25
|
|
|
|
routes, err = ctx.router.FindBlindedPaths(
|
|
|
|
dave, 1000, probabilitySrc, &BlindedPathRestrictions{
|
|
|
|
MinDistanceFromIntroNode: 2,
|
|
|
|
NumHops: 2,
|
|
|
|
MaxNumPaths: 3,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
require.NoError(t, err)
|
|
|
|
assertPaths(routes, []string{
|
|
|
|
"alice,charlie,dave",
|
|
|
|
"alice,bob,dave",
|
|
|
|
"alice,frank,dave",
|
|
|
|
})
|
|
|
|
|
|
|
|
// Just to show that the above result was not a fluke, let's change
|
|
|
|
// the C->D link to be the weak one.
|
|
|
|
missionControl[charlie][dave] = 0.125
|
|
|
|
routes, err = ctx.router.FindBlindedPaths(
|
|
|
|
dave, 1000, probabilitySrc, &BlindedPathRestrictions{
|
|
|
|
MinDistanceFromIntroNode: 2,
|
|
|
|
NumHops: 2,
|
|
|
|
MaxNumPaths: 3,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
require.NoError(t, err)
|
|
|
|
assertPaths(routes, []string{
|
|
|
|
"alice,bob,dave",
|
|
|
|
"alice,frank,dave",
|
|
|
|
"alice,charlie,dave",
|
|
|
|
})
|
|
|
|
|
|
|
|
// Change the MaxNumPaths to 1 to assert that only the best route is
|
|
|
|
// returned.
|
|
|
|
routes, err = ctx.router.FindBlindedPaths(
|
|
|
|
dave, 1000, probabilitySrc, &BlindedPathRestrictions{
|
|
|
|
MinDistanceFromIntroNode: 2,
|
|
|
|
NumHops: 2,
|
|
|
|
MaxNumPaths: 1,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
require.NoError(t, err)
|
|
|
|
assertPaths(routes, []string{
|
|
|
|
"alice,bob,dave",
|
|
|
|
})
|
|
|
|
|
|
|
|
// Test the edge case where Dave, the recipient, is also the
|
|
|
|
// introduction node.
|
|
|
|
routes, err = ctx.router.FindBlindedPaths(
|
|
|
|
dave, 1000, probabilitySrc, &BlindedPathRestrictions{
|
|
|
|
MinDistanceFromIntroNode: 0,
|
|
|
|
NumHops: 0,
|
|
|
|
MaxNumPaths: 1,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
require.NoError(t, err)
|
|
|
|
assertPaths(routes, []string{
|
|
|
|
"dave",
|
|
|
|
})
|
|
|
|
|
|
|
|
// Finally, we make one of the routes have a probability less than the
|
|
|
|
// minimum. This means we expect that route not to be chosen.
|
|
|
|
missionControl[charlie][dave] = DefaultMinRouteProbability
|
|
|
|
routes, err = ctx.router.FindBlindedPaths(
|
|
|
|
dave, 1000, probabilitySrc, &BlindedPathRestrictions{
|
|
|
|
MinDistanceFromIntroNode: 2,
|
|
|
|
NumHops: 2,
|
|
|
|
MaxNumPaths: 3,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
require.NoError(t, err)
|
|
|
|
assertPaths(routes, []string{
|
|
|
|
"alice,bob,dave",
|
|
|
|
"alice,frank,dave",
|
|
|
|
})
|
2024-08-05 14:27:11 +02:00
|
|
|
|
|
|
|
// Test that if the user explicitly indicates that we should ignore
|
|
|
|
// the Frank node during path selection, then this is done.
|
|
|
|
routes, err = ctx.router.FindBlindedPaths(
|
|
|
|
dave, 1000, probabilitySrc, &BlindedPathRestrictions{
|
|
|
|
MinDistanceFromIntroNode: 2,
|
|
|
|
NumHops: 2,
|
|
|
|
MaxNumPaths: 3,
|
|
|
|
NodeOmissionSet: fn.NewSet(frank),
|
|
|
|
},
|
|
|
|
)
|
|
|
|
require.NoError(t, err)
|
|
|
|
assertPaths(routes, []string{
|
|
|
|
"alice,bob,dave",
|
|
|
|
})
|
2024-05-05 13:36:26 +02:00
|
|
|
}
|