Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

lnwire+htlcswitch+routing: report and interpret incorrect_or_unknown_payment_details height #3186

Closed
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions channeldb/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@ var (
number: 11,
migration: migrateInvoices,
},
{
// The DB version where final cltv deltas are stored in
// mission control.
number: 12,
migration: migrateMcFinalCltv,
},
}

// Big endian is the preferred byte order, due to cursor scans over
Expand Down
20 changes: 20 additions & 0 deletions channeldb/migration_12_mc_final_cltv.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package channeldb

import "github.com/coreos/bbolt"

// migrateMcFinalCltv clears mission control to start with a clean slate on
// which final cltv delta values are stored too. Because mission control data is
// not critical we opt for simply clearing the existing data rather than a
// read/update/write migration.
func migrateMcFinalCltv(tx *bbolt.Tx) error {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shall we commence the new migration package proposal with this PR? As it's the first target migration for 0.9, and the migration itself is pretty light.

More on topic for this PR: perhaps we should just start migrating this data? It's not critical, but certain services could see degradation in service quality after updating beyond this PR. We currently don't have a way of loading external data, so there's no way for them to preserve the mission control data if they consider it precious enough to do so.

log.Infof("Migrating to new mission control store by clearing " +
"existing data")

resultsKey := []byte("missioncontrol-results")
if err := tx.DeleteBucket(resultsKey); err != nil {
return err
}

log.Infof("Migration to new mission control completed!")
return nil
}
249 changes: 133 additions & 116 deletions lnrpc/routerrpc/router.pb.go

Large diffs are not rendered by default.

11 changes: 11 additions & 0 deletions lnrpc/routerrpc/router.proto
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,17 @@ message SendToRouteRequest {

/// Route that should be used to attempt to complete the payment.
lnrpc.Route route = 2;

/**
The final cltv delta requirement for the payment request. This field is only
required to properly update mission control in case the payment fails with
incorrect_or_unknown_payment_details. Matching the final cltv delta with
the expiry height of the final htlc and the returned acceptance height in
the failure message, allows discerning an incorrect final expiry delta from
an unexpected delay along the route. Only in the latter case, mission
control will apply a penalty to the intermediary nodes.
**/
int32 final_cltv_delta = 3;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't this be computed ourselves (internally) at the time of route dispatch? So take the height at payment dispatch time, and subtract that from the CLTV expiry value of the final hop.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could do that, but the route may already have been padded by the caller of SendToRoute. In that case we assume a final_cltv_delta that is greater than it actually is on the receiver side and incorrectly update mission to penalize nodes along the path for delaying.

}

