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 (
"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 {

@ -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.

@ -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.

@ -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,

@ -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)

@ -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.