diff --git a/channeldb/mp_payment.go b/channeldb/mp_payment.go index fd89f3ab..74a0ece8 100644 --- a/channeldb/mp_payment.go +++ b/channeldb/mp_payment.go @@ -2,6 +2,7 @@ package channeldb import ( "bytes" + "errors" "io" "time" @@ -177,6 +178,18 @@ func (m *MPPayment) InFlightHTLCs() []HTLCAttempt { return inflights } +// GetAttempt returns the specified htlc attempt on the payment. +func (m *MPPayment) GetAttempt(id uint64) (*HTLCAttempt, error) { + for _, htlc := range m.HTLCs { + htlc := htlc + if htlc.AttemptID == id { + return &htlc, nil + } + } + + return nil, errors.New("htlc attempt not found on payment") +} + // serializeHTLCSettleInfo serializes the details of a settled htlc. func serializeHTLCSettleInfo(w io.Writer, s *HTLCSettleInfo) error { if _, err := w.Write(s.Preimage[:]); err != nil { diff --git a/routing/control_tower.go b/routing/control_tower.go index be23c4a9..3e028c18 100644 --- a/routing/control_tower.go +++ b/routing/control_tower.go @@ -30,10 +30,12 @@ type ControlTower interface { // error to prevent us from making duplicate payments to the same // payment hash. The provided preimage is atomically saved to the DB // for record keeping. - SettleAttempt(lntypes.Hash, uint64, *channeldb.HTLCSettleInfo) error + SettleAttempt(lntypes.Hash, uint64, *channeldb.HTLCSettleInfo) ( + *channeldb.HTLCAttempt, error) // FailAttempt marks the given payment attempt failed. - FailAttempt(lntypes.Hash, uint64, *channeldb.HTLCFailInfo) error + FailAttempt(lntypes.Hash, uint64, *channeldb.HTLCFailInfo) ( + *channeldb.HTLCAttempt, error) // FetchPayment fetches the payment corresponding to the given payment // hash. @@ -146,38 +148,40 @@ func (p *controlTower) RegisterAttempt(paymentHash lntypes.Hash, // this is a multi shard payment, this might implicitly mean the the // full payment succeeded. func (p *controlTower) SettleAttempt(paymentHash lntypes.Hash, - attemptID uint64, settleInfo *channeldb.HTLCSettleInfo) error { + attemptID uint64, settleInfo *channeldb.HTLCSettleInfo) ( + *channeldb.HTLCAttempt, error) { p.paymentsMtx.Lock(paymentHash) defer p.paymentsMtx.Unlock(paymentHash) payment, err := p.db.SettleAttempt(paymentHash, attemptID, settleInfo) if err != nil { - return err + return nil, err } // Notify subscribers of success event. p.notifySubscribers(paymentHash, payment) - return nil + return payment.GetAttempt(attemptID) } // FailAttempt marks the given payment attempt failed. func (p *controlTower) FailAttempt(paymentHash lntypes.Hash, - attemptID uint64, failInfo *channeldb.HTLCFailInfo) error { + attemptID uint64, failInfo *channeldb.HTLCFailInfo) ( + *channeldb.HTLCAttempt, error) { p.paymentsMtx.Lock(paymentHash) defer p.paymentsMtx.Unlock(paymentHash) payment, err := p.db.FailAttempt(paymentHash, attemptID, failInfo) if err != nil { - return err + return nil, err } // Notify subscribers of failed attempt. p.notifySubscribers(paymentHash, payment) - return nil + return payment.GetAttempt(attemptID) } // FetchPayment fetches the payment corresponding to the given payment hash. diff --git a/routing/control_tower_test.go b/routing/control_tower_test.go index 7907e37e..ebd64f65 100644 --- a/routing/control_tower_test.go +++ b/routing/control_tower_test.go @@ -104,15 +104,18 @@ func TestControlTowerSubscribeSuccess(t *testing.T) { } // Mark the payment as successful. - err = pControl.SettleAttempt( - info.PaymentHash, attempt.AttemptID, - &channeldb.HTLCSettleInfo{ - Preimage: preimg, - }, + settleInfo := channeldb.HTLCSettleInfo{ + Preimage: preimg, + } + htlcAttempt, err := pControl.SettleAttempt( + info.PaymentHash, attempt.AttemptID, &settleInfo, ) if err != nil { t.Fatal(err) } + if *htlcAttempt.Settle != settleInfo { + t.Fatalf("unexpected settle info returned") + } // Register a third subscriber after the payment succeeded. subscriber3, err := pControl.SubscribePayment(info.PaymentHash) @@ -215,13 +218,18 @@ func testPaymentControlSubscribeFail(t *testing.T, registerAttempt bool) { } // Fail the payment attempt. - err := pControl.FailAttempt( - info.PaymentHash, attempt.AttemptID, - &channeldb.HTLCFailInfo{}, + failInfo := channeldb.HTLCFailInfo{ + Reason: channeldb.HTLCFailInternal, + } + htlcAttempt, err := pControl.FailAttempt( + info.PaymentHash, attempt.AttemptID, &failInfo, ) if err != nil { t.Fatalf("unable to fail htlc: %v", err) } + if *htlcAttempt.Failure != failInfo { + t.Fatalf("unexpected fail info returned") + } } // Mark the payment as failed. diff --git a/routing/mock_test.go b/routing/mock_test.go index ae27bc7e..e2f46687 100644 --- a/routing/mock_test.go +++ b/routing/mock_test.go @@ -301,7 +301,8 @@ func (m *mockControlTower) RegisterAttempt(phash lntypes.Hash, } func (m *mockControlTower) SettleAttempt(phash lntypes.Hash, - pid uint64, settleInfo *channeldb.HTLCSettleInfo) error { + pid uint64, settleInfo *channeldb.HTLCSettleInfo) ( + *channeldb.HTLCAttempt, error) { m.Lock() defer m.Unlock() @@ -313,7 +314,7 @@ func (m *mockControlTower) SettleAttempt(phash lntypes.Hash, // Only allow setting attempts if the payment is known. p, ok := m.payments[phash] if !ok { - return channeldb.ErrPaymentNotInitiated + return nil, channeldb.ErrPaymentNotInitiated } // Find the attempt with this pid, and set the settle info. @@ -323,24 +324,26 @@ func (m *mockControlTower) SettleAttempt(phash lntypes.Hash, } if a.Settle != nil { - return channeldb.ErrAttemptAlreadySettled + return nil, channeldb.ErrAttemptAlreadySettled } if a.Failure != nil { - return channeldb.ErrAttemptAlreadyFailed + return nil, channeldb.ErrAttemptAlreadyFailed } p.attempts[i].Settle = settleInfo // Mark the payment successful on first settled attempt. m.successful[phash] = struct{}{} - return nil + return &channeldb.HTLCAttempt{ + Settle: settleInfo, + }, nil } - return fmt.Errorf("pid not found") + return nil, fmt.Errorf("pid not found") } func (m *mockControlTower) FailAttempt(phash lntypes.Hash, pid uint64, - failInfo *channeldb.HTLCFailInfo) error { + failInfo *channeldb.HTLCFailInfo) (*channeldb.HTLCAttempt, error) { m.Lock() defer m.Unlock() @@ -352,7 +355,7 @@ func (m *mockControlTower) FailAttempt(phash lntypes.Hash, pid uint64, // Only allow failing attempts if the payment is known. p, ok := m.payments[phash] if !ok { - return channeldb.ErrPaymentNotInitiated + return nil, channeldb.ErrPaymentNotInitiated } // Find the attempt with this pid, and set the failure info. @@ -362,17 +365,19 @@ func (m *mockControlTower) FailAttempt(phash lntypes.Hash, pid uint64, } if a.Settle != nil { - return channeldb.ErrAttemptAlreadySettled + return nil, channeldb.ErrAttemptAlreadySettled } if a.Failure != nil { - return channeldb.ErrAttemptAlreadyFailed + return nil, channeldb.ErrAttemptAlreadyFailed } p.attempts[i].Failure = failInfo - return nil + return &channeldb.HTLCAttempt{ + Failure: failInfo, + }, nil } - return fmt.Errorf("pid not found") + return nil, fmt.Errorf("pid not found") } func (m *mockControlTower) Fail(phash lntypes.Hash, diff --git a/routing/payment_lifecycle.go b/routing/payment_lifecycle.go index dfda814a..30a07668 100644 --- a/routing/payment_lifecycle.go +++ b/routing/payment_lifecycle.go @@ -345,6 +345,9 @@ type launchOutcome struct { // reflect this error. This can be errors like not enough local // balance for the given route etc. err error + + // attempt is the attempt structure as recorded in the database. + attempt *channeldb.HTLCAttempt } // launchShard creates and sends an HTLC attempt along the given route, @@ -382,14 +385,15 @@ func (p *shardHandler) launchShard(rt *route.Route) (*channeldb.HTLCAttemptInfo, if sendErr != nil { // TODO(joostjager): Distinguish unexpected internal errors // from real send errors. - err := p.failAttempt(attempt, sendErr) + htlcAttempt, err := p.failAttempt(attempt, sendErr) if err != nil { return nil, nil, err } // Return a launchOutcome indicating the shard failed. return attempt, &launchOutcome{ - err: sendErr, + attempt: htlcAttempt, + err: sendErr, }, nil } @@ -398,9 +402,8 @@ func (p *shardHandler) launchShard(rt *route.Route) (*channeldb.HTLCAttemptInfo, // shardResult holds the resulting outcome of a shard sent. type shardResult struct { - // preimage is the payment preimage in case of a settled HTLC. Only set - // if err is non-nil. - preimage lntypes.Preimage + // attempt is the attempt structure as recorded in the database. + attempt *channeldb.HTLCAttempt // err indicates that the shard failed. err error @@ -493,13 +496,14 @@ func (p *shardHandler) collectResult(attempt *channeldb.HTLCAttemptInfo) ( "the Switch, retrying.", attempt.AttemptID, p.paymentHash) - cErr := p.failAttempt(attempt, err) + attempt, cErr := p.failAttempt(attempt, err) if cErr != nil { return nil, cErr } return &shardResult{ - err: err, + attempt: attempt, + err: err, }, nil // A critical, unexpected error was encountered. @@ -533,13 +537,14 @@ func (p *shardHandler) collectResult(attempt *channeldb.HTLCAttemptInfo) ( // In case of a payment failure, fail the attempt with the control // tower and return. if result.Error != nil { - err := p.failAttempt(attempt, result.Error) + attempt, err := p.failAttempt(attempt, result.Error) if err != nil { return nil, err } return &shardResult{ - err: result.Error, + attempt: attempt, + err: result.Error, }, nil } @@ -558,7 +563,7 @@ func (p *shardHandler) collectResult(attempt *channeldb.HTLCAttemptInfo) ( // In case of success we atomically store settle result to the DB move // the shard to the settled state. - err = p.router.cfg.Control.SettleAttempt( + htlcAttempt, err := p.router.cfg.Control.SettleAttempt( p.paymentHash, attempt.AttemptID, &channeldb.HTLCSettleInfo{ Preimage: result.Preimage, @@ -571,7 +576,7 @@ func (p *shardHandler) collectResult(attempt *channeldb.HTLCAttemptInfo) ( } return &shardResult{ - preimage: result.Preimage, + attempt: htlcAttempt, }, nil } @@ -691,7 +696,7 @@ func (p *shardHandler) handleSendError(attempt *channeldb.HTLCAttemptInfo, // failAttempt calls control tower to fail the current payment attempt. func (p *shardHandler) failAttempt(attempt *channeldb.HTLCAttemptInfo, - sendError error) error { + sendError error) (*channeldb.HTLCAttempt, error) { log.Warnf("Attempt %v for payment %v failed: %v", attempt.AttemptID, p.paymentHash, sendError) diff --git a/routing/router.go b/routing/router.go index d0ac67fd..1777a23c 100644 --- a/routing/router.go +++ b/routing/router.go @@ -1827,7 +1827,7 @@ func (r *ChannelRouter) SendToRoute(hash lntypes.Hash, rt *route.Route) ( // We got a successful result. if result.err == nil { - return result.preimage, nil + return result.attempt.Settle.Preimage, nil } // The shard failed, break switch to handle it.