Merge pull request #8095 from carlaKC/7882-mcblinding
missioncontrol: add invalid onion blinding handling for blinded paths
This commit is contained in:
10 files changed
+1034
-575
No files matched your search
@@ -69,7 +69,9 @@
|
|||||||
* Support for [pathfinding]((https://github.com/lightningnetwork/lnd/pull/7267)
|
* Support for [pathfinding]((https://github.com/lightningnetwork/lnd/pull/7267)
|
||||||
and payment to blinded paths has been added via the `QueryRoutes` (and
|
and payment to blinded paths has been added via the `QueryRoutes` (and
|
||||||
SendToRouteV2) APIs. This functionality is surfaced in `lncli queryroutes`
|
SendToRouteV2) APIs. This functionality is surfaced in `lncli queryroutes`
|
||||||
where the required flags are tagged with `(blinded paths)`.
|
where the required flags are tagged with `(blinded paths)`. Updates to mission
|
||||||
|
control to [handle pathfinding errors](https://github.com/lightningnetwork/lnd/pull/8095)
|
||||||
|
for blinded paths are also included.
|
||||||
* A new config value,
|
* A new config value,
|
||||||
[http-header-timeout](https://github.com/lightningnetwork/lnd/pull/7715), is added so users can specify the amount of time the http server will wait for a request to complete before closing the connection. The default value is 5 seconds.
|
[http-header-timeout](https://github.com/lightningnetwork/lnd/pull/7715), is added so users can specify the amount of time the http server will wait for a request to complete before closing the connection. The default value is 5 seconds.
|
||||||
|
|
||||||
|
|||||||
+568
-563
File diff suppressed because it is too large.
Load diff
@@ -4598,6 +4598,7 @@ message Failure {
|
|||||||
EXPIRY_TOO_FAR = 22;
|
EXPIRY_TOO_FAR = 22;
|
||||||
MPP_TIMEOUT = 23;
|
MPP_TIMEOUT = 23;
|
||||||
INVALID_ONION_PAYLOAD = 24;
|
INVALID_ONION_PAYLOAD = 24;
|
||||||
|
INVALID_ONION_BLINDING = 25;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
An internal error occurred.
|
An internal error occurred.
|
||||||
|
|||||||
@@ -2849,6 +2849,7 @@
|
|||||||
"EXPIRY_TOO_FAR",
|
"EXPIRY_TOO_FAR",
|
||||||
"MPP_TIMEOUT",
|
"MPP_TIMEOUT",
|
||||||
"INVALID_ONION_PAYLOAD",
|
"INVALID_ONION_PAYLOAD",
|
||||||
|
"INVALID_ONION_BLINDING",
|
||||||
"INTERNAL_FAILURE",
|
"INTERNAL_FAILURE",
|
||||||
"UNKNOWN_FAILURE",
|
"UNKNOWN_FAILURE",
|
||||||
"UNREADABLE_FAILURE"
|
"UNREADABLE_FAILURE"
|
||||||
|
|||||||
@@ -577,6 +577,7 @@
|
|||||||
"EXPIRY_TOO_FAR",
|
"EXPIRY_TOO_FAR",
|
||||||
"MPP_TIMEOUT",
|
"MPP_TIMEOUT",
|
||||||
"INVALID_ONION_PAYLOAD",
|
"INVALID_ONION_PAYLOAD",
|
||||||
|
"INVALID_ONION_BLINDING",
|
||||||
"INTERNAL_FAILURE",
|
"INTERNAL_FAILURE",
|
||||||
"UNKNOWN_FAILURE",
|
"UNKNOWN_FAILURE",
|
||||||
"UNREADABLE_FAILURE"
|
"UNREADABLE_FAILURE"
|
||||||
|
|||||||
@@ -1495,6 +1495,10 @@ func marshallWireError(msg lnwire.FailureMessage,
|
|||||||
case *lnwire.InvalidOnionPayload:
|
case *lnwire.InvalidOnionPayload:
|
||||||
response.Code = lnrpc.Failure_INVALID_ONION_PAYLOAD
|
response.Code = lnrpc.Failure_INVALID_ONION_PAYLOAD
|
||||||
|
|
||||||
|
case *lnwire.FailInvalidBlinding:
|
||||||
|
response.Code = lnrpc.Failure_INVALID_ONION_BLINDING
|
||||||
|
response.OnionSha_256 = onionErr.OnionSHA256[:]
|
||||||
|
|
||||||
case nil:
|
case nil:
|
||||||
response.Code = lnrpc.Failure_UNKNOWN_FAILURE
|
response.Code = lnrpc.Failure_UNKNOWN_FAILURE
|
||||||
|
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ const (
|
|||||||
CodeExpiryTooFar FailCode = 21
|
CodeExpiryTooFar FailCode = 21
|
||||||
CodeInvalidOnionPayload = FlagPerm | 22
|
CodeInvalidOnionPayload = FlagPerm | 22
|
||||||
CodeMPPTimeout FailCode = 23
|
CodeMPPTimeout FailCode = 23
|
||||||
|
CodeInvalidBlinding = FlagBadOnion | FlagPerm | 24 //nolint:lll
|
||||||
)
|
)
|
||||||
|
|
||||||
// String returns the string representation of the failure code.
|
// String returns the string representation of the failure code.
|
||||||
@@ -158,6 +159,9 @@ func (c FailCode) String() string {
|
|||||||
case CodeMPPTimeout:
|
case CodeMPPTimeout:
|
||||||
return "MPPTimeout"
|
return "MPPTimeout"
|
||||||
|
|
||||||
|
case CodeInvalidBlinding:
|
||||||
|
return "InvalidBlinding"
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return "<unknown>"
|
return "<unknown>"
|
||||||
}
|
}
|
||||||
@@ -1233,6 +1237,51 @@ func (f *FailMPPTimeout) Error() string {
|
|||||||
return f.Code().String()
|
return f.Code().String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FailInvalidBlinding is returned if there has been a route blinding related
|
||||||
|
// error.
|
||||||
|
type FailInvalidBlinding struct {
|
||||||
|
OnionSHA256 [sha256.Size]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// Code returns the failure unique code.
|
||||||
|
//
|
||||||
|
// NOTE: Part of the FailureMessage interface.
|
||||||
|
func (f *FailInvalidBlinding) Code() FailCode {
|
||||||
|
return CodeInvalidBlinding
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a human readable string describing the target FailureMessage.
|
||||||
|
//
|
||||||
|
// NOTE: Implements the error interface.
|
||||||
|
func (f *FailInvalidBlinding) Error() string {
|
||||||
|
return f.Code().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode decodes the failure from bytes stream.
|
||||||
|
//
|
||||||
|
// NOTE: Part of the Serializable interface.
|
||||||
|
func (f *FailInvalidBlinding) Decode(r io.Reader, _ uint32) error {
|
||||||
|
return ReadElement(r, f.OnionSHA256[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode writes the failure in bytes stream.
|
||||||
|
//
|
||||||
|
// NOTE: Part of the Serializable interface.
|
||||||
|
func (f *FailInvalidBlinding) Encode(w *bytes.Buffer, _ uint32) error {
|
||||||
|
return WriteBytes(w, f.OnionSHA256[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewInvalidBlinding creates new instance of FailInvalidBlinding.
|
||||||
|
func NewInvalidBlinding(onion []byte) *FailInvalidBlinding {
|
||||||
|
// The spec allows empty onion hashes for invalid blinding, so we only
|
||||||
|
// include our onion hash if it's provided.
|
||||||
|
if onion == nil {
|
||||||
|
return &FailInvalidBlinding{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &FailInvalidBlinding{OnionSHA256: sha256.Sum256(onion)}
|
||||||
|
}
|
||||||
|
|
||||||
// DecodeFailure decodes, validates, and parses the lnwire onion failure, for
|
// DecodeFailure decodes, validates, and parses the lnwire onion failure, for
|
||||||
// the provided protocol version.
|
// the provided protocol version.
|
||||||
func DecodeFailure(r io.Reader, pver uint32) (FailureMessage, error) {
|
func DecodeFailure(r io.Reader, pver uint32) (FailureMessage, error) {
|
||||||
@@ -1451,6 +1500,9 @@ func makeEmptyOnionError(code FailCode) (FailureMessage, error) {
|
|||||||
case CodeMPPTimeout:
|
case CodeMPPTimeout:
|
||||||
return &FailMPPTimeout{}, nil
|
return &FailMPPTimeout{}, nil
|
||||||
|
|
||||||
|
case CodeInvalidBlinding:
|
||||||
|
return &FailInvalidBlinding{}, nil
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return nil, errors.Errorf("unknown error code: %v", code)
|
return nil, errors.Errorf("unknown error code: %v", code)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ var onionFailures = []FailureMessage{
|
|||||||
NewFinalIncorrectCltvExpiry(testCtlvExpiry),
|
NewFinalIncorrectCltvExpiry(testCtlvExpiry),
|
||||||
NewFinalIncorrectHtlcAmount(testAmount),
|
NewFinalIncorrectHtlcAmount(testAmount),
|
||||||
NewInvalidOnionPayload(testType, testOffset),
|
NewInvalidOnionPayload(testType, testOffset),
|
||||||
|
NewInvalidBlinding(testOnionHash),
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestEncodeDecodeCode tests the ability of onion errors to be properly encoded
|
// TestEncodeDecodeCode tests the ability of onion errors to be properly encoded
|
||||||
|
|||||||
@@ -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) {
|
||||||
@@ -164,6 +201,17 @@ func (i *interpretedResult) processPaymentOutcomeFinal(
|
|||||||
|
|
||||||
n := len(route.Hops)
|
n := len(route.Hops)
|
||||||
|
|
||||||
|
failNode := func() {
|
||||||
|
i.failNode(route, n)
|
||||||
|
|
||||||
|
// Other channels in the route forwarded correctly.
|
||||||
|
if n > 1 {
|
||||||
|
i.successPairRange(route, 0, n-2)
|
||||||
|
}
|
||||||
|
|
||||||
|
i.finalFailureReason = &reasonError
|
||||||
|
}
|
||||||
|
|
||||||
// If a failure from the final node is received, we will fail the
|
// If a failure from the final node is received, we will fail the
|
||||||
// payment in almost all cases. Only when the penultimate node sends an
|
// payment in almost all cases. Only when the penultimate node sends an
|
||||||
// incorrect htlc, we want to retry via another route. Invalid onion
|
// incorrect htlc, we want to retry via another route. Invalid onion
|
||||||
@@ -219,18 +267,26 @@ func (i *interpretedResult) processPaymentOutcomeFinal(
|
|||||||
// destination correctly. Continue the payment process.
|
// destination correctly. Continue the payment process.
|
||||||
i.successPairRange(route, 0, n-1)
|
i.successPairRange(route, 0, n-1)
|
||||||
|
|
||||||
|
// We do not expect to receive an invalid blinding error from the final
|
||||||
|
// node in the route. This could erroneously happen in the following
|
||||||
|
// cases:
|
||||||
|
// 1. Unblinded node: misuses the error code.
|
||||||
|
// 2. A receiving introduction node: erroneously sends the error code,
|
||||||
|
// as the spec indicates that receiving introduction nodes should
|
||||||
|
// use regular errors.
|
||||||
|
//
|
||||||
|
// Note that we expect the case where this error is sent from a node
|
||||||
|
// after the introduction node to be handled elsewhere as this is part
|
||||||
|
// of a more general class of errors where the introduction node has
|
||||||
|
// failed to convert errors for the blinded route.
|
||||||
|
case *lnwire.FailInvalidBlinding:
|
||||||
|
failNode()
|
||||||
|
|
||||||
|
// All other errors are considered terminal if coming from the
|
||||||
|
// final hop. They indicate that something is wrong at the
|
||||||
|
// recipient, so we do apply a penalty.
|
||||||
default:
|
default:
|
||||||
// All other errors are considered terminal if coming from the
|
failNode()
|
||||||
// final hop. They indicate that something is wrong at the
|
|
||||||
// recipient, so we do apply a penalty.
|
|
||||||
i.failNode(route, n)
|
|
||||||
|
|
||||||
// Other channels in the route forwarded correctly.
|
|
||||||
if n >= 2 {
|
|
||||||
i.successPairRange(route, 0, n-2)
|
|
||||||
}
|
|
||||||
|
|
||||||
i.finalFailureReason = &reasonError
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -394,6 +450,70 @@ func (i *interpretedResult) processPaymentOutcomeIntermediate(
|
|||||||
case *lnwire.FailExpiryTooSoon:
|
case *lnwire.FailExpiryTooSoon:
|
||||||
reportAll()
|
reportAll()
|
||||||
|
|
||||||
|
// We only expect to get FailInvalidBlinding from an introduction node
|
||||||
|
// in a blinded route. The introduction node in a blinded route is
|
||||||
|
// always responsible for reporting errors for the blinded portion of
|
||||||
|
// the route (to protect the privacy of the members of the route), so
|
||||||
|
// we need to be careful not to unfairly "shoot the messenger".
|
||||||
|
//
|
||||||
|
// The introduction node has no incentive to falsely report errors to
|
||||||
|
// sabotage the blinded route because:
|
||||||
|
// 1. Its ability to route this payment is strictly tied to the
|
||||||
|
// blinded route.
|
||||||
|
// 2. The pubkeys in the blinded route are ephemeral, so doing so
|
||||||
|
// will have no impact on the nodes beyond the individual payment.
|
||||||
|
//
|
||||||
|
// Here we handle a few cases where we could unexpectedly receive this
|
||||||
|
// error:
|
||||||
|
// 1. Outside of a blinded route: erring node is not spec compliant.
|
||||||
|
// 2. Before the introduction point: erring node is not spec compliant.
|
||||||
|
//
|
||||||
|
// Note that we expect the case where this error is sent from a node
|
||||||
|
// after the introduction node to be handled elsewhere as this is part
|
||||||
|
// of a more general class of errors where the introduction node has
|
||||||
|
// failed to convert errors for the blinded route.
|
||||||
|
case *lnwire.FailInvalidBlinding:
|
||||||
|
introIdx, isBlinded := introductionPointIndex(route)
|
||||||
|
|
||||||
|
// Deal with cases where a node has incorrectly returned a
|
||||||
|
// blinding error:
|
||||||
|
// 1. A node before the introduction point returned it.
|
||||||
|
// 2. A node in a non-blinded route returned it.
|
||||||
|
if errorSourceIdx < introIdx || !isBlinded {
|
||||||
|
reportNode()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, the error was at the introduction node. All
|
||||||
|
// nodes up until the introduction node forwarded correctly,
|
||||||
|
// so we award them as successful.
|
||||||
|
if introIdx >= 1 {
|
||||||
|
i.successPairRange(route, 0, introIdx-1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the hop after the introduction node that sent us an
|
||||||
|
// error is the final recipient, then we finally fail the
|
||||||
|
// payment because the receiver has generated a blinded route
|
||||||
|
// that they're unable to use. We have this special case so
|
||||||
|
// that we don't penalize the introduction node, and there is
|
||||||
|
// no point in retrying the payment while LND only supports
|
||||||
|
// one blinded route per payment.
|
||||||
|
//
|
||||||
|
// Note that if LND is extended to support multiple blinded
|
||||||
|
// routes, this will terminate the payment without re-trying
|
||||||
|
// the other routes.
|
||||||
|
if introIdx == len(route.Hops)-1 {
|
||||||
|
i.finalFailureReason = &reasonError
|
||||||
|
} else {
|
||||||
|
// If there are other hops between the recipient and
|
||||||
|
// introduction node, then we just penalize the last
|
||||||
|
// hop in the blinded route to minimize the storage of
|
||||||
|
// results for ephemeral keys.
|
||||||
|
i.failPairBalance(
|
||||||
|
route, len(route.Hops)-1,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// In all other cases, we penalize the reporting node. These are all
|
// In all other cases, we penalize the reporting node. These are all
|
||||||
// failures that should not happen.
|
// failures that should not happen.
|
||||||
default:
|
default:
|
||||||
@@ -401,6 +521,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,71 @@ 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},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// blindedSingleHop is a blinded path with a single blinded hop after
|
||||||
|
// the introduction node.
|
||||||
|
blindedSingleHop = 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},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// blindedIntroReceiver is a blinded path where the introduction node
|
||||||
|
// is the recipient.
|
||||||
|
blindedIntroReceiver = route.Route{
|
||||||
|
SourcePubKey: hops[0],
|
||||||
|
TotalAmount: 100,
|
||||||
|
Hops: []*route.Hop{
|
||||||
|
{PubKeyBytes: hops[1], AmtToForward: 95},
|
||||||
|
{
|
||||||
|
PubKeyBytes: hops[2],
|
||||||
|
AmtToForward: 90,
|
||||||
|
BlindingPoint: blindingPoint,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func getTestPair(from, to int) DirectedNodePair {
|
func getTestPair(from, to int) DirectedNodePair {
|
||||||
@@ -366,6 +436,194 @@ 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,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Test a multi-hop blinded route where the failure occurs at the
|
||||||
|
// introduction point.
|
||||||
|
{
|
||||||
|
name: "blinded multi-hop introduction",
|
||||||
|
route: &blindedMultiHop,
|
||||||
|
failureSrcIdx: 2,
|
||||||
|
failure: &lnwire.FailInvalidBlinding{},
|
||||||
|
|
||||||
|
expectedResult: &interpretedResult{
|
||||||
|
pairResults: map[DirectedNodePair]pairResult{
|
||||||
|
getTestPair(0, 1): successPairResult(100),
|
||||||
|
getTestPair(1, 2): successPairResult(99),
|
||||||
|
getTestPair(3, 4): failPairResult(88),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// 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",
|
||||||
|
route: &blindedMultiToIntroduction,
|
||||||
|
failureSrcIdx: 1,
|
||||||
|
failure: &lnwire.FailInvalidBlinding{},
|
||||||
|
|
||||||
|
expectedResult: &interpretedResult{
|
||||||
|
pairResults: map[DirectedNodePair]pairResult{
|
||||||
|
getTestPair(0, 1): successPairResult(100),
|
||||||
|
getTestPair(2, 3): failPairResult(75),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Test a single-hop blinded route where the recipient is directly
|
||||||
|
// connected to the introduction node.
|
||||||
|
{
|
||||||
|
name: "blinded single hop introduction failure",
|
||||||
|
route: &blindedSingleHop,
|
||||||
|
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",
|
||||||
|
route: &blindedMultiHop,
|
||||||
|
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",
|
||||||
|
route: &routeThreeHop,
|
||||||
|
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",
|
||||||
|
route: &routeThreeHop,
|
||||||
|
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],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// A node in a non-blinded route returns a blinding related error.
|
||||||
|
{
|
||||||
|
name: "final node unexpected blinding",
|
||||||
|
route: &routeThreeHop,
|
||||||
|
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",
|
||||||
|
route: &blindedIntroReceiver,
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestResultInterpretation executes a list of test cases that test the result
|
// TestResultInterpretation executes a list of test cases that test the result
|
||||||
|
|||||||
Reference in new issue
Block a user