lnd/lnwire/onion_error_test.go

314 lines
8.9 KiB
Go
Raw Normal View History

package lnwire
import (
"bytes"
"encoding/binary"
"encoding/hex"
"io"
"reflect"
"testing"
"github.com/davecgh/go-spew/spew"
"github.com/lightningnetwork/lnd/fn"
"github.com/stretchr/testify/require"
)
var (
testOnionHash = [OnionPacketSize]byte{}
testAmount = MilliSatoshi(1)
testCtlvExpiry = uint32(2)
testFlags = uint16(2)
testType = uint64(3)
testOffset = uint16(24)
sig, _ = NewSigFromSignature(testSig)
testChannelUpdate = ChannelUpdate1{
Signature: sig,
ShortChannelID: NewShortChanIDFromInt(1),
Timestamp: 1,
MessageFlags: 0,
ChannelFlags: 1,
ExtraOpaqueData: make([]byte, 0),
}
)
var onionFailures = []FailureMessage{
&FailInvalidRealm{},
&FailTemporaryNodeFailure{},
&FailPermanentNodeFailure{},
&FailRequiredNodeFeatureMissing{},
&FailPermanentChannelFailure{},
&FailRequiredChannelFeatureMissing{},
&FailUnknownNextPeer{},
&FailIncorrectPaymentAmount{},
&FailFinalExpiryTooSoon{},
2019-12-20 11:25:08 +01:00
&FailMPPTimeout{},
NewFailIncorrectDetails(99, 100),
NewInvalidOnionVersion(testOnionHash[:]),
NewInvalidOnionHmac(testOnionHash[:]),
NewInvalidOnionKey(testOnionHash[:]),
NewTemporaryChannelFailure(&testChannelUpdate),
NewTemporaryChannelFailure(nil),
NewAmountBelowMinimum(testAmount, testChannelUpdate),
NewFeeInsufficient(testAmount, testChannelUpdate),
NewIncorrectCltvExpiry(testCtlvExpiry, testChannelUpdate),
NewExpiryTooSoon(testChannelUpdate),
NewChannelDisabled(testFlags, testChannelUpdate),
NewFinalIncorrectCltvExpiry(testCtlvExpiry),
NewFinalIncorrectHtlcAmount(testAmount),
NewInvalidOnionPayload(testType, testOffset),
NewInvalidBlinding(fn.Some(testOnionHash)),
}
// TestEncodeDecodeCode tests the ability of onion errors to be properly encoded
// and decoded.
func TestEncodeDecodeCode(t *testing.T) {
for _, failure1 := range onionFailures {
var b bytes.Buffer
if err := EncodeFailure(&b, failure1, 0); err != nil {
t.Fatalf("unable to encode failure code(%v): %v",
failure1.Code(), err)
}
failure2, err := DecodeFailure(&b, 0)
if err != nil {
t.Fatalf("unable to decode failure code(%v): %v",
failure1.Code(), err)
}
if !reflect.DeepEqual(failure1, failure2) {
t.Fatalf("expected %v, got %v", spew.Sdump(failure1),
spew.Sdump(failure2))
}
}
}
// TestEncodeDecodeTlv tests the ability of onion errors to be properly encoded
// and decoded with tlv data present.
func TestEncodeDecodeTlv(t *testing.T) {
t.Parallel()
for _, testFailure := range onionFailures {
testFailure := testFailure
code := testFailure.Code().String()
t.Run(code, func(t *testing.T) {
t.Parallel()
testEncodeDecodeTlv(t, testFailure)
})
}
}
var testTlv, _ = hex.DecodeString("fd023104deadbeef")
2023-01-05 08:49:31 +01:00
func testEncodeDecodeTlv(t *testing.T, testFailure FailureMessage) {
var failureMessageBuffer bytes.Buffer
err := EncodeFailureMessage(&failureMessageBuffer, testFailure, 0)
require.NoError(t, err)
failureMessageBuffer.Write(testTlv)
failure, err := DecodeFailureMessage(&failureMessageBuffer, 0)
require.NoError(t, err)
// FailIncorrectDetails already reads tlv data. Adapt the expected data.
if incorrectDetails, ok := testFailure.(*FailIncorrectDetails); ok {
incorrectDetails.extraOpaqueData = testTlv
}
require.Equal(t, testFailure, failure)
}
// TestChannelUpdateCompatibilityParsing tests that we're able to properly read
// out channel update messages encoded in an onion error payload that was
// written in the legacy (type prefixed) format.
func TestChannelUpdateCompatibilityParsing(t *testing.T) {
t.Parallel()
// We'll start by taking out test channel update, and encoding it into
// a set of raw bytes.
var b bytes.Buffer
if err := testChannelUpdate.Encode(&b, 0); err != nil {
t.Fatalf("unable to encode chan update: %v", err)
}
// Now that we have the set of bytes encoded, we'll ensure that we're
// able to decode it using our compatibility method, as it's a regular
// encoded channel update message.
var newChanUpdate ChannelUpdate1
err := parseChannelUpdateCompatibilityMode(
&b, uint16(b.Len()), &newChanUpdate, 0,
)
require.NoError(t, err, "unable to parse channel update")
// At this point, we'll ensure that we get the exact same failure out
// on the other side.
if !reflect.DeepEqual(testChannelUpdate, newChanUpdate) {
t.Fatalf("mismatched channel updates: %v", err)
}
// We'll now reset then re-encoded the same channel update to try it in
// the proper compatible mode.
b.Reset()
// Before we encode the update itself, we'll also write out the 2-byte
// type in order to simulate the compat mode.
var tByte [2]byte
binary.BigEndian.PutUint16(tByte[:], MsgChannelUpdate)
b.Write(tByte[:])
if err := testChannelUpdate.Encode(&b, 0); err != nil {
t.Fatalf("unable to encode chan update: %v", err)
}
// We should be able to properly parse the encoded channel update
// message even with the extra two bytes.
var newChanUpdate2 ChannelUpdate1
err = parseChannelUpdateCompatibilityMode(
&b, uint16(b.Len()), &newChanUpdate2, 0,
)
require.NoError(t, err, "unable to parse channel update")
if !reflect.DeepEqual(newChanUpdate2, newChanUpdate) {
t.Fatalf("mismatched channel updates: %v", err)
}
}
// TestWriteOnionErrorChanUpdate tests that we write an exact size for the
// channel update in order to be more compliant with the parsers of other
// implementations.
func TestWriteOnionErrorChanUpdate(t *testing.T) {
t.Parallel()
// First, we'll write out the raw channel update so we can obtain the
// raw serialized length.
var b bytes.Buffer
update := testChannelUpdate
trueUpdateLength, err := WriteMessage(&b, &update, 0)
if err != nil {
t.Fatalf("unable to write update: %v", err)
}
// Next, we'll use the function to encode the update as we would in a
// onion error message.
var errorBuf bytes.Buffer
err = writeOnionErrorChanUpdate(&errorBuf, &update, 0)
require.NoError(t, err, "unable to encode onion error")
// Finally, read the length encoded and ensure that it matches the raw
// length.
var encodedLen uint16
if err := ReadElement(&errorBuf, &encodedLen); err != nil {
t.Fatalf("unable to read len: %v", err)
}
if uint16(trueUpdateLength) != encodedLen {
t.Fatalf("wrong length written: expected %v, got %v",
trueUpdateLength, encodedLen)
}
}
// TestFailIncorrectDetailsOptionalAmount tests that we're able to decode an
// FailIncorrectDetails error that doesn't have the optional amount. This
// ensures we're able to decode FailIncorrectDetails messages from older nodes.
func TestFailIncorrectDetailsOptionalAmount(t *testing.T) {
t.Parallel()
onionError := &mockFailIncorrectDetailsNoAmt{}
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)
require.NoError(t, err, "unable to decode error")
invalidDetailsErr, ok := onionError2.(*FailIncorrectDetails)
if !ok {
t.Fatalf("expected FailIncorrectDetails, but got %T",
onionError2)
}
if invalidDetailsErr.amount != 0 {
t.Fatalf("expected amount to be zero")
}
if invalidDetailsErr.height != 0 {
t.Fatalf("height incorrect")
}
}
type mockFailIncorrectDetailsNoAmt struct {
}
func (f *mockFailIncorrectDetailsNoAmt) Code() FailCode {
return CodeIncorrectOrUnknownPaymentDetails
}
func (f *mockFailIncorrectDetailsNoAmt) Error() string {
return ""
}
func (f *mockFailIncorrectDetailsNoAmt) Decode(r io.Reader, pver uint32) error {
return nil
}
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)
require.NoError(t, err, "unable to decode error")
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 *bytes.Buffer,
pver uint32) error {
return WriteUint64(w, f.amount)
}