mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-03-26 01:33:02 +01:00
lntest/itest: add coverage for querying routes to blinded paths
This commit is contained in:
parent
0e179fae1e
commit
eb31ff553b
@ -546,4 +546,8 @@ var allTestCases = []*lntest.TestCase{
|
||||
Name: "update pending open channels",
|
||||
TestFunc: testUpdateOnPendingOpenChannels,
|
||||
},
|
||||
{
|
||||
Name: "query blinded route",
|
||||
TestFunc: testQueryBlindedRoutes,
|
||||
},
|
||||
}
|
||||
|
273
itest/lnd_route_blinding.go
Normal file
273
itest/lnd_route_blinding.go
Normal file
@ -0,0 +1,273 @@
|
||||
package itest
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/lightningnetwork/lnd/chainreg"
|
||||
"github.com/lightningnetwork/lnd/lnrpc"
|
||||
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
|
||||
"github.com/lightningnetwork/lnd/lntest"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// testQueryBlindedRoutes tests querying routes to blinded routes. To do this,
|
||||
// it sets up a nework of Alice - Bob - Carol and creates a mock blinded route
|
||||
// that uses Carol as the introduction node (plus dummy hops to cover multiple
|
||||
// hops). The test simply asserts that the structure of the route is as
|
||||
// expected.
|
||||
func testQueryBlindedRoutes(ht *lntest.HarnessTest) {
|
||||
var (
|
||||
// Convenience aliases.
|
||||
alice = ht.Alice
|
||||
bob = ht.Bob
|
||||
)
|
||||
|
||||
// Setup a two hop channel network: Alice -- Bob -- Carol.
|
||||
// We set our proportional fee for these channels to zero, so that
|
||||
// our calculations are easier. This is okay, because we're not testing
|
||||
// the basic mechanics of pathfinding in this test.
|
||||
chanAmt := btcutil.Amount(100000)
|
||||
chanPointAliceBob := ht.OpenChannel(
|
||||
alice, bob, lntest.OpenChannelParams{
|
||||
Amt: chanAmt,
|
||||
BaseFee: 10000,
|
||||
FeeRate: 0,
|
||||
UseBaseFee: true,
|
||||
UseFeeRate: true,
|
||||
},
|
||||
)
|
||||
|
||||
carol := ht.NewNode("Carol", nil)
|
||||
ht.EnsureConnected(bob, carol)
|
||||
|
||||
var bobCarolBase uint64 = 2000
|
||||
chanPointBobCarol := ht.OpenChannel(
|
||||
bob, carol, lntest.OpenChannelParams{
|
||||
Amt: chanAmt,
|
||||
BaseFee: bobCarolBase,
|
||||
FeeRate: 0,
|
||||
UseBaseFee: true,
|
||||
UseFeeRate: true,
|
||||
},
|
||||
)
|
||||
|
||||
// Wait for Alice to see Bob/Carol's channel because she'll need it for
|
||||
// pathfinding.
|
||||
ht.AssertTopologyChannelOpen(alice, chanPointBobCarol)
|
||||
|
||||
// Lookup full channel info so that we have channel ids for our route.
|
||||
aliceBobChan := ht.GetChannelByChanPoint(alice, chanPointAliceBob)
|
||||
bobCarolChan := ht.GetChannelByChanPoint(bob, chanPointBobCarol)
|
||||
|
||||
// Sanity check that bob's fee is as expected.
|
||||
chanInfoReq := &lnrpc.ChanInfoRequest{
|
||||
ChanId: bobCarolChan.ChanId,
|
||||
}
|
||||
|
||||
bobCarolInfo := bob.RPC.GetChanInfo(chanInfoReq)
|
||||
|
||||
// Our test relies on knowing the fee rate for bob - carol to set the
|
||||
// fees we expect for our route. Perform a quick sanity check that our
|
||||
// policy is as expected.
|
||||
var policy *lnrpc.RoutingPolicy
|
||||
if bobCarolInfo.Node1Pub == bob.PubKeyStr {
|
||||
policy = bobCarolInfo.Node1Policy
|
||||
} else {
|
||||
policy = bobCarolInfo.Node2Policy
|
||||
}
|
||||
require.Equal(ht, bobCarolBase, uint64(policy.FeeBaseMsat), "base fee")
|
||||
require.EqualValues(ht, 0, policy.FeeRateMilliMsat, "fee rate")
|
||||
|
||||
// We'll also need the current block height to calculate our locktimes.
|
||||
info := alice.RPC.GetInfo()
|
||||
|
||||
// Since we created channels with default parameters, we can assume
|
||||
// that all of our channels have the default cltv delta.
|
||||
bobCarolDelta := uint32(chainreg.DefaultBitcoinTimeLockDelta)
|
||||
|
||||
// Create arbitrary pubkeys for use in our blinded route. They're not
|
||||
// actually used functionally in this test, so we can just make them up.
|
||||
var (
|
||||
_, blindingPoint = btcec.PrivKeyFromBytes([]byte{1})
|
||||
_, carolBlinded = btcec.PrivKeyFromBytes([]byte{2})
|
||||
_, blindedHop1 = btcec.PrivKeyFromBytes([]byte{3})
|
||||
_, blindedHop2 = btcec.PrivKeyFromBytes([]byte{4})
|
||||
|
||||
encryptedDataCarol = []byte{1, 2, 3}
|
||||
encryptedData1 = []byte{4, 5, 6}
|
||||
encryptedData2 = []byte{7, 8, 9}
|
||||
|
||||
blindingBytes = blindingPoint.SerializeCompressed()
|
||||
carolBlindedBytes = carolBlinded.SerializeCompressed()
|
||||
blinded1Bytes = blindedHop1.SerializeCompressed()
|
||||
blinded2Bytes = blindedHop2.SerializeCompressed()
|
||||
)
|
||||
|
||||
// Now we create a blinded route which uses carol as an introduction
|
||||
// node followed by two dummy hops (the arbitrary pubkeys in our
|
||||
// blinded route above:
|
||||
// Carol --- B1 --- B2
|
||||
route := &lnrpc.BlindedPath{
|
||||
IntroductionNode: carol.PubKey[:],
|
||||
BlindingPoint: blindingBytes,
|
||||
BlindedHops: []*lnrpc.BlindedHop{
|
||||
{
|
||||
// The first hop in the blinded route is
|
||||
// expected to be the introduction node.
|
||||
BlindedNode: carolBlindedBytes,
|
||||
EncryptedData: encryptedDataCarol,
|
||||
},
|
||||
{
|
||||
BlindedNode: blinded1Bytes,
|
||||
EncryptedData: encryptedData1,
|
||||
},
|
||||
{
|
||||
BlindedNode: blinded2Bytes,
|
||||
EncryptedData: encryptedData2,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Create a blinded payment that has aggregate cltv and fee params
|
||||
// for our route.
|
||||
var (
|
||||
blindedBaseFee uint64 = 1500
|
||||
blindedCltvDelta uint32 = 125
|
||||
)
|
||||
|
||||
blindedPayment := &lnrpc.BlindedPaymentPath{
|
||||
BlindedPath: route,
|
||||
BaseFeeMsat: blindedBaseFee,
|
||||
TotalCltvDelta: blindedCltvDelta,
|
||||
}
|
||||
|
||||
// Query for a route to the blinded path constructed above.
|
||||
var paymentAmt int64 = 100_000
|
||||
|
||||
req := &lnrpc.QueryRoutesRequest{
|
||||
AmtMsat: paymentAmt,
|
||||
BlindedPaymentPaths: []*lnrpc.BlindedPaymentPath{
|
||||
blindedPayment,
|
||||
},
|
||||
}
|
||||
|
||||
resp := alice.RPC.QueryRoutes(req)
|
||||
require.Len(ht, resp.Routes, 1)
|
||||
|
||||
// Payment amount and cltv will be included for the bob/carol edge
|
||||
// (because we apply on the outgoing hop), and the blinded portion of
|
||||
// the route.
|
||||
totalFee := bobCarolBase + blindedBaseFee
|
||||
totalAmt := uint64(paymentAmt) + totalFee
|
||||
totalCltv := info.BlockHeight + bobCarolDelta + blindedCltvDelta
|
||||
|
||||
// Alice -> Bob
|
||||
// Forward: total - bob carol fees
|
||||
// Expiry: total - bob carol delta
|
||||
//
|
||||
// Bob -> Carol
|
||||
// Forward: 101500 (total + blinded fees)
|
||||
// Expiry: Height + blinded cltv delta
|
||||
// Encrypted Data: enc_carol
|
||||
//
|
||||
// Carol -> Blinded 1
|
||||
// Forward/ Expiry: 0
|
||||
// Encrypted Data: enc_1
|
||||
//
|
||||
// Blinded 1 -> Blinded 2
|
||||
// Forward/ Expiry: Height
|
||||
// Encrypted Data: enc_2
|
||||
hop0Amount := int64(totalAmt - bobCarolBase)
|
||||
hop0Expiry := totalCltv - bobCarolDelta
|
||||
finalHopExpiry := totalCltv - bobCarolDelta - blindedCltvDelta
|
||||
|
||||
expectedRoute := &lnrpc.Route{
|
||||
TotalTimeLock: totalCltv,
|
||||
TotalAmtMsat: int64(totalAmt),
|
||||
TotalFeesMsat: int64(totalFee),
|
||||
Hops: []*lnrpc.Hop{
|
||||
{
|
||||
ChanId: aliceBobChan.ChanId,
|
||||
Expiry: hop0Expiry,
|
||||
AmtToForwardMsat: hop0Amount,
|
||||
FeeMsat: int64(bobCarolBase),
|
||||
PubKey: bob.PubKeyStr,
|
||||
},
|
||||
{
|
||||
ChanId: bobCarolChan.ChanId,
|
||||
PubKey: carol.PubKeyStr,
|
||||
BlindingPoint: blindingBytes,
|
||||
FeeMsat: int64(blindedBaseFee),
|
||||
EncryptedData: encryptedDataCarol,
|
||||
},
|
||||
{
|
||||
PubKey: hex.EncodeToString(
|
||||
blinded1Bytes,
|
||||
),
|
||||
EncryptedData: encryptedData1,
|
||||
},
|
||||
{
|
||||
PubKey: hex.EncodeToString(
|
||||
blinded2Bytes,
|
||||
),
|
||||
AmtToForwardMsat: paymentAmt,
|
||||
Expiry: finalHopExpiry,
|
||||
EncryptedData: encryptedData2,
|
||||
TotalAmtMsat: uint64(paymentAmt),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
r := resp.Routes[0]
|
||||
assert.Equal(ht, expectedRoute.TotalTimeLock, r.TotalTimeLock)
|
||||
assert.Equal(ht, expectedRoute.TotalAmtMsat, r.TotalAmtMsat)
|
||||
assert.Equal(ht, expectedRoute.TotalFeesMsat, r.TotalFeesMsat)
|
||||
|
||||
assert.Equal(ht, len(expectedRoute.Hops), len(r.Hops))
|
||||
for i, hop := range expectedRoute.Hops {
|
||||
assert.Equal(ht, hop.PubKey, r.Hops[i].PubKey,
|
||||
"hop: %v pubkey", i)
|
||||
|
||||
assert.Equal(ht, hop.ChanId, r.Hops[i].ChanId,
|
||||
"hop: %v chan id", i)
|
||||
|
||||
assert.Equal(ht, hop.Expiry, r.Hops[i].Expiry,
|
||||
"hop: %v expiry", i)
|
||||
|
||||
assert.Equal(ht, hop.AmtToForwardMsat,
|
||||
r.Hops[i].AmtToForwardMsat, "hop: %v forward", i)
|
||||
|
||||
assert.Equal(ht, hop.FeeMsat, r.Hops[i].FeeMsat,
|
||||
"hop: %v fee", i)
|
||||
|
||||
assert.Equal(ht, hop.BlindingPoint, r.Hops[i].BlindingPoint,
|
||||
"hop: %v blinding point", i)
|
||||
|
||||
assert.Equal(ht, hop.EncryptedData, r.Hops[i].EncryptedData,
|
||||
"hop: %v encrypted data", i)
|
||||
}
|
||||
|
||||
// Dispatch a payment to our blinded route.
|
||||
preimage := [33]byte{1, 2, 3}
|
||||
hash := sha256.Sum256(preimage[:])
|
||||
|
||||
sendReq := &routerrpc.SendToRouteRequest{
|
||||
PaymentHash: hash[:],
|
||||
Route: r,
|
||||
}
|
||||
|
||||
htlcAttempt := alice.RPC.SendToRouteV2(sendReq)
|
||||
|
||||
// Since Carol doesn't understand blinded routes, we expect her to fail
|
||||
// the payment because the onion payload is invalid (missing amount to
|
||||
// forward).
|
||||
require.NotNil(ht, htlcAttempt.Failure)
|
||||
require.Equal(ht, uint32(2), htlcAttempt.Failure.FailureSourceIndex)
|
||||
|
||||
ht.CloseChannel(alice, chanPointAliceBob)
|
||||
ht.CloseChannel(bob, chanPointBobCarol)
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user