Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

route blinding [4/4]: update MC on receive from blinded path #9004

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 104 additions & 0 deletions channeldb/invoice_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ import (
"testing"
"time"

"github.com/btcsuite/btcd/btcec/v2"
"github.com/lightningnetwork/lnd/channeldb/models"
invpkg "github.com/lightningnetwork/lnd/invoices"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing/route"
"github.com/lightningnetwork/lnd/tlv"
"github.com/stretchr/testify/require"
)
Expand Down Expand Up @@ -103,3 +105,105 @@ func TestEncodeDecodeAmpInvoiceState(t *testing.T) {
// The two states should match.
require.Equal(t, ampState, ampState2)
}

func genPubKey(t *testing.T) route.Vertex {
pk, err := btcec.NewPrivateKey()
require.NoError(t, err)

return route.NewVertex(pk.PubKey())
}

func genPrivKey(t *testing.T) *btcec.PrivateKey {
pk, err := btcec.NewPrivateKey()
require.NoError(t, err)

return pk
}

// TestEncodeDecodeBlindedPathMap asserts that the nested TLV
// encoding+decoding for the BlindedPathsInfo struct works as expected.
func TestEncodeDecodeBlindedPathMap(t *testing.T) {
t.Parallel()

blindedPath := models.BlindedPathsInfo{
genPubKey(t): {
Route: &models.MCRoute{
SourcePubKey: genPubKey(t),
TotalAmount: 10000,
Hops: []*models.MCHop{
{
PubKeyBytes: genPubKey(t),
AmtToFwd: 60,
ChannelID: 40,
},
{
PubKeyBytes: genPubKey(t),
AmtToFwd: 50,
ChannelID: 10,
HasBlindingPoint: true,
},
},
},
SessionKey: genPrivKey(t),
},
genPubKey(t): {
Route: &models.MCRoute{
SourcePubKey: genPubKey(t),
TotalAmount: 30,
Hops: []*models.MCHop{
{
PubKeyBytes: genPubKey(t),
AmtToFwd: 50,
ChannelID: 10,
HasBlindingPoint: true,
},
},
},
SessionKey: genPrivKey(t),
},
}

// We'll now make a sample invoice stream, and use that to encode the
// amp state we created above.
tlvStream, err := tlv.NewStream(
tlv.MakeDynamicRecord(
blindedPathsInfoType, &blindedPath,
blindedPathMapRecordSize(blindedPath),
models.BlindedPathInfoEncoder,
models.BlindedPathInfoDecoder,
),
)
require.Nil(t, err)

// Next encode the stream into a set of raw bytes.
var b bytes.Buffer
err = tlvStream.Encode(&b)
require.Nil(t, err)

// Now create a new blank blinded path map, which we'll use to decode
// the bytes into.
blindedPath2 := make(models.BlindedPathsInfo)

// Decode from the raw stream into this blank mpa.
tlvStream, err = tlv.NewStream(
tlv.MakeDynamicRecord(
blindedPathsInfoType, &blindedPath2, nil,
models.BlindedPathInfoEncoder,
models.BlindedPathInfoDecoder,
),
)
require.Nil(t, err)

err = tlvStream.Decode(&b)
require.Nil(t, err)

// The two states should match.
require.Len(t, blindedPath2, len(blindedPath))

for k, m1 := range blindedPath {
m2, ok := blindedPath2[k]
require.True(t, ok)

require.Equal(t, m1, m2)
}
}
69 changes: 53 additions & 16 deletions channeldb/invoices.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,22 +109,23 @@ const (
// prevents against the database being rolled back to an older
// format where the surrounding logic might assume a different set of
// fields are known.
memoType tlv.Type = 0
payReqType tlv.Type = 1
createTimeType tlv.Type = 2
settleTimeType tlv.Type = 3
addIndexType tlv.Type = 4
settleIndexType tlv.Type = 5
preimageType tlv.Type = 6
valueType tlv.Type = 7
cltvDeltaType tlv.Type = 8
expiryType tlv.Type = 9
paymentAddrType tlv.Type = 10
featuresType tlv.Type = 11
invStateType tlv.Type = 12
amtPaidType tlv.Type = 13
hodlInvoiceType tlv.Type = 14
invoiceAmpStateType tlv.Type = 15
memoType tlv.Type = 0
payReqType tlv.Type = 1
createTimeType tlv.Type = 2
settleTimeType tlv.Type = 3
addIndexType tlv.Type = 4
settleIndexType tlv.Type = 5
preimageType tlv.Type = 6
valueType tlv.Type = 7
cltvDeltaType tlv.Type = 8
expiryType tlv.Type = 9
paymentAddrType tlv.Type = 10
featuresType tlv.Type = 11
invStateType tlv.Type = 12
amtPaidType tlv.Type = 13
hodlInvoiceType tlv.Type = 14
invoiceAmpStateType tlv.Type = 15
blindedPathsInfoType tlv.Type = 16

// A set of tlv type definitions used to serialize the invoice AMP
// state along-side the main invoice body.
Expand Down Expand Up @@ -1182,6 +1183,26 @@ func putInvoice(invoices, invoiceIndex, payAddrIndex, addIndex kvdb.RwBucket,
return nextAddSeqNo, nil
}

func blindedPathMapRecordSize(a models.BlindedPathsInfo) func() uint64 {
var (
b bytes.Buffer
buf [8]byte
)

// We know that encoding works since the tests pass in the build this
// file is checked into, so we'll simplify things and simply encode it
// ourselves then report the total amount of bytes used.
if err := models.BlindedPathInfoEncoder(&b, a, &buf); err != nil {
// This should never error out, but we log it just in case it
// does.
log.Errorf("encoding the blinded path map failed: %v", err)
}

return func() uint64 {
return uint64(len(b.Bytes()))
}
}

// recordSize returns the amount of bytes this TLV record will occupy when
// encoded.
func ampRecordSize(a *invpkg.AMPInvoiceState) func() uint64 {
Expand Down Expand Up @@ -1277,6 +1298,14 @@ func serializeInvoice(w io.Writer, i *invpkg.Invoice) error {
ampRecordSize(&i.AMPState),
ampStateEncoder, ampStateDecoder,
),

// Blinded Path info.
tlv.MakeDynamicRecord(
blindedPathsInfoType, &i.BlindedPaths,
blindedPathMapRecordSize(i.BlindedPaths),
models.BlindedPathInfoEncoder,
models.BlindedPathInfoDecoder,
),
)
if err != nil {
return err
Expand Down Expand Up @@ -1644,6 +1673,7 @@ func deserializeInvoice(r io.Reader) (invpkg.Invoice, error) {

var i invpkg.Invoice
i.AMPState = make(invpkg.AMPInvoiceState)
i.BlindedPaths = make(models.BlindedPathsInfo)
tlvStream, err := tlv.NewStream(
// Memo and payreq.
tlv.MakePrimitiveRecord(memoType, &i.Memo),
Expand Down Expand Up @@ -1674,6 +1704,13 @@ func deserializeInvoice(r io.Reader) (invpkg.Invoice, error) {
invoiceAmpStateType, &i.AMPState, nil,
ampStateEncoder, ampStateDecoder,
),

// Blinded Path info.
tlv.MakeDynamicRecord(
blindedPathsInfoType, &i.BlindedPaths, nil,
models.BlindedPathInfoEncoder,
models.BlindedPathInfoDecoder,
),
)
if err != nil {
return i, err
Expand Down
126 changes: 126 additions & 0 deletions channeldb/models/blinded_path_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package models

import (
"bytes"
"testing"

"github.com/btcsuite/btcd/btcec/v2"
"github.com/lightningnetwork/lnd/routing/route"
"github.com/stretchr/testify/require"
)

func genPubKey(t *testing.T) route.Vertex {
pk, err := btcec.NewPrivateKey()
require.NoError(t, err)

return route.NewVertex(pk.PubKey())
}

func genPrivKey(t *testing.T) *btcec.PrivateKey {
pk, err := btcec.NewPrivateKey()
require.NoError(t, err)

return pk
}

// TestBlindedPathInfoSerialise tests the BlindedPathInfo TLV encoder and
// decoder.
func TestBlindedPathInfoSerialise(t *testing.T) {
info := &BlindedPathInfo{
Route: &MCRoute{
SourcePubKey: genPubKey(t),
TotalAmount: 600,
Hops: []*MCHop{
{
PubKeyBytes: genPubKey(t),
AmtToFwd: 60,
ChannelID: 40,
},
{
PubKeyBytes: genPubKey(t),
AmtToFwd: 50,
ChannelID: 10,
HasBlindingPoint: true,
},
},
},
SessionKey: genPrivKey(t),
}

var b bytes.Buffer
err := info.Serialize(&b)
require.NoError(t, err)

info2, err := DeserializeBlindedPathInfo(&b)
require.NoError(t, err)

compareBlindedPathInfo(t, info, info2)

infoSet := &BlindedPathsInfo{
genPubKey(t): &BlindedPathInfo{
Route: &MCRoute{
SourcePubKey: genPubKey(t),
TotalAmount: 600,
Hops: []*MCHop{
{
PubKeyBytes: genPubKey(t),
AmtToFwd: 60,
ChannelID: 40,
},
{
PubKeyBytes: genPubKey(t),
AmtToFwd: 50,
ChannelID: 10,
HasBlindingPoint: true,
},
},
},
SessionKey: genPrivKey(t),
},
genPubKey(t): &BlindedPathInfo{
Route: &MCRoute{
SourcePubKey: genPubKey(t),
TotalAmount: 60,
Hops: []*MCHop{
{
PubKeyBytes: genPubKey(t),
AmtToFwd: 60,
ChannelID: 400,
},
},
},
SessionKey: genPrivKey(t),
},
}

var b1 bytes.Buffer
err = BlindedPathInfoEncoder(&b1, infoSet, nil)
require.NoError(t, err)

infoSet2 := make(BlindedPathsInfo)
err = BlindedPathInfoDecoder(
&b1, &infoSet2, nil, uint64(len(b1.Bytes())),
)
require.NoError(t, err)
}

func compareBlindedPathInfo(t *testing.T, info, info2 *BlindedPathInfo) {
require.EqualValues(t, info.SessionKey, info2.SessionKey)
require.EqualValues(
t, info.Route.SourcePubKey, info2.Route.SourcePubKey,
)
require.EqualValues(
t, info.Route.TotalAmount, info2.Route.TotalAmount,
)
require.Len(t, info.Route.Hops, len(info2.Route.Hops))

for i, hop := range info.Route.Hops {
hop2 := info2.Route.Hops[i]
require.EqualValues(t, hop.PubKeyBytes, hop2.PubKeyBytes)
require.EqualValues(t, hop.ChannelID, hop2.ChannelID)
require.EqualValues(
t, hop.HasBlindingPoint, hop2.HasBlindingPoint,
)
require.EqualValues(t, hop.AmtToFwd, hop2.AmtToFwd)
}
}
Loading
Loading