routing+refactor: let BlindedEdge carry BlindedPayment

This commit is purely a refactor. In it, we let the `BlindedEdge` struct
carry a pointer to the `BlindedPayment` that it was derived from. This
is done now because later on in the PR series, we will need more
information about the `BlindedPayment` that an edge was derived from.

Since we now pass in the whole BlindedPayment, we swap out the
`cipherText` member for a `hopIndex` member so that we dont carry around
two sources of truth in the same struct.
This commit is contained in:
Elle Mouton 2024-05-13 13:44:56 +02:00
parent a9655357ca
commit f7a9aa875e
No known key found for this signature in database
GPG key ID: D7D916376026F177
6 changed files with 109 additions and 60 deletions

View file

@ -2,8 +2,8 @@ package routing
import (
"errors"
"fmt"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/lightningnetwork/lnd/channeldb/models"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing/route"
@ -61,11 +61,42 @@ func (p *PrivateEdge) IntermediatePayloadSize(amount lnwire.MilliSatoshi,
}
// BlindedEdge implements the AdditionalEdge interface. Blinded hops are viewed
// as additional edges because they are appened at the end of a normal route.
// as additional edges because they are appended at the end of a normal route.
type BlindedEdge struct {
policy *models.CachedEdgePolicy
cipherText []byte
blindingPoint *btcec.PublicKey
policy *models.CachedEdgePolicy
// blindedPayment is the BlindedPayment that this blinded edge was
// derived from.
blindedPayment *BlindedPayment
// hopIndex is the index of the hop in the blinded payment path that
// this edge is associated with.
hopIndex int
}
// NewBlindedEdge constructs a new BlindedEdge which packages the policy info
// for a specific hop within the given blinded payment path. The hop index
// should correspond to the hop within the blinded payment that this edge is
// associated with.
func NewBlindedEdge(policy *models.CachedEdgePolicy, payment *BlindedPayment,
hopIndex int) (*BlindedEdge, error) {
if payment == nil {
return nil, fmt.Errorf("blinded payment cannot be nil for " +
"blinded edge")
}
if hopIndex < 0 || hopIndex >= len(payment.BlindedPath.BlindedHops) {
return nil, fmt.Errorf("the hop index %d is outside the "+
"valid range between 0 and %d", hopIndex,
len(payment.BlindedPath.BlindedHops)-1)
}
return &BlindedEdge{
policy: policy,
hopIndex: hopIndex,
blindedPayment: payment,
}, nil
}
// EdgePolicy return the policy of the BlindedEdge.
@ -78,9 +109,11 @@ func (b *BlindedEdge) EdgePolicy() *models.CachedEdgePolicy {
func (b *BlindedEdge) IntermediatePayloadSize(_ lnwire.MilliSatoshi, _ uint32,
_ uint64) uint64 {
blindedPath := b.blindedPayment.BlindedPath
hop := route.Hop{
BlindingPoint: b.blindingPoint,
EncryptedData: b.cipherText,
BlindingPoint: blindedPath.BlindingPoint,
EncryptedData: blindedPath.BlindedHops[b.hopIndex].CipherText,
}
// For blinded paths the next chanID is in the encrypted data tlv.

View file

@ -42,9 +42,13 @@ func TestIntermediatePayloadSize(t *testing.T) {
hop: route.Hop{
EncryptedData: []byte{12, 13},
},
edge: &BlindedEdge{
cipherText: []byte{12, 13},
},
edge: &BlindedEdge{blindedPayment: &BlindedPayment{
BlindedPath: &sphinx.BlindedPath{
BlindedHops: []*sphinx.BlindedHopInfo{
{CipherText: []byte{12, 13}},
},
},
}},
},
{
name: "Blinded edge - introduction point",
@ -52,10 +56,14 @@ func TestIntermediatePayloadSize(t *testing.T) {
EncryptedData: []byte{12, 13},
BlindingPoint: blindedPoint,
},
edge: &BlindedEdge{
cipherText: []byte{12, 13},
blindingPoint: blindedPoint,
},
edge: &BlindedEdge{blindedPayment: &BlindedPayment{
BlindedPath: &sphinx.BlindedPath{
BlindingPoint: blindedPoint,
BlindedHops: []*sphinx.BlindedHopInfo{
{CipherText: []byte{12, 13}},
},
},
}},
},
}

View file

@ -88,13 +88,13 @@ func (b *BlindedPayment) Validate() error {
// the case of multiple blinded hops, CLTV delta is fully accounted for in the
// hints (both for intermediate hops and the final_cltv_delta for the receiving
// node).
func (b *BlindedPayment) toRouteHints() RouteHints {
func (b *BlindedPayment) toRouteHints() (RouteHints, error) {
// If we just have a single hop in our blinded route, it just contains
// an introduction node (this is a valid path according to the spec).
// Since we have the un-blinded node ID for the introduction node, we
// don't need to add any route hints.
if len(b.BlindedPath.BlindedHops) == 1 {
return nil
return nil, nil
}
hintCount := len(b.BlindedPath.BlindedHops) - 1
@ -136,14 +136,13 @@ func (b *BlindedPayment) toRouteHints() RouteHints {
ToNodeFeatures: features,
}
hints[fromNode] = []AdditionalEdge{
&BlindedEdge{
policy: edgePolicy,
cipherText: b.BlindedPath.BlindedHops[0].CipherText,
blindingPoint: b.BlindedPath.BlindingPoint,
},
edge, err := NewBlindedEdge(edgePolicy, b, 0)
if err != nil {
return nil, err
}
hints[fromNode] = []AdditionalEdge{edge}
// Start at an offset of 1 because the first node in our blinded hops
// is the introduction node and terminate at the second-last node
// because we're dealing with hops as pairs.
@ -169,14 +168,13 @@ func (b *BlindedPayment) toRouteHints() RouteHints {
ToNodeFeatures: features,
}
hints[fromNode] = []AdditionalEdge{
&BlindedEdge{
policy: edgePolicy,
cipherText: b.BlindedPath.BlindedHops[i].
CipherText,
},
edge, err := NewBlindedEdge(edgePolicy, b, i)
if err != nil {
return nil, err
}
hints[fromNode] = []AdditionalEdge{edge}
}
return hints
return hints, nil
}

View file

@ -128,7 +128,9 @@ func TestBlindedPaymentToHints(t *testing.T) {
HtlcMaximum: htlcMax,
Features: features,
}
require.Nil(t, blindedPayment.toRouteHints())
hints, err := blindedPayment.toRouteHints()
require.NoError(t, err)
require.Nil(t, hints)
// Populate the blinded payment with hops.
blindedPayment.BlindedPath.BlindedHops = []*sphinx.BlindedHopInfo{
@ -146,41 +148,43 @@ func TestBlindedPaymentToHints(t *testing.T) {
},
}
policy1 := &models.CachedEdgePolicy{
TimeLockDelta: cltvDelta,
MinHTLC: lnwire.MilliSatoshi(htlcMin),
MaxHTLC: lnwire.MilliSatoshi(htlcMax),
FeeBaseMSat: lnwire.MilliSatoshi(baseFee),
FeeProportionalMillionths: lnwire.MilliSatoshi(
ppmFee,
),
ToNodePubKey: func() route.Vertex {
return vb2
},
ToNodeFeatures: features,
}
policy2 := &models.CachedEdgePolicy{
ToNodePubKey: func() route.Vertex {
return vb3
},
ToNodeFeatures: features,
}
blindedEdge1, err := NewBlindedEdge(policy1, blindedPayment, 0)
require.NoError(t, err)
blindedEdge2, err := NewBlindedEdge(policy2, blindedPayment, 1)
require.NoError(t, err)
expected := RouteHints{
v1: {
//nolint:lll
&BlindedEdge{
policy: &models.CachedEdgePolicy{
TimeLockDelta: cltvDelta,
MinHTLC: lnwire.MilliSatoshi(htlcMin),
MaxHTLC: lnwire.MilliSatoshi(htlcMax),
FeeBaseMSat: lnwire.MilliSatoshi(baseFee),
FeeProportionalMillionths: lnwire.MilliSatoshi(
ppmFee,
),
ToNodePubKey: func() route.Vertex {
return vb2
},
ToNodeFeatures: features,
},
blindingPoint: blindedPoint,
cipherText: cipherText,
},
blindedEdge1,
},
vb2: {
&BlindedEdge{
policy: &models.CachedEdgePolicy{
ToNodePubKey: func() route.Vertex {
return vb3
},
ToNodeFeatures: features,
},
cipherText: cipherText,
},
blindedEdge2,
},
}
actual := blindedPayment.toRouteHints()
actual, err := blindedPayment.toRouteHints()
require.NoError(t, err)
require.Equal(t, len(expected), len(actual))
for vertex, expectedHint := range expected {

View file

@ -3341,7 +3341,9 @@ func TestBlindedRouteConstruction(t *testing.T) {
// that make up the graph we'll give to route construction. The hints
// map is keyed by source node, so we can retrieve our blinded edges
// accordingly.
blindedEdges := blindedPayment.toRouteHints()
blindedEdges, err := blindedPayment.toRouteHints()
require.NoError(t, err)
carolDaveEdge := blindedEdges[carolVertex][0]
daveEveEdge := blindedEdges[daveBlindedVertex][0]

View file

@ -2001,6 +2001,7 @@ func NewRouteRequest(source route.Vertex, target *route.Vertex,
// Assume that we're starting off with a regular payment.
requestHints = routeHints
requestExpiry = finalExpiry
err error
)
if blindedPayment != nil {
@ -2038,7 +2039,10 @@ func NewRouteRequest(source route.Vertex, target *route.Vertex,
requestExpiry = blindedPayment.CltvExpiryDelta
}
requestHints = blindedPayment.toRouteHints()
requestHints, err = blindedPayment.toRouteHints()
if err != nil {
return nil, err
}
}
requestTarget, err := getTargetNode(target, blindedPayment)