mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-01-19 05:45:21 +01:00
routing: handle introduction node failure to convert error
This commit adds handling for errors that originate after the introduction node when making payment to a blinded route. This indicates that the introduction node is not obeying the spec, so it is punished for the violation.
This commit is contained in:
parent
d017fe01e3
commit
f91589bef9
@ -108,6 +108,16 @@ func (i *interpretedResult) processFail(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the payment was to a blinded route and we received an error from
|
||||||
|
// after the introduction point, handle this error separately - there
|
||||||
|
// has been a protocol violation from the introduction node. This
|
||||||
|
// penalty applies regardless of the error code that is returned.
|
||||||
|
introIdx, isBlinded := introductionPointIndex(rt)
|
||||||
|
if isBlinded && introIdx < *errSourceIdx {
|
||||||
|
i.processPaymentOutcomeBadIntro(rt, introIdx, *errSourceIdx)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
switch *errSourceIdx {
|
switch *errSourceIdx {
|
||||||
|
|
||||||
// We are the source of the failure.
|
// We are the source of the failure.
|
||||||
@ -129,6 +139,33 @@ func (i *interpretedResult) processFail(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// processPaymentOutcomeBadIntro handles the case where we have made payment
|
||||||
|
// to a blinded route, but received an error from a node after the introduction
|
||||||
|
// node. This indicates that the introduction node is not obeying the route
|
||||||
|
// blinding specification, as we expect all errors from the introduction node
|
||||||
|
// to be source from it.
|
||||||
|
func (i *interpretedResult) processPaymentOutcomeBadIntro(route *route.Route,
|
||||||
|
introIdx, errSourceIdx int) {
|
||||||
|
|
||||||
|
// We fail the introduction node for not obeying the specification.
|
||||||
|
i.failNode(route, introIdx)
|
||||||
|
|
||||||
|
// Other preceding channels in the route forwarded correctly. Note
|
||||||
|
// that we do not assign success to the incoming link to the
|
||||||
|
// introduction node because it has not handled the error correctly.
|
||||||
|
if introIdx > 1 {
|
||||||
|
i.successPairRange(route, 0, introIdx-2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the source of the failure was from the final node, we also set
|
||||||
|
// a final failure reason because the recipient can't process the
|
||||||
|
// payment (independent of the introduction failing to convert the
|
||||||
|
// error, we can't complete the payment if the last hop fails).
|
||||||
|
if errSourceIdx == len(route.Hops) {
|
||||||
|
i.finalFailureReason = &reasonError
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// processPaymentOutcomeSelf handles failures sent by ourselves.
|
// processPaymentOutcomeSelf handles failures sent by ourselves.
|
||||||
func (i *interpretedResult) processPaymentOutcomeSelf(
|
func (i *interpretedResult) processPaymentOutcomeSelf(
|
||||||
rt *route.Route, failure lnwire.FailureMessage) {
|
rt *route.Route, failure lnwire.FailureMessage) {
|
||||||
@ -401,6 +438,20 @@ func (i *interpretedResult) processPaymentOutcomeIntermediate(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// introductionPointIndex returns the index of an introduction point in a
|
||||||
|
// route, using the same indexing in the route that we use for errorSourceIdx
|
||||||
|
// (i.e., that we consider our own node to be at index zero). A boolean is
|
||||||
|
// returned to indicate whether the route contains a blinded portion at all.
|
||||||
|
func introductionPointIndex(route *route.Route) (int, bool) {
|
||||||
|
for i, hop := range route.Hops {
|
||||||
|
if hop.BlindingPoint != nil {
|
||||||
|
return i + 1, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
// processPaymentOutcomeUnknown processes a payment outcome for which no failure
|
// processPaymentOutcomeUnknown processes a payment outcome for which no failure
|
||||||
// message or source is available.
|
// message or source is available.
|
||||||
func (i *interpretedResult) processPaymentOutcomeUnknown(route *route.Route) {
|
func (i *interpretedResult) processPaymentOutcomeUnknown(route *route.Route) {
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/btcec/v2"
|
||||||
"github.com/davecgh/go-spew/spew"
|
"github.com/davecgh/go-spew/spew"
|
||||||
"github.com/lightningnetwork/lnd/lnwire"
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
"github.com/lightningnetwork/lnd/routing/route"
|
"github.com/lightningnetwork/lnd/routing/route"
|
||||||
@ -14,6 +15,10 @@ var (
|
|||||||
{1, 0}, {1, 1}, {1, 2}, {1, 3}, {1, 4},
|
{1, 0}, {1, 1}, {1, 2}, {1, 3}, {1, 4},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// blindingPoint provides a non-nil blinding point (value is never
|
||||||
|
// used).
|
||||||
|
blindingPoint = &btcec.PublicKey{}
|
||||||
|
|
||||||
routeOneHop = route.Route{
|
routeOneHop = route.Route{
|
||||||
SourcePubKey: hops[0],
|
SourcePubKey: hops[0],
|
||||||
TotalAmount: 100,
|
TotalAmount: 100,
|
||||||
@ -51,6 +56,40 @@ var (
|
|||||||
{PubKeyBytes: hops[4], AmtToForward: 90},
|
{PubKeyBytes: hops[4], AmtToForward: 90},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// blindedMultiHop is a blinded path where there are cleartext hops
|
||||||
|
// before the introduction node, and an intermediate blinded hop before
|
||||||
|
// the recipient after it.
|
||||||
|
blindedMultiHop = route.Route{
|
||||||
|
SourcePubKey: hops[0],
|
||||||
|
TotalAmount: 100,
|
||||||
|
Hops: []*route.Hop{
|
||||||
|
{PubKeyBytes: hops[1], AmtToForward: 99},
|
||||||
|
{
|
||||||
|
PubKeyBytes: hops[2],
|
||||||
|
AmtToForward: 95,
|
||||||
|
BlindingPoint: blindingPoint,
|
||||||
|
},
|
||||||
|
{PubKeyBytes: hops[3], AmtToForward: 88},
|
||||||
|
{PubKeyBytes: hops[4], AmtToForward: 77},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// blindedMultiToIntroduction is a blinded path which goes directly
|
||||||
|
// to the introduction node, with multiple blinded hops after it.
|
||||||
|
blindedMultiToIntroduction = route.Route{
|
||||||
|
SourcePubKey: hops[0],
|
||||||
|
TotalAmount: 100,
|
||||||
|
Hops: []*route.Hop{
|
||||||
|
{
|
||||||
|
PubKeyBytes: hops[1],
|
||||||
|
AmtToForward: 90,
|
||||||
|
BlindingPoint: blindingPoint,
|
||||||
|
},
|
||||||
|
{PubKeyBytes: hops[2], AmtToForward: 75},
|
||||||
|
{PubKeyBytes: hops[3], AmtToForward: 58},
|
||||||
|
},
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func getTestPair(from, to int) DirectedNodePair {
|
func getTestPair(from, to int) DirectedNodePair {
|
||||||
@ -366,6 +405,52 @@ var resultTestCases = []resultTestCase{
|
|||||||
policyFailure: getPolicyFailure(1, 2),
|
policyFailure: getPolicyFailure(1, 2),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
// 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",
|
||||||
|
route: &blindedMultiToIntroduction,
|
||||||
|
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",
|
||||||
|
route: &blindedMultiHop,
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestResultInterpretation executes a list of test cases that test the result
|
// TestResultInterpretation executes a list of test cases that test the result
|
||||||
|
Loading…
Reference in New Issue
Block a user