135a0a9f7f
Currently we use the AMP record type, this allows us to change it easily as we add new known records. We also bump this to 0x0c instead of 0x0a.
262 lines
6.3 KiB
Go
262 lines
6.3 KiB
Go
package hop_test
|
|
|
|
import (
|
|
"bytes"
|
|
"reflect"
|
|
"testing"
|
|
|
|
"github.com/lightningnetwork/lnd/htlcswitch/hop"
|
|
"github.com/lightningnetwork/lnd/lnwire"
|
|
"github.com/lightningnetwork/lnd/record"
|
|
)
|
|
|
|
const testUnknownRequiredType = 0x10
|
|
|
|
type decodePayloadTest struct {
|
|
name string
|
|
payload []byte
|
|
expErr error
|
|
expCustomRecords map[uint64][]byte
|
|
shouldHaveMPP bool
|
|
}
|
|
|
|
var decodePayloadTests = []decodePayloadTest{
|
|
{
|
|
name: "final hop valid",
|
|
payload: []byte{0x02, 0x00, 0x04, 0x00},
|
|
},
|
|
{
|
|
name: "intermediate hop valid",
|
|
payload: []byte{0x02, 0x00, 0x04, 0x00, 0x06, 0x08, 0x01, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
},
|
|
},
|
|
{
|
|
name: "final hop no amount",
|
|
payload: []byte{0x04, 0x00},
|
|
expErr: hop.ErrInvalidPayload{
|
|
Type: record.AmtOnionType,
|
|
Violation: hop.OmittedViolation,
|
|
FinalHop: true,
|
|
},
|
|
},
|
|
{
|
|
name: "intermediate hop no amount",
|
|
payload: []byte{0x04, 0x00, 0x06, 0x08, 0x01, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00,
|
|
},
|
|
expErr: hop.ErrInvalidPayload{
|
|
Type: record.AmtOnionType,
|
|
Violation: hop.OmittedViolation,
|
|
FinalHop: false,
|
|
},
|
|
},
|
|
{
|
|
name: "final hop no expiry",
|
|
payload: []byte{0x02, 0x00},
|
|
expErr: hop.ErrInvalidPayload{
|
|
Type: record.LockTimeOnionType,
|
|
Violation: hop.OmittedViolation,
|
|
FinalHop: true,
|
|
},
|
|
},
|
|
{
|
|
name: "intermediate hop no expiry",
|
|
payload: []byte{0x02, 0x00, 0x06, 0x08, 0x01, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00,
|
|
},
|
|
expErr: hop.ErrInvalidPayload{
|
|
Type: record.LockTimeOnionType,
|
|
Violation: hop.OmittedViolation,
|
|
FinalHop: false,
|
|
},
|
|
},
|
|
{
|
|
name: "final hop next sid present",
|
|
payload: []byte{0x02, 0x00, 0x04, 0x00, 0x06, 0x08, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
},
|
|
expErr: hop.ErrInvalidPayload{
|
|
Type: record.NextHopOnionType,
|
|
Violation: hop.IncludedViolation,
|
|
FinalHop: true,
|
|
},
|
|
},
|
|
{
|
|
name: "required type after omitted hop id",
|
|
payload: []byte{
|
|
0x02, 0x00, 0x04, 0x00,
|
|
testUnknownRequiredType, 0x00,
|
|
},
|
|
expErr: hop.ErrInvalidPayload{
|
|
Type: testUnknownRequiredType,
|
|
Violation: hop.RequiredViolation,
|
|
FinalHop: true,
|
|
},
|
|
},
|
|
{
|
|
name: "required type after included hop id",
|
|
payload: []byte{
|
|
0x02, 0x00, 0x04, 0x00, 0x06, 0x08, 0x01, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00,
|
|
testUnknownRequiredType, 0x00,
|
|
},
|
|
expErr: hop.ErrInvalidPayload{
|
|
Type: testUnknownRequiredType,
|
|
Violation: hop.RequiredViolation,
|
|
FinalHop: false,
|
|
},
|
|
},
|
|
{
|
|
name: "required type zero final hop",
|
|
payload: []byte{0x00, 0x00, 0x02, 0x00, 0x04, 0x00},
|
|
expErr: hop.ErrInvalidPayload{
|
|
Type: 0,
|
|
Violation: hop.RequiredViolation,
|
|
FinalHop: true,
|
|
},
|
|
},
|
|
{
|
|
name: "required type zero final hop zero sid",
|
|
payload: []byte{0x00, 0x00, 0x02, 0x00, 0x04, 0x00, 0x06, 0x08,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
},
|
|
expErr: hop.ErrInvalidPayload{
|
|
Type: record.NextHopOnionType,
|
|
Violation: hop.IncludedViolation,
|
|
FinalHop: true,
|
|
},
|
|
},
|
|
{
|
|
name: "required type zero intermediate hop",
|
|
payload: []byte{0x00, 0x00, 0x02, 0x00, 0x04, 0x00, 0x06, 0x08,
|
|
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
},
|
|
expErr: hop.ErrInvalidPayload{
|
|
Type: 0,
|
|
Violation: hop.RequiredViolation,
|
|
FinalHop: false,
|
|
},
|
|
},
|
|
{
|
|
name: "required type in custom range",
|
|
payload: []byte{0x02, 0x00, 0x04, 0x00,
|
|
0xfe, 0x00, 0x01, 0x00, 0x00, 0x02, 0x10, 0x11,
|
|
},
|
|
expCustomRecords: map[uint64][]byte{
|
|
65536: {0x10, 0x11},
|
|
},
|
|
},
|
|
{
|
|
name: "valid intermediate hop",
|
|
payload: []byte{0x02, 0x00, 0x04, 0x00, 0x06, 0x08, 0x01, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
},
|
|
expErr: nil,
|
|
},
|
|
{
|
|
name: "valid final hop",
|
|
payload: []byte{0x02, 0x00, 0x04, 0x00},
|
|
expErr: nil,
|
|
},
|
|
{
|
|
name: "intermediate hop with mpp",
|
|
payload: []byte{
|
|
// amount
|
|
0x02, 0x00,
|
|
// cltv
|
|
0x04, 0x00,
|
|
// next hop id
|
|
0x06, 0x08,
|
|
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
// mpp
|
|
0x08, 0x21,
|
|
0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
|
|
0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
|
|
0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
|
|
0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
|
|
0x08,
|
|
},
|
|
expErr: hop.ErrInvalidPayload{
|
|
Type: record.MPPOnionType,
|
|
Violation: hop.IncludedViolation,
|
|
FinalHop: false,
|
|
},
|
|
},
|
|
{
|
|
name: "final hop with mpp",
|
|
payload: []byte{
|
|
// amount
|
|
0x02, 0x00,
|
|
// cltv
|
|
0x04, 0x00,
|
|
// mpp
|
|
0x08, 0x21,
|
|
0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
|
|
0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
|
|
0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
|
|
0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
|
|
0x08,
|
|
},
|
|
expErr: nil,
|
|
shouldHaveMPP: true,
|
|
},
|
|
}
|
|
|
|
// TestDecodeHopPayloadRecordValidation asserts that parsing the payloads in the
|
|
// tests yields the expected errors depending on whether the proper fields were
|
|
// included or omitted.
|
|
func TestDecodeHopPayloadRecordValidation(t *testing.T) {
|
|
for _, test := range decodePayloadTests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
testDecodeHopPayloadValidation(t, test)
|
|
})
|
|
}
|
|
}
|
|
|
|
func testDecodeHopPayloadValidation(t *testing.T, test decodePayloadTest) {
|
|
var (
|
|
testTotalMsat = lnwire.MilliSatoshi(8)
|
|
testAddr = [32]byte{
|
|
0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
|
|
0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
|
|
0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
|
|
0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
|
|
}
|
|
)
|
|
|
|
p, err := hop.NewPayloadFromReader(bytes.NewReader(test.payload))
|
|
if !reflect.DeepEqual(test.expErr, err) {
|
|
t.Fatalf("expected error mismatch, want: %v, got: %v",
|
|
test.expErr, err)
|
|
}
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
// Assert MPP fields if we expect them.
|
|
if test.shouldHaveMPP {
|
|
if p.MPP == nil {
|
|
t.Fatalf("payload should have MPP record")
|
|
}
|
|
if p.MPP.TotalMsat() != testTotalMsat {
|
|
t.Fatalf("invalid total msat")
|
|
}
|
|
if p.MPP.PaymentAddr() != testAddr {
|
|
t.Fatalf("invalid payment addr")
|
|
}
|
|
} else if p.MPP != nil {
|
|
t.Fatalf("unexpected MPP payload")
|
|
}
|
|
|
|
// Convert expected nil map to empty map, because we always expect an
|
|
// initiated map from the payload.
|
|
expCustomRecords := make(record.CustomSet)
|
|
if test.expCustomRecords != nil {
|
|
expCustomRecords = test.expCustomRecords
|
|
}
|
|
if !reflect.DeepEqual(expCustomRecords, p.CustomRecords()) {
|
|
t.Fatalf("invalid custom records")
|
|
}
|
|
}
|