mirror of
https://github.com/lightningnetwork/lnd.git
synced 2024-11-19 09:53:54 +01:00
364 lines
8.6 KiB
Go
364 lines
8.6 KiB
Go
package record
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"math"
|
|
"testing"
|
|
|
|
"github.com/btcsuite/btcd/btcec/v2"
|
|
"github.com/lightningnetwork/lnd/lnwire"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
//nolint:lll
|
|
const pubkeyStr = "02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619"
|
|
|
|
func pubkey(t *testing.T) *btcec.PublicKey {
|
|
t.Helper()
|
|
|
|
nodeBytes, err := hex.DecodeString(pubkeyStr)
|
|
require.NoError(t, err)
|
|
|
|
nodePk, err := btcec.ParsePubKey(nodeBytes)
|
|
require.NoError(t, err)
|
|
|
|
return nodePk
|
|
}
|
|
|
|
// TestBlindedDataEncoding tests encoding and decoding of blinded data blobs.
|
|
// These tests specifically cover cases where the variable length encoded
|
|
// integers values have different numbers of leading zeros trimmed because
|
|
// these TLVs are the first composite records with variable length tlvs
|
|
// (previously, a variable length integer would take up the whole record).
|
|
func TestBlindedDataEncoding(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
baseFee lnwire.MilliSatoshi
|
|
htlcMin lnwire.MilliSatoshi
|
|
features *lnwire.FeatureVector
|
|
constraints bool
|
|
}{
|
|
{
|
|
name: "zero variable values",
|
|
baseFee: 0,
|
|
htlcMin: 0,
|
|
},
|
|
{
|
|
name: "zeros trimmed",
|
|
baseFee: math.MaxUint32 / 2,
|
|
htlcMin: math.MaxUint64 / 2,
|
|
},
|
|
{
|
|
name: "no zeros trimmed",
|
|
baseFee: math.MaxUint32,
|
|
htlcMin: math.MaxUint64,
|
|
},
|
|
{
|
|
name: "nil feature vector",
|
|
features: nil,
|
|
},
|
|
{
|
|
name: "non-nil, but empty feature vector",
|
|
features: lnwire.EmptyFeatureVector(),
|
|
},
|
|
{
|
|
name: "populated feature vector",
|
|
features: lnwire.NewFeatureVector(
|
|
lnwire.NewRawFeatureVector(lnwire.AMPOptional),
|
|
lnwire.Features,
|
|
),
|
|
},
|
|
{
|
|
name: "no payment constraints",
|
|
constraints: true,
|
|
},
|
|
}
|
|
|
|
for _, testCase := range tests {
|
|
testCase := testCase
|
|
t.Run(testCase.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// Create a standard set of blinded route data, using
|
|
// the values from our test case for the variable
|
|
// length encoded values.
|
|
channelID := lnwire.NewShortChanIDFromInt(1)
|
|
info := PaymentRelayInfo{
|
|
FeeRate: 2,
|
|
CltvExpiryDelta: 3,
|
|
BaseFee: testCase.baseFee,
|
|
}
|
|
|
|
var constraints *PaymentConstraints
|
|
if testCase.constraints {
|
|
constraints = &PaymentConstraints{
|
|
MaxCltvExpiry: 4,
|
|
HtlcMinimumMsat: testCase.htlcMin,
|
|
}
|
|
}
|
|
|
|
encodedData := NewNonFinalBlindedRouteData(
|
|
channelID, pubkey(t), info, constraints,
|
|
testCase.features,
|
|
)
|
|
|
|
encoded, err := EncodeBlindedRouteData(encodedData)
|
|
require.NoError(t, err)
|
|
|
|
b := bytes.NewBuffer(encoded)
|
|
decodedData, err := DecodeBlindedRouteData(b)
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, encodedData, decodedData)
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestBlindedDataFinalHopEncoding tests the encoding and decoding of a blinded
|
|
// data blob intended for the final hop of a blinded path where only the pathID
|
|
// will potentially be set.
|
|
func TestBlindedDataFinalHopEncoding(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
pathID []byte
|
|
constraints bool
|
|
}{
|
|
{
|
|
name: "with path ID",
|
|
pathID: []byte{1, 2, 3, 4, 5, 6},
|
|
},
|
|
{
|
|
name: "with no path ID",
|
|
pathID: nil,
|
|
},
|
|
{
|
|
name: "with path ID and constraints",
|
|
pathID: []byte{1, 2, 3, 4, 5, 6},
|
|
constraints: true,
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
test := test
|
|
t.Run(test.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var constraints *PaymentConstraints
|
|
if test.constraints {
|
|
constraints = &PaymentConstraints{
|
|
MaxCltvExpiry: 4,
|
|
HtlcMinimumMsat: 5,
|
|
}
|
|
}
|
|
|
|
encodedData := NewFinalHopBlindedRouteData(
|
|
constraints, test.pathID,
|
|
)
|
|
|
|
encoded, err := EncodeBlindedRouteData(encodedData)
|
|
require.NoError(t, err)
|
|
|
|
b := bytes.NewBuffer(encoded)
|
|
decodedData, err := DecodeBlindedRouteData(b)
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, encodedData, decodedData)
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestDummyHopBlindedDataEncoding tests the encoding and decoding of a blinded
|
|
// data blob intended for hops preceding a dummy hop in a blinded path. These
|
|
// hops provide the reader with a signal that the next hop may be a dummy hop.
|
|
func TestDummyHopBlindedDataEncoding(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
priv, err := btcec.NewPrivateKey()
|
|
require.NoError(t, err)
|
|
|
|
info := PaymentRelayInfo{
|
|
FeeRate: 2,
|
|
CltvExpiryDelta: 3,
|
|
BaseFee: 30,
|
|
}
|
|
|
|
constraints := PaymentConstraints{
|
|
MaxCltvExpiry: 4,
|
|
HtlcMinimumMsat: 100,
|
|
}
|
|
|
|
routeData := NewDummyHopRouteData(priv.PubKey(), info, constraints)
|
|
|
|
encoded, err := EncodeBlindedRouteData(routeData)
|
|
require.NoError(t, err)
|
|
|
|
// Assert the size of an average dummy hop payload in case we need to
|
|
// update this constant in future.
|
|
require.Len(t, encoded, AverageDummyHopPayloadSize)
|
|
|
|
b := bytes.NewBuffer(encoded)
|
|
decodedData, err := DecodeBlindedRouteData(b)
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, routeData, decodedData)
|
|
}
|
|
|
|
// TestBlindedRouteDataPadding tests the PadBy method of BlindedRouteData.
|
|
func TestBlindedRouteDataPadding(t *testing.T) {
|
|
newBlindedRouteData := func() *BlindedRouteData {
|
|
channelID := lnwire.NewShortChanIDFromInt(1)
|
|
info := PaymentRelayInfo{
|
|
FeeRate: 2,
|
|
CltvExpiryDelta: 3,
|
|
BaseFee: 30,
|
|
}
|
|
|
|
constraints := &PaymentConstraints{
|
|
MaxCltvExpiry: 4,
|
|
HtlcMinimumMsat: 100,
|
|
}
|
|
|
|
return NewNonFinalBlindedRouteData(
|
|
channelID, pubkey(t), info, constraints, nil,
|
|
)
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
paddingSize int
|
|
expectedSizeIncrease uint64
|
|
}{
|
|
{
|
|
// Calling PadBy with an n value of 0 in the case where
|
|
// there is not yet a padding field will result in a
|
|
// zero length TLV entry being added. This will add 2
|
|
// bytes for the type and length fields.
|
|
name: "no extra padding",
|
|
expectedSizeIncrease: 2,
|
|
},
|
|
{
|
|
name: "small padding (length " +
|
|
"field of 1 byte)",
|
|
paddingSize: 200,
|
|
expectedSizeIncrease: 202,
|
|
},
|
|
{
|
|
name: "medium padding (length field " +
|
|
"of 3 bytes)",
|
|
paddingSize: 256,
|
|
expectedSizeIncrease: 260,
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
data := newBlindedRouteData()
|
|
|
|
prePaddingEncoding, err := EncodeBlindedRouteData(data)
|
|
require.NoError(t, err)
|
|
|
|
data.PadBy(test.paddingSize)
|
|
|
|
postPaddingEncoding, err := EncodeBlindedRouteData(data)
|
|
require.NoError(t, err)
|
|
|
|
require.EqualValues(
|
|
t, test.expectedSizeIncrease,
|
|
len(postPaddingEncoding)-
|
|
len(prePaddingEncoding),
|
|
)
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestBlindedRouteVectors tests encoding/decoding of the test vectors for
|
|
// blinded route data provided in the specification.
|
|
//
|
|
//nolint:lll
|
|
func TestBlindingSpecTestVectors(t *testing.T) {
|
|
nextBlindingOverrideStr, err := hex.DecodeString("031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f")
|
|
require.NoError(t, err)
|
|
nextBlindingOverride, err := btcec.ParsePubKey(nextBlindingOverrideStr)
|
|
require.NoError(t, err)
|
|
|
|
tests := []struct {
|
|
encoded string
|
|
expectedPaymentData *BlindedRouteData
|
|
expectedPadding int
|
|
}{
|
|
{
|
|
encoded: "011a0000000000000000000000000000000000000000000000000000020800000000000006c10a0800240000009627100c06000b69e505dc0e00fd023103123456",
|
|
expectedPaymentData: NewNonFinalBlindedRouteData(
|
|
lnwire.ShortChannelID{
|
|
BlockHeight: 0,
|
|
TxIndex: 0,
|
|
TxPosition: 1729,
|
|
},
|
|
nil,
|
|
PaymentRelayInfo{
|
|
CltvExpiryDelta: 36,
|
|
FeeRate: 150,
|
|
BaseFee: 10000,
|
|
},
|
|
&PaymentConstraints{
|
|
MaxCltvExpiry: 748005,
|
|
HtlcMinimumMsat: 1500,
|
|
},
|
|
lnwire.NewFeatureVector(
|
|
lnwire.NewRawFeatureVector(),
|
|
lnwire.Features,
|
|
),
|
|
),
|
|
expectedPadding: 26,
|
|
},
|
|
{
|
|
encoded: "020800000000000004510821031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f0a0800300000006401f40c06000b69c105dc0e00",
|
|
expectedPaymentData: NewNonFinalBlindedRouteData(
|
|
lnwire.ShortChannelID{
|
|
TxPosition: 1105,
|
|
},
|
|
nextBlindingOverride,
|
|
PaymentRelayInfo{
|
|
CltvExpiryDelta: 48,
|
|
FeeRate: 100,
|
|
BaseFee: 500,
|
|
},
|
|
&PaymentConstraints{
|
|
MaxCltvExpiry: 747969,
|
|
HtlcMinimumMsat: 1500,
|
|
},
|
|
lnwire.NewFeatureVector(
|
|
lnwire.NewRawFeatureVector(),
|
|
lnwire.Features,
|
|
),
|
|
),
|
|
},
|
|
}
|
|
|
|
for i, test := range tests {
|
|
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
|
|
route, err := hex.DecodeString(test.encoded)
|
|
require.NoError(t, err)
|
|
|
|
buff := bytes.NewBuffer(route)
|
|
|
|
decodedRoute, err := DecodeBlindedRouteData(buff)
|
|
require.NoError(t, err)
|
|
|
|
if test.expectedPadding != 0 {
|
|
test.expectedPaymentData.PadBy(
|
|
test.expectedPadding,
|
|
)
|
|
}
|
|
|
|
require.Equal(
|
|
t, test.expectedPaymentData, decodedRoute,
|
|
)
|
|
})
|
|
}
|
|
}
|