Skip to content

Commit

Permalink
routing: bugfix for mc reporting of blinded paths
Browse files Browse the repository at this point in the history
When reporting an error  or a success case of a payment to a
blinded paths, the amounts to forward for intermediate hops
are set to 0 so we need to use the receiver amount instead.
  • Loading branch information
ziggie1984 committed Dec 9, 2024
1 parent ddd4a8c commit 84dc991
Show file tree
Hide file tree
Showing 2 changed files with 118 additions and 15 deletions.
79 changes: 72 additions & 7 deletions routing/result_interpretation.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,27 @@ func interpretResult(rt *mcRoute,

// processSuccess processes a successful payment attempt.
func (i *interpretedResult) processSuccess(route *mcRoute) {
// For successes, all nodes must have acted in the right way. Therefore
// we mark all of them with a success result.
// For successes, all nodes must have acted in the right way.
// Therefore we mark all of them with a success result. However we need
// to handle the blinded route part separately because for intermediate
// blinded nodes the amount field is set to zero so we use the receiver
// amount.
introIdx, isBlinded := introductionPointIndex(route)
if isBlinded {
// Report success for all the pairs until the introduction
// point.
i.successPairRange(route, 0, introIdx-1)

// Handle the blinded route part.
//
// NOTE: The introIdx index here does describe the node after
// the introduction point.
i.markBlindedRouteSuccess(route, introIdx)

return
}

// Mark nodes as successful in the non-blinded case of the payment.
i.successPairRange(route, 0, len(route.hops.Val)-1)
}

Expand Down Expand Up @@ -517,11 +536,22 @@ func (i *interpretedResult) processPaymentOutcomeIntermediate(route *mcRoute,
if introIdx == len(route.hops.Val)-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.Val)-1)
// We penalize the final hop of the blinded route which
// is sufficient to not reuse this route again and is
// also more memory efficient because the other hops
// of the blinded path are ephemeral and will only be
// used in conjunction with the final hop. Moreover we
// don't want to punish the introduction node because
// the blinded failure does not necessarily mean that
// the introduction node was at fault.
//
// TODO(ziggie): Make sure we only keep mc data for
// blinded paths, in both the success and failure case,
// in memory during the time of the payment and remove
// it afterwards. Blinded paths and their blinded hop
// keys are always changing per blinded route so there
// is no point in persisting this data.
i.failBlindedRoute(route)
}

// In all other cases, we penalize the reporting node. These are all
Expand Down Expand Up @@ -828,6 +858,41 @@ func (i *interpretedResult) successPairRange(rt *mcRoute, fromIdx, toIdx int) {
}
}

// failBlindedRoute marks a blinded route as failed for the specific amount to
// send by only punishing the last pair.
func (i *interpretedResult) failBlindedRoute(rt *mcRoute) {
// We fail the last pair of the route, in order to fail the complete
// blinded route. This is because the combination of ephemeral pubkeys
// is unique to the route. We fail the last pair in order to not punish
// the introduction node, since we don't want to disincentivize them
// from providing that service.
pair, _ := getPair(rt, len(rt.hops.Val)-1)

// Since all the hops along a blinded path don't have any amount set, we
// extract the minimal amount to punish from the value that is tried to
// be sent to the receiver.
amt := rt.hops.Val[len(rt.hops.Val)-1].amtToFwd.Val

i.pairResults[pair] = failPairResult(amt)
}

// markBlindedRouteSuccess marks the hops of the blinded route AFTER the
// introduction node as successful.
//
// NOTE: The introIdx must be the index of the first hop of the blinded route
// AFTER the introduction node.
func (i *interpretedResult) markBlindedRouteSuccess(rt *mcRoute, introIdx int) {
// For blinded hops we do not have the forwarding amount so we take the
// minimal amount which went through the route by looking at the last
// hop.
successAmt := rt.hops.Val[len(rt.hops.Val)-1].amtToFwd.Val
for idx := introIdx; idx < len(rt.hops.Val); idx++ {
pair, _ := getPair(rt, idx)

i.pairResults[pair] = successPairResult(successAmt)
}
}

// getPair returns a node pair from the route and the amount passed between that
// pair.
func getPair(rt *mcRoute, channelIdx int) (DirectedNodePair,
Expand Down
54 changes: 46 additions & 8 deletions routing/result_interpretation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,11 @@ var (
BlindingPoint: genTestPubKey(),
},
{
PubKeyBytes: hops[3],
AmtToForward: 88,
PubKeyBytes: hops[3],
// The amounts for the blinded hops are set ß
// and the forwarding info is part of the
// encrypted data.
AmtToForward: 0,
},
},
})
Expand All @@ -145,12 +148,18 @@ var (
BlindingPoint: genTestPubKey(),
},
{
PubKeyBytes: hops[2],
AmtToForward: 75,
PubKeyBytes: hops[2],
// The amounts for the blinded hops are set ß
// and the forwarding info is part of the
// encrypted data.
AmtToForward: 0,
},
{
PubKeyBytes: hops[3],
AmtToForward: 58,
PubKeyBytes: hops[3],
// The amounts for the blinded hops are set ß
// and the forwarding info is part of the
// encrypted data.
AmtToForward: 0,
},
},
})
Expand Down Expand Up @@ -552,7 +561,12 @@ var resultTestCases = []resultTestCase{
pairResults: map[DirectedNodePair]pairResult{
getTestPair(0, 1): successPairResult(100),
getTestPair(1, 2): successPairResult(99),
getTestPair(3, 4): failPairResult(88),

// 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),
},
},
},
Expand All @@ -567,7 +581,12 @@ var resultTestCases = []resultTestCase{
expectedResult: &interpretedResult{
pairResults: map[DirectedNodePair]pairResult{
getTestPair(0, 1): successPairResult(100),
getTestPair(2, 3): failPairResult(75),

// 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),
},
},
},
Expand Down Expand Up @@ -682,6 +701,25 @@ var resultTestCases = []resultTestCase{
finalFailureReason: &reasonError,
},
},
// 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),
},
},
},
}

// TestResultInterpretation executes a list of test cases that test the result
Expand Down

0 comments on commit 84dc991

Please sign in to comment.