d08e8ddd61
An InvalidOnionPayload implies that the onion was successfully received by the reporting node, but that they were unable to extract the contents. Since we assume our own behavior is correct, this mostly likely poins to an error in the reporter's implementation or that we sent an unknown required type. Therefore we only penalize that single hop, and consider the failure terminal if the receiver reported it.
298 lines
7.5 KiB
Go
298 lines
7.5 KiB
Go
package routing
|
|
|
|
import (
|
|
"reflect"
|
|
"testing"
|
|
|
|
"github.com/davecgh/go-spew/spew"
|
|
"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},
|
|
}
|
|
|
|
routeOneHop = route.Route{
|
|
SourcePubKey: hops[0],
|
|
TotalAmount: 100,
|
|
Hops: []*route.Hop{
|
|
{PubKeyBytes: hops[1], AmtToForward: 99},
|
|
},
|
|
}
|
|
|
|
routeTwoHop = route.Route{
|
|
SourcePubKey: hops[0],
|
|
TotalAmount: 100,
|
|
Hops: []*route.Hop{
|
|
{PubKeyBytes: hops[1], AmtToForward: 99},
|
|
{PubKeyBytes: hops[2], AmtToForward: 97},
|
|
},
|
|
}
|
|
|
|
routeFourHop = 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},
|
|
},
|
|
}
|
|
)
|
|
|
|
func getTestPair(from, to int) DirectedNodePair {
|
|
return NewDirectedNodePair(hops[from], hops[to])
|
|
}
|
|
|
|
func getPolicyFailure(from, to int) *DirectedNodePair {
|
|
pair := getTestPair(from, to)
|
|
return &pair
|
|
}
|
|
|
|
type resultTestCase struct {
|
|
name string
|
|
route *route.Route
|
|
success bool
|
|
failureSrcIdx int
|
|
failure lnwire.FailureMessage
|
|
|
|
expectedResult *interpretedResult
|
|
}
|
|
|
|
var resultTestCases = []resultTestCase{
|
|
// Tests that a temporary channel failure result is properly
|
|
// interpreted.
|
|
{
|
|
name: "fail",
|
|
route: &routeTwoHop,
|
|
failureSrcIdx: 1,
|
|
failure: lnwire.NewTemporaryChannelFailure(nil),
|
|
|
|
expectedResult: &interpretedResult{
|
|
pairResults: map[DirectedNodePair]pairResult{
|
|
getTestPair(0, 1): successPairResult(),
|
|
getTestPair(1, 2): failPairResult(99),
|
|
},
|
|
},
|
|
},
|
|
|
|
// Tests that a expiry too soon failure result is properly interpreted.
|
|
{
|
|
name: "fail expiry too soon",
|
|
route: &routeFourHop,
|
|
failureSrcIdx: 3,
|
|
failure: lnwire.NewExpiryTooSoon(lnwire.ChannelUpdate{}),
|
|
|
|
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),
|
|
getTestPair(2, 3): failPairResult(0),
|
|
getTestPair(3, 2): failPairResult(0),
|
|
},
|
|
},
|
|
},
|
|
|
|
// 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",
|
|
route: &routeTwoHop,
|
|
failureSrcIdx: 2,
|
|
failure: lnwire.NewFailIncorrectDetails(97, 0),
|
|
|
|
expectedResult: &interpretedResult{
|
|
pairResults: map[DirectedNodePair]pairResult{
|
|
getTestPair(0, 1): successPairResult(),
|
|
getTestPair(1, 2): successPairResult(),
|
|
},
|
|
finalFailureReason: &reasonIncorrectDetails,
|
|
},
|
|
},
|
|
|
|
// Tests a successful direct payment.
|
|
{
|
|
name: "success direct",
|
|
route: &routeOneHop,
|
|
success: true,
|
|
|
|
expectedResult: &interpretedResult{
|
|
pairResults: map[DirectedNodePair]pairResult{
|
|
getTestPair(0, 1): successPairResult(),
|
|
},
|
|
},
|
|
},
|
|
|
|
// Tests a successful two hop payment.
|
|
{
|
|
name: "success",
|
|
route: &routeTwoHop,
|
|
success: true,
|
|
|
|
expectedResult: &interpretedResult{
|
|
pairResults: map[DirectedNodePair]pairResult{
|
|
getTestPair(0, 1): successPairResult(),
|
|
getTestPair(1, 2): successPairResult(),
|
|
},
|
|
},
|
|
},
|
|
|
|
// Tests a malformed htlc from a direct peer.
|
|
{
|
|
name: "fail malformed htlc from direct peer",
|
|
route: &routeTwoHop,
|
|
failureSrcIdx: 0,
|
|
failure: lnwire.NewInvalidOnionKey(nil),
|
|
|
|
expectedResult: &interpretedResult{
|
|
nodeFailure: &hops[1],
|
|
pairResults: map[DirectedNodePair]pairResult{
|
|
getTestPair(1, 0): failPairResult(0),
|
|
getTestPair(1, 2): failPairResult(0),
|
|
},
|
|
},
|
|
},
|
|
|
|
// Tests a malformed htlc from a direct peer that is also the final
|
|
// destination.
|
|
{
|
|
name: "fail malformed htlc from direct final peer",
|
|
route: &routeOneHop,
|
|
failureSrcIdx: 0,
|
|
failure: lnwire.NewInvalidOnionKey(nil),
|
|
|
|
expectedResult: &interpretedResult{
|
|
finalFailureReason: &reasonError,
|
|
nodeFailure: &hops[1],
|
|
pairResults: map[DirectedNodePair]pairResult{
|
|
getTestPair(1, 0): failPairResult(0),
|
|
},
|
|
},
|
|
},
|
|
|
|
// 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",
|
|
route: &routeFourHop,
|
|
failureSrcIdx: 2,
|
|
failure: lnwire.NewFeeInsufficient(0, lnwire.ChannelUpdate{}),
|
|
|
|
expectedResult: &interpretedResult{
|
|
pairResults: map[DirectedNodePair]pairResult{
|
|
getTestPair(0, 1): {
|
|
success: true,
|
|
},
|
|
getTestPair(1, 2): {},
|
|
getTestPair(2, 1): {},
|
|
},
|
|
policyFailure: getPolicyFailure(2, 3),
|
|
},
|
|
},
|
|
|
|
// 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.
|
|
{
|
|
name: "fail invalid onion payload final hop",
|
|
route: &routeFourHop,
|
|
failureSrcIdx: 4,
|
|
failure: lnwire.NewInvalidOnionPayload(0, 0),
|
|
|
|
expectedResult: &interpretedResult{
|
|
pairResults: map[DirectedNodePair]pairResult{
|
|
getTestPair(0, 1): {
|
|
success: true,
|
|
},
|
|
getTestPair(1, 2): {
|
|
success: true,
|
|
},
|
|
getTestPair(2, 3): {
|
|
success: true,
|
|
},
|
|
getTestPair(4, 3): {},
|
|
},
|
|
finalFailureReason: &reasonError,
|
|
nodeFailure: &hops[4],
|
|
},
|
|
},
|
|
|
|
// 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",
|
|
route: &routeFourHop,
|
|
failureSrcIdx: 3,
|
|
failure: lnwire.NewInvalidOnionPayload(0, 0),
|
|
|
|
expectedResult: &interpretedResult{
|
|
pairResults: map[DirectedNodePair]pairResult{
|
|
getTestPair(0, 1): {
|
|
success: true,
|
|
},
|
|
getTestPair(1, 2): {
|
|
success: true,
|
|
},
|
|
getTestPair(3, 2): {},
|
|
getTestPair(3, 4): {},
|
|
},
|
|
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",
|
|
route: &routeOneHop,
|
|
failureSrcIdx: 1,
|
|
failure: lnwire.NewInvalidOnionPayload(0, 0),
|
|
|
|
expectedResult: &interpretedResult{
|
|
pairResults: map[DirectedNodePair]pairResult{
|
|
getTestPair(1, 0): {},
|
|
},
|
|
finalFailureReason: &reasonError,
|
|
nodeFailure: &hops[1],
|
|
},
|
|
},
|
|
}
|
|
|
|
// TestResultInterpretation executes a list of test cases that test the result
|
|
// interpretation logic.
|
|
func TestResultInterpretation(t *testing.T) {
|
|
emptyResults := make(map[DirectedNodePair]pairResult)
|
|
|
|
for _, testCase := range resultTestCases {
|
|
t.Run(testCase.name, func(t *testing.T) {
|
|
i := interpretResult(
|
|
testCase.route, testCase.success,
|
|
&testCase.failureSrcIdx, testCase.failure,
|
|
)
|
|
|
|
expected := testCase.expectedResult
|
|
|
|
// Replace nil pairResults with empty map to satisfy
|
|
// DeepEqual.
|
|
if expected.pairResults == nil {
|
|
expected.pairResults = emptyResults
|
|
}
|
|
|
|
if !reflect.DeepEqual(i, expected) {
|
|
t.Fatalf("unexpected result\nwant: %v\ngot: %v",
|
|
spew.Sdump(expected), spew.Sdump(i))
|
|
}
|
|
})
|
|
}
|
|
}
|