lnd/lntest/itest/lnd_routing_test.go

2177 lines
69 KiB
Go

package itest
import (
"bytes"
"context"
"encoding/hex"
"fmt"
"strings"
"testing"
"time"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/lightningnetwork/lnd/chainreg"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
"github.com/lightningnetwork/lnd/lntest"
"github.com/lightningnetwork/lnd/lntest/wait"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
)
type singleHopSendToRouteCase struct {
name string
// streaming tests streaming SendToRoute if true, otherwise tests
// synchronous SenToRoute.
streaming bool
// routerrpc submits the request to the routerrpc subserver if true,
// otherwise submits to the main rpc server.
routerrpc bool
}
var singleHopSendToRouteCases = []singleHopSendToRouteCase{
{
name: "regular main sync",
},
{
name: "regular main stream",
streaming: true,
},
{
name: "regular routerrpc sync",
routerrpc: true,
},
{
name: "mpp main sync",
},
{
name: "mpp main stream",
streaming: true,
},
{
name: "mpp routerrpc sync",
routerrpc: true,
},
}
// testSingleHopSendToRoute tests that payments are properly processed through a
// provided route with a single hop. We'll create the following network
// topology:
// Carol --100k--> Dave
// We'll query the daemon for routes from Carol to Dave and then send payments
// by feeding the route back into the various SendToRoute RPC methods. Here we
// test all three SendToRoute endpoints, forcing each to perform both a regular
// payment and an MPP payment.
func testSingleHopSendToRoute(net *lntest.NetworkHarness, t *harnessTest) {
for _, test := range singleHopSendToRouteCases {
test := test
t.t.Run(test.name, func(t1 *testing.T) {
ht := newHarnessTest(t1, t.lndHarness)
ht.RunTestCase(&testCase{
name: test.name,
test: func(_ *lntest.NetworkHarness, tt *harnessTest) {
testSingleHopSendToRouteCase(net, tt, test)
},
})
})
}
}
func testSingleHopSendToRouteCase(net *lntest.NetworkHarness, t *harnessTest,
test singleHopSendToRouteCase) {
const chanAmt = btcutil.Amount(100000)
const paymentAmtSat = 1000
const numPayments = 5
const amountPaid = int64(numPayments * paymentAmtSat)
ctxb := context.Background()
var networkChans []*lnrpc.ChannelPoint
// Create Carol and Dave, then establish a channel between them. Carol
// is the sole funder of the channel with 100k satoshis. The network
// topology should look like:
// Carol -> 100k -> Dave
carol := net.NewNode(t.t, "Carol", nil)
defer shutdownAndAssert(net, t, carol)
dave := net.NewNode(t.t, "Dave", nil)
defer shutdownAndAssert(net, t, dave)
ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
net.ConnectNodes(ctxt, t.t, carol, dave)
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
net.SendCoins(ctxt, t.t, btcutil.SatoshiPerBitcoin, carol)
// Open a channel with 100k satoshis between Carol and Dave with Carol
// being the sole funder of the channel.
ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout)
chanPointCarol := openChannelAndAssert(
ctxt, t, net, carol, dave,
lntest.OpenChannelParams{
Amt: chanAmt,
},
)
networkChans = append(networkChans, chanPointCarol)
carolChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointCarol)
if err != nil {
t.Fatalf("unable to get txid: %v", err)
}
carolFundPoint := wire.OutPoint{
Hash: *carolChanTXID,
Index: chanPointCarol.OutputIndex,
}
// Wait for all nodes to have seen all channels.
nodes := []*lntest.HarnessNode{carol, dave}
for _, chanPoint := range networkChans {
for _, node := range nodes {
txid, err := lnrpc.GetChanPointFundingTxid(chanPoint)
if err != nil {
t.Fatalf("unable to get txid: %v", err)
}
point := wire.OutPoint{
Hash: *txid,
Index: chanPoint.OutputIndex,
}
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
err = node.WaitForNetworkChannelOpen(ctxt, chanPoint)
if err != nil {
t.Fatalf("%s(%d): timeout waiting for "+
"channel(%s) open: %v", node.Name(),
node.NodeID, point, err)
}
}
}
// Create invoices for Dave, which expect a payment from Carol.
payReqs, rHashes, _, err := createPayReqs(
dave, paymentAmtSat, numPayments,
)
if err != nil {
t.Fatalf("unable to create pay reqs: %v", err)
}
// Reconstruct payment addresses.
var payAddrs [][]byte
for _, payReq := range payReqs {
ctx, _ := context.WithTimeout(
context.Background(), defaultTimeout,
)
resp, err := dave.DecodePayReq(
ctx,
&lnrpc.PayReqString{PayReq: payReq},
)
if err != nil {
t.Fatalf("decode pay req: %v", err)
}
payAddrs = append(payAddrs, resp.PaymentAddr)
}
// Assert Carol and Dave are synced to the chain before proceeding, to
// ensure the queried route will have a valid final CLTV once the HTLC
// reaches Dave.
_, minerHeight, err := net.Miner.Client.GetBestBlock()
if err != nil {
t.Fatalf("unable to get best height: %v", err)
}
ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout)
defer cancel()
require.NoError(t.t, waitForNodeBlockHeight(ctxt, carol, minerHeight))
require.NoError(t.t, waitForNodeBlockHeight(ctxt, dave, minerHeight))
// Query for routes to pay from Carol to Dave using the default CLTV
// config.
routesReq := &lnrpc.QueryRoutesRequest{
PubKey: dave.PubKeyStr,
Amt: paymentAmtSat,
}
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
routes, err := carol.QueryRoutes(ctxt, routesReq)
if err != nil {
t.Fatalf("unable to get route from %s: %v",
carol.Name(), err)
}
// There should only be one route to try, so take the first item.
r := routes.Routes[0]
// Construct a closure that will set MPP fields on the route, which
// allows us to test MPP payments.
setMPPFields := func(i int) {
hop := r.Hops[len(r.Hops)-1]
hop.TlvPayload = true
hop.MppRecord = &lnrpc.MPPRecord{
PaymentAddr: payAddrs[i],
TotalAmtMsat: paymentAmtSat * 1000,
}
}
// Construct closures for each of the payment types covered:
// - main rpc server sync
// - main rpc server streaming
// - routerrpc server sync
sendToRouteSync := func() {
for i, rHash := range rHashes {
setMPPFields(i)
sendReq := &lnrpc.SendToRouteRequest{
PaymentHash: rHash,
Route: r,
}
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
resp, err := carol.SendToRouteSync(
ctxt, sendReq,
)
if err != nil {
t.Fatalf("unable to send to route for "+
"%s: %v", carol.Name(), err)
}
if resp.PaymentError != "" {
t.Fatalf("received payment error from %s: %v",
carol.Name(), resp.PaymentError)
}
}
}
sendToRouteStream := func() {
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
alicePayStream, err := carol.SendToRoute(ctxt) // nolint:staticcheck
if err != nil {
t.Fatalf("unable to create payment stream for "+
"carol: %v", err)
}
for i, rHash := range rHashes {
setMPPFields(i)
sendReq := &lnrpc.SendToRouteRequest{
PaymentHash: rHash,
Route: routes.Routes[0],
}
err := alicePayStream.Send(sendReq)
if err != nil {
t.Fatalf("unable to send payment: %v", err)
}
resp, err := alicePayStream.Recv()
if err != nil {
t.Fatalf("unable to send payment: %v", err)
}
if resp.PaymentError != "" {
t.Fatalf("received payment error: %v",
resp.PaymentError)
}
}
}
sendToRouteRouterRPC := func() {
for i, rHash := range rHashes {
setMPPFields(i)
sendReq := &routerrpc.SendToRouteRequest{
PaymentHash: rHash,
Route: r,
}
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
resp, err := carol.RouterClient.SendToRouteV2(
ctxt, sendReq,
)
if err != nil {
t.Fatalf("unable to send to route for "+
"%s: %v", carol.Name(), err)
}
if resp.Failure != nil {
t.Fatalf("received payment error from %s: %v",
carol.Name(), resp.Failure)
}
}
}
// Using Carol as the node as the source, send the payments
// synchronously via the the routerrpc's SendToRoute, or via the main RPC
// server's SendToRoute streaming or sync calls.
switch {
case !test.routerrpc && test.streaming:
sendToRouteStream()
case !test.routerrpc && !test.streaming:
sendToRouteSync()
case test.routerrpc && !test.streaming:
sendToRouteRouterRPC()
default:
t.Fatalf("routerrpc does not support streaming send_to_route")
}
// Verify that the payment's from Carol's PoV have the correct payment
// hash and amount.
ctxt, _ = context.WithTimeout(ctxt, defaultTimeout)
paymentsResp, err := carol.ListPayments(
ctxt, &lnrpc.ListPaymentsRequest{},
)
if err != nil {
t.Fatalf("error when obtaining %s payments: %v",
carol.Name(), err)
}
if len(paymentsResp.Payments) != numPayments {
t.Fatalf("incorrect number of payments, got %v, want %v",
len(paymentsResp.Payments), numPayments)
}
for i, p := range paymentsResp.Payments {
// Assert that the payment hashes for each payment match up.
rHashHex := hex.EncodeToString(rHashes[i])
if p.PaymentHash != rHashHex {
t.Fatalf("incorrect payment hash for payment %d, "+
"want: %s got: %s",
i, rHashHex, p.PaymentHash)
}
// Assert that each payment has no invoice since the payment was
// completed using SendToRoute.
if p.PaymentRequest != "" {
t.Fatalf("incorrect payment request for payment: %d, "+
"want: \"\", got: %s",
i, p.PaymentRequest)
}
// Assert the payment amount is correct.
if p.ValueSat != paymentAmtSat {
t.Fatalf("incorrect payment amt for payment %d, "+
"want: %d, got: %d",
i, paymentAmtSat, p.ValueSat)
}
// Assert exactly one htlc was made.
if len(p.Htlcs) != 1 {
t.Fatalf("expected 1 htlc for payment %d, got: %d",
i, len(p.Htlcs))
}
// Assert the htlc's route is populated.
htlc := p.Htlcs[0]
if htlc.Route == nil {
t.Fatalf("expected route for payment %d", i)
}
// Assert the hop has exactly one hop.
if len(htlc.Route.Hops) != 1 {
t.Fatalf("expected 1 hop for payment %d, got: %d",
i, len(htlc.Route.Hops))
}
// If this is an MPP test, assert the MPP record's fields are
// properly populated. Otherwise the hop should not have an MPP
// record.
hop := htlc.Route.Hops[0]
if hop.MppRecord == nil {
t.Fatalf("expected mpp record for mpp payment")
}
if hop.MppRecord.TotalAmtMsat != paymentAmtSat*1000 {
t.Fatalf("incorrect mpp total msat for payment %d "+
"want: %d, got: %d",
i, paymentAmtSat*1000,
hop.MppRecord.TotalAmtMsat)
}
expAddr := payAddrs[i]
if !bytes.Equal(hop.MppRecord.PaymentAddr, expAddr) {
t.Fatalf("incorrect mpp payment addr for payment %d "+
"want: %x, got: %x",
i, expAddr, hop.MppRecord.PaymentAddr)
}
}
// Verify that the invoices's from Dave's PoV have the correct payment
// hash and amount.
ctxt, _ = context.WithTimeout(ctxt, defaultTimeout)
invoicesResp, err := dave.ListInvoices(
ctxt, &lnrpc.ListInvoiceRequest{},
)
if err != nil {
t.Fatalf("error when obtaining %s payments: %v",
dave.Name(), err)
}
if len(invoicesResp.Invoices) != numPayments {
t.Fatalf("incorrect number of invoices, got %v, want %v",
len(invoicesResp.Invoices), numPayments)
}
for i, inv := range invoicesResp.Invoices {
// Assert that the payment hashes match up.
if !bytes.Equal(inv.RHash, rHashes[i]) {
t.Fatalf("incorrect payment hash for invoice %d, "+
"want: %x got: %x",
i, rHashes[i], inv.RHash)
}
// Assert that the amount paid to the invoice is correct.
if inv.AmtPaidSat != paymentAmtSat {
t.Fatalf("incorrect payment amt for invoice %d, "+
"want: %d, got %d",
i, paymentAmtSat, inv.AmtPaidSat)
}
}
// At this point all the channels within our proto network should be
// shifted by 5k satoshis in the direction of Dave, the sink within the
// payment flow generated above. The order of asserts corresponds to
// increasing of time is needed to embed the HTLC in commitment
// transaction, in channel Carol->Dave, order is Dave and then Carol.
assertAmountPaid(t, "Carol(local) => Dave(remote)", dave,
carolFundPoint, int64(0), amountPaid)
assertAmountPaid(t, "Carol(local) => Dave(remote)", carol,
carolFundPoint, amountPaid, int64(0))
ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout)
closeChannelAndAssert(ctxt, t, net, carol, chanPointCarol, false)
}
// testMultiHopSendToRoute tests that payments are properly processed
// through a provided route. We'll create the following network topology:
// Alice --100k--> Bob --100k--> Carol
// We'll query the daemon for routes from Alice to Carol and then
// send payments through the routes.
func testMultiHopSendToRoute(net *lntest.NetworkHarness, t *harnessTest) {
ctxb := context.Background()
const chanAmt = btcutil.Amount(100000)
var networkChans []*lnrpc.ChannelPoint
// Open a channel with 100k satoshis between Alice and Bob with Alice
// being the sole funder of the channel.
ctxt, _ := context.WithTimeout(ctxb, channelOpenTimeout)
chanPointAlice := openChannelAndAssert(
ctxt, t, net, net.Alice, net.Bob,
lntest.OpenChannelParams{
Amt: chanAmt,
},
)
networkChans = append(networkChans, chanPointAlice)
aliceChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointAlice)
if err != nil {
t.Fatalf("unable to get txid: %v", err)
}
aliceFundPoint := wire.OutPoint{
Hash: *aliceChanTXID,
Index: chanPointAlice.OutputIndex,
}
// Create Carol and establish a channel from Bob. Bob is the sole funder
// of the channel with 100k satoshis. The network topology should look like:
// Alice -> Bob -> Carol
carol := net.NewNode(t.t, "Carol", nil)
defer shutdownAndAssert(net, t, carol)
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
net.ConnectNodes(ctxt, t.t, carol, net.Bob)
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
net.SendCoins(ctxt, t.t, btcutil.SatoshiPerBitcoin, net.Bob)
ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout)
chanPointBob := openChannelAndAssert(
ctxt, t, net, net.Bob, carol,
lntest.OpenChannelParams{
Amt: chanAmt,
},
)
networkChans = append(networkChans, chanPointBob)
bobChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointBob)
if err != nil {
t.Fatalf("unable to get txid: %v", err)
}
bobFundPoint := wire.OutPoint{
Hash: *bobChanTXID,
Index: chanPointBob.OutputIndex,
}
// Wait for all nodes to have seen all channels.
nodes := []*lntest.HarnessNode{net.Alice, net.Bob, carol}
nodeNames := []string{"Alice", "Bob", "Carol"}
for _, chanPoint := range networkChans {
for i, node := range nodes {
txid, err := lnrpc.GetChanPointFundingTxid(chanPoint)
if err != nil {
t.Fatalf("unable to get txid: %v", err)
}
point := wire.OutPoint{
Hash: *txid,
Index: chanPoint.OutputIndex,
}
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
err = node.WaitForNetworkChannelOpen(ctxt, chanPoint)
if err != nil {
t.Fatalf("%s(%d): timeout waiting for "+
"channel(%s) open: %v", nodeNames[i],
node.NodeID, point, err)
}
}
}
// Create 5 invoices for Carol, which expect a payment from Alice for 1k
// satoshis with a different preimage each time.
const (
numPayments = 5
paymentAmt = 1000
)
_, rHashes, invoices, err := createPayReqs(
carol, paymentAmt, numPayments,
)
if err != nil {
t.Fatalf("unable to create pay reqs: %v", err)
}
// Construct a route from Alice to Carol for each of the invoices
// created above. We set FinalCltvDelta to 40 since by default
// QueryRoutes returns the last hop with a final cltv delta of 9 where
// as the default in htlcswitch is 40.
routesReq := &lnrpc.QueryRoutesRequest{
PubKey: carol.PubKeyStr,
Amt: paymentAmt,
FinalCltvDelta: chainreg.DefaultBitcoinTimeLockDelta,
}
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
routes, err := net.Alice.QueryRoutes(ctxt, routesReq)
if err != nil {
t.Fatalf("unable to get route: %v", err)
}
// We'll wait for all parties to recognize the new channels within the
// network.
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
err = carol.WaitForNetworkChannelOpen(ctxt, chanPointBob)
if err != nil {
t.Fatalf("bob didn't advertise his channel in time: %v", err)
}
time.Sleep(time.Millisecond * 50)
// Using Alice as the source, pay to the 5 invoices from Carol created
// above.
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
for i, rHash := range rHashes {
// Manually set the MPP payload a new for each payment since
// the payment addr will change with each invoice, although we
// can re-use the route itself.
route := *routes.Routes[0]
route.Hops[len(route.Hops)-1].TlvPayload = true
route.Hops[len(route.Hops)-1].MppRecord = &lnrpc.MPPRecord{
PaymentAddr: invoices[i].PaymentAddr,
TotalAmtMsat: int64(
lnwire.NewMSatFromSatoshis(paymentAmt),
),
}
sendReq := &routerrpc.SendToRouteRequest{
PaymentHash: rHash,
Route: &route,
}
resp, err := net.Alice.RouterClient.SendToRouteV2(ctxt, sendReq)
if err != nil {
t.Fatalf("unable to send payment: %v", err)
}
if resp.Failure != nil {
t.Fatalf("received payment error: %v", resp.Failure)
}
}
// When asserting the amount of satoshis moved, we'll factor in the
// default base fee, as we didn't modify the fee structure when
// creating the seed nodes in the network.
const baseFee = 1
// At this point all the channels within our proto network should be
// shifted by 5k satoshis in the direction of Carol, the sink within the
// payment flow generated above. The order of asserts corresponds to
// increasing of time is needed to embed the HTLC in commitment
// transaction, in channel Alice->Bob->Carol, order is Carol, Bob,
// Alice.
const amountPaid = int64(5000)
assertAmountPaid(t, "Bob(local) => Carol(remote)", carol,
bobFundPoint, int64(0), amountPaid)
assertAmountPaid(t, "Bob(local) => Carol(remote)", net.Bob,
bobFundPoint, amountPaid, int64(0))
assertAmountPaid(t, "Alice(local) => Bob(remote)", net.Bob,
aliceFundPoint, int64(0), amountPaid+(baseFee*numPayments))
assertAmountPaid(t, "Alice(local) => Bob(remote)", net.Alice,
aliceFundPoint, amountPaid+(baseFee*numPayments), int64(0))
ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout)
closeChannelAndAssert(ctxt, t, net, net.Alice, chanPointAlice, false)
ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout)
closeChannelAndAssert(ctxt, t, net, carol, chanPointBob, false)
}
// testSendToRouteErrorPropagation tests propagation of errors that occur
// while processing a multi-hop payment through an unknown route.
func testSendToRouteErrorPropagation(net *lntest.NetworkHarness, t *harnessTest) {
ctxb := context.Background()
const chanAmt = btcutil.Amount(100000)
// Open a channel with 100k satoshis between Alice and Bob with Alice
// being the sole funder of the channel.
ctxt, _ := context.WithTimeout(ctxb, channelOpenTimeout)
chanPointAlice := openChannelAndAssert(
ctxt, t, net, net.Alice, net.Bob,
lntest.OpenChannelParams{
Amt: chanAmt,
},
)
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
err := net.Alice.WaitForNetworkChannelOpen(ctxt, chanPointAlice)
if err != nil {
t.Fatalf("alice didn't advertise her channel: %v", err)
}
// Create a new nodes (Carol and Charlie), load her with some funds,
// then establish a connection between Carol and Charlie with a channel
// that has identical capacity to the one created above.Then we will
// get route via queryroutes call which will be fake route for Alice ->
// Bob graph.
//
// The network topology should now look like: Alice -> Bob; Carol -> Charlie.
carol := net.NewNode(t.t, "Carol", nil)
defer shutdownAndAssert(net, t, carol)
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
net.SendCoins(ctxt, t.t, btcutil.SatoshiPerBitcoin, carol)
charlie := net.NewNode(t.t, "Charlie", nil)
defer shutdownAndAssert(net, t, charlie)
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
net.SendCoins(ctxt, t.t, btcutil.SatoshiPerBitcoin, charlie)
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
net.ConnectNodes(ctxt, t.t, carol, charlie)
ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout)
chanPointCarol := openChannelAndAssert(
ctxt, t, net, carol, charlie,
lntest.OpenChannelParams{
Amt: chanAmt,
},
)
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
err = carol.WaitForNetworkChannelOpen(ctxt, chanPointCarol)
if err != nil {
t.Fatalf("carol didn't advertise her channel: %v", err)
}
// Query routes from Carol to Charlie which will be an invalid route
// for Alice -> Bob.
fakeReq := &lnrpc.QueryRoutesRequest{
PubKey: charlie.PubKeyStr,
Amt: int64(1),
}
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
fakeRoute, err := carol.QueryRoutes(ctxt, fakeReq)
if err != nil {
t.Fatalf("unable get fake route: %v", err)
}
// Create 1 invoices for Bob, which expect a payment from Alice for 1k
// satoshis
const paymentAmt = 1000
invoice := &lnrpc.Invoice{
Memo: "testing",
Value: paymentAmt,
}
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
resp, err := net.Bob.AddInvoice(ctxt, invoice)
if err != nil {
t.Fatalf("unable to add invoice: %v", err)
}
rHash := resp.RHash
// Using Alice as the source, pay to the 5 invoices from Bob created above.
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
alicePayStream, err := net.Alice.SendToRoute(ctxt) // nolint:staticcheck
if err != nil {
t.Fatalf("unable to create payment stream for alice: %v", err)
}
sendReq := &lnrpc.SendToRouteRequest{
PaymentHash: rHash,
Route: fakeRoute.Routes[0],
}
if err := alicePayStream.Send(sendReq); err != nil {
t.Fatalf("unable to send payment: %v", err)
}
// At this place we should get an rpc error with notification
// that edge is not found on hop(0)
if _, err := alicePayStream.Recv(); err != nil && strings.Contains(err.Error(),
"edge not found") {
} else if err != nil {
t.Fatalf("payment stream has been closed but fake route has consumed: %v", err)
}
ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout)
closeChannelAndAssert(ctxt, t, net, net.Alice, chanPointAlice, false)
ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout)
closeChannelAndAssert(ctxt, t, net, carol, chanPointCarol, false)
}
// testPrivateChannels tests that a private channel can be used for
// routing by the two endpoints of the channel, but is not known by
// the rest of the nodes in the graph.
func testPrivateChannels(net *lntest.NetworkHarness, t *harnessTest) {
ctxb := context.Background()
const chanAmt = btcutil.Amount(100000)
var networkChans []*lnrpc.ChannelPoint
// We create the following topology:
//
// Dave --100k--> Alice --200k--> Bob
// ^ ^
// | |
// 100k 100k
// | |
// +---- Carol ----+
//
// where the 100k channel between Carol and Alice is private.
// Open a channel with 200k satoshis between Alice and Bob.
ctxt, _ := context.WithTimeout(ctxb, channelOpenTimeout)
chanPointAlice := openChannelAndAssert(
ctxt, t, net, net.Alice, net.Bob,
lntest.OpenChannelParams{
Amt: chanAmt * 2,
},
)
networkChans = append(networkChans, chanPointAlice)
aliceChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointAlice)
if err != nil {
t.Fatalf("unable to get txid: %v", err)
}
aliceFundPoint := wire.OutPoint{
Hash: *aliceChanTXID,
Index: chanPointAlice.OutputIndex,
}
// Create Dave, and a channel to Alice of 100k.
dave := net.NewNode(t.t, "Dave", nil)
defer shutdownAndAssert(net, t, dave)
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
net.ConnectNodes(ctxt, t.t, dave, net.Alice)
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
net.SendCoins(ctxt, t.t, btcutil.SatoshiPerBitcoin, dave)
ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout)
chanPointDave := openChannelAndAssert(
ctxt, t, net, dave, net.Alice,
lntest.OpenChannelParams{
Amt: chanAmt,
},
)
networkChans = append(networkChans, chanPointDave)
daveChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointDave)
if err != nil {
t.Fatalf("unable to get txid: %v", err)
}
daveFundPoint := wire.OutPoint{
Hash: *daveChanTXID,
Index: chanPointDave.OutputIndex,
}
// Next, we'll create Carol and establish a channel from her to
// Dave of 100k.
carol := net.NewNode(t.t, "Carol", nil)
defer shutdownAndAssert(net, t, carol)
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
net.ConnectNodes(ctxt, t.t, carol, dave)
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
net.SendCoins(ctxt, t.t, btcutil.SatoshiPerBitcoin, carol)
ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout)
chanPointCarol := openChannelAndAssert(
ctxt, t, net, carol, dave,
lntest.OpenChannelParams{
Amt: chanAmt,
},
)
networkChans = append(networkChans, chanPointCarol)
carolChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointCarol)
if err != nil {
t.Fatalf("unable to get txid: %v", err)
}
carolFundPoint := wire.OutPoint{
Hash: *carolChanTXID,
Index: chanPointCarol.OutputIndex,
}
// Wait for all nodes to have seen all these channels, as they
// are all public.
nodes := []*lntest.HarnessNode{net.Alice, net.Bob, carol, dave}
nodeNames := []string{"Alice", "Bob", "Carol", "Dave"}
for _, chanPoint := range networkChans {
for i, node := range nodes {
txid, err := lnrpc.GetChanPointFundingTxid(chanPoint)
if err != nil {
t.Fatalf("unable to get txid: %v", err)
}
point := wire.OutPoint{
Hash: *txid,
Index: chanPoint.OutputIndex,
}
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
err = node.WaitForNetworkChannelOpen(ctxt, chanPoint)
if err != nil {
t.Fatalf("%s(%d): timeout waiting for "+
"channel(%s) open: %v", nodeNames[i],
node.NodeID, point, err)
}
}
}
// Now create a _private_ channel directly between Carol and
// Alice of 100k.
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
net.ConnectNodes(ctxt, t.t, carol, net.Alice)
ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout)
chanOpenUpdate := openChannelStream(
ctxt, t, net, carol, net.Alice,
lntest.OpenChannelParams{
Amt: chanAmt,
Private: true,
},
)
if err != nil {
t.Fatalf("unable to open channel: %v", err)
}
// One block is enough to make the channel ready for use, since the
// nodes have defaultNumConfs=1 set.
block := mineBlocks(t, net, 1, 1)[0]
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
chanPointPrivate, err := net.WaitForChannelOpen(ctxt, chanOpenUpdate)
if err != nil {
t.Fatalf("error while waiting for channel open: %v", err)
}
fundingTxID, err := lnrpc.GetChanPointFundingTxid(chanPointPrivate)
if err != nil {
t.Fatalf("unable to get txid: %v", err)
}
assertTxInBlock(t, block, fundingTxID)
// The channel should be listed in the peer information returned by
// both peers.
privateFundPoint := wire.OutPoint{
Hash: *fundingTxID,
Index: chanPointPrivate.OutputIndex,
}
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
err = net.AssertChannelExists(ctxt, carol, &privateFundPoint)
if err != nil {
t.Fatalf("unable to assert channel existence: %v", err)
}
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
err = net.AssertChannelExists(ctxt, net.Alice, &privateFundPoint)
if err != nil {
t.Fatalf("unable to assert channel existence: %v", err)
}
// The channel should be available for payments between Carol and Alice.
// We check this by sending payments from Carol to Bob, that
// collectively would deplete at least one of Carol's channels.
// Create 2 invoices for Bob, each of 70k satoshis. Since each of
// Carol's channels is of size 100k, these payments cannot succeed
// by only using one of the channels.
const numPayments = 2
const paymentAmt = 70000
payReqs, _, _, err := createPayReqs(
net.Bob, paymentAmt, numPayments,
)
if err != nil {
t.Fatalf("unable to create pay reqs: %v", err)
}
time.Sleep(time.Millisecond * 50)
// Let Carol pay the invoices.
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
err = completePaymentRequests(
ctxt, carol, carol.RouterClient, payReqs, true,
)
if err != nil {
t.Fatalf("unable to send payments: %v", err)
}
// When asserting the amount of satoshis moved, we'll factor in the
// default base fee, as we didn't modify the fee structure when
// creating the seed nodes in the network.
const baseFee = 1
// Bob should have received 140k satoshis from Alice.
assertAmountPaid(t, "Alice(local) => Bob(remote)", net.Bob,
aliceFundPoint, int64(0), 2*paymentAmt)
// Alice sent 140k to Bob.
assertAmountPaid(t, "Alice(local) => Bob(remote)", net.Alice,
aliceFundPoint, 2*paymentAmt, int64(0))
// Alice received 70k + fee from Dave.
assertAmountPaid(t, "Dave(local) => Alice(remote)", net.Alice,
daveFundPoint, int64(0), paymentAmt+baseFee)
// Dave sent 70k+fee to Alice.
assertAmountPaid(t, "Dave(local) => Alice(remote)", dave,
daveFundPoint, paymentAmt+baseFee, int64(0))
// Dave received 70k+fee of two hops from Carol.
assertAmountPaid(t, "Carol(local) => Dave(remote)", dave,
carolFundPoint, int64(0), paymentAmt+baseFee*2)
// Carol sent 70k+fee of two hops to Dave.
assertAmountPaid(t, "Carol(local) => Dave(remote)", carol,
carolFundPoint, paymentAmt+baseFee*2, int64(0))
// Alice received 70k+fee from Carol.
assertAmountPaid(t, "Carol(local) [private=>] Alice(remote)",
net.Alice, privateFundPoint, int64(0), paymentAmt+baseFee)
// Carol sent 70k+fee to Alice.
assertAmountPaid(t, "Carol(local) [private=>] Alice(remote)",
carol, privateFundPoint, paymentAmt+baseFee, int64(0))
// Alice should also be able to route payments using this channel,
// so send two payments of 60k back to Carol.
const paymentAmt60k = 60000
payReqs, _, _, err = createPayReqs(
carol, paymentAmt60k, numPayments,
)
if err != nil {
t.Fatalf("unable to create pay reqs: %v", err)
}
time.Sleep(time.Millisecond * 50)
// Let Bob pay the invoices.
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
err = completePaymentRequests(
ctxt, net.Alice, net.Alice.RouterClient, payReqs, true,
)
if err != nil {
t.Fatalf("unable to send payments: %v", err)
}
// Finally, we make sure Dave and Bob does not know about the
// private channel between Carol and Alice. We first mine
// plenty of blocks, such that the channel would have been
// announced in case it was public.
mineBlocks(t, net, 10, 0)
// We create a helper method to check how many edges each of the
// nodes know about. Carol and Alice should know about 4, while
// Bob and Dave should only know about 3, since one channel is
// private.
numChannels := func(node *lntest.HarnessNode, includeUnannounced bool) int {
req := &lnrpc.ChannelGraphRequest{
IncludeUnannounced: includeUnannounced,
}
ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
chanGraph, err := node.DescribeGraph(ctxt, req)
if err != nil {
t.Fatalf("unable go describegraph: %v", err)
}
return len(chanGraph.Edges)
}
var predErr error
err = wait.Predicate(func() bool {
aliceChans := numChannels(net.Alice, true)
if aliceChans != 4 {
predErr = fmt.Errorf("expected Alice to know 4 edges, "+
"had %v", aliceChans)
return false
}
alicePubChans := numChannels(net.Alice, false)
if alicePubChans != 3 {
predErr = fmt.Errorf("expected Alice to know 3 public edges, "+
"had %v", alicePubChans)
return false
}
bobChans := numChannels(net.Bob, true)
if bobChans != 3 {
predErr = fmt.Errorf("expected Bob to know 3 edges, "+
"had %v", bobChans)
return false
}
carolChans := numChannels(carol, true)
if carolChans != 4 {
predErr = fmt.Errorf("expected Carol to know 4 edges, "+
"had %v", carolChans)
return false
}
carolPubChans := numChannels(carol, false)
if carolPubChans != 3 {
predErr = fmt.Errorf("expected Carol to know 3 public edges, "+
"had %v", carolPubChans)
return false
}
daveChans := numChannels(dave, true)
if daveChans != 3 {
predErr = fmt.Errorf("expected Dave to know 3 edges, "+
"had %v", daveChans)
return false
}
return true
}, defaultTimeout)
if err != nil {
t.Fatalf("%v", predErr)
}
// Close all channels.
ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout)
closeChannelAndAssert(ctxt, t, net, net.Alice, chanPointAlice, false)
ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout)
closeChannelAndAssert(ctxt, t, net, dave, chanPointDave, false)
ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout)
closeChannelAndAssert(ctxt, t, net, carol, chanPointCarol, false)
ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout)
closeChannelAndAssert(ctxt, t, net, carol, chanPointPrivate, false)
}
// testUpdateChannelPolicyForPrivateChannel tests when a private channel
// updates its channel edge policy, we will use the updated policy to send our
// payment.
// The topology is created as: Alice -> Bob -> Carol, where Alice -> Bob is
// public and Bob -> Carol is private. After an invoice is created by Carol,
// Bob will update the base fee via UpdateChannelPolicy, we will test that
// Alice will not fail the payment and send it using the updated channel
// policy.
func testUpdateChannelPolicyForPrivateChannel(net *lntest.NetworkHarness,
t *harnessTest) {
ctxb := context.Background()
defer ctxb.Done()
// We'll create the following topology first,
// Alice <--public:100k--> Bob <--private:100k--> Carol
const chanAmt = btcutil.Amount(100000)
// Open a channel with 100k satoshis between Alice and Bob.
ctxt, _ := context.WithTimeout(ctxb, channelOpenTimeout)
chanPointAliceBob := openChannelAndAssert(
ctxt, t, net, net.Alice, net.Bob,
lntest.OpenChannelParams{
Amt: chanAmt,
},
)
defer closeChannelAndAssert(
ctxt, t, net, net.Alice, chanPointAliceBob, false,
)
// Get Alice's funding point.
aliceChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointAliceBob)
require.NoError(t.t, err, "unable to get txid")
aliceFundPoint := wire.OutPoint{
Hash: *aliceChanTXID,
Index: chanPointAliceBob.OutputIndex,
}
// Create a new node Carol.
carol := net.NewNode(t.t, "Carol", nil)
defer shutdownAndAssert(net, t, carol)
// Connect Carol to Bob.
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
net.ConnectNodes(ctxt, t.t, carol, net.Bob)
// Open a channel with 100k satoshis between Bob and Carol.
ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout)
chanPointBobCarol := openChannelAndAssert(
ctxt, t, net, net.Bob, carol,
lntest.OpenChannelParams{
Amt: chanAmt,
Private: true,
},
)
defer closeChannelAndAssert(
ctxt, t, net, net.Bob, chanPointBobCarol, false,
)
// Get Bob's funding point.
bobChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointBobCarol)
require.NoError(t.t, err, "unable to get txid")
bobFundPoint := wire.OutPoint{
Hash: *bobChanTXID,
Index: chanPointBobCarol.OutputIndex,
}
// We should have the following topology now,
// Alice <--public:100k--> Bob <--private:100k--> Carol
//
// Now we will create an invoice for Carol.
const paymentAmt = 20000
invoice := &lnrpc.Invoice{
Memo: "routing hints",
Value: paymentAmt,
Private: true,
}
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
resp, err := carol.AddInvoice(ctxt, invoice)
require.NoError(t.t, err, "unable to create invoice for carol")
// Bob now updates the channel edge policy for the private channel.
const (
baseFeeMSat = 33000
)
timeLockDelta := uint32(chainreg.DefaultBitcoinTimeLockDelta)
updateFeeReq := &lnrpc.PolicyUpdateRequest{
BaseFeeMsat: baseFeeMSat,
TimeLockDelta: timeLockDelta,
Scope: &lnrpc.PolicyUpdateRequest_ChanPoint{
ChanPoint: chanPointBobCarol,
},
}
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
_, err = net.Bob.UpdateChannelPolicy(ctxt, updateFeeReq)
require.NoError(t.t, err, "unable to update chan policy")
// Alice pays the invoices. She will use the updated baseFeeMSat in the
// payment
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
payReqs := []string{resp.PaymentRequest}
require.NoError(t.t,
completePaymentRequests(
ctxt, net.Alice, net.Alice.RouterClient, payReqs, true,
), "unable to send payment",
)
// Check that Alice did make the payment with two HTLCs, one failed and
// one succeeded.
ctxt, _ = context.WithTimeout(ctxt, defaultTimeout)
paymentsResp, err := net.Alice.ListPayments(
ctxt, &lnrpc.ListPaymentsRequest{},
)
require.NoError(t.t, err, "failed to obtain payments for Alice")
require.Equal(t.t, 1, len(paymentsResp.Payments), "expected 1 payment")
htlcs := paymentsResp.Payments[0].Htlcs
require.Equal(t.t, 2, len(htlcs), "expected to have 2 HTLCs")
require.Equal(
t.t, lnrpc.HTLCAttempt_FAILED, htlcs[0].Status,
"the first HTLC attempt should fail",
)
require.Equal(
t.t, lnrpc.HTLCAttempt_SUCCEEDED, htlcs[1].Status,
"the second HTLC attempt should succeed",
)
// Carol should have received 20k satoshis from Bob.
assertAmountPaid(t, "Carol(remote) [<=private] Bob(local)",
carol, bobFundPoint, 0, paymentAmt)
// Bob should have sent 20k satoshis to Carol.
assertAmountPaid(t, "Bob(local) [private=>] Carol(remote)",
net.Bob, bobFundPoint, paymentAmt, 0)
// Calcuate the amount in satoshis.
amtExpected := int64(paymentAmt + baseFeeMSat/1000)
// Bob should have received 20k satoshis + fee from Alice.
assertAmountPaid(t, "Bob(remote) <= Alice(local)",
net.Bob, aliceFundPoint, 0, amtExpected)
// Alice should have sent 20k satoshis + fee to Bob.
assertAmountPaid(t, "Alice(local) => Bob(remote)",
net.Alice, aliceFundPoint, amtExpected, 0)
}
// testInvoiceRoutingHints tests that the routing hints for an invoice are
// created properly.
func testInvoiceRoutingHints(net *lntest.NetworkHarness, t *harnessTest) {
ctxb := context.Background()
const chanAmt = btcutil.Amount(100000)
// Throughout this test, we'll be opening a channel between Alice and
// several other parties.
//
// First, we'll create a private channel between Alice and Bob. This
// will be the only channel that will be considered as a routing hint
// throughout this test. We'll include a push amount since we currently
// require channels to have enough remote balance to cover the invoice's
// payment.
ctxt, _ := context.WithTimeout(ctxb, channelOpenTimeout)
chanPointBob := openChannelAndAssert(
ctxt, t, net, net.Alice, net.Bob,
lntest.OpenChannelParams{
Amt: chanAmt,
PushAmt: chanAmt / 2,
Private: true,
},
)
// Then, we'll create Carol's node and open a public channel between her
// and Alice. This channel will not be considered as a routing hint due
// to it being public.
carol := net.NewNode(t.t, "Carol", nil)
defer shutdownAndAssert(net, t, carol)
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
net.ConnectNodes(ctxt, t.t, net.Alice, carol)
ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout)
chanPointCarol := openChannelAndAssert(
ctxt, t, net, net.Alice, carol,
lntest.OpenChannelParams{
Amt: chanAmt,
PushAmt: chanAmt / 2,
},
)
// We'll also create a public channel between Bob and Carol to ensure
// that Bob gets selected as the only routing hint. We do this as
// we should only include routing hints for nodes that are publicly
// advertised, otherwise we'd end up leaking information about nodes
// that wish to stay unadvertised.
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
net.ConnectNodes(ctxt, t.t, net.Bob, carol)
ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout)
chanPointBobCarol := openChannelAndAssert(
ctxt, t, net, net.Bob, carol,
lntest.OpenChannelParams{
Amt: chanAmt,
PushAmt: chanAmt / 2,
},
)
// Then, we'll create Dave's node and open a private channel between him
// and Alice. We will not include a push amount in order to not consider
// this channel as a routing hint as it will not have enough remote
// balance for the invoice's amount.
dave := net.NewNode(t.t, "Dave", nil)
defer shutdownAndAssert(net, t, dave)
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
net.ConnectNodes(ctxt, t.t, net.Alice, dave)
ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout)
chanPointDave := openChannelAndAssert(
ctxt, t, net, net.Alice, dave,
lntest.OpenChannelParams{
Amt: chanAmt,
Private: true,
},
)
// Finally, we'll create Eve's node and open a private channel between
// her and Alice. This time though, we'll take Eve's node down after the
// channel has been created to avoid populating routing hints for
// inactive channels.
eve := net.NewNode(t.t, "Eve", nil)
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
net.ConnectNodes(ctxt, t.t, net.Alice, eve)
ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout)
chanPointEve := openChannelAndAssert(
ctxt, t, net, net.Alice, eve,
lntest.OpenChannelParams{
Amt: chanAmt,
PushAmt: chanAmt / 2,
Private: true,
},
)
// Make sure all the channels have been opened.
chanNames := []string{
"alice-bob", "alice-carol", "bob-carol", "alice-dave",
"alice-eve",
}
aliceChans := []*lnrpc.ChannelPoint{
chanPointBob, chanPointCarol, chanPointBobCarol, chanPointDave,
chanPointEve,
}
for i, chanPoint := range aliceChans {
ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
err := net.Alice.WaitForNetworkChannelOpen(ctxt, chanPoint)
if err != nil {
t.Fatalf("timed out waiting for channel open %s: %v",
chanNames[i], err)
}
}
// Now that the channels are open, we'll take down Eve's node.
shutdownAndAssert(net, t, eve)
// Create an invoice for Alice that will populate the routing hints.
invoice := &lnrpc.Invoice{
Memo: "routing hints",
Value: int64(chanAmt / 4),
Private: true,
}
// Due to the way the channels were set up above, the channel between
// Alice and Bob should be the only channel used as a routing hint.
var predErr error
var decoded *lnrpc.PayReq
err := wait.Predicate(func() bool {
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
resp, err := net.Alice.AddInvoice(ctxt, invoice)
if err != nil {
predErr = fmt.Errorf("unable to add invoice: %v", err)
return false
}
// We'll decode the invoice's payment request to determine which
// channels were used as routing hints.
payReq := &lnrpc.PayReqString{
PayReq: resp.PaymentRequest,
}
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
decoded, err = net.Alice.DecodePayReq(ctxt, payReq)
if err != nil {
predErr = fmt.Errorf("unable to decode payment "+
"request: %v", err)
return false
}
if len(decoded.RouteHints) != 1 {
predErr = fmt.Errorf("expected one route hint, got %d",
len(decoded.RouteHints))
return false
}
return true
}, defaultTimeout)
if err != nil {
t.Fatalf(predErr.Error())
}
hops := decoded.RouteHints[0].HopHints
if len(hops) != 1 {
t.Fatalf("expected one hop in route hint, got %d", len(hops))
}
chanID := hops[0].ChanId
// We'll need the short channel ID of the channel between Alice and Bob
// to make sure the routing hint is for this channel.
listReq := &lnrpc.ListChannelsRequest{}
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
listResp, err := net.Alice.ListChannels(ctxt, listReq)
if err != nil {
t.Fatalf("unable to retrieve alice's channels: %v", err)
}
var aliceBobChanID uint64
for _, channel := range listResp.Channels {
if channel.RemotePubkey == net.Bob.PubKeyStr {
aliceBobChanID = channel.ChanId
}
}
if aliceBobChanID == 0 {
t.Fatalf("channel between alice and bob not found")
}
if chanID != aliceBobChanID {
t.Fatalf("expected channel ID %d, got %d", aliceBobChanID,
chanID)
}
// Now that we've confirmed the routing hints were added correctly, we
// can close all the channels and shut down all the nodes created.
ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout)
closeChannelAndAssert(ctxt, t, net, net.Alice, chanPointBob, false)
ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout)
closeChannelAndAssert(ctxt, t, net, net.Alice, chanPointCarol, false)
ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout)
closeChannelAndAssert(ctxt, t, net, net.Bob, chanPointBobCarol, false)
ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout)
closeChannelAndAssert(ctxt, t, net, net.Alice, chanPointDave, false)
// The channel between Alice and Eve should be force closed since Eve
// is offline.
ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout)
closeChannelAndAssert(ctxt, t, net, net.Alice, chanPointEve, true)
// Cleanup by mining the force close and sweep transaction.
cleanupForceClose(t, net, net.Alice, chanPointEve)
}
// testMultiHopOverPrivateChannels tests that private channels can be used as
// intermediate hops in a route for payments.
func testMultiHopOverPrivateChannels(net *lntest.NetworkHarness, t *harnessTest) {
ctxb := context.Background()
// We'll test that multi-hop payments over private channels work as
// intended. To do so, we'll create the following topology:
// private public private
// Alice <--100k--> Bob <--100k--> Carol <--100k--> Dave
const chanAmt = btcutil.Amount(100000)
// First, we'll open a private channel between Alice and Bob with Alice
// being the funder.
ctxt, _ := context.WithTimeout(ctxb, channelOpenTimeout)
chanPointAlice := openChannelAndAssert(
ctxt, t, net, net.Alice, net.Bob,
lntest.OpenChannelParams{
Amt: chanAmt,
Private: true,
},
)
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
err := net.Alice.WaitForNetworkChannelOpen(ctxt, chanPointAlice)
if err != nil {
t.Fatalf("alice didn't see the channel alice <-> bob before "+
"timeout: %v", err)
}
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
err = net.Bob.WaitForNetworkChannelOpen(ctxt, chanPointAlice)
if err != nil {
t.Fatalf("bob didn't see the channel alice <-> bob before "+
"timeout: %v", err)
}
// Retrieve Alice's funding outpoint.
aliceChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointAlice)
if err != nil {
t.Fatalf("unable to get txid: %v", err)
}
aliceFundPoint := wire.OutPoint{
Hash: *aliceChanTXID,
Index: chanPointAlice.OutputIndex,
}
// Next, we'll create Carol's node and open a public channel between
// her and Bob with Bob being the funder.
carol := net.NewNode(t.t, "Carol", nil)
defer shutdownAndAssert(net, t, carol)
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
net.ConnectNodes(ctxt, t.t, net.Bob, carol)
ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout)
chanPointBob := openChannelAndAssert(
ctxt, t, net, net.Bob, carol,
lntest.OpenChannelParams{
Amt: chanAmt,
},
)
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
err = net.Bob.WaitForNetworkChannelOpen(ctxt, chanPointBob)
if err != nil {
t.Fatalf("bob didn't see the channel bob <-> carol before "+
"timeout: %v", err)
}
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
err = carol.WaitForNetworkChannelOpen(ctxt, chanPointBob)
if err != nil {
t.Fatalf("carol didn't see the channel bob <-> carol before "+
"timeout: %v", err)
}
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
err = net.Alice.WaitForNetworkChannelOpen(ctxt, chanPointBob)
if err != nil {
t.Fatalf("alice didn't see the channel bob <-> carol before "+
"timeout: %v", err)
}
// Retrieve Bob's funding outpoint.
bobChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointBob)
if err != nil {
t.Fatalf("unable to get txid: %v", err)
}
bobFundPoint := wire.OutPoint{
Hash: *bobChanTXID,
Index: chanPointBob.OutputIndex,
}
// Next, we'll create Dave's node and open a private channel between him
// and Carol with Carol being the funder.
dave := net.NewNode(t.t, "Dave", nil)
defer shutdownAndAssert(net, t, dave)
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
net.ConnectNodes(ctxt, t.t, carol, dave)
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
net.SendCoins(ctxt, t.t, btcutil.SatoshiPerBitcoin, carol)
ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout)
chanPointCarol := openChannelAndAssert(
ctxt, t, net, carol, dave,
lntest.OpenChannelParams{
Amt: chanAmt,
Private: true,
},
)
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
err = carol.WaitForNetworkChannelOpen(ctxt, chanPointCarol)
if err != nil {
t.Fatalf("carol didn't see the channel carol <-> dave before "+
"timeout: %v", err)
}
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
err = dave.WaitForNetworkChannelOpen(ctxt, chanPointCarol)
if err != nil {
t.Fatalf("dave didn't see the channel carol <-> dave before "+
"timeout: %v", err)
}
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
err = dave.WaitForNetworkChannelOpen(ctxt, chanPointBob)
if err != nil {
t.Fatalf("dave didn't see the channel bob <-> carol before "+
"timeout: %v", err)
}
// Retrieve Carol's funding point.
carolChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointCarol)
if err != nil {
t.Fatalf("unable to get txid: %v", err)
}
carolFundPoint := wire.OutPoint{
Hash: *carolChanTXID,
Index: chanPointCarol.OutputIndex,
}
// Now that all the channels are set up according to the topology from
// above, we can proceed to test payments. We'll create an invoice for
// Dave of 20k satoshis and pay it with Alice. Since there is no public
// route from Alice to Dave, we'll need to use the private channel
// between Carol and Dave as a routing hint encoded in the invoice.
const paymentAmt = 20000
// Create the invoice for Dave.
invoice := &lnrpc.Invoice{
Memo: "two hopz!",
Value: paymentAmt,
Private: true,
}
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
resp, err := dave.AddInvoice(ctxt, invoice)
if err != nil {
t.Fatalf("unable to add invoice for dave: %v", err)
}
// Let Alice pay the invoice.
payReqs := []string{resp.PaymentRequest}
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
err = completePaymentRequests(
ctxt, net.Alice, net.Alice.RouterClient, payReqs, true,
)
if err != nil {
t.Fatalf("unable to send payments from alice to dave: %v", err)
}
// When asserting the amount of satoshis moved, we'll factor in the
// default base fee, as we didn't modify the fee structure when opening
// the channels.
const baseFee = 1
// Dave should have received 20k satoshis from Carol.
assertAmountPaid(t, "Carol(local) [private=>] Dave(remote)",
dave, carolFundPoint, 0, paymentAmt)
// Carol should have sent 20k satoshis to Dave.
assertAmountPaid(t, "Carol(local) [private=>] Dave(remote)",
carol, carolFundPoint, paymentAmt, 0)
// Carol should have received 20k satoshis + fee for one hop from Bob.
assertAmountPaid(t, "Bob(local) => Carol(remote)",
carol, bobFundPoint, 0, paymentAmt+baseFee)
// Bob should have sent 20k satoshis + fee for one hop to Carol.
assertAmountPaid(t, "Bob(local) => Carol(remote)",
net.Bob, bobFundPoint, paymentAmt+baseFee, 0)
// Bob should have received 20k satoshis + fee for two hops from Alice.
assertAmountPaid(t, "Alice(local) [private=>] Bob(remote)", net.Bob,
aliceFundPoint, 0, paymentAmt+baseFee*2)
// Alice should have sent 20k satoshis + fee for two hops to Bob.
assertAmountPaid(t, "Alice(local) [private=>] Bob(remote)", net.Alice,
aliceFundPoint, paymentAmt+baseFee*2, 0)
// At this point, the payment was successful. We can now close all the
// channels and shutdown the nodes created throughout this test.
ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout)
closeChannelAndAssert(ctxt, t, net, net.Alice, chanPointAlice, false)
ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout)
closeChannelAndAssert(ctxt, t, net, net.Bob, chanPointBob, false)
ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout)
closeChannelAndAssert(ctxt, t, net, carol, chanPointCarol, false)
}
// computeFee calculates the payment fee as specified in BOLT07
func computeFee(baseFee, feeRate, amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {
return baseFee + amt*feeRate/1000000
}
// testQueryRoutes checks the response of queryroutes.
// We'll create the following network topology:
// Alice --> Bob --> Carol --> Dave
// and query the daemon for routes from Alice to Dave.
func testQueryRoutes(net *lntest.NetworkHarness, t *harnessTest) {
ctxb := context.Background()
const chanAmt = btcutil.Amount(100000)
var networkChans []*lnrpc.ChannelPoint
// Open a channel between Alice and Bob.
ctxt, _ := context.WithTimeout(ctxb, channelOpenTimeout)
chanPointAlice := openChannelAndAssert(
ctxt, t, net, net.Alice, net.Bob,
lntest.OpenChannelParams{
Amt: chanAmt,
},
)
networkChans = append(networkChans, chanPointAlice)
// Create Carol and establish a channel from Bob.
carol := net.NewNode(t.t, "Carol", nil)
defer shutdownAndAssert(net, t, carol)
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
net.ConnectNodes(ctxt, t.t, carol, net.Bob)
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
net.SendCoins(ctxt, t.t, btcutil.SatoshiPerBitcoin, net.Bob)
ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout)
chanPointBob := openChannelAndAssert(
ctxt, t, net, net.Bob, carol,
lntest.OpenChannelParams{
Amt: chanAmt,
},
)
networkChans = append(networkChans, chanPointBob)
// Create Dave and establish a channel from Carol.
dave := net.NewNode(t.t, "Dave", nil)
defer shutdownAndAssert(net, t, dave)
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
net.ConnectNodes(ctxt, t.t, dave, carol)
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
net.SendCoins(ctxt, t.t, btcutil.SatoshiPerBitcoin, carol)
ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout)
chanPointCarol := openChannelAndAssert(
ctxt, t, net, carol, dave,
lntest.OpenChannelParams{
Amt: chanAmt,
},
)
networkChans = append(networkChans, chanPointCarol)
// Wait for all nodes to have seen all channels.
nodes := []*lntest.HarnessNode{net.Alice, net.Bob, carol, dave}
nodeNames := []string{"Alice", "Bob", "Carol", "Dave"}
for _, chanPoint := range networkChans {
for i, node := range nodes {
txid, err := lnrpc.GetChanPointFundingTxid(chanPoint)
if err != nil {
t.Fatalf("unable to get txid: %v", err)
}
point := wire.OutPoint{
Hash: *txid,
Index: chanPoint.OutputIndex,
}
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
err = node.WaitForNetworkChannelOpen(ctxt, chanPoint)
if err != nil {
t.Fatalf("%s(%d): timeout waiting for "+
"channel(%s) open: %v", nodeNames[i],
node.NodeID, point, err)
}
}
}
// Query for routes to pay from Alice to Dave.
const paymentAmt = 1000
routesReq := &lnrpc.QueryRoutesRequest{
PubKey: dave.PubKeyStr,
Amt: paymentAmt,
}
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
routesRes, err := net.Alice.QueryRoutes(ctxt, routesReq)
if err != nil {
t.Fatalf("unable to get route: %v", err)
}
const mSat = 1000
feePerHopMSat := computeFee(1000, 1, paymentAmt*mSat)
for i, route := range routesRes.Routes {
expectedTotalFeesMSat :=
lnwire.MilliSatoshi(len(route.Hops)-1) * feePerHopMSat
expectedTotalAmtMSat := (paymentAmt * mSat) + expectedTotalFeesMSat
if route.TotalFees != route.TotalFeesMsat/mSat { // nolint:staticcheck
t.Fatalf("route %v: total fees %v (msat) does not "+
"round down to %v (sat)",
i, route.TotalFeesMsat, route.TotalFees) // nolint:staticcheck
}
if route.TotalFeesMsat != int64(expectedTotalFeesMSat) {
t.Fatalf("route %v: total fees in msat expected %v got %v",
i, expectedTotalFeesMSat, route.TotalFeesMsat)
}
if route.TotalAmt != route.TotalAmtMsat/mSat { // nolint:staticcheck
t.Fatalf("route %v: total amt %v (msat) does not "+
"round down to %v (sat)",
i, route.TotalAmtMsat, route.TotalAmt) // nolint:staticcheck
}
if route.TotalAmtMsat != int64(expectedTotalAmtMSat) {
t.Fatalf("route %v: total amt in msat expected %v got %v",
i, expectedTotalAmtMSat, route.TotalAmtMsat)
}
// For all hops except the last, we check that fee equals feePerHop
// and amount to forward deducts feePerHop on each hop.
expectedAmtToForwardMSat := expectedTotalAmtMSat
for j, hop := range route.Hops[:len(route.Hops)-1] {
expectedAmtToForwardMSat -= feePerHopMSat
if hop.Fee != hop.FeeMsat/mSat { // nolint:staticcheck
t.Fatalf("route %v hop %v: fee %v (msat) does not "+
"round down to %v (sat)",
i, j, hop.FeeMsat, hop.Fee) // nolint:staticcheck
}
if hop.FeeMsat != int64(feePerHopMSat) {
t.Fatalf("route %v hop %v: fee in msat expected %v got %v",
i, j, feePerHopMSat, hop.FeeMsat)
}
if hop.AmtToForward != hop.AmtToForwardMsat/mSat { // nolint:staticcheck
t.Fatalf("route %v hop %v: amt to forward %v (msat) does not "+
"round down to %v (sat)",
i, j, hop.AmtToForwardMsat, hop.AmtToForward) // nolint:staticcheck
}
if hop.AmtToForwardMsat != int64(expectedAmtToForwardMSat) {
t.Fatalf("route %v hop %v: amt to forward in msat "+
"expected %v got %v",
i, j, expectedAmtToForwardMSat, hop.AmtToForwardMsat)
}
}
// Last hop should have zero fee and amount to forward should equal
// payment amount.
hop := route.Hops[len(route.Hops)-1]
if hop.Fee != 0 || hop.FeeMsat != 0 { // nolint:staticcheck
t.Fatalf("route %v hop %v: fee expected 0 got %v (sat) %v (msat)",
i, len(route.Hops)-1, hop.Fee, hop.FeeMsat) // nolint:staticcheck
}
if hop.AmtToForward != hop.AmtToForwardMsat/mSat { // nolint:staticcheck
t.Fatalf("route %v hop %v: amt to forward %v (msat) does not "+
"round down to %v (sat)",
i, len(route.Hops)-1, hop.AmtToForwardMsat, hop.AmtToForward) // nolint:staticcheck
}
if hop.AmtToForwardMsat != paymentAmt*mSat {
t.Fatalf("route %v hop %v: amt to forward in msat "+
"expected %v got %v",
i, len(route.Hops)-1, paymentAmt*mSat, hop.AmtToForwardMsat)
}
}
// While we're here, we test updating mission control's config values
// and assert that they are correctly updated and check that our mission
// control import function updates appropriately.
testMissionControlCfg(t.t, net.Alice)
testMissionControlImport(
t.t, net.Alice, net.Bob.PubKey[:], carol.PubKey[:],
)
// We clean up the test case by closing channels that were created for
// the duration of the tests.
ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout)
closeChannelAndAssert(ctxt, t, net, net.Alice, chanPointAlice, false)
ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout)
closeChannelAndAssert(ctxt, t, net, net.Bob, chanPointBob, false)
ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout)
closeChannelAndAssert(ctxt, t, net, carol, chanPointCarol, false)
}
// testMissionControlCfg tests getting and setting of a node's mission control
// config, resetting to the original values after testing so that no other
// tests are affected.
func testMissionControlCfg(t *testing.T, node *lntest.HarnessNode) {
ctxb := context.Background()
startCfg, err := node.RouterClient.GetMissionControlConfig(
ctxb, &routerrpc.GetMissionControlConfigRequest{},
)
require.NoError(t, err)
cfg := &routerrpc.MissionControlConfig{
HalfLifeSeconds: 8000,
HopProbability: 0.8,
Weight: 0.3,
MaximumPaymentResults: 30,
MinimumFailureRelaxInterval: 60,
}
_, err = node.RouterClient.SetMissionControlConfig(
ctxb, &routerrpc.SetMissionControlConfigRequest{
Config: cfg,
},
)
require.NoError(t, err)
resp, err := node.RouterClient.GetMissionControlConfig(
ctxb, &routerrpc.GetMissionControlConfigRequest{},
)
require.NoError(t, err)
require.True(t, proto.Equal(cfg, resp.Config))
_, err = node.RouterClient.SetMissionControlConfig(
ctxb, &routerrpc.SetMissionControlConfigRequest{
Config: startCfg.Config,
},
)
require.NoError(t, err)
}
// testMissionControlImport tests import of mission control results from an
// external source.
func testMissionControlImport(t *testing.T, node *lntest.HarnessNode,
fromNode, toNode []byte) {
ctxb := context.Background()
// Reset mission control so that our query will return the default
// probability for our first request.
_, err := node.RouterClient.ResetMissionControl(
ctxb, &routerrpc.ResetMissionControlRequest{},
)
require.NoError(t, err, "could not reset mission control")
// Get our baseline probability for a 10 msat hop between our target
// nodes.
var amount int64 = 10
probReq := &routerrpc.QueryProbabilityRequest{
FromNode: fromNode,
ToNode: toNode,
AmtMsat: amount,
}
importHistory := &routerrpc.PairData{
FailTime: time.Now().Unix(),
FailAmtMsat: amount,
}
// Assert that our history is not already equal to the value we want to
// set. This should not happen because we have just cleared our state.
resp1, err := node.RouterClient.QueryProbability(ctxb, probReq)
require.NoError(t, err, "query probability failed")
require.Zero(t, resp1.History.FailTime)
require.Zero(t, resp1.History.FailAmtMsat)
// Now, we import a single entry which tracks a failure of the amount
// we want to query between our nodes.
req := &routerrpc.XImportMissionControlRequest{
Pairs: []*routerrpc.PairHistory{
{
NodeFrom: fromNode,
NodeTo: toNode,
History: importHistory,
},
},
}
_, err = node.RouterClient.XImportMissionControl(ctxb, req)
require.NoError(t, err, "could not import config")
resp2, err := node.RouterClient.QueryProbability(ctxb, probReq)
require.NoError(t, err, "query probability failed")
require.Equal(t, importHistory.FailTime, resp2.History.FailTime)
require.Equal(t, importHistory.FailAmtMsat, resp2.History.FailAmtMsat)
// Finally, check that we will fail if inconsistent sat/msat values are
// set.
importHistory.FailAmtSat = amount * 2
_, err = node.RouterClient.XImportMissionControl(ctxb, req)
require.Error(t, err, "mismatched import amounts succeeded")
}
// testRouteFeeCutoff tests that we are able to prevent querying routes and
// sending payments that incur a fee higher than the fee limit.
func testRouteFeeCutoff(net *lntest.NetworkHarness, t *harnessTest) {
ctxb := context.Background()
// For this test, we'll create the following topology:
//
// --- Bob ---
// / \
// Alice ---- ---- Dave
// \ /
// -- Carol --
//
// Alice will attempt to send payments to Dave that should not incur a
// fee greater than the fee limit expressed as a percentage of the
// amount and as a fixed amount of satoshis.
const chanAmt = btcutil.Amount(100000)
// Open a channel between Alice and Bob.
ctxt, _ := context.WithTimeout(ctxb, channelOpenTimeout)
chanPointAliceBob := openChannelAndAssert(
ctxt, t, net, net.Alice, net.Bob,
lntest.OpenChannelParams{
Amt: chanAmt,
},
)
// Create Carol's node and open a channel between her and Alice with
// Alice being the funder.
carol := net.NewNode(t.t, "Carol", nil)
defer shutdownAndAssert(net, t, carol)
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
net.ConnectNodes(ctxt, t.t, carol, net.Alice)
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
net.SendCoins(ctxt, t.t, btcutil.SatoshiPerBitcoin, carol)
ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout)
chanPointAliceCarol := openChannelAndAssert(
ctxt, t, net, net.Alice, carol,
lntest.OpenChannelParams{
Amt: chanAmt,
},
)
// Create Dave's node and open a channel between him and Bob with Bob
// being the funder.
dave := net.NewNode(t.t, "Dave", nil)
defer shutdownAndAssert(net, t, dave)
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
net.ConnectNodes(ctxt, t.t, dave, net.Bob)
ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout)
chanPointBobDave := openChannelAndAssert(
ctxt, t, net, net.Bob, dave,
lntest.OpenChannelParams{
Amt: chanAmt,
},
)
// Open a channel between Carol and Dave.
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
net.ConnectNodes(ctxt, t.t, carol, dave)
ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout)
chanPointCarolDave := openChannelAndAssert(
ctxt, t, net, carol, dave,
lntest.OpenChannelParams{
Amt: chanAmt,
},
)
// Now that all the channels were set up, we'll wait for all the nodes
// to have seen all the channels.
nodes := []*lntest.HarnessNode{net.Alice, net.Bob, carol, dave}
nodeNames := []string{"alice", "bob", "carol", "dave"}
networkChans := []*lnrpc.ChannelPoint{
chanPointAliceBob, chanPointAliceCarol, chanPointBobDave,
chanPointCarolDave,
}
for _, chanPoint := range networkChans {
for i, node := range nodes {
txid, err := lnrpc.GetChanPointFundingTxid(chanPoint)
if err != nil {
t.Fatalf("unable to get txid: %v", err)
}
outpoint := wire.OutPoint{
Hash: *txid,
Index: chanPoint.OutputIndex,
}
ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
err = node.WaitForNetworkChannelOpen(ctxt, chanPoint)
if err != nil {
t.Fatalf("%s(%d) timed out waiting for "+
"channel(%s) open: %v", nodeNames[i],
node.NodeID, outpoint, err)
}
}
}
// The payments should only be successful across the route:
// Alice -> Bob -> Dave
// Therefore, we'll update the fee policy on Carol's side for the
// channel between her and Dave to invalidate the route:
// Alice -> Carol -> Dave
baseFee := int64(10000)
feeRate := int64(5)
timeLockDelta := uint32(chainreg.DefaultBitcoinTimeLockDelta)
maxHtlc := calculateMaxHtlc(chanAmt)
expectedPolicy := &lnrpc.RoutingPolicy{
FeeBaseMsat: baseFee,
FeeRateMilliMsat: testFeeBase * feeRate,
TimeLockDelta: timeLockDelta,
MinHtlc: 1000, // default value
MaxHtlcMsat: maxHtlc,
}
updateFeeReq := &lnrpc.PolicyUpdateRequest{
BaseFeeMsat: baseFee,
FeeRate: float64(feeRate),
TimeLockDelta: timeLockDelta,
MaxHtlcMsat: maxHtlc,
Scope: &lnrpc.PolicyUpdateRequest_ChanPoint{
ChanPoint: chanPointCarolDave,
},
}
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
if _, err := carol.UpdateChannelPolicy(ctxt, updateFeeReq); err != nil {
t.Fatalf("unable to update chan policy: %v", err)
}
// Wait for Alice to receive the channel update from Carol.
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
aliceSub := subscribeGraphNotifications(ctxt, t, net.Alice)
defer close(aliceSub.quit)
waitForChannelUpdate(
t, aliceSub,
[]expectedChanUpdate{
{carol.PubKeyStr, expectedPolicy, chanPointCarolDave},
},
)
// We'll also need the channel IDs for Bob's channels in order to
// confirm the route of the payments.
listReq := &lnrpc.ListChannelsRequest{}
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
listResp, err := net.Bob.ListChannels(ctxt, listReq)
if err != nil {
t.Fatalf("unable to retrieve bob's channels: %v", err)
}
var aliceBobChanID, bobDaveChanID uint64
for _, channel := range listResp.Channels {
switch channel.RemotePubkey {
case net.Alice.PubKeyStr:
aliceBobChanID = channel.ChanId
case dave.PubKeyStr:
bobDaveChanID = channel.ChanId
}
}
if aliceBobChanID == 0 {
t.Fatalf("channel between alice and bob not found")
}
if bobDaveChanID == 0 {
t.Fatalf("channel between bob and dave not found")
}
hopChanIDs := []uint64{aliceBobChanID, bobDaveChanID}
// checkRoute is a helper closure to ensure the route contains the
// correct intermediate hops.
checkRoute := func(route *lnrpc.Route) {
if len(route.Hops) != 2 {
t.Fatalf("expected two hops, got %d", len(route.Hops))
}
for i, hop := range route.Hops {
if hop.ChanId != hopChanIDs[i] {
t.Fatalf("expected chan id %d, got %d",
hopChanIDs[i], hop.ChanId)
}
}
}
// We'll be attempting to send two payments from Alice to Dave. One will
// have a fee cutoff expressed as a percentage of the amount and the
// other will have it expressed as a fixed amount of satoshis.
const paymentAmt = 100
carolFee := computeFee(lnwire.MilliSatoshi(baseFee), 1, paymentAmt)
// testFeeCutoff is a helper closure that will ensure the different
// types of fee limits work as intended when querying routes and sending
// payments.
testFeeCutoff := func(feeLimit *lnrpc.FeeLimit) {
queryRoutesReq := &lnrpc.QueryRoutesRequest{
PubKey: dave.PubKeyStr,
Amt: paymentAmt,
FeeLimit: feeLimit,
}
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
routesResp, err := net.Alice.QueryRoutes(ctxt, queryRoutesReq)
if err != nil {
t.Fatalf("unable to get routes: %v", err)
}
checkRoute(routesResp.Routes[0])
invoice := &lnrpc.Invoice{Value: paymentAmt}
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
invoiceResp, err := dave.AddInvoice(ctxt, invoice)
if err != nil {
t.Fatalf("unable to create invoice: %v", err)
}
sendReq := &routerrpc.SendPaymentRequest{
PaymentRequest: invoiceResp.PaymentRequest,
TimeoutSeconds: 60,
FeeLimitMsat: noFeeLimitMsat,
}
switch limit := feeLimit.Limit.(type) {
case *lnrpc.FeeLimit_Fixed:
sendReq.FeeLimitMsat = 1000 * limit.Fixed
case *lnrpc.FeeLimit_Percent:
sendReq.FeeLimitMsat = 1000 * paymentAmt * limit.Percent / 100
}
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
result := sendAndAssertSuccess(ctxt, t, net.Alice, sendReq)
checkRoute(result.Htlcs[0].Route)
}
// We'll start off using percentages first. Since the fee along the
// route using Carol as an intermediate hop is 10% of the payment's
// amount, we'll use a lower percentage in order to invalid that route.
feeLimitPercent := &lnrpc.FeeLimit{
Limit: &lnrpc.FeeLimit_Percent{
Percent: baseFee/1000 - 1,
},
}
testFeeCutoff(feeLimitPercent)
// Now we'll test using fixed fee limit amounts. Since we computed the
// fee for the route using Carol as an intermediate hop earlier, we can
// use a smaller value in order to invalidate that route.
feeLimitFixed := &lnrpc.FeeLimit{
Limit: &lnrpc.FeeLimit_Fixed{
Fixed: int64(carolFee.ToSatoshis()) - 1,
},
}
testFeeCutoff(feeLimitFixed)
// Once we're done, close the channels and shut down the nodes created
// throughout this test.
ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout)
closeChannelAndAssert(ctxt, t, net, net.Alice, chanPointAliceBob, false)
ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout)
closeChannelAndAssert(ctxt, t, net, net.Alice, chanPointAliceCarol, false)
ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout)
closeChannelAndAssert(ctxt, t, net, net.Bob, chanPointBobDave, false)
ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout)
closeChannelAndAssert(ctxt, t, net, carol, chanPointCarolDave, false)
}