github.com/decred/dcrlnd@v0.7.6/htlcswitch/hop/payload_test.go (about)

     1  package hop_test
     2  
     3  import (
     4  	"bytes"
     5  	"reflect"
     6  	"testing"
     7  
     8  	"github.com/decred/dcrlnd/htlcswitch/hop"
     9  	"github.com/decred/dcrlnd/lnwire"
    10  	"github.com/decred/dcrlnd/record"
    11  	"github.com/stretchr/testify/require"
    12  )
    13  
    14  const testUnknownRequiredType = 0x10
    15  
    16  type decodePayloadTest struct {
    17  	name             string
    18  	payload          []byte
    19  	expErr           error
    20  	expCustomRecords map[uint64][]byte
    21  	shouldHaveMPP    bool
    22  	shouldHaveAMP    bool
    23  }
    24  
    25  var decodePayloadTests = []decodePayloadTest{
    26  	{
    27  		name:    "final hop valid",
    28  		payload: []byte{0x02, 0x00, 0x04, 0x00},
    29  	},
    30  	{
    31  		name: "intermediate hop valid",
    32  		payload: []byte{0x02, 0x00, 0x04, 0x00, 0x06, 0x08, 0x01, 0x00,
    33  			0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    34  		},
    35  	},
    36  	{
    37  		name:    "final hop no amount",
    38  		payload: []byte{0x04, 0x00},
    39  		expErr: hop.ErrInvalidPayload{
    40  			Type:      record.AmtOnionType,
    41  			Violation: hop.OmittedViolation,
    42  			FinalHop:  true,
    43  		},
    44  	},
    45  	{
    46  		name: "intermediate hop no amount",
    47  		payload: []byte{0x04, 0x00, 0x06, 0x08, 0x01, 0x00, 0x00, 0x00,
    48  			0x00, 0x00, 0x00, 0x00,
    49  		},
    50  		expErr: hop.ErrInvalidPayload{
    51  			Type:      record.AmtOnionType,
    52  			Violation: hop.OmittedViolation,
    53  			FinalHop:  false,
    54  		},
    55  	},
    56  	{
    57  		name:    "final hop no expiry",
    58  		payload: []byte{0x02, 0x00},
    59  		expErr: hop.ErrInvalidPayload{
    60  			Type:      record.LockTimeOnionType,
    61  			Violation: hop.OmittedViolation,
    62  			FinalHop:  true,
    63  		},
    64  	},
    65  	{
    66  		name: "intermediate hop no expiry",
    67  		payload: []byte{0x02, 0x00, 0x06, 0x08, 0x01, 0x00, 0x00, 0x00,
    68  			0x00, 0x00, 0x00, 0x00,
    69  		},
    70  		expErr: hop.ErrInvalidPayload{
    71  			Type:      record.LockTimeOnionType,
    72  			Violation: hop.OmittedViolation,
    73  			FinalHop:  false,
    74  		},
    75  	},
    76  	{
    77  		name: "final hop next sid present",
    78  		payload: []byte{0x02, 0x00, 0x04, 0x00, 0x06, 0x08, 0x00, 0x00,
    79  			0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    80  		},
    81  		expErr: hop.ErrInvalidPayload{
    82  			Type:      record.NextHopOnionType,
    83  			Violation: hop.IncludedViolation,
    84  			FinalHop:  true,
    85  		},
    86  	},
    87  	{
    88  		name: "required type after omitted hop id",
    89  		payload: []byte{
    90  			0x02, 0x00, 0x04, 0x00,
    91  			testUnknownRequiredType, 0x00,
    92  		},
    93  		expErr: hop.ErrInvalidPayload{
    94  			Type:      testUnknownRequiredType,
    95  			Violation: hop.RequiredViolation,
    96  			FinalHop:  true,
    97  		},
    98  	},
    99  	{
   100  		name: "required type after included hop id",
   101  		payload: []byte{
   102  			0x02, 0x00, 0x04, 0x00, 0x06, 0x08, 0x01, 0x00, 0x00,
   103  			0x00, 0x00, 0x00, 0x00, 0x00,
   104  			testUnknownRequiredType, 0x00,
   105  		},
   106  		expErr: hop.ErrInvalidPayload{
   107  			Type:      testUnknownRequiredType,
   108  			Violation: hop.RequiredViolation,
   109  			FinalHop:  false,
   110  		},
   111  	},
   112  	{
   113  		name:    "required type zero final hop",
   114  		payload: []byte{0x00, 0x00, 0x02, 0x00, 0x04, 0x00},
   115  		expErr: hop.ErrInvalidPayload{
   116  			Type:      0,
   117  			Violation: hop.RequiredViolation,
   118  			FinalHop:  true,
   119  		},
   120  	},
   121  	{
   122  		name: "required type zero final hop zero sid",
   123  		payload: []byte{0x00, 0x00, 0x02, 0x00, 0x04, 0x00, 0x06, 0x08,
   124  			0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   125  		},
   126  		expErr: hop.ErrInvalidPayload{
   127  			Type:      record.NextHopOnionType,
   128  			Violation: hop.IncludedViolation,
   129  			FinalHop:  true,
   130  		},
   131  	},
   132  	{
   133  		name: "required type zero intermediate hop",
   134  		payload: []byte{0x00, 0x00, 0x02, 0x00, 0x04, 0x00, 0x06, 0x08,
   135  			0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   136  		},
   137  		expErr: hop.ErrInvalidPayload{
   138  			Type:      0,
   139  			Violation: hop.RequiredViolation,
   140  			FinalHop:  false,
   141  		},
   142  	},
   143  	{
   144  		name: "required type in custom range",
   145  		payload: []byte{0x02, 0x00, 0x04, 0x00,
   146  			0xfe, 0x00, 0x01, 0x00, 0x00, 0x02, 0x10, 0x11,
   147  		},
   148  		expCustomRecords: map[uint64][]byte{
   149  			65536: {0x10, 0x11},
   150  		},
   151  	},
   152  	{
   153  		name: "valid intermediate hop",
   154  		payload: []byte{0x02, 0x00, 0x04, 0x00, 0x06, 0x08, 0x01, 0x00,
   155  			0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   156  		},
   157  		expErr: nil,
   158  	},
   159  	{
   160  		name:    "valid final hop",
   161  		payload: []byte{0x02, 0x00, 0x04, 0x00},
   162  		expErr:  nil,
   163  	},
   164  	{
   165  		name: "intermediate hop with mpp",
   166  		payload: []byte{
   167  			// amount
   168  			0x02, 0x00,
   169  			// cltv
   170  			0x04, 0x00,
   171  			// next hop id
   172  			0x06, 0x08,
   173  			0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   174  			// mpp
   175  			0x08, 0x21,
   176  			0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
   177  			0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
   178  			0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
   179  			0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
   180  			0x08,
   181  		},
   182  		expErr: hop.ErrInvalidPayload{
   183  			Type:      record.MPPOnionType,
   184  			Violation: hop.IncludedViolation,
   185  			FinalHop:  false,
   186  		},
   187  	},
   188  	{
   189  		name: "intermediate hop with amp",
   190  		payload: []byte{
   191  			// amount
   192  			0x02, 0x00,
   193  			// cltv
   194  			0x04, 0x00,
   195  			// next hop id
   196  			0x06, 0x08,
   197  			0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   198  			// amp
   199  			0x0e, 0x41,
   200  			// amp.root_share
   201  			0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12,
   202  			0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12,
   203  			0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12,
   204  			0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12,
   205  			// amp.set_id
   206  			0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13,
   207  			0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13,
   208  			0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13,
   209  			0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13,
   210  			// amp.child_index
   211  			0x09,
   212  		},
   213  		expErr: hop.ErrInvalidPayload{
   214  			Type:      record.AMPOnionType,
   215  			Violation: hop.IncludedViolation,
   216  			FinalHop:  false,
   217  		},
   218  	},
   219  	{
   220  		name: "final hop with mpp",
   221  		payload: []byte{
   222  			// amount
   223  			0x02, 0x00,
   224  			// cltv
   225  			0x04, 0x00,
   226  			// mpp
   227  			0x08, 0x21,
   228  			0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
   229  			0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
   230  			0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
   231  			0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
   232  			0x08,
   233  		},
   234  		expErr:        nil,
   235  		shouldHaveMPP: true,
   236  	},
   237  	{
   238  		name: "final hop with amp",
   239  		payload: []byte{
   240  			// amount
   241  			0x02, 0x00,
   242  			// cltv
   243  			0x04, 0x00,
   244  			// amp
   245  			0x0e, 0x41,
   246  			// amp.root_share
   247  			0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12,
   248  			0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12,
   249  			0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12,
   250  			0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12,
   251  			// amp.set_id
   252  			0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13,
   253  			0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13,
   254  			0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13,
   255  			0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13,
   256  			// amp.child_index
   257  			0x09,
   258  		},
   259  		shouldHaveAMP: true,
   260  	},
   261  }
   262  
   263  // TestDecodeHopPayloadRecordValidation asserts that parsing the payloads in the
   264  // tests yields the expected errors depending on whether the proper fields were
   265  // included or omitted.
   266  func TestDecodeHopPayloadRecordValidation(t *testing.T) {
   267  	for _, test := range decodePayloadTests {
   268  		t.Run(test.name, func(t *testing.T) {
   269  			testDecodeHopPayloadValidation(t, test)
   270  		})
   271  	}
   272  }
   273  
   274  func testDecodeHopPayloadValidation(t *testing.T, test decodePayloadTest) {
   275  	var (
   276  		testTotalMAtoms = lnwire.MilliAtom(8)
   277  		testAddr        = [32]byte{
   278  			0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
   279  			0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
   280  			0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
   281  			0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
   282  		}
   283  
   284  		testRootShare = [32]byte{
   285  			0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12,
   286  			0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12,
   287  			0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12,
   288  			0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12,
   289  		}
   290  		testSetID = [32]byte{
   291  			0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13,
   292  			0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13,
   293  			0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13,
   294  			0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13,
   295  		}
   296  		testChildIndex = uint32(9)
   297  	)
   298  
   299  	p, err := hop.NewPayloadFromReader(bytes.NewReader(test.payload))
   300  	if !reflect.DeepEqual(test.expErr, err) {
   301  		t.Fatalf("expected error mismatch, want: %v, got: %v",
   302  			test.expErr, err)
   303  	}
   304  	if err != nil {
   305  		return
   306  	}
   307  
   308  	// Assert MPP fields if we expect them.
   309  	if test.shouldHaveMPP {
   310  		if p.MPP == nil {
   311  			t.Fatalf("payload should have MPP record")
   312  		}
   313  		if p.MPP.TotalMAtoms() != testTotalMAtoms {
   314  			t.Fatalf("invalid total msat")
   315  		}
   316  		if p.MPP.PaymentAddr() != testAddr {
   317  			t.Fatalf("invalid payment addr")
   318  		}
   319  	} else if p.MPP != nil {
   320  		t.Fatalf("unexpected MPP payload")
   321  	}
   322  
   323  	if test.shouldHaveAMP {
   324  		if p.AMP == nil {
   325  			t.Fatalf("payload should have AMP record")
   326  		}
   327  		require.Equal(t, testRootShare, p.AMP.RootShare())
   328  		require.Equal(t, testSetID, p.AMP.SetID())
   329  		require.Equal(t, testChildIndex, p.AMP.ChildIndex())
   330  	} else if p.AMP != nil {
   331  		t.Fatalf("unexpected AMP payload")
   332  	}
   333  
   334  	// Convert expected nil map to empty map, because we always expect an
   335  	// initiated map from the payload.
   336  	expCustomRecords := make(record.CustomSet)
   337  	if test.expCustomRecords != nil {
   338  		expCustomRecords = test.expCustomRecords
   339  	}
   340  	if !reflect.DeepEqual(expCustomRecords, p.CustomRecords()) {
   341  		t.Fatalf("invalid custom records")
   342  	}
   343  }