344 lines
8.6 KiB
Go
344 lines
8.6 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"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
const testUnknownRequiredType = 0x10
|
|
|
|
type decodePayloadTest struct {
|
|
name string
|
|
payload []byte
|
|
expErr error
|
|
expCustomRecords map[uint64][]byte
|
|
shouldHaveMPP bool
|
|
shouldHaveAMP 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: "intermediate hop with amp",
|
|
payload: []byte{
|
|
// amount
|
|
0x02, 0x00,
|
|
// cltv
|
|
0x04, 0x00,
|
|
// next hop id
|
|
0x06, 0x08,
|
|
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
// amp
|
|
0x0e, 0x41,
|
|
// amp.root_share
|
|
0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12,
|
|
0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12,
|
|
0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12,
|
|
0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12,
|
|
// amp.set_id
|
|
0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13,
|
|
0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13,
|
|
0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13,
|
|
0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13,
|
|
// amp.child_index
|
|
0x09,
|
|
},
|
|
expErr: hop.ErrInvalidPayload{
|
|
Type: record.AMPOnionType,
|
|
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,
|
|
},
|
|
{
|
|
name: "final hop with amp",
|
|
payload: []byte{
|
|
// amount
|
|
0x02, 0x00,
|
|
// cltv
|
|
0x04, 0x00,
|
|
// amp
|
|
0x0e, 0x41,
|
|
// amp.root_share
|
|
0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12,
|
|
0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12,
|
|
0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12,
|
|
0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12,
|
|
// amp.set_id
|
|
0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13,
|
|
0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13,
|
|
0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13,
|
|
0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13,
|
|
// amp.child_index
|
|
0x09,
|
|
},
|
|
shouldHaveAMP: 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,
|
|
}
|
|
|
|
testRootShare = [32]byte{
|
|
0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12,
|
|
0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12,
|
|
0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12,
|
|
0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12,
|
|
}
|
|
testSetID = [32]byte{
|
|
0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13,
|
|
0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13,
|
|
0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13,
|
|
0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13,
|
|
}
|
|
testChildIndex = uint32(9)
|
|
)
|
|
|
|
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")
|
|
}
|
|
|
|
if test.shouldHaveAMP {
|
|
if p.AMP == nil {
|
|
t.Fatalf("payload should have AMP record")
|
|
}
|
|
require.Equal(t, testRootShare, p.AMP.RootShare())
|
|
require.Equal(t, testSetID, p.AMP.SetID())
|
|
require.Equal(t, testChildIndex, p.AMP.ChildIndex())
|
|
} else if p.AMP != nil {
|
|
t.Fatalf("unexpected AMP 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")
|
|
}
|
|
}
|