channeldb+routing: store full htlc failure reason
This commit extends the htlc fail info with the full failure reason that was received over the wire. In a later commit, this info will also be exposed on the rpc interface. Furthermore it serves as a building block to make SendToRoute reliable across restarts.
This commit is contained in:
parent
48c0e42c26
commit
f86e68a1a2
@ -1,11 +1,14 @@
|
||||
package channeldb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/routing/route"
|
||||
)
|
||||
|
||||
@ -57,11 +60,45 @@ type HTLCSettleInfo struct {
|
||||
SettleTime time.Time
|
||||
}
|
||||
|
||||
// HTLCFailReason is the reason an htlc failed.
|
||||
type HTLCFailReason byte
|
||||
|
||||
const (
|
||||
// HTLCFailUnknown is recorded for htlcs that failed with an unknown
|
||||
// reason.
|
||||
HTLCFailUnknown HTLCFailReason = 0
|
||||
|
||||
// HTLCFailUnknown is recorded for htlcs that had a failure message that
|
||||
// couldn't be decrypted.
|
||||
HTLCFailUnreadable HTLCFailReason = 1
|
||||
|
||||
// HTLCFailInternal is recorded for htlcs that failed because of an
|
||||
// internal error.
|
||||
HTLCFailInternal HTLCFailReason = 2
|
||||
|
||||
// HTLCFailMessage is recorded for htlcs that failed with a network
|
||||
// failure message.
|
||||
HTLCFailMessage HTLCFailReason = 3
|
||||
)
|
||||
|
||||
// HTLCFailInfo encapsulates the information that augments an HTLCAttempt in the
|
||||
// event that the HTLC fails.
|
||||
type HTLCFailInfo struct {
|
||||
// FailTime is the time at which this HTLC was failed.
|
||||
FailTime time.Time
|
||||
|
||||
// Message is the wire message that failed this HTLC. This field will be
|
||||
// populated when the failure reason is HTLCFailMessage.
|
||||
Message lnwire.FailureMessage
|
||||
|
||||
// Reason is the failure reason for this HTLC.
|
||||
Reason HTLCFailReason
|
||||
|
||||
// The position in the path of the intermediate or final node that
|
||||
// generated the failure message. Position zero is the sender node. This
|
||||
// field will be populated when the failure reason is either
|
||||
// HTLCFailMessage or HTLCFailUnknown.
|
||||
FailureSourceIndex uint32
|
||||
}
|
||||
|
||||
// MPPayment is a wrapper around a payment's PaymentCreationInfo and
|
||||
@ -130,7 +167,20 @@ func serializeHTLCFailInfo(w io.Writer, f *HTLCFailInfo) error {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
// Write failure. If there is no failure message, write an empty
|
||||
// byte slice.
|
||||
var messageBytes bytes.Buffer
|
||||
if f.Message != nil {
|
||||
err := lnwire.EncodeFailureMessage(&messageBytes, f.Message, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := wire.WriteVarBytes(w, 0, messageBytes.Bytes()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return WriteElements(w, byte(f.Reason), f.FailureSourceIndex)
|
||||
}
|
||||
|
||||
// deserializeHTLCFailInfo deserializes the details of a failed htlc including
|
||||
@ -143,6 +193,29 @@ func deserializeHTLCFailInfo(r io.Reader) (*HTLCFailInfo, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Read failure.
|
||||
failureBytes, err := wire.ReadVarBytes(
|
||||
r, 0, lnwire.FailureMessageLength, "failure",
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(failureBytes) > 0 {
|
||||
f.Message, err = lnwire.DecodeFailureMessage(
|
||||
bytes.NewReader(failureBytes), 0,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var reason byte
|
||||
err = ReadElements(r, &reason, &f.FailureSourceIndex)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f.Reason = HTLCFailReason(reason)
|
||||
|
||||
return f, nil
|
||||
}
|
||||
|
||||
|
@ -126,7 +126,9 @@ func TestPaymentControlSwitchFail(t *testing.T) {
|
||||
}
|
||||
|
||||
err = pControl.FailAttempt(
|
||||
info.PaymentHash, 2, &HTLCFailInfo{},
|
||||
info.PaymentHash, 2, &HTLCFailInfo{
|
||||
Reason: HTLCFailUnreadable,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@ -362,7 +364,9 @@ func TestPaymentControlDeleteNonInFligt(t *testing.T) {
|
||||
// Fail the payment attempt.
|
||||
err := pControl.FailAttempt(
|
||||
info.PaymentHash, attempt.AttemptID,
|
||||
&HTLCFailInfo{},
|
||||
&HTLCFailInfo{
|
||||
Reason: HTLCFailUnreadable,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to fail htlc: %v", err)
|
||||
|
@ -60,7 +60,7 @@ func (p *paymentLifecycle) resumePayment() ([32]byte, *route.Route, error) {
|
||||
if sendErr != nil {
|
||||
// TODO(joostjager): Distinguish unexpected
|
||||
// internal errors from real send errors.
|
||||
err = p.failAttempt()
|
||||
err = p.failAttempt(sendErr)
|
||||
if err != nil {
|
||||
return [32]byte{}, nil, err
|
||||
}
|
||||
@ -117,7 +117,7 @@ func (p *paymentLifecycle) resumePayment() ([32]byte, *route.Route, error) {
|
||||
"the Switch, retrying.", p.attempt.AttemptID,
|
||||
p.payment.PaymentHash)
|
||||
|
||||
err = p.failAttempt()
|
||||
err = p.failAttempt(err)
|
||||
if err != nil {
|
||||
return [32]byte{}, nil, err
|
||||
}
|
||||
@ -158,7 +158,7 @@ func (p *paymentLifecycle) resumePayment() ([32]byte, *route.Route, error) {
|
||||
log.Errorf("Attempt to send payment %x failed: %v",
|
||||
p.payment.PaymentHash, result.Error)
|
||||
|
||||
err = p.failAttempt()
|
||||
err = p.failAttempt(result.Error)
|
||||
if err != nil {
|
||||
return [32]byte{}, nil, err
|
||||
}
|
||||
@ -447,13 +447,60 @@ func (p *paymentLifecycle) handleSendError(sendErr error) error {
|
||||
}
|
||||
|
||||
// failAttempt calls control tower to fail the current payment attempt.
|
||||
func (p *paymentLifecycle) failAttempt() error {
|
||||
failInfo := &channeldb.HTLCFailInfo{
|
||||
FailTime: p.router.cfg.Clock.Now(),
|
||||
}
|
||||
func (p *paymentLifecycle) failAttempt(sendError error) error {
|
||||
failInfo := marshallError(
|
||||
sendError,
|
||||
p.router.cfg.Clock.Now(),
|
||||
)
|
||||
|
||||
return p.router.cfg.Control.FailAttempt(
|
||||
p.payment.PaymentHash, p.attempt.AttemptID,
|
||||
failInfo,
|
||||
)
|
||||
}
|
||||
|
||||
// marshallError marshall an error as received from the switch to a structure
|
||||
// that is suitable for database storage.
|
||||
func marshallError(sendError error, time time.Time) *channeldb.HTLCFailInfo {
|
||||
response := &channeldb.HTLCFailInfo{
|
||||
FailTime: time,
|
||||
}
|
||||
|
||||
switch sendError {
|
||||
|
||||
case htlcswitch.ErrPaymentIDNotFound:
|
||||
response.Reason = channeldb.HTLCFailInternal
|
||||
return response
|
||||
|
||||
case htlcswitch.ErrUnreadableFailureMessage:
|
||||
response.Reason = channeldb.HTLCFailUnreadable
|
||||
return response
|
||||
}
|
||||
|
||||
rtErr, ok := sendError.(htlcswitch.ClearTextError)
|
||||
if !ok {
|
||||
response.Reason = channeldb.HTLCFailInternal
|
||||
return response
|
||||
}
|
||||
|
||||
message := rtErr.WireMessage()
|
||||
if message != nil {
|
||||
response.Reason = channeldb.HTLCFailMessage
|
||||
response.Message = message
|
||||
} else {
|
||||
response.Reason = channeldb.HTLCFailUnknown
|
||||
}
|
||||
|
||||
// If the ClearTextError received is a ForwardingError, the error
|
||||
// originated from a node along the route, not locally on our outgoing
|
||||
// link. We set failureSourceIdx to the index of the node where the
|
||||
// failure occurred. If the error is not a ForwardingError, the failure
|
||||
// occurred at our node, so we leave the index as 0 to indicate that
|
||||
// we failed locally.
|
||||
fErr, ok := rtErr.(*htlcswitch.ForwardingError)
|
||||
if ok {
|
||||
response.FailureSourceIndex = uint32(fErr.FailureSourceIdx)
|
||||
}
|
||||
|
||||
return response
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user