Skip to content

Commit decfdb6

Browse files
committed
routing: bugfix for mc reporting of blinded paths
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.
1 parent 9327940 commit decfdb6

File tree

2 files changed

+123
-19
lines changed

2 files changed

+123
-19
lines changed

routing/result_interpretation.go

Lines changed: 72 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,27 @@ func interpretResult(rt *mcRoute,
100100

101101
// processSuccess processes a successful payment attempt.
102102
func (i *interpretedResult) processSuccess(route *mcRoute) {
103-
// For successes, all nodes must have acted in the right way. Therefore
104-
// we mark all of them with a success result.
103+
// For successes, all nodes must have acted in the right way.
104+
// Therefore we mark all of them with a success result. However we need
105+
// to handle the blinded route part separately because for intermediate
106+
// blinded nodes the amount field is set to zero so we use the receiver
107+
// amount.
108+
introIdx, isBlinded := introductionPointIndex(route)
109+
if isBlinded {
110+
// Report success for all the pairs until the introduction
111+
// point.
112+
i.successPairRange(route, 0, introIdx-1)
113+
114+
// Handle the blinded route part.
115+
//
116+
// NOTE: The introIdx index here does describe the node after
117+
// the introduction point.
118+
i.markBlindedRouteSuccess(route, introIdx)
119+
120+
return
121+
}
122+
123+
// Mark nodes as successful in the non-blinded case of the payment.
105124
i.successPairRange(route, 0, len(route.hops.Val)-1)
106125
}
107126

@@ -517,11 +536,22 @@ func (i *interpretedResult) processPaymentOutcomeIntermediate(route *mcRoute,
517536
if introIdx == len(route.hops.Val)-1 {
518537
i.finalFailureReason = &reasonError
519538
} else {
520-
// If there are other hops between the recipient and
521-
// introduction node, then we just penalize the last
522-
// hop in the blinded route to minimize the storage of
523-
// results for ephemeral keys.
524-
i.failPairBalance(route, len(route.hops.Val)-1)
539+
// We penalize the final hop of the blinded route which
540+
// is sufficient to not reuse this route again and is
541+
// also more memory efficient because the other hops
542+
// of the blinded path are ephemeral and will only be
543+
// used in conjunction with the final hop. Moreover we
544+
// don't want to punish the introduction node because
545+
// the blinded failure does not necessarily mean that
546+
// the introduction node was at fault.
547+
//
548+
// TODO(ziggie): Make sure we only keep mc data for
549+
// blinded paths, in both the success and failure case,
550+
// in memory during the time of the payment and remove
551+
// it afterwards. Blinded paths and their blinded hop
552+
// keys are always changing per blinded route so there
553+
// is no point in persisting this data.
554+
i.failBlindedRoute(route)
525555
}
526556

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

861+
// failBlindedRoute marks a blinded route as failed for the specific amount to
862+
// send by only punishing the last pair.
863+
func (i *interpretedResult) failBlindedRoute(rt *mcRoute) {
864+
// We fail the last pair of the route, in order to fail the complete
865+
// blinded route. This is because the combination of ephemeral pubkeys
866+
// is unique to the route. We fail the last pair in order to not punish
867+
// the introduction node, since we don't want to disincentivize them
868+
// from providing that service.
869+
pair, _ := getPair(rt, len(rt.hops.Val)-1)
870+
871+
// Since all the hops along a blinded path don't have any amount set, we
872+
// extract the minimal amount to punish from the value that is tried to
873+
// be sent to the receiver.
874+
amt := rt.hops.Val[len(rt.hops.Val)-1].amtToFwd.Val
875+
876+
i.pairResults[pair] = failPairResult(amt)
877+
}
878+
879+
// markBlindedRouteSuccess marks the hops of the blinded route AFTER the
880+
// introduction node as successful.
881+
//
882+
// NOTE: The introIdx must be the index of the first hop of the blinded route
883+
// AFTER the introduction node.
884+
func (i *interpretedResult) markBlindedRouteSuccess(rt *mcRoute, introIdx int) {
885+
// For blinded hops we do not have the forwarding amount so we take the
886+
// minimal amount which went through the route by looking at the last
887+
// hop.
888+
successAmt := rt.hops.Val[len(rt.hops.Val)-1].amtToFwd.Val
889+
for idx := introIdx; idx < len(rt.hops.Val); idx++ {
890+
pair, _ := getPair(rt, idx)
891+
892+
i.pairResults[pair] = successPairResult(successAmt)
893+
}
894+
}
895+
831896
// getPair returns a node pair from the route and the amount passed between that
832897
// pair.
833898
func getPair(rt *mcRoute, channelIdx int) (DirectedNodePair,

routing/result_interpretation_test.go

Lines changed: 51 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -96,13 +96,17 @@ var (
9696
AmtToForward: 99,
9797
},
9898
{
99-
PubKeyBytes: hops[2],
100-
AmtToForward: 95,
99+
PubKeyBytes: hops[2],
100+
// Intermediate blinded hops don't have an
101+
// amount set.
102+
AmtToForward: 0,
101103
BlindingPoint: genTestPubKey(),
102104
},
103105
{
104-
PubKeyBytes: hops[3],
105-
AmtToForward: 88,
106+
PubKeyBytes: hops[3],
107+
// Intermediate blinded hops don't have an
108+
// amount set.
109+
AmtToForward: 0,
106110
},
107111
{
108112
PubKeyBytes: hops[4],
@@ -122,8 +126,10 @@ var (
122126
AmtToForward: 99,
123127
},
124128
{
125-
PubKeyBytes: hops[2],
126-
AmtToForward: 95,
129+
PubKeyBytes: hops[2],
130+
// Intermediate blinded hops don't have an
131+
// amount set.
132+
AmtToForward: 0,
127133
BlindingPoint: genTestPubKey(),
128134
},
129135
{
@@ -140,13 +146,17 @@ var (
140146
TotalAmount: 100,
141147
Hops: []*route.Hop{
142148
{
143-
PubKeyBytes: hops[1],
144-
AmtToForward: 90,
149+
PubKeyBytes: hops[1],
150+
// Intermediate blinded hops don't have an
151+
// amount set.
152+
AmtToForward: 0,
145153
BlindingPoint: genTestPubKey(),
146154
},
147155
{
148-
PubKeyBytes: hops[2],
149-
AmtToForward: 75,
156+
PubKeyBytes: hops[2],
157+
// Intermediate blinded hops don't have an
158+
// amount set.
159+
AmtToForward: 0,
150160
},
151161
{
152162
PubKeyBytes: hops[3],
@@ -552,7 +562,12 @@ var resultTestCases = []resultTestCase{
552562
pairResults: map[DirectedNodePair]pairResult{
553563
getTestPair(0, 1): successPairResult(100),
554564
getTestPair(1, 2): successPairResult(99),
555-
getTestPair(3, 4): failPairResult(88),
565+
566+
// The amount for the last hop is always the
567+
// receiver amount because the amount to forward
568+
// is always set to 0 for intermediate blinded
569+
// hops.
570+
getTestPair(3, 4): failPairResult(77),
556571
},
557572
},
558573
},
@@ -567,7 +582,12 @@ var resultTestCases = []resultTestCase{
567582
expectedResult: &interpretedResult{
568583
pairResults: map[DirectedNodePair]pairResult{
569584
getTestPair(0, 1): successPairResult(100),
570-
getTestPair(2, 3): failPairResult(75),
585+
586+
// The amount for the last hop is always the
587+
// receiver amount because the amount to forward
588+
// is always set to 0 for intermediate blinded
589+
// hops.
590+
getTestPair(2, 3): failPairResult(58),
571591
},
572592
},
573593
},
@@ -682,6 +702,25 @@ var resultTestCases = []resultTestCase{
682702
finalFailureReason: &reasonError,
683703
},
684704
},
705+
// Test a multi-hop blinded route and that in a success case the amounts
706+
// for the blinded route part are correctly set to the receiver amount.
707+
{
708+
name: "blinded multi-hop success",
709+
route: blindedMultiToIntroduction,
710+
success: true,
711+
expectedResult: &interpretedResult{
712+
pairResults: map[DirectedNodePair]pairResult{
713+
getTestPair(0, 1): successPairResult(100),
714+
715+
// For the route blinded part of the route the
716+
// success amount is determined by the receiver
717+
// amount because the intermediate blinded hops
718+
// set the forwarded amount to 0.
719+
getTestPair(1, 2): successPairResult(58),
720+
getTestPair(2, 3): successPairResult(58),
721+
},
722+
},
723+
},
685724
}
686725

687726
// TestResultInterpretation executes a list of test cases that test the result

0 commit comments

Comments
 (0)