routing: return full htlc attempt from shard handler

This commit is contained in:
Joost Jager 2020-05-06 15:44:36 +02:00
parent 8e7c0757ec
commit cc37485432
No known key found for this signature in database
GPG Key ID: A61B9D4C393C59C7
6 changed files with 76 additions and 41 deletions

@ -2,6 +2,7 @@ package channeldb
import ( import (
"bytes" "bytes"
"errors"
"io" "io"
"time" "time"
@ -177,6 +178,18 @@ func (m *MPPayment) InFlightHTLCs() []HTLCAttempt {
return inflights 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. // serializeHTLCSettleInfo serializes the details of a settled htlc.
func serializeHTLCSettleInfo(w io.Writer, s *HTLCSettleInfo) error { func serializeHTLCSettleInfo(w io.Writer, s *HTLCSettleInfo) error {
if _, err := w.Write(s.Preimage[:]); err != nil { if _, err := w.Write(s.Preimage[:]); err != nil {

@ -30,10 +30,12 @@ type ControlTower interface {
// error to prevent us from making duplicate payments to the same // error to prevent us from making duplicate payments to the same
// payment hash. The provided preimage is atomically saved to the DB // payment hash. The provided preimage is atomically saved to the DB
// for record keeping. // 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 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 // FetchPayment fetches the payment corresponding to the given payment
// hash. // hash.
@ -146,38 +148,40 @@ func (p *controlTower) RegisterAttempt(paymentHash lntypes.Hash,
// this is a multi shard payment, this might implicitly mean the the // this is a multi shard payment, this might implicitly mean the the
// full payment succeeded. // full payment succeeded.
func (p *controlTower) SettleAttempt(paymentHash lntypes.Hash, 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) p.paymentsMtx.Lock(paymentHash)
defer p.paymentsMtx.Unlock(paymentHash) defer p.paymentsMtx.Unlock(paymentHash)
payment, err := p.db.SettleAttempt(paymentHash, attemptID, settleInfo) payment, err := p.db.SettleAttempt(paymentHash, attemptID, settleInfo)
if err != nil { if err != nil {
return err return nil, err
} }
// Notify subscribers of success event. // Notify subscribers of success event.
p.notifySubscribers(paymentHash, payment) p.notifySubscribers(paymentHash, payment)
return nil return payment.GetAttempt(attemptID)
} }
// FailAttempt marks the given payment attempt failed. // FailAttempt marks the given payment attempt failed.
func (p *controlTower) FailAttempt(paymentHash lntypes.Hash, 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) p.paymentsMtx.Lock(paymentHash)
defer p.paymentsMtx.Unlock(paymentHash) defer p.paymentsMtx.Unlock(paymentHash)
payment, err := p.db.FailAttempt(paymentHash, attemptID, failInfo) payment, err := p.db.FailAttempt(paymentHash, attemptID, failInfo)
if err != nil { if err != nil {
return err return nil, err
} }
// Notify subscribers of failed attempt. // Notify subscribers of failed attempt.
p.notifySubscribers(paymentHash, payment) p.notifySubscribers(paymentHash, payment)
return nil return payment.GetAttempt(attemptID)
} }
// FetchPayment fetches the payment corresponding to the given payment hash. // FetchPayment fetches the payment corresponding to the given payment hash.

@ -104,15 +104,18 @@ func TestControlTowerSubscribeSuccess(t *testing.T) {
} }
// Mark the payment as successful. // Mark the payment as successful.
err = pControl.SettleAttempt( settleInfo := channeldb.HTLCSettleInfo{
info.PaymentHash, attempt.AttemptID, Preimage: preimg,
&channeldb.HTLCSettleInfo{ }
Preimage: preimg, htlcAttempt, err := pControl.SettleAttempt(
}, info.PaymentHash, attempt.AttemptID, &settleInfo,
) )
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if *htlcAttempt.Settle != settleInfo {
t.Fatalf("unexpected settle info returned")
}
// Register a third subscriber after the payment succeeded. // Register a third subscriber after the payment succeeded.
subscriber3, err := pControl.SubscribePayment(info.PaymentHash) subscriber3, err := pControl.SubscribePayment(info.PaymentHash)
@ -215,13 +218,18 @@ func testPaymentControlSubscribeFail(t *testing.T, registerAttempt bool) {
} }
// Fail the payment attempt. // Fail the payment attempt.
err := pControl.FailAttempt( failInfo := channeldb.HTLCFailInfo{
info.PaymentHash, attempt.AttemptID, Reason: channeldb.HTLCFailInternal,
&channeldb.HTLCFailInfo{}, }
htlcAttempt, err := pControl.FailAttempt(
info.PaymentHash, attempt.AttemptID, &failInfo,
) )
if err != nil { if err != nil {
t.Fatalf("unable to fail htlc: %v", err) t.Fatalf("unable to fail htlc: %v", err)
} }
if *htlcAttempt.Failure != failInfo {
t.Fatalf("unexpected fail info returned")
}
} }
// Mark the payment as failed. // Mark the payment as failed.

@ -301,7 +301,8 @@ func (m *mockControlTower) RegisterAttempt(phash lntypes.Hash,
} }
func (m *mockControlTower) SettleAttempt(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() m.Lock()
defer m.Unlock() defer m.Unlock()
@ -313,7 +314,7 @@ func (m *mockControlTower) SettleAttempt(phash lntypes.Hash,
// Only allow setting attempts if the payment is known. // Only allow setting attempts if the payment is known.
p, ok := m.payments[phash] p, ok := m.payments[phash]
if !ok { if !ok {
return channeldb.ErrPaymentNotInitiated return nil, channeldb.ErrPaymentNotInitiated
} }
// Find the attempt with this pid, and set the settle info. // 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 { if a.Settle != nil {
return channeldb.ErrAttemptAlreadySettled return nil, channeldb.ErrAttemptAlreadySettled
} }
if a.Failure != nil { if a.Failure != nil {
return channeldb.ErrAttemptAlreadyFailed return nil, channeldb.ErrAttemptAlreadyFailed
} }
p.attempts[i].Settle = settleInfo p.attempts[i].Settle = settleInfo
// Mark the payment successful on first settled attempt. // Mark the payment successful on first settled attempt.
m.successful[phash] = struct{}{} 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, func (m *mockControlTower) FailAttempt(phash lntypes.Hash, pid uint64,
failInfo *channeldb.HTLCFailInfo) error { failInfo *channeldb.HTLCFailInfo) (*channeldb.HTLCAttempt, error) {
m.Lock() m.Lock()
defer m.Unlock() 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. // Only allow failing attempts if the payment is known.
p, ok := m.payments[phash] p, ok := m.payments[phash]
if !ok { if !ok {
return channeldb.ErrPaymentNotInitiated return nil, channeldb.ErrPaymentNotInitiated
} }
// Find the attempt with this pid, and set the failure info. // 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 { if a.Settle != nil {
return channeldb.ErrAttemptAlreadySettled return nil, channeldb.ErrAttemptAlreadySettled
} }
if a.Failure != nil { if a.Failure != nil {
return channeldb.ErrAttemptAlreadyFailed return nil, channeldb.ErrAttemptAlreadyFailed
} }
p.attempts[i].Failure = failInfo 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, func (m *mockControlTower) Fail(phash lntypes.Hash,

@ -345,6 +345,9 @@ type launchOutcome struct {
// reflect this error. This can be errors like not enough local // reflect this error. This can be errors like not enough local
// balance for the given route etc. // balance for the given route etc.
err error 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, // 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 { if sendErr != nil {
// TODO(joostjager): Distinguish unexpected internal errors // TODO(joostjager): Distinguish unexpected internal errors
// from real send errors. // from real send errors.
err := p.failAttempt(attempt, sendErr) htlcAttempt, err := p.failAttempt(attempt, sendErr)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
// Return a launchOutcome indicating the shard failed. // Return a launchOutcome indicating the shard failed.
return attempt, &launchOutcome{ return attempt, &launchOutcome{
err: sendErr, attempt: htlcAttempt,
err: sendErr,
}, nil }, nil
} }
@ -398,9 +402,8 @@ func (p *shardHandler) launchShard(rt *route.Route) (*channeldb.HTLCAttemptInfo,
// shardResult holds the resulting outcome of a shard sent. // shardResult holds the resulting outcome of a shard sent.
type shardResult struct { type shardResult struct {
// preimage is the payment preimage in case of a settled HTLC. Only set // attempt is the attempt structure as recorded in the database.
// if err is non-nil. attempt *channeldb.HTLCAttempt
preimage lntypes.Preimage
// err indicates that the shard failed. // err indicates that the shard failed.
err error err error
@ -493,13 +496,14 @@ func (p *shardHandler) collectResult(attempt *channeldb.HTLCAttemptInfo) (
"the Switch, retrying.", attempt.AttemptID, "the Switch, retrying.", attempt.AttemptID,
p.paymentHash) p.paymentHash)
cErr := p.failAttempt(attempt, err) attempt, cErr := p.failAttempt(attempt, err)
if cErr != nil { if cErr != nil {
return nil, cErr return nil, cErr
} }
return &shardResult{ return &shardResult{
err: err, attempt: attempt,
err: err,
}, nil }, nil
// A critical, unexpected error was encountered. // 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 // In case of a payment failure, fail the attempt with the control
// tower and return. // tower and return.
if result.Error != nil { if result.Error != nil {
err := p.failAttempt(attempt, result.Error) attempt, err := p.failAttempt(attempt, result.Error)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &shardResult{ return &shardResult{
err: result.Error, attempt: attempt,
err: result.Error,
}, nil }, 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 // In case of success we atomically store settle result to the DB move
// the shard to the settled state. // the shard to the settled state.
err = p.router.cfg.Control.SettleAttempt( htlcAttempt, err := p.router.cfg.Control.SettleAttempt(
p.paymentHash, attempt.AttemptID, p.paymentHash, attempt.AttemptID,
&channeldb.HTLCSettleInfo{ &channeldb.HTLCSettleInfo{
Preimage: result.Preimage, Preimage: result.Preimage,
@ -571,7 +576,7 @@ func (p *shardHandler) collectResult(attempt *channeldb.HTLCAttemptInfo) (
} }
return &shardResult{ return &shardResult{
preimage: result.Preimage, attempt: htlcAttempt,
}, nil }, nil
} }
@ -691,7 +696,7 @@ func (p *shardHandler) handleSendError(attempt *channeldb.HTLCAttemptInfo,
// failAttempt calls control tower to fail the current payment attempt. // failAttempt calls control tower to fail the current payment attempt.
func (p *shardHandler) failAttempt(attempt *channeldb.HTLCAttemptInfo, 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, log.Warnf("Attempt %v for payment %v failed: %v", attempt.AttemptID,
p.paymentHash, sendError) p.paymentHash, sendError)

@ -1827,7 +1827,7 @@ func (r *ChannelRouter) SendToRoute(hash lntypes.Hash, rt *route.Route) (
// We got a successful result. // We got a successful result.
if result.err == nil { if result.err == nil {
return result.preimage, nil return result.attempt.Settle.Preimage, nil
} }
// The shard failed, break switch to handle it. // The shard failed, break switch to handle it.