message SendToRouteResponse {
Expand Down
4 changes: 3 additions & 1 deletion lnrpc/routerrpc/router_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,9 @@ func (s *Server) SendToRoute(ctx context.Context,
return nil, err
}

preimage, err := s.cfg.Router.SendToRoute(hash, route)
preimage, err := s.cfg.Router.SendToRoute(
hash, route, req.FinalCltvDelta,
)

// In the success case, return the preimage.
if err == nil {
Expand Down
1,054 changes: 535 additions & 519 deletions lnrpc/rpc.pb.go

Large diffs are not rendered by default.

11 changes: 11 additions & 0 deletions lnrpc/rpc.proto
Original file line number Diff line number Diff line change
Expand Up @@ -910,6 +910,17 @@ message SendToRouteRequest {

/// Route that should be used to attempt to complete the payment.
Route route = 4;

/**
The final cltv delta requirement for the payment request. This field is only
required to properly update mission control in case the payment fails with
incorrect_or_unknown_payment_details. Matching the final cltv delta with
the expiry height of the final htlc and the returned acceptance height in
the failure message, allows discerning an incorrect final expiry delta from
an unexpected delay along the route. Only in the latter case, mission
control will apply a penalty to the intermediary nodes.
**/
int32 final_cltv_delta = 5;
}

message ChannelPoint {
Expand Down
5 changes: 5 additions & 0 deletions lnrpc/rpc.swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -3411,6 +3411,11 @@
"route": {
"$ref": "#/definitions/lnrpcRoute",
"description": "/ Route that should be used to attempt to complete the payment."
},
"final_cltv_delta": {
"type": "integer",
"format": "int32",
"description": "*\nThe final cltv delta requirement for the payment request. This field is only\nrequired to properly update mission control in case the payment fails with\nincorrect_or_unknown_payment_details. Matching the final cltv delta with\nthe expiry height of the final htlc and the returned acceptance height in\nthe failure message, allows discerning an incorrect final expiry delta from\nan unexpected delay along the route. Only in the latter case, mission\ncontrol will apply a penalty to the intermediary nodes."
}
}
},
Expand Down
10 changes: 6 additions & 4 deletions routing/missioncontrol.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ type paymentResult struct {
success bool
failureSourceIdx *int
failure lnwire.FailureMessage
finalCltvDelta uint32
}

// NewMissionControl returns a new instance of missionControl.
Expand Down Expand Up @@ -385,8 +386,8 @@ func (m *MissionControl) GetHistorySnapshot() *MissionControlSnapshot {
// returns a reason if this failure is a final failure. In that case no further
// payment attempts need to be made.
func (m *MissionControl) ReportPaymentFail(paymentID uint64, rt *route.Route,
failureSourceIdx *int, failure lnwire.FailureMessage) (
*channeldb.FailureReason, error) {
finalCltvDelta uint32, failureSourceIdx *int,
failure lnwire.FailureMessage) (*channeldb.FailureReason, error) {

timestamp := m.now()

Expand All @@ -398,6 +399,7 @@ func (m *MissionControl) ReportPaymentFail(paymentID uint64, rt *route.Route,
failureSourceIdx: failureSourceIdx,
failure: failure,
route: rt,
finalCltvDelta: finalCltvDelta,
}

return m.processPaymentResult(result)
Expand Down Expand Up @@ -446,8 +448,8 @@ func (m *MissionControl) applyPaymentResult(

// Interpret result.
i := interpretResult(
result.route, result.success, result.failureSourceIdx,
result.failure,
result.route, result.finalCltvDelta, result.success,
result.failureSourceIdx, result.failure,
)

// Update mission control state using the interpretation.
Expand Down
5 changes: 3 additions & 2 deletions routing/missioncontrol_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ func serializeResult(rp *paymentResult) ([]byte, []byte, error) {
&b,
uint64(rp.timeFwd.UnixNano()),
uint64(rp.timeReply.UnixNano()),
rp.success, dbFailureSourceIdx,
rp.success, rp.finalCltvDelta, dbFailureSourceIdx,
)
if err != nil {
return nil, nil, err
Expand Down Expand Up @@ -174,7 +174,8 @@ func deserializeResult(k, v []byte) (*paymentResult, error) {
)

err := channeldb.ReadElements(
r, &timeFwd, &timeReply, &result.success, &dbFailureSourceIdx,
r, &timeFwd, &timeReply, &result.success, &result.finalCltvDelta,
&dbFailureSourceIdx,
)
if err != nil {
return nil, err
Expand Down
1 change: 1 addition & 0 deletions routing/missioncontrol_store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ func TestMissionControlStore(t *testing.T) {
id: 99,
timeReply: testTime,
timeFwd: testTime.Add(-time.Minute),
finalCltvDelta: 40,
}

result2 := result1
Expand Down
10 changes: 6 additions & 4 deletions routing/missioncontrol_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,10 @@ var (
},
}

mcTestTime = time.Date(2018, time.January, 9, 14, 00, 00, 0, time.UTC)
mcTestNode1 = mcTestRoute.Hops[0].PubKeyBytes
mcTestNode2 = mcTestRoute.Hops[1].PubKeyBytes
mcTestTime = time.Date(2018, time.January, 9, 14, 00, 00, 0, time.UTC)
mcTestNode1 = mcTestRoute.Hops[0].PubKeyBytes
mcTestNode2 = mcTestRoute.Hops[1].PubKeyBytes
mcFinalCltvDelta = uint32(40)
)

type mcTestContext struct {
Expand Down Expand Up @@ -110,7 +111,8 @@ func (ctx *mcTestContext) reportFailure(amt lnwire.MilliSatoshi,

errorSourceIdx := 1
ctx.mc.ReportPaymentFail(
ctx.pid, mcTestRoute, &errorSourceIdx, failure,
ctx.pid, mcTestRoute, mcFinalCltvDelta,
&errorSourceIdx, failure,
)
}

Expand Down
4 changes: 2 additions & 2 deletions routing/mock_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,8 @@ type mockMissionControl struct {
var _ MissionController = (*mockMissionControl)(nil)

func (m *mockMissionControl) ReportPaymentFail(paymentID uint64, rt *route.Route,
failureSourceIdx *int, failure lnwire.FailureMessage) (
*channeldb.FailureReason, error) {
finalCltvDelta uint32, failureSourceIdx *int,
failure lnwire.FailureMessage) (*channeldb.FailureReason, error) {

return nil, nil
}
Expand Down
3 changes: 2 additions & 1 deletion routing/payment_lifecycle.go
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,8 @@ func (p *paymentLifecycle) sendPaymentAttempt(firstHop lnwire.ShortChannelID,
func (p *paymentLifecycle) handleSendError(sendErr error) error {

reason := p.router.processSendError(
p.attempt.PaymentID, &p.attempt.Route, sendErr,
p.attempt.PaymentID, &p.attempt.Route, uint32(p.finalCLTVDelta),
sendErr,
)
if reason == nil {
// Save the forwarding error so it can be returned if
Expand Down
36 changes: 26 additions & 10 deletions routing/result_interpretation.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ type interpretedResult struct {

// interpretResult interprets a payment outcome and returns an object that
// contains information required to update mission control.
func interpretResult(rt *route.Route, success bool, failureSrcIdx *int,
failure lnwire.FailureMessage) *interpretedResult {
func interpretResult(rt *route.Route, finalCltvDelta uint32, success bool,
failureSrcIdx *int, failure lnwire.FailureMessage) *interpretedResult {

i := &interpretedResult{
pairResults: make(map[DirectedNodePair]pairResult),
Expand All @@ -68,7 +68,7 @@ func interpretResult(rt *route.Route, success bool, failureSrcIdx *int,
if success {
i.processSuccess(rt)
} else {
i.processFail(rt, failureSrcIdx, failure)
i.processFail(rt, finalCltvDelta, failureSrcIdx, failure)
}
return i
}
Expand All @@ -82,7 +82,7 @@ func (i *interpretedResult) processSuccess(route *route.Route) {

// processFail processes a failed payment attempt.
func (i *interpretedResult) processFail(
rt *route.Route, errSourceIdx *int,
rt *route.Route, finalCltvDelta uint32, errSourceIdx *int,
failure lnwire.FailureMessage) {

if errSourceIdx == nil {
Expand All @@ -99,7 +99,7 @@ func (i *interpretedResult) processFail(
// A failure from the final hop was received.
case len(rt.Hops):
i.processPaymentOutcomeFinal(
rt, failure,
rt, finalCltvDelta, failure,
)

// An intermediate hop failed. Interpret the outcome, update reputation
Expand Down Expand Up @@ -142,7 +142,8 @@ func (i *interpretedResult) processPaymentOutcomeSelf(

// processPaymentOutcomeFinal handles failures sent by the final hop.
func (i *interpretedResult) processPaymentOutcomeFinal(
route *route.Route, failure lnwire.FailureMessage) {
route *route.Route, finalCltvDelta uint32,
failure lnwire.FailureMessage) {

n := len(route.Hops)

Expand All @@ -151,7 +152,7 @@ func (i *interpretedResult) processPaymentOutcomeFinal(
// incorrect htlc, we want to retry via another route. Invalid onion
// failures are not expected, because the final node wouldn't be able to
// encrypt that failure.
switch failure.(type) {
switch msg := failure.(type) {

// Expiry or amount of the HTLC doesn't match the onion, try another
// route.
Expand All @@ -178,14 +179,29 @@ func (i *interpretedResult) processPaymentOutcomeFinal(
i.successPairRange(route, 0, n-2)
}

// We used the wrong amount, fail the payment. This is a legacy failure
// that we still need to handle.
case *lnwire.FailIncorrectPaymentAmount:
// Assign all pairs a success result, as the payment reached the
// destination correctly.
i.successPairRange(route, 0, n-1)
i.finalFailureReason = &reasonIncorrectDetails

// We are using wrong payment hash or amount, fail the payment.
case *lnwire.FailIncorrectPaymentAmount,
*lnwire.FailIncorrectDetails:
case *lnwire.FailIncorrectDetails:
// If the htlc reached the receiver with not enough blocks left,
// there must have been a delay on the path.
finalExpiry := route.Hops[len(route.Hops)-1].OutgoingTimeLock
if msg.Height()+finalCltvDelta > finalExpiry {
// As we don't know which node is to blame, we penalize
// all pairs along the route and retry.
i.failPairRange(route, 0, n-1)
return
}

// Assign all pairs a success result, as the payment reached the
// destination correctly.
i.successPairRange(route, 0, n-1)

i.finalFailureReason = &reasonIncorrectDetails

// The HTLC that was extended to the final hop expires too soon. Fail
Expand Down
Loading