lnwire+htlcswitch: report height for invalid payment details failure
Extends the invalid payment details failure with the new accept height field. This allows sender to distinguish between a genuine invalid details situation and a delay caused by intermediate nodes.
This commit is contained in:
9 files changed
+212
-118
No files matched your search
+27
-4
@@ -333,13 +333,19 @@ func (f *FailIncorrectPaymentAmount) Error() string {
|
||||
type FailIncorrectDetails struct {
|
||||
// amount is the value of the extended HTLC.
|
||||
amount MilliSatoshi
|
||||
|
||||
// height is the block height when the htlc was received.
|
||||
height uint32
|
||||
}
|
||||
|
||||
// NewFailIncorrectDetails makes a new instance of the FailIncorrectDetails
|
||||
// error bound to the specified HTLC amount.
|
||||
func NewFailIncorrectDetails(amt MilliSatoshi) *FailIncorrectDetails {
|
||||
// error bound to the specified HTLC amount and acceptance height.
|
||||
func NewFailIncorrectDetails(amt MilliSatoshi,
|
||||
height uint32) *FailIncorrectDetails {
|
||||
|
||||
return &FailIncorrectDetails{
|
||||
amount: amt,
|
||||
height: height,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -348,6 +354,11 @@ func (f *FailIncorrectDetails) Amount() MilliSatoshi {
|
||||
return f.amount
|
||||
}
|
||||
|
||||
// Height is the block height when the htlc was received.
|
||||
func (f *FailIncorrectDetails) Height() uint32 {
|
||||
return f.height
|
||||
}
|
||||
|
||||
// Code returns the failure unique code.
|
||||
//
|
||||
// NOTE: Part of the FailureMessage interface.
|
||||
@@ -360,7 +371,8 @@ func (f *FailIncorrectDetails) Code() FailCode {
|
||||
// NOTE: Implements the error interface.
|
||||
func (f *FailIncorrectDetails) Error() string {
|
||||
return fmt.Sprintf(
|
||||
"%v(amt=%v)", CodeIncorrectOrUnknownPaymentDetails, f.amount,
|
||||
"%v(amt=%v, height=%v)", CodeIncorrectOrUnknownPaymentDetails,
|
||||
f.amount, f.height,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -381,6 +393,17 @@ func (f *FailIncorrectDetails) Decode(r io.Reader, pver uint32) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// At a later stage, the height field was also tacked on. We need to
|
||||
// check for io.EOF here as well.
|
||||
err = ReadElement(r, &f.height)
|
||||
switch {
|
||||
case err == io.EOF:
|
||||
return nil
|
||||
|
||||
case err != nil:
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -388,7 +411,7 @@ func (f *FailIncorrectDetails) Decode(r io.Reader, pver uint32) error {
|
||||
//
|
||||
// NOTE: Part of the Serializable interface.
|
||||
func (f *FailIncorrectDetails) Encode(w io.Writer, pver uint32) error {
|
||||
return WriteElement(w, f.amount)
|
||||
return WriteElements(w, f.amount, f.height)
|
||||
}
|
||||
|
||||
// FailFinalExpiryTooSoon is returned if the cltv_expiry is too low, the final
|
||||
|
||||
@@ -37,7 +37,7 @@ var onionFailures = []FailureMessage{
|
||||
&FailIncorrectPaymentAmount{},
|
||||
&FailFinalExpiryTooSoon{},
|
||||
|
||||
NewFailIncorrectDetails(99),
|
||||
NewFailIncorrectDetails(99, 100),
|
||||
NewInvalidOnionVersion(testOnionHash),
|
||||
NewInvalidOnionHmac(testOnionHash),
|
||||
NewInvalidOnionKey(testOnionHash),
|
||||
@@ -196,6 +196,9 @@ func TestFailIncorrectDetailsOptionalAmount(t *testing.T) {
|
||||
if invalidDetailsErr.amount != 0 {
|
||||
t.Fatalf("expected amount to be zero")
|
||||
}
|
||||
if invalidDetailsErr.height != 0 {
|
||||
t.Fatalf("height incorrect")
|
||||
}
|
||||
}
|
||||
|
||||
type mockFailIncorrectDetailsNoAmt struct {
|
||||
@@ -216,3 +219,57 @@ func (f *mockFailIncorrectDetailsNoAmt) Decode(r io.Reader, pver uint32) error {
|
||||
func (f *mockFailIncorrectDetailsNoAmt) Encode(w io.Writer, pver uint32) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// TestFailIncorrectDetailsOptionalHeight tests that we're able to decode an
|
||||
// FailIncorrectDetails error that doesn't have the optional height. This
|
||||
// ensures we're able to decode FailIncorrectDetails messages from older nodes.
|
||||
func TestFailIncorrectDetailsOptionalHeight(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
onionError := &mockFailIncorrectDetailsNoHeight{
|
||||
amount: uint64(123),
|
||||
}
|
||||
|
||||
var b bytes.Buffer
|
||||
if err := EncodeFailure(&b, onionError, 0); err != nil {
|
||||
t.Fatalf("unable to encode failure: %v", err)
|
||||
}
|
||||
|
||||
onionError2, err := DecodeFailure(bytes.NewReader(b.Bytes()), 0)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to decode error: %v", err)
|
||||
}
|
||||
|
||||
invalidDetailsErr, ok := onionError2.(*FailIncorrectDetails)
|
||||
if !ok {
|
||||
t.Fatalf("expected FailIncorrectDetails, but got %T",
|
||||
onionError2)
|
||||
}
|
||||
|
||||
if invalidDetailsErr.amount != 123 {
|
||||
t.Fatalf("amount incorrect")
|
||||
}
|
||||
if invalidDetailsErr.height != 0 {
|
||||
t.Fatalf("height incorrect")
|
||||
}
|
||||
}
|
||||
|
||||
type mockFailIncorrectDetailsNoHeight struct {
|
||||
amount uint64
|
||||
}
|
||||
|
||||
func (f *mockFailIncorrectDetailsNoHeight) Code() FailCode {
|
||||
return CodeIncorrectOrUnknownPaymentDetails
|
||||
}
|
||||
|
||||
func (f *mockFailIncorrectDetailsNoHeight) Error() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (f *mockFailIncorrectDetailsNoHeight) Decode(r io.Reader, pver uint32) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *mockFailIncorrectDetailsNoHeight) Encode(w io.Writer, pver uint32) error {
|
||||
return WriteElement(w, f.amount)
|
||||
}
|
||||
Reference in new issue
Block a user