2019-08-05 12:29:16 +02:00
|
|
|
package routing
|
|
|
|
|
|
|
|
import (
|
|
|
|
"reflect"
|
|
|
|
"testing"
|
|
|
|
|
2024-10-08 13:43:30 +02:00
|
|
|
"github.com/btcsuite/btcd/btcec/v2"
|
2019-10-30 21:18:52 -07:00
|
|
|
"github.com/davecgh/go-spew/spew"
|
2024-12-03 15:51:05 -07:00
|
|
|
"github.com/lightningnetwork/lnd/fn/v2"
|
2019-08-05 12:29:16 +02:00
|
|
|
"github.com/lightningnetwork/lnd/lnwire"
|
|
|
|
"github.com/lightningnetwork/lnd/routing/route"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
hops = []route.Vertex{
|
|
|
|
{1, 0}, {1, 1}, {1, 2}, {1, 3}, {1, 4},
|
|
|
|
}
|
|
|
|
|
2024-10-08 13:43:30 +02:00
|
|
|
routeOneHop = extractMCRoute(&route.Route{
|
|
|
|
SourcePubKey: hops[0],
|
|
|
|
TotalAmount: 100,
|
|
|
|
Hops: []*route.Hop{
|
|
|
|
{
|
|
|
|
PubKeyBytes: hops[1],
|
|
|
|
AmtToForward: 99,
|
|
|
|
},
|
2019-06-25 10:51:55 +02:00
|
|
|
},
|
2024-10-08 13:43:30 +02:00
|
|
|
})
|
2019-06-25 10:51:55 +02:00
|
|
|
|
2024-10-08 13:43:30 +02:00
|
|
|
routeTwoHop = extractMCRoute(&route.Route{
|
|
|
|
SourcePubKey: hops[0],
|
|
|
|
TotalAmount: 100,
|
|
|
|
Hops: []*route.Hop{
|
|
|
|
{
|
|
|
|
PubKeyBytes: hops[1],
|
|
|
|
AmtToForward: 99,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
PubKeyBytes: hops[2],
|
|
|
|
AmtToForward: 97,
|
|
|
|
},
|
2019-08-05 12:29:16 +02:00
|
|
|
},
|
2024-10-08 13:43:30 +02:00
|
|
|
})
|
2019-08-05 12:29:16 +02:00
|
|
|
|
2024-10-08 13:43:30 +02:00
|
|
|
routeThreeHop = extractMCRoute(&route.Route{
|
|
|
|
SourcePubKey: hops[0],
|
|
|
|
TotalAmount: 100,
|
|
|
|
Hops: []*route.Hop{
|
|
|
|
{
|
|
|
|
PubKeyBytes: hops[1],
|
|
|
|
AmtToForward: 99,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
PubKeyBytes: hops[2],
|
|
|
|
AmtToForward: 97,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
PubKeyBytes: hops[3],
|
|
|
|
AmtToForward: 94,
|
|
|
|
},
|
2019-12-20 08:55:45 +01:00
|
|
|
},
|
2024-10-08 13:43:30 +02:00
|
|
|
})
|
2019-12-20 08:55:45 +01:00
|
|
|
|
2024-10-08 13:43:30 +02:00
|
|
|
routeFourHop = extractMCRoute(&route.Route{
|
|
|
|
SourcePubKey: hops[0],
|
|
|
|
TotalAmount: 100,
|
|
|
|
Hops: []*route.Hop{
|
|
|
|
{
|
|
|
|
PubKeyBytes: hops[1],
|
|
|
|
AmtToForward: 99,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
PubKeyBytes: hops[2],
|
|
|
|
AmtToForward: 97,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
PubKeyBytes: hops[3],
|
|
|
|
AmtToForward: 94,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
PubKeyBytes: hops[4],
|
|
|
|
AmtToForward: 90,
|
|
|
|
},
|
2019-08-05 12:29:16 +02:00
|
|
|
},
|
2024-10-08 13:43:30 +02:00
|
|
|
})
|
2023-11-06 11:07:32 -05:00
|
|
|
|
|
|
|
// blindedMultiHop is a blinded path where there are cleartext hops
|
|
|
|
// before the introduction node, and an intermediate blinded hop before
|
|
|
|
// the recipient after it.
|
2024-10-08 13:43:30 +02:00
|
|
|
blindedMultiHop = extractMCRoute(&route.Route{
|
|
|
|
SourcePubKey: hops[0],
|
|
|
|
TotalAmount: 100,
|
|
|
|
Hops: []*route.Hop{
|
2023-11-06 11:07:32 -05:00
|
|
|
{
|
2024-10-08 13:43:30 +02:00
|
|
|
PubKeyBytes: hops[1],
|
|
|
|
AmtToForward: 99,
|
|
|
|
},
|
|
|
|
{
|
2024-12-09 22:18:27 +01:00
|
|
|
PubKeyBytes: hops[2],
|
|
|
|
// Intermediate blinded hops don't have an
|
|
|
|
// amount set.
|
|
|
|
AmtToForward: 0,
|
2024-10-08 13:43:30 +02:00
|
|
|
BlindingPoint: genTestPubKey(),
|
|
|
|
},
|
|
|
|
{
|
2024-12-09 22:18:27 +01:00
|
|
|
PubKeyBytes: hops[3],
|
|
|
|
// Intermediate blinded hops don't have an
|
|
|
|
// amount set.
|
|
|
|
AmtToForward: 0,
|
2024-10-08 13:43:30 +02:00
|
|
|
},
|
|
|
|
{
|
|
|
|
PubKeyBytes: hops[4],
|
|
|
|
AmtToForward: 77,
|
2023-11-06 11:07:32 -05:00
|
|
|
},
|
|
|
|
},
|
2024-10-08 13:43:30 +02:00
|
|
|
})
|
2023-11-06 11:07:32 -05:00
|
|
|
|
2023-11-06 11:32:30 -05:00
|
|
|
// blindedSingleHop is a blinded path with a single blinded hop after
|
|
|
|
// the introduction node.
|
2024-10-08 13:43:30 +02:00
|
|
|
blindedSingleHop = extractMCRoute(&route.Route{
|
|
|
|
SourcePubKey: hops[0],
|
|
|
|
TotalAmount: 100,
|
|
|
|
Hops: []*route.Hop{
|
2023-11-06 11:32:30 -05:00
|
|
|
{
|
2024-10-08 13:43:30 +02:00
|
|
|
PubKeyBytes: hops[1],
|
|
|
|
AmtToForward: 99,
|
|
|
|
},
|
|
|
|
{
|
2024-12-09 22:18:27 +01:00
|
|
|
PubKeyBytes: hops[2],
|
|
|
|
// Intermediate blinded hops don't have an
|
|
|
|
// amount set.
|
|
|
|
AmtToForward: 0,
|
2024-10-08 13:43:30 +02:00
|
|
|
BlindingPoint: genTestPubKey(),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
PubKeyBytes: hops[3],
|
|
|
|
AmtToForward: 88,
|
2023-11-06 11:32:30 -05:00
|
|
|
},
|
|
|
|
},
|
2024-10-08 13:43:30 +02:00
|
|
|
})
|
2023-11-06 11:32:30 -05:00
|
|
|
|
2023-11-06 11:07:32 -05:00
|
|
|
// blindedMultiToIntroduction is a blinded path which goes directly
|
|
|
|
// to the introduction node, with multiple blinded hops after it.
|
2024-10-08 13:43:30 +02:00
|
|
|
blindedMultiToIntroduction = extractMCRoute(&route.Route{
|
|
|
|
SourcePubKey: hops[0],
|
|
|
|
TotalAmount: 100,
|
|
|
|
Hops: []*route.Hop{
|
|
|
|
{
|
2024-12-09 22:18:27 +01:00
|
|
|
PubKeyBytes: hops[1],
|
|
|
|
// Intermediate blinded hops don't have an
|
|
|
|
// amount set.
|
|
|
|
AmtToForward: 0,
|
2024-10-08 13:43:30 +02:00
|
|
|
BlindingPoint: genTestPubKey(),
|
|
|
|
},
|
|
|
|
{
|
2024-12-09 22:18:27 +01:00
|
|
|
PubKeyBytes: hops[2],
|
|
|
|
// Intermediate blinded hops don't have an
|
|
|
|
// amount set.
|
|
|
|
AmtToForward: 0,
|
2024-10-08 13:43:30 +02:00
|
|
|
},
|
2023-11-06 11:07:32 -05:00
|
|
|
{
|
2024-10-08 13:43:30 +02:00
|
|
|
PubKeyBytes: hops[3],
|
|
|
|
AmtToForward: 58,
|
2023-11-06 11:07:32 -05:00
|
|
|
},
|
|
|
|
},
|
2024-10-08 13:43:30 +02:00
|
|
|
})
|
2023-11-06 11:40:56 -05:00
|
|
|
|
|
|
|
// blindedIntroReceiver is a blinded path where the introduction node
|
|
|
|
// is the recipient.
|
2024-10-08 13:43:30 +02:00
|
|
|
blindedIntroReceiver = extractMCRoute(&route.Route{
|
|
|
|
SourcePubKey: hops[0],
|
|
|
|
TotalAmount: 100,
|
|
|
|
Hops: []*route.Hop{
|
|
|
|
{
|
|
|
|
PubKeyBytes: hops[1],
|
|
|
|
AmtToForward: 95,
|
|
|
|
},
|
2023-11-06 11:40:56 -05:00
|
|
|
{
|
2024-10-08 13:43:30 +02:00
|
|
|
PubKeyBytes: hops[2],
|
|
|
|
AmtToForward: 90,
|
|
|
|
BlindingPoint: genTestPubKey(),
|
2023-11-06 11:40:56 -05:00
|
|
|
},
|
|
|
|
},
|
2024-10-08 13:43:30 +02:00
|
|
|
})
|
2019-08-05 12:29:16 +02:00
|
|
|
)
|
|
|
|
|
2024-10-08 13:43:30 +02:00
|
|
|
func genTestPubKey() *btcec.PublicKey {
|
|
|
|
key, _ := btcec.NewPrivateKey()
|
|
|
|
|
|
|
|
return key.PubKey()
|
|
|
|
}
|
|
|
|
|
2019-06-25 10:51:55 +02:00
|
|
|
func getTestPair(from, to int) DirectedNodePair {
|
|
|
|
return NewDirectedNodePair(hops[from], hops[to])
|
|
|
|
}
|
|
|
|
|
2019-10-30 21:18:52 -07:00
|
|
|
func getPolicyFailure(from, to int) *DirectedNodePair {
|
|
|
|
pair := getTestPair(from, to)
|
|
|
|
return &pair
|
|
|
|
}
|
|
|
|
|
2019-08-05 12:29:16 +02:00
|
|
|
type resultTestCase struct {
|
|
|
|
name string
|
2024-08-06 14:33:07 +02:00
|
|
|
route *mcRoute
|
2019-07-29 14:20:06 +02:00
|
|
|
success bool
|
2019-08-05 12:29:16 +02:00
|
|
|
failureSrcIdx int
|
|
|
|
failure lnwire.FailureMessage
|
|
|
|
|
|
|
|
expectedResult *interpretedResult
|
|
|
|
}
|
|
|
|
|
|
|
|
var resultTestCases = []resultTestCase{
|
|
|
|
// Tests that a temporary channel failure result is properly
|
|
|
|
// interpreted.
|
|
|
|
{
|
|
|
|
name: "fail",
|
2024-10-08 13:43:30 +02:00
|
|
|
route: routeTwoHop,
|
2019-08-05 12:29:16 +02:00
|
|
|
failureSrcIdx: 1,
|
|
|
|
failure: lnwire.NewTemporaryChannelFailure(nil),
|
|
|
|
|
|
|
|
expectedResult: &interpretedResult{
|
2019-07-29 14:20:06 +02:00
|
|
|
pairResults: map[DirectedNodePair]pairResult{
|
2019-09-26 16:33:08 +02:00
|
|
|
getTestPair(0, 1): successPairResult(100),
|
2019-09-30 19:45:49 +02:00
|
|
|
getTestPair(1, 2): failPairResult(99),
|
2019-08-05 12:29:16 +02:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2024-08-06 14:33:07 +02:00
|
|
|
// Tests that an expiry too soon failure result is properly interpreted.
|
2019-08-05 12:29:16 +02:00
|
|
|
{
|
|
|
|
name: "fail expiry too soon",
|
2024-10-08 13:43:30 +02:00
|
|
|
route: routeFourHop,
|
2019-08-05 12:29:16 +02:00
|
|
|
failureSrcIdx: 3,
|
2024-08-21 08:39:37 +02:00
|
|
|
failure: lnwire.NewExpiryTooSoon(lnwire.ChannelUpdate1{}),
|
2019-08-05 12:29:16 +02:00
|
|
|
|
|
|
|
expectedResult: &interpretedResult{
|
2019-07-29 14:20:06 +02:00
|
|
|
pairResults: map[DirectedNodePair]pairResult{
|
2019-09-30 19:45:49 +02:00
|
|
|
getTestPair(0, 1): failPairResult(0),
|
|
|
|
getTestPair(1, 0): failPairResult(0),
|
|
|
|
getTestPair(1, 2): failPairResult(0),
|
|
|
|
getTestPair(2, 1): failPairResult(0),
|
|
|
|
getTestPair(2, 3): failPairResult(0),
|
|
|
|
getTestPair(3, 2): failPairResult(0),
|
2019-07-29 14:20:06 +02:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
// Tests an incorrect payment details result. This should be a final
|
|
|
|
// failure, but mark all pairs along the route as successful.
|
|
|
|
{
|
|
|
|
name: "fail incorrect details",
|
2024-10-08 13:43:30 +02:00
|
|
|
route: routeTwoHop,
|
2019-07-29 14:20:06 +02:00
|
|
|
failureSrcIdx: 2,
|
2019-06-12 12:18:58 +02:00
|
|
|
failure: lnwire.NewFailIncorrectDetails(97, 0),
|
2019-07-29 14:20:06 +02:00
|
|
|
|
|
|
|
expectedResult: &interpretedResult{
|
|
|
|
pairResults: map[DirectedNodePair]pairResult{
|
2019-09-26 16:33:08 +02:00
|
|
|
getTestPair(0, 1): successPairResult(100),
|
|
|
|
getTestPair(1, 2): successPairResult(99),
|
2019-07-29 14:20:06 +02:00
|
|
|
},
|
|
|
|
finalFailureReason: &reasonIncorrectDetails,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
// Tests a successful direct payment.
|
|
|
|
{
|
|
|
|
name: "success direct",
|
2024-10-08 13:43:30 +02:00
|
|
|
route: routeOneHop,
|
2019-07-29 14:20:06 +02:00
|
|
|
success: true,
|
|
|
|
|
|
|
|
expectedResult: &interpretedResult{
|
|
|
|
pairResults: map[DirectedNodePair]pairResult{
|
2019-09-26 16:33:08 +02:00
|
|
|
getTestPair(0, 1): successPairResult(100),
|
2019-07-29 14:20:06 +02:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
// Tests a successful two hop payment.
|
|
|
|
{
|
|
|
|
name: "success",
|
2024-10-08 13:43:30 +02:00
|
|
|
route: routeTwoHop,
|
2019-07-29 14:20:06 +02:00
|
|
|
success: true,
|
|
|
|
|
|
|
|
expectedResult: &interpretedResult{
|
|
|
|
pairResults: map[DirectedNodePair]pairResult{
|
2019-09-26 16:33:08 +02:00
|
|
|
getTestPair(0, 1): successPairResult(100),
|
|
|
|
getTestPair(1, 2): successPairResult(99),
|
2019-06-25 10:51:55 +02:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
// Tests a malformed htlc from a direct peer.
|
|
|
|
{
|
|
|
|
name: "fail malformed htlc from direct peer",
|
2024-10-08 13:43:30 +02:00
|
|
|
route: routeTwoHop,
|
2019-06-25 10:51:55 +02:00
|
|
|
failureSrcIdx: 0,
|
|
|
|
failure: lnwire.NewInvalidOnionKey(nil),
|
|
|
|
|
|
|
|
expectedResult: &interpretedResult{
|
|
|
|
nodeFailure: &hops[1],
|
2019-09-04 16:26:18 +02:00
|
|
|
pairResults: map[DirectedNodePair]pairResult{
|
|
|
|
getTestPair(1, 0): failPairResult(0),
|
|
|
|
getTestPair(1, 2): failPairResult(0),
|
2020-01-21 09:02:25 +01:00
|
|
|
getTestPair(0, 1): failPairResult(0),
|
|
|
|
getTestPair(2, 1): failPairResult(0),
|
2019-09-04 16:26:18 +02:00
|
|
|
},
|
2019-06-25 10:51:55 +02:00
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
// Tests a malformed htlc from a direct peer that is also the final
|
|
|
|
// destination.
|
|
|
|
{
|
|
|
|
name: "fail malformed htlc from direct final peer",
|
2024-10-08 13:43:30 +02:00
|
|
|
route: routeOneHop,
|
2019-06-25 10:51:55 +02:00
|
|
|
failureSrcIdx: 0,
|
|
|
|
failure: lnwire.NewInvalidOnionKey(nil),
|
|
|
|
|
|
|
|
expectedResult: &interpretedResult{
|
|
|
|
finalFailureReason: &reasonError,
|
|
|
|
nodeFailure: &hops[1],
|
2019-09-04 16:26:18 +02:00
|
|
|
pairResults: map[DirectedNodePair]pairResult{
|
|
|
|
getTestPair(1, 0): failPairResult(0),
|
2020-01-21 09:02:25 +01:00
|
|
|
getTestPair(0, 1): failPairResult(0),
|
2019-09-04 16:26:18 +02:00
|
|
|
},
|
2019-08-05 12:29:16 +02:00
|
|
|
},
|
|
|
|
},
|
2019-10-30 21:18:52 -07:00
|
|
|
|
|
|
|
// Tests that a fee insufficient failure to an intermediate hop with
|
|
|
|
// index 2 results in the first hop marked as success, and then a
|
|
|
|
// bidirectional failure for the incoming channel. It should also result
|
|
|
|
// in a policy failure for the outgoing hop.
|
|
|
|
{
|
|
|
|
name: "fail fee insufficient intermediate",
|
2024-10-08 13:43:30 +02:00
|
|
|
route: routeFourHop,
|
2019-10-30 21:18:52 -07:00
|
|
|
failureSrcIdx: 2,
|
2024-08-21 08:39:37 +02:00
|
|
|
failure: lnwire.NewFeeInsufficient(
|
|
|
|
0, lnwire.ChannelUpdate1{},
|
|
|
|
),
|
2019-10-30 21:18:52 -07:00
|
|
|
expectedResult: &interpretedResult{
|
|
|
|
pairResults: map[DirectedNodePair]pairResult{
|
|
|
|
getTestPair(0, 1): {
|
|
|
|
success: true,
|
2019-09-26 16:33:08 +02:00
|
|
|
amt: 100,
|
2019-10-30 21:18:52 -07:00
|
|
|
},
|
|
|
|
getTestPair(1, 2): {},
|
|
|
|
getTestPair(2, 1): {},
|
|
|
|
},
|
|
|
|
policyFailure: getPolicyFailure(2, 3),
|
|
|
|
},
|
|
|
|
},
|
2019-10-30 21:20:08 -07:00
|
|
|
|
|
|
|
// Tests an invalid onion payload from a final hop. The final hop should
|
|
|
|
// be failed while the proceeding hops are reproed as successes. The
|
|
|
|
// failure is terminal since the receiver can't process our onion.
|
|
|
|
{
|
2019-12-20 08:55:45 +01:00
|
|
|
name: "fail invalid onion payload final hop four",
|
2024-10-08 13:43:30 +02:00
|
|
|
route: routeFourHop,
|
2019-10-30 21:20:08 -07:00
|
|
|
failureSrcIdx: 4,
|
|
|
|
failure: lnwire.NewInvalidOnionPayload(0, 0),
|
|
|
|
|
|
|
|
expectedResult: &interpretedResult{
|
|
|
|
pairResults: map[DirectedNodePair]pairResult{
|
|
|
|
getTestPair(0, 1): {
|
|
|
|
success: true,
|
2019-09-26 16:33:08 +02:00
|
|
|
amt: 100,
|
2019-10-30 21:20:08 -07:00
|
|
|
},
|
|
|
|
getTestPair(1, 2): {
|
|
|
|
success: true,
|
2019-09-26 16:33:08 +02:00
|
|
|
amt: 99,
|
2019-10-30 21:20:08 -07:00
|
|
|
},
|
|
|
|
getTestPair(2, 3): {
|
|
|
|
success: true,
|
2019-09-26 16:33:08 +02:00
|
|
|
amt: 97,
|
2019-10-30 21:20:08 -07:00
|
|
|
},
|
|
|
|
getTestPair(4, 3): {},
|
2020-01-21 09:02:25 +01:00
|
|
|
getTestPair(3, 4): {},
|
2019-10-30 21:20:08 -07:00
|
|
|
},
|
|
|
|
finalFailureReason: &reasonError,
|
|
|
|
nodeFailure: &hops[4],
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2019-12-20 08:55:45 +01:00
|
|
|
// Tests an invalid onion payload from a final hop on a three hop route.
|
|
|
|
{
|
|
|
|
name: "fail invalid onion payload final hop three",
|
2024-10-08 13:43:30 +02:00
|
|
|
route: routeThreeHop,
|
2019-12-20 08:55:45 +01:00
|
|
|
failureSrcIdx: 3,
|
|
|
|
failure: lnwire.NewInvalidOnionPayload(0, 0),
|
|
|
|
|
|
|
|
expectedResult: &interpretedResult{
|
|
|
|
pairResults: map[DirectedNodePair]pairResult{
|
|
|
|
getTestPair(0, 1): {
|
|
|
|
success: true,
|
|
|
|
amt: 100,
|
|
|
|
},
|
|
|
|
getTestPair(1, 2): {
|
|
|
|
success: true,
|
|
|
|
amt: 99,
|
|
|
|
},
|
|
|
|
getTestPair(3, 2): {},
|
2020-01-21 09:02:25 +01:00
|
|
|
getTestPair(2, 3): {},
|
2019-12-20 08:55:45 +01:00
|
|
|
},
|
|
|
|
finalFailureReason: &reasonError,
|
|
|
|
nodeFailure: &hops[3],
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2019-10-30 21:20:08 -07:00
|
|
|
// Tests an invalid onion payload from an intermediate hop. Only the
|
|
|
|
// reporting node should be failed. The failure is non-terminal since we
|
|
|
|
// can still try other paths.
|
|
|
|
{
|
|
|
|
name: "fail invalid onion payload intermediate",
|
2024-10-08 13:43:30 +02:00
|
|
|
route: routeFourHop,
|
2019-10-30 21:20:08 -07:00
|
|
|
failureSrcIdx: 3,
|
|
|
|
failure: lnwire.NewInvalidOnionPayload(0, 0),
|
|
|
|
|
|
|
|
expectedResult: &interpretedResult{
|
|
|
|
pairResults: map[DirectedNodePair]pairResult{
|
|
|
|
getTestPair(0, 1): {
|
|
|
|
success: true,
|
2019-09-26 16:33:08 +02:00
|
|
|
amt: 100,
|
2019-10-30 21:20:08 -07:00
|
|
|
},
|
|
|
|
getTestPair(1, 2): {
|
|
|
|
success: true,
|
2019-09-26 16:33:08 +02:00
|
|
|
amt: 99,
|
2019-10-30 21:20:08 -07:00
|
|
|
},
|
|
|
|
getTestPair(3, 2): {},
|
|
|
|
getTestPair(3, 4): {},
|
2020-01-21 09:02:25 +01:00
|
|
|
getTestPair(2, 3): {},
|
|
|
|
getTestPair(4, 3): {},
|
2019-10-30 21:20:08 -07:00
|
|
|
},
|
|
|
|
nodeFailure: &hops[3],
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
// Tests an invalid onion payload in a direct peer that is also the
|
|
|
|
// final hop. The final node should be failed and the error is terminal
|
|
|
|
// since the remote node can't process our onion.
|
|
|
|
{
|
|
|
|
name: "fail invalid onion payload direct",
|
2024-10-08 13:43:30 +02:00
|
|
|
route: routeOneHop,
|
2019-10-30 21:20:08 -07:00
|
|
|
failureSrcIdx: 1,
|
|
|
|
failure: lnwire.NewInvalidOnionPayload(0, 0),
|
|
|
|
|
|
|
|
expectedResult: &interpretedResult{
|
|
|
|
pairResults: map[DirectedNodePair]pairResult{
|
|
|
|
getTestPair(1, 0): {},
|
2020-01-21 09:02:25 +01:00
|
|
|
getTestPair(0, 1): {},
|
2019-10-30 21:20:08 -07:00
|
|
|
},
|
|
|
|
finalFailureReason: &reasonError,
|
|
|
|
nodeFailure: &hops[1],
|
|
|
|
},
|
|
|
|
},
|
2019-12-20 12:25:08 +02:00
|
|
|
|
|
|
|
// Tests a single hop mpp timeout. Test that final node is not
|
|
|
|
// penalized. This is a temporary measure while we decide how to
|
|
|
|
// penalize mpp timeouts.
|
|
|
|
{
|
|
|
|
name: "one hop mpp timeout",
|
2024-10-08 13:43:30 +02:00
|
|
|
route: routeOneHop,
|
2019-12-20 12:25:08 +02:00
|
|
|
failureSrcIdx: 1,
|
|
|
|
failure: &lnwire.FailMPPTimeout{},
|
|
|
|
|
|
|
|
expectedResult: &interpretedResult{
|
2020-03-25 16:21:58 +01:00
|
|
|
pairResults: map[DirectedNodePair]pairResult{
|
|
|
|
getTestPair(0, 1): successPairResult(100),
|
|
|
|
},
|
|
|
|
nodeFailure: nil,
|
2019-12-20 12:25:08 +02:00
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
// Tests a two hop mpp timeout. Test that final node is not penalized
|
|
|
|
// and the intermediate hop is attributed the success. This is a
|
|
|
|
// temporary measure while we decide how to penalize mpp timeouts.
|
|
|
|
{
|
|
|
|
name: "two hop mpp timeout",
|
2024-10-08 13:43:30 +02:00
|
|
|
route: routeTwoHop,
|
2019-12-20 12:25:08 +02:00
|
|
|
failureSrcIdx: 2,
|
|
|
|
failure: &lnwire.FailMPPTimeout{},
|
|
|
|
|
|
|
|
expectedResult: &interpretedResult{
|
|
|
|
pairResults: map[DirectedNodePair]pairResult{
|
|
|
|
getTestPair(0, 1): successPairResult(100),
|
2020-03-25 16:21:58 +01:00
|
|
|
getTestPair(1, 2): successPairResult(99),
|
2019-12-20 12:25:08 +02:00
|
|
|
},
|
2020-03-25 16:21:58 +01:00
|
|
|
nodeFailure: nil,
|
2019-12-20 12:25:08 +02:00
|
|
|
},
|
|
|
|
},
|
2021-09-06 18:40:15 -03:00
|
|
|
|
|
|
|
// Test a channel disabled failure from the final hop in two hops. Only the
|
|
|
|
// disabled channel should be penalized for any amount.
|
|
|
|
{
|
|
|
|
name: "two hop channel disabled",
|
2024-10-08 13:43:30 +02:00
|
|
|
route: routeTwoHop,
|
2021-09-06 18:40:15 -03:00
|
|
|
failureSrcIdx: 1,
|
|
|
|
failure: &lnwire.FailChannelDisabled{},
|
|
|
|
|
|
|
|
expectedResult: &interpretedResult{
|
|
|
|
pairResults: map[DirectedNodePair]pairResult{
|
|
|
|
getTestPair(1, 2): failPairResult(0),
|
|
|
|
getTestPair(2, 1): failPairResult(0),
|
2021-10-24 16:53:50 -03:00
|
|
|
getTestPair(0, 1): successPairResult(100),
|
2021-09-06 18:40:15 -03:00
|
|
|
},
|
|
|
|
policyFailure: getPolicyFailure(1, 2),
|
|
|
|
},
|
|
|
|
},
|
2023-11-06 11:07:32 -05:00
|
|
|
// Test the case where a node after the introduction node returns a
|
|
|
|
// error. In this case the introduction node is penalized because it
|
|
|
|
// has not followed the specification properly.
|
|
|
|
{
|
|
|
|
name: "error after introduction",
|
2024-10-08 13:43:30 +02:00
|
|
|
route: blindedMultiToIntroduction,
|
2023-11-06 11:07:32 -05:00
|
|
|
failureSrcIdx: 2,
|
|
|
|
// Note that the failure code doesn't matter in this case -
|
|
|
|
// all we're worried about is errors originating after the
|
|
|
|
// introduction node.
|
|
|
|
failure: &lnwire.FailExpiryTooSoon{},
|
|
|
|
|
|
|
|
expectedResult: &interpretedResult{
|
|
|
|
pairResults: map[DirectedNodePair]pairResult{
|
|
|
|
getTestPair(0, 1): failPairResult(0),
|
|
|
|
getTestPair(1, 0): failPairResult(0),
|
|
|
|
getTestPair(1, 2): failPairResult(0),
|
|
|
|
getTestPair(2, 1): failPairResult(0),
|
|
|
|
},
|
|
|
|
// Note: introduction node is failed even though the
|
|
|
|
// error source is after it.
|
|
|
|
nodeFailure: &hops[1],
|
|
|
|
},
|
|
|
|
},
|
|
|
|
// Test the case where we get a blinding failure from a blinded final
|
|
|
|
// hop when we expected the introduction node to convert.
|
|
|
|
{
|
|
|
|
name: "final failure expected intro",
|
2024-10-08 13:43:30 +02:00
|
|
|
route: blindedMultiHop,
|
2023-11-06 11:07:32 -05:00
|
|
|
failureSrcIdx: 4,
|
|
|
|
failure: &lnwire.FailInvalidBlinding{},
|
|
|
|
|
|
|
|
expectedResult: &interpretedResult{
|
|
|
|
pairResults: map[DirectedNodePair]pairResult{
|
|
|
|
getTestPair(0, 1): successPairResult(100),
|
|
|
|
getTestPair(1, 2): failPairResult(0),
|
|
|
|
getTestPair(2, 1): failPairResult(0),
|
|
|
|
getTestPair(2, 3): failPairResult(0),
|
|
|
|
getTestPair(3, 2): failPairResult(0),
|
|
|
|
},
|
|
|
|
// Note that the introduction node is penalized, not
|
|
|
|
// the final hop.
|
|
|
|
nodeFailure: &hops[2],
|
|
|
|
finalFailureReason: &reasonError,
|
|
|
|
},
|
|
|
|
},
|
2023-11-06 11:32:30 -05:00
|
|
|
// Test a multi-hop blinded route where the failure occurs at the
|
|
|
|
// introduction point.
|
|
|
|
{
|
|
|
|
name: "blinded multi-hop introduction",
|
2024-10-08 13:43:30 +02:00
|
|
|
route: blindedMultiHop,
|
2023-11-06 11:32:30 -05:00
|
|
|
failureSrcIdx: 2,
|
|
|
|
failure: &lnwire.FailInvalidBlinding{},
|
|
|
|
|
|
|
|
expectedResult: &interpretedResult{
|
|
|
|
pairResults: map[DirectedNodePair]pairResult{
|
|
|
|
getTestPair(0, 1): successPairResult(100),
|
|
|
|
getTestPair(1, 2): successPairResult(99),
|
2024-12-09 22:18:27 +01:00
|
|
|
|
|
|
|
// The amount for the last hop is always the
|
|
|
|
// receiver amount because the amount to forward
|
|
|
|
// is always set to 0 for intermediate blinded
|
|
|
|
// hops.
|
|
|
|
getTestPair(3, 4): failPairResult(77),
|
2023-11-06 11:32:30 -05:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
// Test a multi-hop blinded route where the failure occurs at the
|
|
|
|
// introduction point, which is a direct peer.
|
|
|
|
{
|
|
|
|
name: "blinded multi-hop introduction peer",
|
2024-10-08 13:43:30 +02:00
|
|
|
route: blindedMultiToIntroduction,
|
2023-11-06 11:32:30 -05:00
|
|
|
failureSrcIdx: 1,
|
|
|
|
failure: &lnwire.FailInvalidBlinding{},
|
|
|
|
|
|
|
|
expectedResult: &interpretedResult{
|
|
|
|
pairResults: map[DirectedNodePair]pairResult{
|
|
|
|
getTestPair(0, 1): successPairResult(100),
|
2024-12-09 22:18:27 +01:00
|
|
|
|
|
|
|
// The amount for the last hop is always the
|
|
|
|
// receiver amount because the amount to forward
|
|
|
|
// is always set to 0 for intermediate blinded
|
|
|
|
// hops.
|
|
|
|
getTestPair(2, 3): failPairResult(58),
|
2023-11-06 11:32:30 -05:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
// Test a single-hop blinded route where the recipient is directly
|
|
|
|
// connected to the introduction node.
|
|
|
|
{
|
|
|
|
name: "blinded single hop introduction failure",
|
2024-10-08 13:43:30 +02:00
|
|
|
route: blindedSingleHop,
|
2023-11-06 11:32:30 -05:00
|
|
|
failureSrcIdx: 2,
|
|
|
|
failure: &lnwire.FailInvalidBlinding{},
|
|
|
|
|
|
|
|
expectedResult: &interpretedResult{
|
|
|
|
pairResults: map[DirectedNodePair]pairResult{
|
|
|
|
getTestPair(0, 1): successPairResult(100),
|
|
|
|
getTestPair(1, 2): successPairResult(99),
|
|
|
|
},
|
|
|
|
finalFailureReason: &reasonError,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
// Test the case where a node before the introduction node returns a
|
|
|
|
// blinding error and is penalized for returning the wrong error.
|
|
|
|
{
|
|
|
|
name: "error before introduction",
|
2024-10-08 13:43:30 +02:00
|
|
|
route: blindedMultiHop,
|
2023-11-06 11:32:30 -05:00
|
|
|
failureSrcIdx: 1,
|
|
|
|
failure: &lnwire.FailInvalidBlinding{},
|
|
|
|
|
|
|
|
expectedResult: &interpretedResult{
|
|
|
|
pairResults: map[DirectedNodePair]pairResult{
|
|
|
|
// Failures from failing hops[1].
|
|
|
|
getTestPair(0, 1): failPairResult(0),
|
|
|
|
getTestPair(1, 0): failPairResult(0),
|
|
|
|
getTestPair(1, 2): failPairResult(0),
|
|
|
|
getTestPair(2, 1): failPairResult(0),
|
|
|
|
},
|
|
|
|
nodeFailure: &hops[1],
|
|
|
|
},
|
|
|
|
},
|
|
|
|
// Test the case where an intermediate node that is not in a blinded
|
|
|
|
// route returns an invalid blinding error and there was one
|
|
|
|
// successful hop before the incorrect error.
|
|
|
|
{
|
|
|
|
name: "intermediate unexpected blinding",
|
2024-10-08 13:43:30 +02:00
|
|
|
route: routeThreeHop,
|
2023-11-06 11:32:30 -05:00
|
|
|
failureSrcIdx: 2,
|
|
|
|
failure: &lnwire.FailInvalidBlinding{},
|
|
|
|
|
|
|
|
expectedResult: &interpretedResult{
|
|
|
|
pairResults: map[DirectedNodePair]pairResult{
|
|
|
|
getTestPair(0, 1): successPairResult(100),
|
|
|
|
// Failures from failing hops[2].
|
|
|
|
getTestPair(1, 2): failPairResult(0),
|
|
|
|
getTestPair(2, 1): failPairResult(0),
|
|
|
|
getTestPair(2, 3): failPairResult(0),
|
|
|
|
getTestPair(3, 2): failPairResult(0),
|
|
|
|
},
|
|
|
|
nodeFailure: &hops[2],
|
|
|
|
},
|
|
|
|
},
|
|
|
|
// Test the case where an intermediate node that is not in a blinded
|
|
|
|
// route returns an invalid blinding error and there were no successful
|
|
|
|
// hops before the erring incoming link (the erring node if our peer).
|
|
|
|
{
|
|
|
|
name: "peer unexpected blinding",
|
2024-10-08 13:43:30 +02:00
|
|
|
route: routeThreeHop,
|
2023-11-06 11:32:30 -05:00
|
|
|
failureSrcIdx: 1,
|
|
|
|
failure: &lnwire.FailInvalidBlinding{},
|
|
|
|
|
|
|
|
expectedResult: &interpretedResult{
|
|
|
|
pairResults: map[DirectedNodePair]pairResult{
|
|
|
|
// Failures from failing hops[1].
|
|
|
|
getTestPair(0, 1): failPairResult(0),
|
|
|
|
getTestPair(1, 0): failPairResult(0),
|
|
|
|
getTestPair(1, 2): failPairResult(0),
|
|
|
|
getTestPair(2, 1): failPairResult(0),
|
|
|
|
},
|
|
|
|
nodeFailure: &hops[1],
|
|
|
|
},
|
|
|
|
},
|
2023-11-06 11:40:56 -05:00
|
|
|
// A node in a non-blinded route returns a blinding related error.
|
|
|
|
{
|
|
|
|
name: "final node unexpected blinding",
|
2024-10-08 13:43:30 +02:00
|
|
|
route: routeThreeHop,
|
2023-11-06 11:40:56 -05:00
|
|
|
failureSrcIdx: 3,
|
|
|
|
failure: &lnwire.FailInvalidBlinding{},
|
|
|
|
|
|
|
|
expectedResult: &interpretedResult{
|
|
|
|
pairResults: map[DirectedNodePair]pairResult{
|
|
|
|
getTestPair(0, 1): successPairResult(100),
|
|
|
|
getTestPair(1, 2): successPairResult(99),
|
|
|
|
getTestPair(2, 3): failPairResult(0),
|
|
|
|
getTestPair(3, 2): failPairResult(0),
|
|
|
|
},
|
|
|
|
nodeFailure: &hops[3],
|
|
|
|
finalFailureReason: &reasonError,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
// Introduction node returns invalid blinding erroneously.
|
|
|
|
{
|
|
|
|
name: "final node intro blinding",
|
2024-10-08 13:43:30 +02:00
|
|
|
route: blindedIntroReceiver,
|
2023-11-06 11:40:56 -05:00
|
|
|
failureSrcIdx: 2,
|
|
|
|
failure: &lnwire.FailInvalidBlinding{},
|
|
|
|
|
|
|
|
expectedResult: &interpretedResult{
|
|
|
|
pairResults: map[DirectedNodePair]pairResult{
|
|
|
|
getTestPair(0, 1): successPairResult(100),
|
|
|
|
getTestPair(1, 2): failPairResult(0),
|
|
|
|
getTestPair(2, 1): failPairResult(0),
|
|
|
|
},
|
|
|
|
nodeFailure: &hops[2],
|
|
|
|
finalFailureReason: &reasonError,
|
|
|
|
},
|
|
|
|
},
|
2024-12-09 22:18:27 +01:00
|
|
|
// Test a multi-hop blinded route and that in a success case the amounts
|
|
|
|
// for the blinded route part are correctly set to the receiver amount.
|
|
|
|
{
|
|
|
|
name: "blinded multi-hop success",
|
|
|
|
route: blindedMultiToIntroduction,
|
|
|
|
success: true,
|
|
|
|
expectedResult: &interpretedResult{
|
|
|
|
pairResults: map[DirectedNodePair]pairResult{
|
|
|
|
getTestPair(0, 1): successPairResult(100),
|
|
|
|
|
|
|
|
// For the route blinded part of the route the
|
|
|
|
// success amount is determined by the receiver
|
|
|
|
// amount because the intermediate blinded hops
|
|
|
|
// set the forwarded amount to 0.
|
|
|
|
getTestPair(1, 2): successPairResult(58),
|
|
|
|
getTestPair(2, 3): successPairResult(58),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2019-08-05 12:29:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// TestResultInterpretation executes a list of test cases that test the result
|
|
|
|
// interpretation logic.
|
|
|
|
func TestResultInterpretation(t *testing.T) {
|
2019-07-29 14:20:06 +02:00
|
|
|
emptyResults := make(map[DirectedNodePair]pairResult)
|
2019-08-05 12:29:16 +02:00
|
|
|
|
|
|
|
for _, testCase := range resultTestCases {
|
|
|
|
t.Run(testCase.name, func(t *testing.T) {
|
2024-10-08 13:43:30 +02:00
|
|
|
var failure fn.Option[paymentFailure]
|
|
|
|
if !testCase.success {
|
|
|
|
failure = fn.Some(*newPaymentFailure(
|
|
|
|
&testCase.failureSrcIdx,
|
|
|
|
testCase.failure,
|
|
|
|
))
|
|
|
|
}
|
|
|
|
|
|
|
|
i := interpretResult(testCase.route, failure)
|
2019-08-05 12:29:16 +02:00
|
|
|
|
|
|
|
expected := testCase.expectedResult
|
|
|
|
|
|
|
|
// Replace nil pairResults with empty map to satisfy
|
|
|
|
// DeepEqual.
|
|
|
|
if expected.pairResults == nil {
|
|
|
|
expected.pairResults = emptyResults
|
|
|
|
}
|
|
|
|
|
|
|
|
if !reflect.DeepEqual(i, expected) {
|
2019-10-30 21:18:52 -07:00
|
|
|
t.Fatalf("unexpected result\nwant: %v\ngot: %v",
|
|
|
|
spew.Sdump(expected), spew.Sdump(i))
|
2019-08-05 12:29:16 +02:00
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|