lnd/lntest/itest/lnd_zero_conf_test.go
2022-10-27 01:11:31 +08:00

1071 lines
30 KiB
Go

package itest
import (
"context"
"testing"
"time"
"github.com/btcsuite/btcd/btcutil"
"github.com/go-errors/errors"
"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"
)
// testZeroConfChannelOpen tests that opening a zero-conf channel works and
// sending payments also works.
func testZeroConfChannelOpen(net *lntest.NetworkHarness, t *harnessTest) {
ctxb := context.Background()
// Since option-scid-alias is opt-in, the provided harness nodes will
// not have the feature bit set. Also need to set anchors as those are
// default-off in itests.
scidAliasArgs := []string{
"--protocol.option-scid-alias",
"--protocol.zero-conf",
"--protocol.anchors",
}
carol := net.NewNode(t.t, "Carol", scidAliasArgs)
defer shutdownAndAssert(net, t, carol)
// We'll open a regular public channel between Bob and Carol here.
net.EnsureConnected(t.t, net.Bob, carol)
chanAmt := btcutil.Amount(1_000_000)
fundingPoint := openChannelAndAssert(
t, net, net.Bob, carol,
lntest.OpenChannelParams{
Amt: chanAmt,
},
)
// Wait for both Bob and Carol to view the channel as active.
err := net.Bob.WaitForNetworkChannelOpen(fundingPoint)
require.NoError(t.t, err, "bob didn't report channel")
err = carol.WaitForNetworkChannelOpen(fundingPoint)
require.NoError(t.t, err, "carol didn't report channel")
// Spin-up Dave so Carol can open a zero-conf channel to him.
dave := net.NewNode(t.t, "Dave", scidAliasArgs)
defer shutdownAndAssert(net, t, dave)
// We'll give Carol some coins in order to fund the channel.
net.SendCoins(t.t, btcutil.SatoshiPerBitcoin, carol)
// Ensure that both Carol and Dave are connected.
net.EnsureConnected(t.t, carol, dave)
// Setup a ChannelAcceptor for Dave.
ctxc, cancel := context.WithCancel(ctxb)
acceptStream, err := dave.ChannelAcceptor(ctxc)
require.NoError(t.t, err)
go acceptChannel(t.t, true, acceptStream)
// Open a private zero-conf anchors channel of 1M satoshis.
params := lntest.OpenChannelParams{
Amt: chanAmt,
Private: true,
CommitmentType: lnrpc.CommitmentType_ANCHORS,
ZeroConf: true,
}
chanOpenUpdate := openChannelStream(t, net, carol, dave, params)
// Remove the ChannelAcceptor.
cancel()
// We should receive the OpenStatusUpdate_ChanOpen update without
// having to mine any blocks.
fundingPoint2, err := net.WaitForChannelOpen(chanOpenUpdate)
require.NoError(t.t, err, "error while waiting for channel open")
err = carol.WaitForNetworkChannelOpen(fundingPoint2)
require.NoError(t.t, err, "carol didn't report channel")
err = dave.WaitForNetworkChannelOpen(fundingPoint2)
require.NoError(t.t, err, "dave didn't report channel")
// Attempt to send a 10K satoshi payment from Carol to Dave.
daveInvoiceParams := &lnrpc.Invoice{
Value: int64(10_000),
Private: true,
}
daveInvoiceResp, err := dave.AddInvoice(
ctxb, daveInvoiceParams,
)
require.NoError(t.t, err, "unable to add invoice")
_ = sendAndAssertSuccess(
t, carol, &routerrpc.SendPaymentRequest{
PaymentRequest: daveInvoiceResp.PaymentRequest,
TimeoutSeconds: 60,
FeeLimitMsat: noFeeLimitMsat,
},
)
// Now attempt to send a multi-hop payment from Bob to Dave. This tests
// that Dave issues an invoice with an alias SCID that Carol knows and
// uses to forward to Dave.
daveInvoiceResp2, err := dave.AddInvoice(ctxb, daveInvoiceParams)
require.NoError(t.t, err, "unable to add invoice")
_ = sendAndAssertSuccess(
t, net.Bob, &routerrpc.SendPaymentRequest{
PaymentRequest: daveInvoiceResp2.PaymentRequest,
TimeoutSeconds: 60,
FeeLimitMsat: noFeeLimitMsat,
},
)
// Check that Dave has a zero-conf alias SCID in the graph.
descReq := &lnrpc.ChannelGraphRequest{
IncludeUnannounced: true,
}
err = waitForZeroConfGraphChange(ctxb, dave, descReq, true)
require.NoError(t.t, err)
// We'll now confirm the zero-conf channel between Carol and Dave and
// assert that sending is still possible.
block := mineBlocks(t, net, 6, 1)[0]
// Dave should still have the alias edge in his db.
err = waitForZeroConfGraphChange(ctxb, dave, descReq, true)
require.NoError(t.t, err)
fundingTxID, err := lnrpc.GetChanPointFundingTxid(fundingPoint2)
require.NoError(t.t, err, "unable to get txid")
assertTxInBlock(t, block, fundingTxID)
daveInvoiceResp3, err := dave.AddInvoice(
ctxb, daveInvoiceParams,
)
require.NoError(t.t, err, "unable to add invoice")
_ = sendAndAssertSuccess(
t, net.Bob, &routerrpc.SendPaymentRequest{
PaymentRequest: daveInvoiceResp3.PaymentRequest,
TimeoutSeconds: 60,
FeeLimitMsat: noFeeLimitMsat,
},
)
// Eve will now initiate a zero-conf channel with Carol. This tests
// that the ChannelUpdates sent are correct since they will be
// referring to different alias SCIDs.
eve := net.NewNode(t.t, "Eve", scidAliasArgs)
defer shutdownAndAssert(net, t, eve)
net.EnsureConnected(t.t, eve, carol)
// Give Eve some coins to fund the channel.
net.SendCoins(t.t, btcutil.SatoshiPerBitcoin, eve)
// Setup a ChannelAcceptor.
ctxc, cancel = context.WithCancel(ctxb)
acceptStream, err = carol.ChannelAcceptor(ctxc)
require.NoError(t.t, err)
go acceptChannel(t.t, true, acceptStream)
// We'll open a public zero-conf anchors channel of 1M satoshis.
params.Private = false
chanOpenUpdate2 := openChannelStream(t, net, eve, carol, params)
// Remove the ChannelAcceptor.
cancel()
// Wait to receive the OpenStatusUpdate_ChanOpen update.
fundingPoint3, err := net.WaitForChannelOpen(chanOpenUpdate2)
require.NoError(t.t, err, "error while waiting for channel open")
err = eve.WaitForNetworkChannelOpen(fundingPoint3)
require.NoError(t.t, err, "eve didn't report channel")
err = carol.WaitForNetworkChannelOpen(fundingPoint3)
require.NoError(t.t, err, "carol didn't report channel")
// Attempt to send a 20K satoshi payment from Eve to Dave.
daveInvoiceParams.Value = int64(20_000)
daveInvoiceResp4, err := dave.AddInvoice(
ctxb, daveInvoiceParams,
)
require.NoError(t.t, err, "unable to add invoice")
_ = sendAndAssertSuccess(
t, eve, &routerrpc.SendPaymentRequest{
PaymentRequest: daveInvoiceResp4.PaymentRequest,
TimeoutSeconds: 60,
FeeLimitMsat: noFeeLimitMsat,
},
)
// Assert that Eve has stored the zero-conf alias in her graph.
err = waitForZeroConfGraphChange(ctxb, eve, descReq, true)
require.NoError(t.t, err)
// We'll confirm the zero-conf channel between Eve and Carol and assert
// that sending is still possible.
block = mineBlocks(t, net, 6, 1)[0]
fundingTxID, err = lnrpc.GetChanPointFundingTxid(fundingPoint3)
require.NoError(t.t, err, "unable to get txid")
assertTxInBlock(t, block, fundingTxID)
// Wait until Eve's ZeroConf channel is replaced by the confirmed SCID
// in her graph.
err = waitForZeroConfGraphChange(ctxb, eve, descReq, false)
require.NoError(t.t, err, "expected to not receive error")
// Attempt to send a 6K satoshi payment from Dave to Eve.
eveInvoiceParams := &lnrpc.Invoice{
Value: int64(6_000),
Private: true,
}
eveInvoiceResp, err := eve.AddInvoice(ctxb, eveInvoiceParams)
require.NoError(t.t, err, "unable to add invoice")
// Assert that route hints is empty since the channel is public.
payReq, err := eve.DecodePayReq(ctxb, &lnrpc.PayReqString{
PayReq: eveInvoiceResp.PaymentRequest,
})
require.NoError(t.t, err)
require.True(t.t, len(payReq.RouteHints) == 0)
_ = sendAndAssertSuccess(
t, dave, &routerrpc.SendPaymentRequest{
PaymentRequest: eveInvoiceResp.PaymentRequest,
TimeoutSeconds: 60,
FeeLimitMsat: noFeeLimitMsat,
},
)
}
// testOptionScidAlias checks that opening an option_scid_alias channel-type
// channel or w/o the channel-type works properly.
func testOptionScidAlias(net *lntest.NetworkHarness, t *harnessTest) {
type scidTestCase struct {
name string
// If this is false, then the channel will be a regular non
// channel-type option-scid-alias-feature-bit channel.
chantype bool
private bool
}
var testCases = []scidTestCase{
{
name: "private chan-type",
chantype: true,
private: true,
},
{
name: "public no chan-type",
chantype: false,
private: false,
},
{
name: "private no chan-type",
chantype: false,
private: true,
},
}
for _, testCase := range testCases {
testCase := testCase
success := t.t.Run(testCase.name, func(t *testing.T) {
h := newHarnessTest(t, net)
optionScidAliasScenario(
net, h, testCase.chantype, testCase.private,
)
})
if !success {
break
}
}
}
func optionScidAliasScenario(net *lntest.NetworkHarness, t *harnessTest,
chantype, private bool) {
ctxb := context.Background()
// Option-scid-alias is opt-in, as is anchors.
scidAliasArgs := []string{
"--protocol.option-scid-alias",
"--protocol.anchors",
}
carol := net.NewNode(t.t, "Carol", scidAliasArgs)
defer shutdownAndAssert(net, t, carol)
dave := net.NewNode(t.t, "Dave", scidAliasArgs)
defer shutdownAndAssert(net, t, dave)
// Ensure Bob, Carol are connected.
net.EnsureConnected(t.t, net.Bob, carol)
// Ensure Carol, Dave are connected.
net.EnsureConnected(t.t, carol, dave)
// Give Carol some coins so she can open the channel.
net.SendCoins(t.t, btcutil.SatoshiPerBitcoin, carol)
chanAmt := btcutil.Amount(1_000_000)
params := lntest.OpenChannelParams{
Amt: chanAmt,
Private: private,
CommitmentType: lnrpc.CommitmentType_ANCHORS,
ScidAlias: chantype,
}
fundingPoint := openChannelAndAssert(t, net, carol, dave, params)
if !private {
err := net.Bob.WaitForNetworkChannelOpen(fundingPoint)
require.NoError(t.t, err, "bob didn't report channel")
}
err := carol.WaitForNetworkChannelOpen(fundingPoint)
require.NoError(t.t, err, "carol didn't report channel")
err = dave.WaitForNetworkChannelOpen(fundingPoint)
require.NoError(t.t, err, "dave didn't report channel")
// Assert that a payment from Carol to Dave works as expected.
daveInvoiceParams := &lnrpc.Invoice{
Value: int64(10_000),
Private: true,
}
daveInvoiceResp, err := dave.AddInvoice(ctxb, daveInvoiceParams)
require.NoError(t.t, err)
_ = sendAndAssertSuccess(
t, carol, &routerrpc.SendPaymentRequest{
PaymentRequest: daveInvoiceResp.PaymentRequest,
TimeoutSeconds: 60,
FeeLimitMsat: noFeeLimitMsat,
},
)
// We'll now open a regular public channel between Bob and Carol and
// assert that Bob can pay Dave. We'll also assert that the invoice
// Dave issues has the startingAlias as a hop hint.
fundingPoint2 := openChannelAndAssert(
t, net, net.Bob, carol,
lntest.OpenChannelParams{
Amt: chanAmt,
},
)
err = net.Bob.WaitForNetworkChannelOpen(fundingPoint2)
require.NoError(t.t, err)
err = carol.WaitForNetworkChannelOpen(fundingPoint2)
require.NoError(t.t, err)
// Wait until Dave receives the Bob<->Carol channel.
err = dave.WaitForNetworkChannelOpen(fundingPoint2)
require.NoError(t.t, err)
daveInvoiceResp2, err := dave.AddInvoice(ctxb, daveInvoiceParams)
require.NoError(t.t, err)
davePayReq := &lnrpc.PayReqString{
PayReq: daveInvoiceResp2.PaymentRequest,
}
decodedReq, err := dave.DecodePayReq(ctxb, davePayReq)
require.NoError(t.t, err)
if !private {
require.Equal(t.t, 0, len(decodedReq.RouteHints))
payReq := daveInvoiceResp2.PaymentRequest
_ = sendAndAssertSuccess(
t, net.Bob, &routerrpc.SendPaymentRequest{
PaymentRequest: payReq,
TimeoutSeconds: 60,
FeeLimitMsat: noFeeLimitMsat,
},
)
return
}
require.Equal(t.t, 1, len(decodedReq.RouteHints))
require.Equal(t.t, 1, len(decodedReq.RouteHints[0].HopHints))
startingAlias := lnwire.ShortChannelID{
BlockHeight: 16_000_000,
TxIndex: 0,
TxPosition: 0,
}
daveHopHint := decodedReq.RouteHints[0].HopHints[0].ChanId
require.Equal(t.t, startingAlias.ToUint64(), daveHopHint)
_ = sendAndAssertSuccess(
t, net.Bob, &routerrpc.SendPaymentRequest{
PaymentRequest: daveInvoiceResp2.PaymentRequest,
TimeoutSeconds: 60,
FeeLimitMsat: noFeeLimitMsat,
},
)
}
// waitForZeroConfGraphChange waits for the zero-conf channel to be visible in
// the graph after confirmation or not. The expect argument denotes whether the
// zero-conf is expected in the graph or not. There should always be at least
// one channel of the passed HarnessNode, zero-conf or not.
func waitForZeroConfGraphChange(ctxb context.Context, n *lntest.HarnessNode,
req *lnrpc.ChannelGraphRequest, expect bool) error {
return wait.NoError(func() error {
ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
graph, err := n.DescribeGraph(ctxt, req)
if err != nil {
return err
}
if expect {
// If we expect a zero-conf channel, we'll assert that
// one exists, both policies exist, and we are party to
// the channel.
for _, e := range graph.Edges {
// The BlockHeight will be less than 16_000_000
// if this is not a zero-conf channel.
scid := lnwire.NewShortChanIDFromInt(
e.ChannelId,
)
if scid.BlockHeight < 16_000_000 {
continue
}
// Both edge policies must exist in the zero
// conf case.
if e.Node1Policy == nil ||
e.Node2Policy == nil {
continue
}
// Check if we are party to the zero-conf
// channel.
if e.Node1Pub == n.PubKeyStr ||
e.Node2Pub == n.PubKeyStr {
return nil
}
}
return errors.New("failed to find zero-conf channel " +
"in graph")
}
// If we don't expect a zero-conf channel, we'll assert that
// none exist, that we have a non-zero-conf channel with at
// both policies, and one of the policies in the database is
// ours.
for _, e := range graph.Edges {
scid := lnwire.NewShortChanIDFromInt(e.ChannelId)
if scid.BlockHeight == 16_000_000 {
return errors.New("found zero-conf channel")
}
// One of the edge policies must exist.
if e.Node1Policy == nil || e.Node2Policy == nil {
continue
}
// If we are part of this channel, exit gracefully.
if e.Node1Pub == n.PubKeyStr ||
e.Node2Pub == n.PubKeyStr {
return nil
}
}
return errors.New(
"failed to find non-zero-conf channel in graph",
)
}, defaultTimeout)
}
// testUpdateChannelPolicyScidAlias checks that option-scid-alias, zero-conf
// channel-types, and option-scid-alias feature-bit-only channels have the
// expected graph and that payments work when updating the channel policy.
func testUpdateChannelPolicyScidAlias(net *lntest.NetworkHarness,
t *harnessTest) {
tests := []struct {
name string
// The option-scid-alias channel type.
scidAliasType bool
// The zero-conf channel type.
zeroConf bool
private bool
}{
{
name: "private scid-alias chantype update",
scidAliasType: true,
private: true,
},
{
name: "private zero-conf update",
zeroConf: true,
private: true,
},
{
name: "public zero-conf update",
zeroConf: true,
},
{
name: "public no-chan-type update",
},
{
name: "private no-chan-type update",
private: true,
},
}
for _, test := range tests {
test := test
success := t.t.Run(test.name, func(t *testing.T) {
ht := newHarnessTest(t, net)
testPrivateUpdateAlias(
net, ht, test.zeroConf, test.scidAliasType,
test.private,
)
})
if !success {
return
}
}
}
func testPrivateUpdateAlias(net *lntest.NetworkHarness, t *harnessTest,
zeroConf, scidAliasType, private bool) {
ctxb := context.Background()
defer ctxb.Done()
// We'll create a new node Eve that will not have option-scid-alias
// channels.
eve := net.NewNode(t.t, "Eve", nil)
net.SendCoins(t.t, btcutil.SatoshiPerBitcoin, eve)
defer shutdownAndAssert(net, t, eve)
// Since option-scid-alias is opt-in we'll need to specify the protocol
// arguments when creating a new node.
scidAliasArgs := []string{
"--protocol.option-scid-alias",
"--protocol.zero-conf",
"--protocol.anchors",
}
carol := net.NewNode(t.t, "Carol", scidAliasArgs)
defer shutdownAndAssert(net, t, carol)
// Spin-up Dave who will have an option-scid-alias feature-bit-only or
// channel-type channel with Carol.
dave := net.NewNode(t.t, "Dave", scidAliasArgs)
defer shutdownAndAssert(net, t, dave)
// We'll give Carol some coins in order to fund the channel.
net.SendCoins(t.t, btcutil.SatoshiPerBitcoin, carol)
// Ensure that Carol and Dave are connected.
net.EnsureConnected(t.t, carol, dave)
// We'll open a regular public channel between Eve and Carol here. Eve
// will be the one receiving the onion-encrypted ChannelUpdate.
net.EnsureConnected(t.t, eve, carol)
chanAmt := btcutil.Amount(1_000_000)
fundingPoint := openChannelAndAssert(
t, net, eve, carol,
lntest.OpenChannelParams{
Amt: chanAmt,
PushAmt: chanAmt / 2,
},
)
defer closeChannelAndAssert(t, net, eve, fundingPoint, false)
// Wait for all to view the channel as active.
err := eve.WaitForNetworkChannelOpen(fundingPoint)
require.NoError(t.t, err, "eve didn't report channel")
err = carol.WaitForNetworkChannelOpen(fundingPoint)
require.NoError(t.t, err, "carol didn't report channel")
err = dave.WaitForNetworkChannelOpen(fundingPoint)
require.NoError(t.t, err, "dave didn't report channel")
// Setup a ChannelAcceptor for Dave.
ctxc, cancel := context.WithCancel(ctxb)
acceptStream, err := dave.ChannelAcceptor(ctxc)
require.NoError(t.t, err)
go acceptChannel(t.t, zeroConf, acceptStream)
// Open a private channel, optionally specifying a channel-type.
params := lntest.OpenChannelParams{
Amt: chanAmt,
Private: private,
CommitmentType: lnrpc.CommitmentType_ANCHORS,
ZeroConf: zeroConf,
ScidAlias: scidAliasType,
PushAmt: chanAmt / 2,
}
chanOpenUpdate := openChannelStream(t, net, carol, dave, params)
// Remove the ChannelAcceptor.
cancel()
if !zeroConf {
// If this is not a zero-conf channel, mine a single block to
// confirm the channel.
_ = mineBlocks(t, net, 1, 1)
}
// Wait for both Carol and Dave to see the channel as open.
fundingPoint2, err := net.WaitForChannelOpen(chanOpenUpdate)
require.NoError(t.t, err, "error while waiting for channel open")
err = carol.WaitForNetworkChannelOpen(fundingPoint2)
require.NoError(t.t, err, "carol didn't report channel")
err = dave.WaitForNetworkChannelOpen(fundingPoint2)
require.NoError(t.t, err, "dave didn't report channel")
// Carol will now update the channel edge policy for her channel with
// Dave.
baseFeeMSat := 33000
feeRate := int64(5)
timeLockDelta := uint32(chainreg.DefaultBitcoinTimeLockDelta)
updateFeeReq := &lnrpc.PolicyUpdateRequest{
BaseFeeMsat: int64(baseFeeMSat),
FeeRate: float64(feeRate),
TimeLockDelta: timeLockDelta,
Scope: &lnrpc.PolicyUpdateRequest_ChanPoint{
ChanPoint: fundingPoint2,
},
}
ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
_, err = carol.UpdateChannelPolicy(ctxt, updateFeeReq)
require.NoError(t.t, err, "unable to update chan policy")
expectedPolicy := &lnrpc.RoutingPolicy{
FeeBaseMsat: int64(baseFeeMSat),
FeeRateMilliMsat: testFeeBase * feeRate,
TimeLockDelta: timeLockDelta,
MinHtlc: 1000, // default value
MaxHtlcMsat: calculateMaxHtlc(chanAmt),
}
// Assert that Dave receives Carol's policy update.
assertChannelPolicyUpdate(
t.t, dave, carol.PubKeyStr, expectedPolicy, fundingPoint2,
true,
)
// Have Dave also update his policy.
baseFeeMSat = 15000
feeRate = int64(4)
updateFeeReq = &lnrpc.PolicyUpdateRequest{
BaseFeeMsat: int64(baseFeeMSat),
FeeRate: float64(feeRate),
TimeLockDelta: timeLockDelta,
Scope: &lnrpc.PolicyUpdateRequest_ChanPoint{
ChanPoint: fundingPoint2,
},
}
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
_, err = dave.UpdateChannelPolicy(ctxt, updateFeeReq)
require.NoError(t.t, err, "unable to update chan policy")
expectedPolicy = &lnrpc.RoutingPolicy{
FeeBaseMsat: int64(baseFeeMSat),
FeeRateMilliMsat: testFeeBase * feeRate,
TimeLockDelta: timeLockDelta,
MinHtlc: 1000,
MaxHtlcMsat: calculateMaxHtlc(chanAmt),
}
// Assert that Carol receives Dave's policy update.
assertChannelPolicyUpdate(
t.t, carol, dave.PubKeyStr, expectedPolicy, fundingPoint2,
true,
)
// Assert that if Dave disables the channel, Carol sees it.
disableReq := &routerrpc.UpdateChanStatusRequest{
ChanPoint: fundingPoint2,
Action: routerrpc.ChanStatusAction_DISABLE,
}
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
_, err = dave.RouterClient.UpdateChanStatus(ctxt, disableReq)
require.NoError(t.t, err)
davePolicy := getChannelPolicies(
t, carol, dave.PubKeyStr, fundingPoint2,
)[0]
davePolicy.Disabled = true
assertChannelPolicyUpdate(
t.t, carol, dave.PubKeyStr, davePolicy, fundingPoint2, true,
)
// Assert that if Dave enables the channel, Carol sees it.
enableReq := &routerrpc.UpdateChanStatusRequest{
ChanPoint: fundingPoint2,
Action: routerrpc.ChanStatusAction_ENABLE,
}
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
_, err = dave.RouterClient.UpdateChanStatus(ctxt, enableReq)
require.NoError(t.t, err)
davePolicy.Disabled = false
assertChannelPolicyUpdate(
t.t, carol, dave.PubKeyStr, davePolicy, fundingPoint2, true,
)
// Create an invoice for Carol to pay.
invoiceParams := &lnrpc.Invoice{
Value: int64(10_000),
Private: true,
}
daveInvoiceResp, err := dave.AddInvoice(ctxb, invoiceParams)
require.NoError(t.t, err, "unable to add invoice")
// Carol will attempt to send Dave an HTLC.
payReqs := []string{daveInvoiceResp.PaymentRequest}
require.NoError(
t.t, completePaymentRequests(
carol, carol.RouterClient, payReqs, true,
), "unable to send payment",
)
// Now Eve will create an invoice that Dave will pay.
eveInvoiceResp, err := eve.AddInvoice(ctxb, invoiceParams)
require.NoError(t.t, err, "unable to add invoice")
payReqs = []string{eveInvoiceResp.PaymentRequest}
require.NoError(
t.t, completePaymentRequests(
dave, dave.RouterClient, payReqs, true,
), "unable to send payment",
)
// If this is a public channel, it won't be included in the hop hints,
// so we'll mine enough for 6 confs here. We only expect a tx in the
// mempool for the zero-conf case.
if !private {
var expectTx int
if zeroConf {
expectTx = 1
}
_ = mineBlocks(t, net, 6, expectTx)
// Sleep here so that the edge can be deleted and re-inserted.
// This is necessary since the edge may have a policy for the
// peer that is "correct" but has an invalid signature from the
// PoV of BOLT#7.
time.Sleep(time.Second * 5)
}
// Dave creates an invoice that Eve will pay.
daveInvoiceResp2, err := dave.AddInvoice(ctxb, invoiceParams)
require.NoError(t.t, err, "unable to add invoice")
// Carol then updates the channel policy again.
feeRate = int64(2)
updateFeeReq = &lnrpc.PolicyUpdateRequest{
BaseFeeMsat: int64(baseFeeMSat),
FeeRate: float64(feeRate),
TimeLockDelta: timeLockDelta,
Scope: &lnrpc.PolicyUpdateRequest_ChanPoint{
ChanPoint: fundingPoint2,
},
}
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
_, err = carol.UpdateChannelPolicy(ctxt, updateFeeReq)
require.NoError(t.t, err, "unable to update chan policy")
expectedPolicy = &lnrpc.RoutingPolicy{
FeeBaseMsat: int64(baseFeeMSat),
FeeRateMilliMsat: testFeeBase * feeRate,
TimeLockDelta: timeLockDelta,
MinHtlc: 1000,
MaxHtlcMsat: calculateMaxHtlc(chanAmt),
}
// Assert Dave receives Carol's policy update.
assertChannelPolicyUpdate(
t.t, dave, carol.PubKeyStr, expectedPolicy, fundingPoint2,
true,
)
// If the channel is public, check that Eve receives Carol's policy
// update.
if !private {
assertChannelPolicyUpdate(
t.t, eve, carol.PubKeyStr, expectedPolicy,
fundingPoint2, true,
)
}
// Eve will pay Dave's invoice and should use the updated base fee.
payReqs = []string{daveInvoiceResp2.PaymentRequest}
require.NoError(
t.t, completePaymentRequests(
eve, eve.RouterClient, payReqs, true,
), "unable to send payment",
)
// Eve will issue an invoice that Dave will pay.
eveInvoiceResp2, err := eve.AddInvoice(ctxb, invoiceParams)
require.NoError(t.t, err, "unable to add invoice")
payReqs = []string{eveInvoiceResp2.PaymentRequest}
require.NoError(
t.t, completePaymentRequests(
dave, dave.RouterClient, payReqs, true,
), "unable to send payment",
)
// If this is a private channel, we'll mine 6 blocks here to test the
// funding manager logic that deals with ChannelUpdates. If this is not
// a zero-conf channel, we don't expect a tx in the mempool.
if private {
var expectTx int
if zeroConf {
expectTx = 1
}
_ = mineBlocks(t, net, 6, expectTx)
}
// Dave will issue an invoice and Eve will pay it.
daveInvoiceResp3, err := dave.AddInvoice(ctxb, invoiceParams)
require.NoError(t.t, err, "unable to add invoice")
payReqs = []string{daveInvoiceResp3.PaymentRequest}
require.NoError(
t.t, completePaymentRequests(
eve, eve.RouterClient, payReqs, true,
), "unable to send payment",
)
// Carol will disable the channel, assert that Dave sees it and Eve as
// well if the channel is public.
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
_, err = carol.RouterClient.UpdateChanStatus(ctxt, disableReq)
require.NoError(t.t, err)
carolPolicy := getChannelPolicies(
t, dave, carol.PubKeyStr, fundingPoint2,
)[0]
carolPolicy.Disabled = true
assertChannelPolicyUpdate(
t.t, dave, carol.PubKeyStr, carolPolicy, fundingPoint2, true,
)
if !private {
assertChannelPolicyUpdate(
t.t, eve, carol.PubKeyStr, carolPolicy, fundingPoint2,
true,
)
}
// Carol will enable the channel, assert the same as above.
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
_, err = carol.RouterClient.UpdateChanStatus(ctxt, enableReq)
require.NoError(t.t, err)
carolPolicy.Disabled = false
assertChannelPolicyUpdate(
t.t, dave, carol.PubKeyStr, carolPolicy, fundingPoint2, true,
)
if !private {
assertChannelPolicyUpdate(
t.t, eve, carol.PubKeyStr, carolPolicy, fundingPoint2,
true,
)
}
// Dave will issue an invoice and Eve should pay it after Carol updates
// her channel policy.
daveInvoiceResp4, err := dave.AddInvoice(ctxb, invoiceParams)
require.NoError(t.t, err, "unable to add invoice")
feeRate = int64(3)
updateFeeReq = &lnrpc.PolicyUpdateRequest{
BaseFeeMsat: int64(baseFeeMSat),
FeeRate: float64(feeRate),
TimeLockDelta: timeLockDelta,
Scope: &lnrpc.PolicyUpdateRequest_ChanPoint{
ChanPoint: fundingPoint2,
},
}
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
_, err = carol.UpdateChannelPolicy(ctxt, updateFeeReq)
require.NoError(t.t, err, "unable to update chan policy")
expectedPolicy = &lnrpc.RoutingPolicy{
FeeBaseMsat: int64(baseFeeMSat),
FeeRateMilliMsat: testFeeBase * feeRate,
TimeLockDelta: timeLockDelta,
MinHtlc: 1000,
MaxHtlcMsat: calculateMaxHtlc(chanAmt),
}
// Assert Dave and optionally Eve receives Carol's update.
assertChannelPolicyUpdate(
t.t, dave, carol.PubKeyStr, expectedPolicy, fundingPoint2,
true,
)
if !private {
assertChannelPolicyUpdate(
t.t, eve, carol.PubKeyStr, expectedPolicy,
fundingPoint2, true,
)
}
payReqs = []string{daveInvoiceResp4.PaymentRequest}
require.NoError(
t.t, completePaymentRequests(
eve, eve.RouterClient, payReqs, true,
), "unable to send payment",
)
}
// testOptionScidUpgrade tests that toggling the option-scid-alias feature bit
// correctly upgrades existing channels.
func testOptionScidUpgrade(net *lntest.NetworkHarness, t *harnessTest) {
ctxb := context.Background()
// Start carol with anchors only.
carolArgs := []string{
"--protocol.anchors",
}
carol := net.NewNode(t.t, "carol", carolArgs)
// Start dave with anchors + scid-alias.
daveArgs := []string{
"--protocol.anchors",
"--protocol.option-scid-alias",
}
dave := net.NewNode(t.t, "dave", daveArgs)
defer shutdownAndAssert(net, t, dave)
// Give carol some coins.
net.SendCoins(t.t, btcutil.SatoshiPerBitcoin, carol)
// Ensure carol and are connected.
net.EnsureConnected(t.t, carol, dave)
chanAmt := btcutil.Amount(1_000_000)
fundingPoint := openChannelAndAssert(
t, net, carol, dave,
lntest.OpenChannelParams{
Amt: chanAmt,
PushAmt: chanAmt / 2,
Private: true,
},
)
defer closeChannelAndAssert(t, net, carol, fundingPoint, false)
err := carol.WaitForNetworkChannelOpen(fundingPoint)
require.NoError(t.t, err)
err = dave.WaitForNetworkChannelOpen(fundingPoint)
require.NoError(t.t, err)
// Bob will open a channel to Carol now.
net.EnsureConnected(t.t, net.Bob, carol)
fundingPoint2 := openChannelAndAssert(
t, net, net.Bob, carol,
lntest.OpenChannelParams{
Amt: chanAmt,
},
)
err = net.Bob.WaitForNetworkChannelOpen(fundingPoint2)
require.NoError(t.t, err)
err = carol.WaitForNetworkChannelOpen(fundingPoint2)
require.NoError(t.t, err)
err = dave.WaitForNetworkChannelOpen(fundingPoint2)
require.NoError(t.t, err)
// Carol will now set the option-scid-alias feature bit and restart.
carolArgs = append(carolArgs, "--protocol.option-scid-alias")
carol.SetExtraArgs(carolArgs)
err = net.RestartNode(carol, nil)
require.NoError(t.t, err)
// Dave will create an invoice for Carol to pay, it should contain an
// alias in the hop hints.
daveParams := &lnrpc.Invoice{
Value: int64(10_000),
Private: true,
}
var daveInvoice *lnrpc.AddInvoiceResponse
var startingAlias lnwire.ShortChannelID
startingAlias.BlockHeight = 16_000_000
err = wait.Predicate(func() bool {
ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
invoiceResp, err := dave.AddInvoice(ctxt, daveParams)
if err != nil {
return false
}
payReq := &lnrpc.PayReqString{
PayReq: invoiceResp.PaymentRequest,
}
decodedReq, err := dave.DecodePayReq(ctxb, payReq)
if err != nil {
return false
}
if len(decodedReq.RouteHints) != 1 {
return false
}
if len(decodedReq.RouteHints[0].HopHints) != 1 {
return false
}
hopHint := decodedReq.RouteHints[0].HopHints[0].ChanId
if startingAlias.ToUint64() == hopHint {
daveInvoice = invoiceResp
return true
}
return false
}, defaultTimeout)
require.NoError(t.t, err)
// Carol should be able to pay it.
_ = sendAndAssertSuccess(
t, carol, &routerrpc.SendPaymentRequest{
PaymentRequest: daveInvoice.PaymentRequest,
TimeoutSeconds: 60,
FeeLimitMsat: noFeeLimitMsat,
},
)
daveInvoice2, err := dave.AddInvoice(ctxb, daveParams)
require.NoError(t.t, err)
_ = sendAndAssertSuccess(
t, net.Bob, &routerrpc.SendPaymentRequest{
PaymentRequest: daveInvoice2.PaymentRequest,
TimeoutSeconds: 60,
FeeLimitMsat: noFeeLimitMsat,
},
)
}