package lnwire import ( "bufio" "bytes" "encoding/binary" "io" "reflect" "testing" "github.com/davecgh/go-spew/spew" ) var ( testOnionHash = []byte{} testAmount = MilliSatoshi(1) testCtlvExpiry = uint32(2) testFlags = uint16(2) testType = uint64(3) testOffset = uint16(24) sig, _ = NewSigFromSignature(testSig) testChannelUpdate = ChannelUpdate{ Signature: sig, ShortChannelID: NewShortChanIDFromInt(1), Timestamp: 1, MessageFlags: 0, ChannelFlags: 1, } ) var onionFailures = []FailureMessage{ &FailInvalidRealm{}, &FailTemporaryNodeFailure{}, &FailPermanentNodeFailure{}, &FailRequiredNodeFeatureMissing{}, &FailPermanentChannelFailure{}, &FailRequiredChannelFeatureMissing{}, &FailUnknownNextPeer{}, &FailIncorrectPaymentAmount{}, &FailFinalExpiryTooSoon{}, 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), } // 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)) } } } // TestChannelUpdateCompatabilityParsing 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 TestChannelUpdateCompatabilityParsing(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 ChannelUpdate err := parseChannelUpdateCompatabilityMode( bufio.NewReader(&b), &newChanUpdate, 0, ) if err != nil { t.Fatalf("unable to parse channel update: %v", err) } // 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 ChannelUpdate err = parseChannelUpdateCompatabilityMode( bufio.NewReader(&b), &newChanUpdate2, 0, ) if err != nil { t.Fatalf("unable to parse channel update: %v", err) } 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 if err := update.Encode(&b, 0); err != nil { t.Fatalf("unable to write update: %v", err) } trueUpdateLength := b.Len() // 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) if err != nil { t.Fatalf("unable to encode onion error: %v", err) } // 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) 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 != 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) 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) }