From c1b348135410f5cd017334ed8a783158128d752c Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Fri, 5 Aug 2022 03:34:42 +0800 Subject: [PATCH 01/44] lntemp+itest: refactor `testEtcdFailover` --- lntemp/harness.go | 66 ++++++++ lntemp/harness_assertion.go | 20 ++- lntemp/harness_node_manager.go | 6 +- lntemp/node/config.go | 31 ++++ lntemp/node/harness_node.go | 40 ++++- lntest/itest/list_on_test.go | 4 + lntest/itest/lnd_etcd_failover_test.go | 157 ++++++------------ .../itest/lnd_no_etcd_dummy_failover_test.go | 6 +- lntest/itest/lnd_test_list_on_test.go | 4 - 9 files changed, 202 insertions(+), 132 deletions(-) diff --git a/lntemp/harness.go b/lntemp/harness.go index d69cc330a..d014aec07 100644 --- a/lntemp/harness.go +++ b/lntemp/harness.go @@ -14,6 +14,7 @@ import ( "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" "github.com/go-errors/errors" + "github.com/lightningnetwork/lnd/kvdb/etcd" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" "github.com/lightningnetwork/lnd/lnrpc/walletrpc" @@ -155,6 +156,16 @@ func (h *HarnessTest) ChainBackendName() string { return h.manager.chainBackend.Name() } +// Context returns the run context used in this test. Usaually it should be +// managed by the test itself otherwise undefined behaviors will occur. It can +// be used, however, when a test needs to have its own context being managed +// differently. In that case, instead of using a background context, the run +// context should be used such that the test context scope can be fully +// controlled. +func (h *HarnessTest) Context() context.Context { + return h.runCtx +} + // SetUp starts the initial seeder nodes within the test harness. The initial // node's wallets will be funded wallets with 10x10 BTC outputs each. func (h *HarnessTest) SetupStandbyNodes() { @@ -587,6 +598,61 @@ func (h *HarnessTest) RestoreNodeWithSeed(name string, extraArgs []string, return node } +// NewNodeEtcd starts a new node with seed that'll use an external etcd +// database as its storage. The passed cluster flag indicates that we'd like +// the node to join the cluster leader election. We won't wait until RPC is +// available (this is useful when the node is not expected to become the leader +// right away). +func (h *HarnessTest) NewNodeEtcd(name string, etcdCfg *etcd.Config, + password []byte, cluster bool, + leaderSessionTTL int) *node.HarnessNode { + + // We don't want to use the embedded etcd instance. + h.manager.dbBackend = lntest.BackendBbolt + + extraArgs := node.ExtraArgsEtcd( + etcdCfg, name, cluster, leaderSessionTTL, + ) + node, err := h.manager.newNode(h.T, name, extraArgs, password, true) + require.NoError(h, err, "failed to create new node with etcd") + + // Start the node daemon only. + err = node.StartLndCmd(h.runCtx) + require.NoError(h, err, "failed to start node %s", node.Name()) + + return node +} + +// NewNodeWithSeedEtcd starts a new node with seed that'll use an external etcd +// database as its storage. The passed cluster flag indicates that we'd like +// the node to join the cluster leader election. +func (h *HarnessTest) NewNodeWithSeedEtcd(name string, etcdCfg *etcd.Config, + password []byte, entropy []byte, statelessInit, cluster bool, + leaderSessionTTL int) (*node.HarnessNode, []string, []byte) { + + // We don't want to use the embedded etcd instance. + h.manager.dbBackend = lntest.BackendBbolt + + // Create a request to generate a new aezeed. The new seed will have + // the same password as the internal wallet. + req := &lnrpc.GenSeedRequest{ + AezeedPassphrase: password, + SeedEntropy: nil, + } + + extraArgs := node.ExtraArgsEtcd( + etcdCfg, name, cluster, leaderSessionTTL, + ) + + return h.newNodeWithSeed(name, extraArgs, req, statelessInit) +} + +// KillNode kills the node (but won't wait for the node process to stop). +func (h *HarnessTest) KillNode(hn *node.HarnessNode) { + require.NoErrorf(h, hn.Kill(), "%s: kill got error", hn.Name()) + delete(h.manager.activeNodes, hn.Cfg.NodeID) +} + // SetFeeEstimate sets a fee rate to be returned from fee estimator. // // NOTE: this method will set the fee rate for a conf target of 1, which is the diff --git a/lntemp/harness_assertion.go b/lntemp/harness_assertion.go index 690fbff44..61e79613a 100644 --- a/lntemp/harness_assertion.go +++ b/lntemp/harness_assertion.go @@ -884,7 +884,11 @@ func (h *HarnessTest) assertPaymentStatusWithTimeout(stream rpc.PaymentClient, err := wait.NoError(func() error { // Consume one message. This will raise an error if the message // is not received within DefaultTimeout. - payment := h.ReceivePaymentUpdate(stream) + payment, err := h.ReceivePaymentUpdate(stream) + if err != nil { + return fmt.Errorf("received error from payment "+ + "stream: %s", err) + } // Return if the desired payment state is reached. if payment.Status == status { @@ -895,8 +899,8 @@ func (h *HarnessTest) assertPaymentStatusWithTimeout(stream rpc.PaymentClient, // Return the err so that it can be used for debugging when // timeout is reached. - return fmt.Errorf("payment status, got %v, want %v", - payment.Status, status) + return fmt.Errorf("payment %v status, got %v, want %v", + payment.PaymentHash, payment.Status, status) }, timeout) require.NoError(h, err, "timeout while waiting payment") @@ -907,7 +911,7 @@ func (h *HarnessTest) assertPaymentStatusWithTimeout(stream rpc.PaymentClient, // ReceivePaymentUpdate waits until a message is received on the payment client // stream or the timeout is reached. func (h *HarnessTest) ReceivePaymentUpdate( - stream rpc.PaymentClient) *lnrpc.Payment { + stream rpc.PaymentClient) (*lnrpc.Payment, error) { chanMsg := make(chan *lnrpc.Payment, 1) errChan := make(chan error, 1) @@ -926,16 +930,14 @@ func (h *HarnessTest) ReceivePaymentUpdate( select { case <-time.After(DefaultTimeout): require.Fail(h, "timeout", "timeout waiting for payment update") + return nil, nil case err := <-errChan: - require.Failf(h, "payment stream", - "received err from payment stream: %v", err) + return nil, err case updateMsg := <-chanMsg: - return updateMsg + return updateMsg, nil } - - return nil } // AssertInvoiceSettled asserts a given invoice specified by its payment diff --git a/lntemp/harness_node_manager.go b/lntemp/harness_node_manager.go index 2b5f87e08..0b6aa038c 100644 --- a/lntemp/harness_node_manager.go +++ b/lntemp/harness_node_manager.go @@ -71,8 +71,7 @@ func (nm *nodeManager) nextNodeID() uint32 { // node can be used immediately. Otherwise, the node will require an additional // initialization phase where the wallet is either created or restored. func (nm *nodeManager) newNode(t *testing.T, name string, extraArgs []string, - password []byte, useSeed bool, - opts ...node.Option) (*node.HarnessNode, error) { + password []byte, useSeed bool) (*node.HarnessNode, error) { cfg := &node.BaseNodeConfig{ Name: name, @@ -87,9 +86,6 @@ func (nm *nodeManager) newNode(t *testing.T, name string, extraArgs []string, NetParams: harnessNetParams, HasSeed: useSeed, } - for _, opt := range opts { - opt(cfg) - } node, err := node.NewHarnessNode(t, cfg) if err != nil { diff --git a/lntemp/node/config.go b/lntemp/node/config.go index f9798ede4..84e1d9a71 100644 --- a/lntemp/node/config.go +++ b/lntemp/node/config.go @@ -7,6 +7,7 @@ import ( "github.com/btcsuite/btcd/chaincfg" "github.com/lightningnetwork/lnd/chanbackup" + "github.com/lightningnetwork/lnd/kvdb/etcd" "github.com/lightningnetwork/lnd/lntest" ) @@ -232,3 +233,33 @@ func (cfg *BaseNodeConfig) GenArgs() []string { return args } + +// ExtraArgsEtcd returns extra args for configuring LND to use an external etcd +// database (for remote channel DB and wallet DB). +func ExtraArgsEtcd(etcdCfg *etcd.Config, name string, cluster bool, + leaderSessionTTL int) []string { + + extraArgs := []string{ + "--db.backend=etcd", + fmt.Sprintf("--db.etcd.host=%v", etcdCfg.Host), + fmt.Sprintf("--db.etcd.user=%v", etcdCfg.User), + fmt.Sprintf("--db.etcd.pass=%v", etcdCfg.Pass), + fmt.Sprintf("--db.etcd.namespace=%v", etcdCfg.Namespace), + } + + if etcdCfg.InsecureSkipVerify { + extraArgs = append(extraArgs, "--db.etcd.insecure_skip_verify") + } + + if cluster { + clusterArgs := []string{ + "--cluster.enable-leader-election", + fmt.Sprintf("--cluster.id=%v", name), + fmt.Sprintf("--cluster.leader-session-ttl=%v", + leaderSessionTTL), + } + extraArgs = append(extraArgs, clusterArgs...) + } + + return extraArgs +} diff --git a/lntemp/node/harness_node.go b/lntemp/node/harness_node.go index 0cb8f2341..f8be79dfc 100644 --- a/lntemp/node/harness_node.go +++ b/lntemp/node/harness_node.go @@ -215,6 +215,33 @@ func (hn *HarnessNode) WaitUntilServerActive() error { }) } +// WaitUntilLeader attempts to finish the start procedure by initiating an RPC +// connection and setting up the wallet unlocker client. This is needed when +// a node that has recently been started was waiting to become the leader and +// we're at the point when we expect that it is the leader now (awaiting +// unlock). +func (hn *HarnessNode) WaitUntilLeader(timeout time.Duration) error { + var ( + conn *grpc.ClientConn + connErr error + ) + + if err := wait.NoError(func() error { + conn, connErr = hn.ConnectRPCWithMacaroon(nil) + return connErr + }, timeout); err != nil { + return err + } + + // Since the conn is not authed, only the `WalletUnlocker` and `State` + // clients can be inited from this conn. + hn.conn = conn + hn.RPC = rpc.NewHarnessRPC(hn.runCtx, hn.T, conn, hn.Name()) + + // Wait till the server is starting. + return hn.WaitUntilStarted() +} + // Unlock attempts to unlock the wallet of the target HarnessNode. This method // should be called after the restart of a HarnessNode that was created with a // seed+password. Once this method returns, the HarnessNode will be ready to @@ -399,7 +426,7 @@ func (hn *HarnessNode) Start(ctxt context.Context) error { conn, err := hn.ConnectRPC() if err != nil { err = fmt.Errorf("ConnectRPC err: %w", err) - cmdErr := hn.kill() + cmdErr := hn.Kill() if cmdErr != nil { err = fmt.Errorf("kill process got err: %w: %v", cmdErr, err) @@ -453,6 +480,11 @@ func (hn *HarnessNode) InitNode(macBytes []byte) error { // Init all the RPC clients. hn.InitRPCClients(conn) + // Wait till the server is starting. + if err := hn.WaitUntilStarted(); err != nil { + return fmt.Errorf("waiting for start got: %w", err) + } + return hn.initLightningClient() } @@ -670,7 +702,7 @@ func (hn *HarnessNode) Stop() error { // If the rpc clients are not initiated, we'd kill the process // manually. hn.printErrf("found nil RPC clients") - if err := hn.kill(); err != nil { + if err := hn.Kill(); err != nil { return fmt.Errorf("killing process got: %v", err) } } @@ -719,8 +751,8 @@ func (hn *HarnessNode) Shutdown() error { return nil } -// kill kills the lnd process. -func (hn *HarnessNode) kill() error { +// Kill kills the lnd process. +func (hn *HarnessNode) Kill() error { return hn.cmd.Process.Kill() } diff --git a/lntest/itest/list_on_test.go b/lntest/itest/list_on_test.go index 91e808e5e..55fee1f16 100644 --- a/lntest/itest/list_on_test.go +++ b/lntest/itest/list_on_test.go @@ -231,4 +231,8 @@ var allTestCasesTemp = []*lntemp.TestCase{ Name: "neutrino kit", TestFunc: testNeutrino, }, + { + Name: "etcd failover", + TestFunc: testEtcdFailover, + }, } diff --git a/lntest/itest/lnd_etcd_failover_test.go b/lntest/itest/lnd_etcd_failover_test.go index 4de2d4490..90bd5fbbe 100644 --- a/lntest/itest/lnd_etcd_failover_test.go +++ b/lntest/itest/lnd_etcd_failover_test.go @@ -4,7 +4,6 @@ package itest import ( - "context" "testing" "time" @@ -13,27 +12,24 @@ import ( "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/lncfg" "github.com/lightningnetwork/lnd/lnrpc" - "github.com/lightningnetwork/lnd/lnrpc/routerrpc" + "github.com/lightningnetwork/lnd/lntemp" "github.com/lightningnetwork/lnd/lntest" + "github.com/stretchr/testify/require" ) -func assertLeader(ht *harnessTest, observer cluster.LeaderElector, +func assertLeader(ht *lntemp.HarnessTest, observer cluster.LeaderElector, expected string) { - leader, err := observer.Leader(context.Background()) - if err != nil { - ht.Fatalf("Unable to query leader: %v", err) - } - - if leader != expected { - ht.Fatalf("Leader should be '%v', got: '%v'", expected, leader) - } + leader, err := observer.Leader(ht.Context()) + require.NoError(ht, err, "Unable to query leader") + require.Equalf(ht, expected, leader, + "Leader should be '%v', got: '%v'", expected, leader) } // testEtcdFailover tests that in a cluster setup where two LND nodes form a // single cluster (sharing the same identity) one can hand over the leader role // to the other (failing over after graceful shutdown or forceful abort). -func testEtcdFailover(net *lntest.NetworkHarness, ht *harnessTest) { +func testEtcdFailover(ht *lntemp.HarnessTest) { testCases := []struct { name string kill bool @@ -48,148 +44,97 @@ func testEtcdFailover(net *lntest.NetworkHarness, ht *harnessTest) { for _, test := range testCases { test := test - ht.t.Run(test.name, func(t1 *testing.T) { - ht1 := newHarnessTest(t1, ht.lndHarness) - ht1.RunTestCase(&testCase{ - name: test.name, - test: func(_ *lntest.NetworkHarness, - tt *harnessTest) { - - testEtcdFailoverCase(net, tt, test.kill) - }, - }) + success := ht.Run(test.name, func(t1 *testing.T) { + st := ht.Subtest(t1) + testEtcdFailoverCase(st, test.kill) }) + if !success { + return + } } } -func testEtcdFailoverCase(net *lntest.NetworkHarness, ht *harnessTest, - kill bool) { - - ctxb := context.Background() - +func testEtcdFailoverCase(ht *lntemp.HarnessTest, kill bool) { etcdCfg, cleanup, err := kvdb.StartEtcdTestBackend( - ht.t.TempDir(), uint16(lntest.NextAvailablePort()), + ht.T.TempDir(), uint16(lntest.NextAvailablePort()), uint16(lntest.NextAvailablePort()), "", ) - if err != nil { - ht.Fatalf("Failed to start etcd instance: %v", err) - } + require.NoError(ht, err, "Failed to start etcd instance") defer cleanup() + alice := ht.NewNode("Alice", nil) + + // Give Alice some coins to fund the channel. + ht.FundCoins(btcutil.SatoshiPerBitcoin, alice) + // Make leader election session TTL 5 sec to make the test run fast. const leaderSessionTTL = 5 observer, err := cluster.MakeLeaderElector( - ctxb, cluster.EtcdLeaderElector, "observer", + ht.Context(), cluster.EtcdLeaderElector, "observer", lncfg.DefaultEtcdElectionPrefix, leaderSessionTTL, etcdCfg, ) - if err != nil { - ht.Fatalf("Cannot start election observer: %v", err) - } + require.NoError(ht, err, "Cannot start election observer") password := []byte("the quick brown fox jumps the lazy dog") entropy := [16]byte{1, 2, 3} stateless := false cluster := true - carol1, _, _, err := net.NewNodeWithSeedEtcd( + carol1, _, _ := ht.NewNodeWithSeedEtcd( "Carol-1", etcdCfg, password, entropy[:], stateless, cluster, leaderSessionTTL, ) - if err != nil { - ht.Fatalf("unable to start Carol-1: %v", err) - } - - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - info1, err := carol1.GetInfo(ctxt, &lnrpc.GetInfoRequest{}) - if err != nil { - ht.Fatalf("unable to get info: %v", err) - } - - net.ConnectNodes(ht.t, carol1, net.Alice) + info1 := carol1.RPC.GetInfo() + ht.ConnectNodes(carol1, alice) // Open a channel with 100k satoshis between Carol and Alice with Alice // being the sole funder of the channel. - chanAmt := btcutil.Amount(100000) - _ = openChannelAndAssert( - ht, net, net.Alice, carol1, - lntest.OpenChannelParams{ - Amt: chanAmt, - }, - ) + chanAmt := btcutil.Amount(100_000) + ht.OpenChannel(alice, carol1, lntemp.OpenChannelParams{Amt: chanAmt}) // At this point Carol-1 is the elected leader, while Carol-2 will wait // to become the leader when Carol-1 stops. - carol2, err := net.NewNodeEtcd( - "Carol-2", etcdCfg, password, cluster, false, leaderSessionTTL, + carol2 := ht.NewNodeEtcd( + "Carol-2", etcdCfg, password, cluster, leaderSessionTTL, ) - if err != nil { - ht.Fatalf("Unable to start Carol-2: %v", err) - } - assertLeader(ht, observer, "Carol-1") amt := btcutil.Amount(1000) - payReqs, _, _, err := createPayReqs(carol1, amt, 2) - if err != nil { - ht.Fatalf("Carol-2 is unable to create payment requests: %v", - err) - } - sendAndAssertSuccess( - ht, net.Alice, &routerrpc.SendPaymentRequest{ - PaymentRequest: payReqs[0], - TimeoutSeconds: 60, - FeeLimitSat: noFeeLimitMsat, - }, - ) + payReqs, _, _ := ht.CreatePayReqs(carol1, amt, 2) + ht.CompletePaymentRequests(alice, []string{payReqs[0]}) // Shut down or kill Carol-1 and wait for Carol-2 to become the leader. failoverTimeout := time.Duration(2*leaderSessionTTL) * time.Second if kill { - err = net.KillNode(carol1) - if err != nil { - ht.Fatalf("Can't kill Carol-1: %v", err) - } + ht.KillNode(carol1) } else { - shutdownAndAssert(net, ht, carol1) + ht.Shutdown(carol1) } err = carol2.WaitUntilLeader(failoverTimeout) - if err != nil { - ht.Fatalf("Waiting for Carol-2 to become the leader failed: %v", - err) - } - + require.NoError(ht, err, "Waiting for Carol-2 to become the leader "+ + "failed") assertLeader(ht, observer, "Carol-2") - err = carol2.Unlock(&lnrpc.UnlockWalletRequest{ - WalletPassword: password, - }) - if err != nil { - ht.Fatalf("Unlocking Carol-2 was not successful: %v", err) - } - - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + req := &lnrpc.UnlockWalletRequest{WalletPassword: password} + err = carol2.Unlock(req) + require.NoError(ht, err, "Unlocking Carol-2 failed") // Make sure Carol-1 and Carol-2 have the same identity. - info2, err := carol2.GetInfo(ctxt, &lnrpc.GetInfoRequest{}) - if err != nil { - ht.Fatalf("unable to get info: %v", err) - } - if info1.IdentityPubkey != info2.IdentityPubkey { - ht.Fatalf("Carol-1 and Carol-2 must have the same identity: "+ - "%v vs %v", info1.IdentityPubkey, info2.IdentityPubkey) - } + info2 := carol2.RPC.GetInfo() + require.Equal(ht, info1.IdentityPubkey, info2.IdentityPubkey, + "Carol-1 and Carol-2 must have the same identity") + + // Make sure the nodes are connected before moving forward. Otherwise + // we may get a link not found error. + ht.AssertConnected(alice, carol2) // Now let Alice pay the second invoice but this time we expect Carol-2 // to receive the payment. - sendAndAssertSuccess( - ht, net.Alice, &routerrpc.SendPaymentRequest{ - PaymentRequest: payReqs[1], - TimeoutSeconds: 60, - FeeLimitSat: noFeeLimitMsat, - }, - ) + ht.CompletePaymentRequests(alice, []string{payReqs[1]}) - shutdownAndAssert(net, ht, carol2) + // Manually shutdown the node as it will mess up with our cleanup + // process. + ht.Shutdown(carol2) } diff --git a/lntest/itest/lnd_no_etcd_dummy_failover_test.go b/lntest/itest/lnd_no_etcd_dummy_failover_test.go index c2a9a188b..721b60cf6 100644 --- a/lntest/itest/lnd_no_etcd_dummy_failover_test.go +++ b/lntest/itest/lnd_no_etcd_dummy_failover_test.go @@ -3,10 +3,8 @@ package itest -import ( - "github.com/lightningnetwork/lnd/lntest" -) +import "github.com/lightningnetwork/lnd/lntemp" // testEtcdFailover is an empty itest when LND is not compiled with etcd // support. -func testEtcdFailover(net *lntest.NetworkHarness, ht *harnessTest) {} +func testEtcdFailover(ht *lntemp.HarnessTest) {} diff --git a/lntest/itest/lnd_test_list_on_test.go b/lntest/itest/lnd_test_list_on_test.go index fe56fcf0d..b7aaf9c69 100644 --- a/lntest/itest/lnd_test_list_on_test.go +++ b/lntest/itest/lnd_test_list_on_test.go @@ -210,10 +210,6 @@ var allTestCases = []*testCase{ name: "wallet import pubkey", test: testWalletImportPubKey, }, - { - name: "etcd_failover", - test: testEtcdFailover, - }, { name: "max htlc pathfind", test: testMaxHtlcPathfind, From 984d3ece12f391daefb0a6503e3bd99bf2d11b64 Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Fri, 5 Aug 2022 17:48:35 +0800 Subject: [PATCH 02/44] lntemp+itest: refactor `testHoldInvoiceForceClose` --- lntemp/rpc/lnd.go | 12 ++ lntest/itest/list_on_test.go | 4 + lntest/itest/lnd_hold_invoice_force_test.go | 149 ++++++++++---------- lntest/itest/lnd_test_list_on_test.go | 4 - 4 files changed, 94 insertions(+), 75 deletions(-) diff --git a/lntemp/rpc/lnd.go b/lntemp/rpc/lnd.go index 63ab65b90..efd70a62d 100644 --- a/lntemp/rpc/lnd.go +++ b/lntemp/rpc/lnd.go @@ -567,3 +567,15 @@ func (h *HarnessRPC) VerifyChanBackup( return resp } + +// LookupInvoice queries the node's invoices using the specified rHash. +func (h *HarnessRPC) LookupInvoice(rHash []byte) *lnrpc.Invoice { + ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout) + defer cancel() + + payHash := &lnrpc.PaymentHash{RHash: rHash} + resp, err := h.LN.LookupInvoice(ctxt, payHash) + h.NoError(err, "LookupInvoice") + + return resp +} diff --git a/lntest/itest/list_on_test.go b/lntest/itest/list_on_test.go index 55fee1f16..8305cd39b 100644 --- a/lntest/itest/list_on_test.go +++ b/lntest/itest/list_on_test.go @@ -235,4 +235,8 @@ var allTestCasesTemp = []*lntemp.TestCase{ Name: "etcd failover", TestFunc: testEtcdFailover, }, + { + Name: "hold invoice force close", + TestFunc: testHoldInvoiceForceClose, + }, } diff --git a/lntest/itest/lnd_hold_invoice_force_test.go b/lntest/itest/lnd_hold_invoice_force_test.go index 7fd911d16..613281305 100644 --- a/lntest/itest/lnd_hold_invoice_force_test.go +++ b/lntest/itest/lnd_hold_invoice_force_test.go @@ -1,14 +1,13 @@ package itest import ( - "context" "fmt" "github.com/lightningnetwork/lnd/lncfg" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/invoicesrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" - "github.com/lightningnetwork/lnd/lntest" + "github.com/lightningnetwork/lnd/lntemp" "github.com/lightningnetwork/lnd/lntest/wait" "github.com/lightningnetwork/lnd/lntypes" "github.com/stretchr/testify/require" @@ -16,17 +15,11 @@ import ( // testHoldInvoiceForceClose tests cancellation of accepted hold invoices which // would otherwise trigger force closes when they expire. -func testHoldInvoiceForceClose(net *lntest.NetworkHarness, t *harnessTest) { - ctxb, cancel := context.WithCancel(context.Background()) - defer cancel() - +func testHoldInvoiceForceClose(ht *lntemp.HarnessTest) { // Open a channel between alice and bob. - chanReq := lntest.OpenChannelParams{ - Amt: 300000, - } - - chanPoint := openChannelAndAssert( - t, net, net.Alice, net.Bob, chanReq, + alice, bob := ht.Alice, ht.Bob + chanPoint := ht.OpenChannel( + alice, bob, lntemp.OpenChannelParams{Amt: 300000}, ) // Create a non-dust hold invoice for bob. @@ -39,87 +32,76 @@ func testHoldInvoiceForceClose(net *lntest.NetworkHarness, t *harnessTest) { CltvExpiry: 40, Hash: payHash[:], } + bobInvoice := bob.RPC.AddHoldInvoice(invoiceReq) - ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) - defer cancel() - bobInvoice, err := net.Bob.AddHoldInvoice(ctxt, invoiceReq) - require.NoError(t.t, err) + // Subscribe the invoice. + stream := bob.RPC.SubscribeSingleInvoice(payHash[:]) // Pay this invoice from Alice -> Bob, we should achieve this with a // single htlc. - _, err = net.Alice.RouterClient.SendPaymentV2( - ctxb, &routerrpc.SendPaymentRequest{ - PaymentRequest: bobInvoice.PaymentRequest, - TimeoutSeconds: 60, - FeeLimitMsat: noFeeLimitMsat, - }, - ) - require.NoError(t.t, err) + req := &routerrpc.SendPaymentRequest{ + PaymentRequest: bobInvoice.PaymentRequest, + TimeoutSeconds: 60, + FeeLimitMsat: noFeeLimitMsat, + } + alice.RPC.SendPayment(req) - waitForInvoiceAccepted(t, net.Bob, payHash) + ht.AssertInvoiceState(stream, lnrpc.Invoice_ACCEPTED) // Once the HTLC has cleared, alice and bob should both have a single // htlc locked in. - nodes := []*lntest.HarnessNode{net.Alice, net.Bob} - err = wait.NoError(func() error { - return assertActiveHtlcs(nodes, payHash[:]) - }, defaultTimeout) - require.NoError(t.t, err) + ht.AssertActiveHtlcs(alice, payHash[:]) + ht.AssertActiveHtlcs(bob, payHash[:]) // Get our htlc expiry height and current block height so that we // can mine the exact number of blocks required to expire the htlc. - chans, err := net.Alice.ListChannels(ctxb, &lnrpc.ListChannelsRequest{}) - require.NoError(t.t, err) - require.Len(t.t, chans.Channels, 1) - require.Len(t.t, chans.Channels[0].PendingHtlcs, 1) - activeHtlc := chans.Channels[0].PendingHtlcs[0] + channel := ht.QueryChannelByChanPoint(alice, chanPoint) + require.Len(ht, channel.PendingHtlcs, 1) + activeHtlc := channel.PendingHtlcs[0] - require.NoError(t.t, net.Alice.WaitForBlockchainSync()) - require.NoError(t.t, net.Bob.WaitForBlockchainSync()) - - info, err := net.Alice.GetInfo(ctxb, &lnrpc.GetInfoRequest{}) - require.NoError(t.t, err) + _, currentHeight := ht.Miner.GetBestBlock() // Now we will mine blocks until the htlc expires, and wait for each // node to sync to our latest height. Sanity check that we won't // underflow. - require.Greater( - t.t, activeHtlc.ExpirationHeight, info.BlockHeight, - "expected expiry after current height", - ) - blocksTillExpiry := activeHtlc.ExpirationHeight - info.BlockHeight + require.Greater(ht, activeHtlc.ExpirationHeight, uint32(currentHeight), + "expected expiry after current height") + blocksTillExpiry := activeHtlc.ExpirationHeight - uint32(currentHeight) // Alice will go to chain with some delta, sanity check that we won't // underflow and subtract this from our mined blocks. - require.Greater( - t.t, blocksTillExpiry, - uint32(lncfg.DefaultOutgoingBroadcastDelta), - ) - blocksTillForce := blocksTillExpiry - lncfg.DefaultOutgoingBroadcastDelta + require.Greater(ht, blocksTillExpiry, + uint32(lncfg.DefaultOutgoingBroadcastDelta)) - mineBlocksSlow(t, net, blocksTillForce, 0) + // blocksTillForce is the number of blocks should be mined to + // trigger a force close from Alice iff the invoice cancelation + // failed. This value is 48 in current test setup. + blocksTillForce := blocksTillExpiry - + lncfg.DefaultOutgoingBroadcastDelta - require.NoError(t.t, net.Alice.WaitForBlockchainSync()) - require.NoError(t.t, net.Bob.WaitForBlockchainSync()) + // blocksTillCancel is the number of blocks should be mined to trigger + // an invoice cancelation from Bob. This value is 30 in current test + // setup. + blocksTillCancel := blocksTillExpiry - + lncfg.DefaultHoldInvoiceExpiryDelta - // Our channel should not have been force closed, instead we expect our - // channel to still be open and our invoice to have been canceled before - // expiry. - chanInfo, err := getChanInfo(net.Alice) - require.NoError(t.t, err) + // When using ht.MineBlocks, for bitcoind backend, the block height + // synced differ significantly among subsystems. From observation, the + // LNWL syncs much faster than other subsystems, with more than 10 + // blocks ahead. For this test case, CRTR may be lagging behind for + // more than 20 blocks. Thus we use slow mining instead. + // TODO(yy): fix block height asymmetry among all the subsystems. + // + // We first mine enough blocks to trigger an invoice cancelation. + ht.MineBlocks(blocksTillCancel) - fundingTxID, err := lnrpc.GetChanPointFundingTxid(chanPoint) - require.NoError(t.t, err) - chanStr := fmt.Sprintf("%v:%v", fundingTxID, chanPoint.OutputIndex) - require.Equal(t.t, chanStr, chanInfo.ChannelPoint) + // Wait for the nodes to be synced. + ht.WaitForBlockchainSync(alice) + ht.WaitForBlockchainSync(bob) - err = wait.NoError(func() error { - inv, err := net.Bob.LookupInvoice(ctxt, &lnrpc.PaymentHash{ - RHash: payHash[:], - }) - if err != nil { - return err - } + // Check that the invoice is canceled by Bob. + err := wait.NoError(func() error { + inv := bob.RPC.LookupInvoice(payHash[:]) if inv.State != lnrpc.Invoice_CANCELED { return fmt.Errorf("expected canceled invoice, got: %v", @@ -135,8 +117,33 @@ func testHoldInvoiceForceClose(net *lntest.NetworkHarness, t *harnessTest) { return nil }, defaultTimeout) - require.NoError(t.t, err, "expected canceled invoice") + require.NoError(ht, err, "expected canceled invoice") + + // We now continue to mine more blocks to the point where it could have + // triggered a force close if the invoice cancelation was failed. + // + // NOTE: we need to mine blocks in two sections because of a following + // case has happened frequently with bitcoind backend, + // - when mining all the blocks together, subsystems were syncing + // blocks under very different speed. + // - Bob would cancel the invoice in INVC, and send an UpdateFailHTLC + // in PEER. + // - Alice, however, would need to receive the message before her + // subsystem CNCT being synced to the force close height. This didn't + // happen in bitcoind backend, as Alice's CNCT was syncing way faster + // than Bob's INVC, causing the channel being force closed before the + // invoice cancelation message was received by Alice. + ht.MineBlocks(blocksTillForce - blocksTillCancel) + + // Wait for the nodes to be synced. + ht.WaitForBlockchainSync(alice) + ht.WaitForBlockchainSync(bob) + + // Check that Alice has not closed the channel because there are no + // outgoing HTLCs in her channel as the only HTLC has already been + // canceled. + ht.AssertNumPendingForceClose(alice, 0) // Clean up the channel. - closeChannelAndAssert(t, net, net.Alice, chanPoint, false) + ht.CloseChannel(alice, chanPoint) } diff --git a/lntest/itest/lnd_test_list_on_test.go b/lntest/itest/lnd_test_list_on_test.go index b7aaf9c69..498d66b1e 100644 --- a/lntest/itest/lnd_test_list_on_test.go +++ b/lntest/itest/lnd_test_list_on_test.go @@ -114,10 +114,6 @@ var allTestCases = []*testCase{ name: "hold invoice sender persistence", test: testHoldInvoicePersistence, }, - { - name: "hold invoice force close", - test: testHoldInvoiceForceClose, - }, { name: "cpfp", test: testCPFP, From 16bac64ecb71d32942215ef998c425b4f8214855 Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Fri, 5 Aug 2022 17:59:00 +0800 Subject: [PATCH 03/44] lntemp+itest: refactor `testHoldInvoicePersistence` --- lntemp/harness_assertion.go | 33 ++ lntemp/rpc/invoices.go | 17 + lntemp/rpc/lnd.go | 12 + lntest/itest/list_on_test.go | 4 + lntest/itest/lnd_hold_persistence_test.go | 374 +++++----------------- lntest/itest/lnd_test_list_on_test.go | 4 - 6 files changed, 148 insertions(+), 296 deletions(-) diff --git a/lntemp/harness_assertion.go b/lntemp/harness_assertion.go index 61e79613a..e92bba564 100644 --- a/lntemp/harness_assertion.go +++ b/lntemp/harness_assertion.go @@ -1726,3 +1726,36 @@ func (h *HarnessTest) CreateBurnAddr(addrType lnrpc.AddressType) ([]byte, return h.PayToAddrScript(addr), addr } + +// ReceiveTrackPayment waits until a message is received on the track payment +// stream or the timeout is reached. +func (h *HarnessTest) ReceiveTrackPayment( + stream rpc.TrackPaymentClient) *lnrpc.Payment { + + chanMsg := make(chan *lnrpc.Payment) + errChan := make(chan error) + go func() { + // Consume one message. This will block until the message is + // received. + resp, err := stream.Recv() + if err != nil { + errChan <- err + return + } + chanMsg <- resp + }() + + select { + case <-time.After(DefaultTimeout): + require.Fail(h, "timeout", "timeout trakcing payment") + + case err := <-errChan: + require.Failf(h, "err from stream", + "received err from stream: %v", err) + + case updateMsg := <-chanMsg: + return updateMsg + } + + return nil +} diff --git a/lntemp/rpc/invoices.go b/lntemp/rpc/invoices.go index 41c5bb1fd..5b771968e 100644 --- a/lntemp/rpc/invoices.go +++ b/lntemp/rpc/invoices.go @@ -5,6 +5,7 @@ import ( "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/invoicesrpc" + "github.com/lightningnetwork/lnd/lnrpc/routerrpc" ) // ===================== @@ -82,3 +83,19 @@ func (h *HarnessRPC) SubscribeSingleInvoice(rHash []byte) SingleInvoiceClient { return client } + +type TrackPaymentClient routerrpc.Router_TrackPaymentV2Client + +// TrackPaymentV2 creates a subscription client for given invoice and +// asserts its creation. +func (h *HarnessRPC) TrackPaymentV2(payHash []byte) TrackPaymentClient { + req := &routerrpc.TrackPaymentRequest{PaymentHash: payHash} + + // TrackPaymentV2 needs to have the context alive for the entire test + // case as the returned client will be used for send and receive events + // stream. Thus we use runCtx here instead of a timeout context. + client, err := h.Router.TrackPaymentV2(h.runCtx, req) + h.NoError(err, "TrackPaymentV2") + + return client +} diff --git a/lntemp/rpc/lnd.go b/lntemp/rpc/lnd.go index efd70a62d..a8a21e5ae 100644 --- a/lntemp/rpc/lnd.go +++ b/lntemp/rpc/lnd.go @@ -579,3 +579,15 @@ func (h *HarnessRPC) LookupInvoice(rHash []byte) *lnrpc.Invoice { return resp } + +// DecodePayReq makes a RPC call to node's DecodePayReq and asserts. +func (h *HarnessRPC) DecodePayReq(req string) *lnrpc.PayReq { + ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout) + defer cancel() + + payReq := &lnrpc.PayReqString{PayReq: req} + resp, err := h.LN.DecodePayReq(ctxt, payReq) + h.NoError(err, "DecodePayReq") + + return resp +} diff --git a/lntest/itest/list_on_test.go b/lntest/itest/list_on_test.go index 8305cd39b..3227a260c 100644 --- a/lntest/itest/list_on_test.go +++ b/lntest/itest/list_on_test.go @@ -239,4 +239,8 @@ var allTestCasesTemp = []*lntemp.TestCase{ Name: "hold invoice force close", TestFunc: testHoldInvoiceForceClose, }, + { + Name: "hold invoice sender persistence", + TestFunc: testHoldInvoicePersistence, + }, } diff --git a/lntest/itest/lnd_hold_persistence_test.go b/lntest/itest/lnd_hold_persistence_test.go index 11a90a056..da5424ca3 100644 --- a/lntest/itest/lnd_hold_persistence_test.go +++ b/lntest/itest/lnd_hold_persistence_test.go @@ -1,18 +1,14 @@ package itest import ( - "context" - "crypto/rand" "fmt" - "io" - "sync" - "time" "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/invoicesrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" - "github.com/lightningnetwork/lnd/lntest" + "github.com/lightningnetwork/lnd/lntemp" + "github.com/lightningnetwork/lnd/lntemp/rpc" "github.com/lightningnetwork/lnd/lntest/wait" "github.com/lightningnetwork/lnd/lntypes" "github.com/stretchr/testify/require" @@ -21,74 +17,47 @@ import ( // testHoldInvoicePersistence tests that a sender to a hold-invoice, can be // restarted before the payment gets settled, and still be able to receive the // preimage. -func testHoldInvoicePersistence(net *lntest.NetworkHarness, t *harnessTest) { - ctxb := context.Background() - +func testHoldInvoicePersistence(ht *lntemp.HarnessTest) { const ( chanAmt = btcutil.Amount(1000000) numPayments = 10 + reason = lnrpc.PaymentFailureReason_FAILURE_REASON_INCORRECT_PAYMENT_DETAILS //nolint:lll ) // Create carol, and clean up when the test finishes. - carol := net.NewNode(t.t, "Carol", nil) - defer shutdownAndAssert(net, t, carol) + carol := ht.NewNode("Carol", nil) // Connect Alice to Carol. - net.ConnectNodes(t.t, net.Alice, carol) + alice, bob := ht.Alice, ht.Bob + ht.ConnectNodes(alice, carol) // Open a channel between Alice and Carol which is private so that we // cover the addition of hop hints for hold invoices. - chanPointAlice := openChannelAndAssert( - t, net, net.Alice, carol, - lntest.OpenChannelParams{ + chanPointAlice := ht.OpenChannel( + alice, carol, lntemp.OpenChannelParams{ Amt: chanAmt, Private: true, }, ) - // Wait for Alice and Carol to receive the channel edge from the - // funding manager. - err := net.Alice.WaitForNetworkChannelOpen(chanPointAlice) - if err != nil { - t.Fatalf("alice didn't see the alice->carol channel before "+ - "timeout: %v", err) - } - - err = carol.WaitForNetworkChannelOpen(chanPointAlice) - if err != nil { - t.Fatalf("carol didn't see the carol->alice channel before "+ - "timeout: %v", err) - } - // For Carol to include her private channel with Alice as a hop hint, // we need Alice to be perceived as a "public" node, meaning that she // has at least one public channel in the graph. We open a public // channel from Alice -> Bob and wait for Carol to see it. - chanPointBob := openChannelAndAssert( - t, net, net.Alice, net.Bob, - lntest.OpenChannelParams{ + chanPointBob := ht.OpenChannel( + alice, bob, lntemp.OpenChannelParams{ Amt: chanAmt, }, ) - // Wait for Alice and Carol to see the open channel - err = net.Alice.WaitForNetworkChannelOpen(chanPointBob) - require.NoError(t.t, err, "alice didn't see the alice->bob "+ - "channel before timeout") - - err = carol.WaitForNetworkChannelOpen(chanPointBob) - require.NoError(t.t, err, "carol didn't see the alice->bob "+ - "channel before timeout") + // Wait for Carol to see the open channel Alice-Bob. + ht.AssertTopologyChannelOpen(carol, chanPointBob) // Create preimages for all payments we are going to initiate. var preimages []lntypes.Preimage for i := 0; i < numPayments; i++ { var preimage lntypes.Preimage - _, err = rand.Read(preimage[:]) - if err != nil { - t.Fatalf("unable to generate preimage: %v", err) - } - + copy(preimage[:], ht.Random32Bytes()) preimages = append(preimages, preimage) } @@ -96,9 +65,15 @@ func testHoldInvoicePersistence(net *lntest.NetworkHarness, t *harnessTest) { var ( payAmt = btcutil.Amount(4) payReqs []string - invoiceStreams []invoicesrpc.Invoices_SubscribeSingleInvoiceClient + invoiceStreams []rpc.SingleInvoiceClient ) + assertInvoiceState := func(state lnrpc.Invoice_InvoiceState) { + for _, client := range invoiceStreams { + ht.AssertInvoiceState(client, state) + } + } + for _, preimage := range preimages { payHash := preimage.Hash() @@ -110,97 +85,42 @@ func testHoldInvoicePersistence(net *lntest.NetworkHarness, t *harnessTest) { Hash: payHash[:], Private: true, } - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - resp, err := carol.AddHoldInvoice(ctxt, invoiceReq) - if err != nil { - t.Fatalf("unable to add invoice: %v", err) - } - - ctx, cancel := context.WithCancel(ctxb) - defer cancel() - - stream, err := carol.SubscribeSingleInvoice( - ctx, - &invoicesrpc.SubscribeSingleInvoiceRequest{ - RHash: payHash[:], - }, - ) - if err != nil { - t.Fatalf("unable to subscribe to invoice: %v", err) - } + resp := carol.RPC.AddHoldInvoice(invoiceReq) + payReqs = append(payReqs, resp.PaymentRequest) // We expect all of our invoices to have hop hints attached, // since Carol and Alice are connected with a private channel. // We assert that we have one hop hint present to ensure that // we've got coverage for hop hints. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - decodeReq := &lnrpc.PayReqString{ - PayReq: resp.PaymentRequest, - } - invoice, err := net.Alice.DecodePayReq(ctxt, decodeReq) - require.NoError(t.t, err, "could not decode invoice") - require.Len(t.t, invoice.RouteHints, 1) + invoice := alice.RPC.DecodePayReq(resp.PaymentRequest) + require.Len(ht, invoice.RouteHints, 1) + stream := carol.RPC.SubscribeSingleInvoice(payHash[:]) invoiceStreams = append(invoiceStreams, stream) - payReqs = append(payReqs, resp.PaymentRequest) } // Wait for all the invoices to reach the OPEN state. - for _, stream := range invoiceStreams { - invoice, err := stream.Recv() - if err != nil { - t.Fatalf("err: %v", err) - } - - if invoice.State != lnrpc.Invoice_OPEN { - t.Fatalf("expected OPEN, got state: %v", invoice.State) - } - } + assertInvoiceState(lnrpc.Invoice_OPEN) // Let Alice initiate payments for all the created invoices. - var paymentStreams []routerrpc.Router_SendPaymentV2Client for _, payReq := range payReqs { - ctx, cancel := context.WithCancel(ctxb) - defer cancel() + req := &routerrpc.SendPaymentRequest{ + PaymentRequest: payReq, + TimeoutSeconds: 60, + FeeLimitSat: 1000000, + } - payStream, err := net.Alice.RouterClient.SendPaymentV2( - ctx, &routerrpc.SendPaymentRequest{ - PaymentRequest: payReq, - TimeoutSeconds: 60, - FeeLimitSat: 1000000, - }, + // Wait for inflight status update. + ht.SendPaymentAndAssertStatus( + alice, req, lnrpc.Payment_IN_FLIGHT, ) - if err != nil { - t.Fatalf("unable to send alice htlc: %v", err) - } - - paymentStreams = append(paymentStreams, payStream) } - // Wait for inlight status update. - for _, payStream := range paymentStreams { - payment, err := payStream.Recv() - if err != nil { - t.Fatalf("Failed receiving status update: %v", err) - } - - if payment.Status != lnrpc.Payment_IN_FLIGHT { - t.Fatalf("state not in flight: %v", payment.Status) - } - } - - // The payments should now show up in Alice's ListInvoices, with a zero + // The payments should now show up in Alice's ListPayments, with a zero // preimage, indicating they are not yet settled. - err = wait.NoError(func() error { - req := &lnrpc.ListPaymentsRequest{ - IncludeIncomplete: true, - } - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - paymentsResp, err := net.Alice.ListPayments(ctxt, req) - if err != nil { - return fmt.Errorf("error when obtaining payments: %v", - err) - } + var zeroPreimg lntypes.Preimage + err := wait.NoError(func() error { + payments := ht.AssertNumPayments(alice, numPayments) // Gather the payment hashes we are looking for in the // response. @@ -209,18 +129,16 @@ func testHoldInvoicePersistence(net *lntest.NetworkHarness, t *harnessTest) { payHashes[preimg.Hash().String()] = struct{}{} } - var zeroPreimg lntypes.Preimage - for _, payment := range paymentsResp.Payments { + for _, payment := range payments { _, ok := payHashes[payment.PaymentHash] if !ok { continue } // The preimage should NEVER be non-zero at this point. - if payment.PaymentPreimage != zeroPreimg.String() { - t.Fatalf("expected zero preimage, got %v", - payment.PaymentPreimage) - } + require.Equal(ht, zeroPreimg.String(), + payment.PaymentPreimage, + "expected zero preimage") // We wait for the payment attempt to have been // properly recorded in the DB. @@ -237,201 +155,73 @@ func testHoldInvoicePersistence(net *lntest.NetworkHarness, t *harnessTest) { return nil }, defaultTimeout) - if err != nil { - t.Fatalf("predicate not satisfied: %v", err) - } + require.NoError(ht, err, "timeout checking alice's payments") // Wait for all invoices to be accepted. - for _, stream := range invoiceStreams { - invoice, err := stream.Recv() - if err != nil { - t.Fatalf("err: %v", err) - } - - if invoice.State != lnrpc.Invoice_ACCEPTED { - t.Fatalf("expected ACCEPTED, got state: %v", - invoice.State) - } - } + assertInvoiceState(lnrpc.Invoice_ACCEPTED) // Restart alice. This to ensure she will still be able to handle // settling the invoices after a restart. - if err := net.RestartNode(net.Alice, nil); err != nil { - t.Fatalf("Node restart failed: %v", err) - } + ht.RestartNode(alice) + + // Ensure the connections are made. + // + // TODO(yy): we shouldn't need these two lines since the connections + // are permanent, they'd reconnect automatically upon Alice's restart. + // However, we'd sometimes see the error `unable to gracefully close + // channel while peer is offline (try force closing it instead): + // channel link not found` from closing the channels in the end, + // indicating there's something wrong with the peer conn. We need to + // investigate and fix it in peer conn management. + ht.EnsureConnected(alice, bob) + ht.EnsureConnected(alice, carol) // Now after a restart, we must re-track the payments. We set up a // goroutine for each to track their status updates. - var ( - statusUpdates []chan *lnrpc.Payment - wg sync.WaitGroup - quit = make(chan struct{}) - ) - - defer close(quit) for _, preimg := range preimages { hash := preimg.Hash() - ctx, cancel := context.WithCancel(ctxb) - defer cancel() + payStream := alice.RPC.TrackPaymentV2(hash[:]) + ht.ReceiveTrackPayment(payStream) - payStream, err := net.Alice.RouterClient.TrackPaymentV2( - ctx, &routerrpc.TrackPaymentRequest{ - PaymentHash: hash[:], - }, + ht.AssertPaymentStatus( + alice, preimg, lnrpc.Payment_IN_FLIGHT, ) - if err != nil { - t.Fatalf("unable to send track payment: %v", err) - } - - // We set up a channel where we'll forward any status update. - upd := make(chan *lnrpc.Payment) - wg.Add(1) - go func() { - defer wg.Done() - - for { - payment, err := payStream.Recv() - if err != nil { - close(upd) - return - } - - select { - case upd <- payment: - case <-quit: - return - } - } - }() - - statusUpdates = append(statusUpdates, upd) - } - - // Wait for the in-flight status update. - for _, upd := range statusUpdates { - select { - case payment, ok := <-upd: - if !ok { - t.Fatalf("failed getting payment update") - } - - if payment.Status != lnrpc.Payment_IN_FLIGHT { - t.Fatalf("state not in in flight: %v", - payment.Status) - } - case <-time.After(5 * time.Second): - t.Fatalf("in flight status not received") - } } // Settle invoices half the invoices, cancel the rest. for i, preimage := range preimages { - var expectedState lnrpc.Invoice_InvoiceState - - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) if i%2 == 0 { - settle := &invoicesrpc.SettleInvoiceMsg{ - Preimage: preimage[:], - } - _, err = carol.SettleInvoice(ctxt, settle) - - expectedState = lnrpc.Invoice_SETTLED + carol.RPC.SettleInvoice(preimage[:]) + ht.AssertInvoiceState( + invoiceStreams[i], lnrpc.Invoice_SETTLED, + ) } else { hash := preimage.Hash() - settle := &invoicesrpc.CancelInvoiceMsg{ - PaymentHash: hash[:], - } - _, err = carol.CancelInvoice(ctxt, settle) - - expectedState = lnrpc.Invoice_CANCELED - } - if err != nil { - t.Fatalf("unable to cancel/settle invoice: %v", err) - } - - stream := invoiceStreams[i] - invoice, err := stream.Recv() - require.NoError(t.t, err) - require.Equal(t.t, expectedState, invoice.State) - } - - // Make sure we get the expected status update. - for i, upd := range statusUpdates { - // Read until the payment is in a terminal state. - var payment *lnrpc.Payment - for payment == nil { - select { - case p, ok := <-upd: - if !ok { - t.Fatalf("failed getting payment update") - } - - if p.Status == lnrpc.Payment_IN_FLIGHT { - continue - } - - payment = p - case <-time.After(5 * time.Second): - t.Fatalf("in flight status not received") - } - } - - // Assert terminal payment state. - if i%2 == 0 { - if payment.Status != lnrpc.Payment_SUCCEEDED { - t.Fatalf("state not succeeded : %v", - payment.Status) - } - } else { - if payment.FailureReason != - lnrpc.PaymentFailureReason_FAILURE_REASON_INCORRECT_PAYMENT_DETAILS { - - t.Fatalf("state not failed: %v", - payment.FailureReason) - } + carol.RPC.CancelInvoice(hash[:]) + ht.AssertInvoiceState( + invoiceStreams[i], lnrpc.Invoice_CANCELED, + ) } } // Check that Alice's invoices to be shown as settled and failed // accordingly, and preimages matching up. - req := &lnrpc.ListPaymentsRequest{ - IncludeIncomplete: true, - } - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - paymentsResp, err := net.Alice.ListPayments(ctxt, req) - if err != nil { - t.Fatalf("error when obtaining Alice payments: %v", err) - } - for i, preimage := range preimages { - paymentHash := preimage.Hash() - var p string - for _, resp := range paymentsResp.Payments { - if resp.PaymentHash == paymentHash.String() { - p = resp.PaymentPreimage - break - } - } - if p == "" { - t.Fatalf("payment not found") - } - + for i, preimg := range preimages { if i%2 == 0 { - if p != preimage.String() { - t.Fatalf("preimage doesn't match: %v vs %v", - p, preimage.String()) - } + ht.AssertPaymentStatus( + alice, preimg, lnrpc.Payment_SUCCEEDED, + ) } else { - if p != lntypes.ZeroHash.String() { - t.Fatalf("preimage not zero: %v", p) - } + payment := ht.AssertPaymentStatus( + alice, preimg, lnrpc.Payment_FAILED, + ) + require.Equal(ht, reason, payment.FailureReason, + "wrong failure reason") } } - // Check that all of our invoice streams are terminated by the server - // since the invoices have completed. - for _, stream := range invoiceStreams { - _, err = stream.Recv() - require.Equal(t.t, io.EOF, err) - } + // Finally, close all channels. + ht.CloseChannel(alice, chanPointBob) + ht.CloseChannel(alice, chanPointAlice) } diff --git a/lntest/itest/lnd_test_list_on_test.go b/lntest/itest/lnd_test_list_on_test.go index 498d66b1e..4e81f52fd 100644 --- a/lntest/itest/lnd_test_list_on_test.go +++ b/lntest/itest/lnd_test_list_on_test.go @@ -110,10 +110,6 @@ var allTestCases = []*testCase{ name: "route fee cutoff", test: testRouteFeeCutoff, }, - { - name: "hold invoice sender persistence", - test: testHoldInvoicePersistence, - }, { name: "cpfp", test: testCPFP, From 638872520e0091e31d7eca5d5f10be24bf4eb55e Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Fri, 5 Aug 2022 18:12:11 +0800 Subject: [PATCH 04/44] itest: refactor `testMaxChannelSize` --- lntest/itest/list_on_test.go | 4 + lntest/itest/lnd_max_channel_size_test.go | 95 ++++++++--------------- lntest/itest/lnd_test_list_on_test.go | 4 - 3 files changed, 35 insertions(+), 68 deletions(-) diff --git a/lntest/itest/list_on_test.go b/lntest/itest/list_on_test.go index 3227a260c..baa553f21 100644 --- a/lntest/itest/list_on_test.go +++ b/lntest/itest/list_on_test.go @@ -243,4 +243,8 @@ var allTestCasesTemp = []*lntemp.TestCase{ Name: "hold invoice sender persistence", TestFunc: testHoldInvoicePersistence, }, + { + Name: "maximum channel size", + TestFunc: testMaxChannelSize, + }, } diff --git a/lntest/itest/lnd_max_channel_size_test.go b/lntest/itest/lnd_max_channel_size_test.go index 100f5bb34..d795fb2e8 100644 --- a/lntest/itest/lnd_max_channel_size_test.go +++ b/lntest/itest/lnd_max_channel_size_test.go @@ -5,79 +5,50 @@ package itest import ( "fmt" - "strings" "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/funding" - "github.com/lightningnetwork/lnd/lntest" + "github.com/lightningnetwork/lnd/lntemp" + "github.com/lightningnetwork/lnd/lnwallet" ) -// testMaxChannelSize tests that lnd handles --maxchansize parameter -// correctly. Wumbo nodes should enforce a default soft limit of 10 BTC by -// default. This limit can be adjusted with --maxchansize config option -func testMaxChannelSize(net *lntest.NetworkHarness, t *harnessTest) { - // We'll make two new nodes, both wumbo but with the default - // limit on maximum channel size (10 BTC) - wumboNode := net.NewNode( - t.t, "wumbo", []string{"--protocol.wumbo-channels"}, +// testMaxChannelSize tests that lnd handles --maxchansize parameter correctly. +// Wumbo nodes should enforce a default soft limit of 10 BTC by default. This +// limit can be adjusted with --maxchansize config option. +func testMaxChannelSize(ht *lntemp.HarnessTest) { + // We'll make two new nodes, both wumbo but with the default limit on + // maximum channel size (10 BTC) + wumboNode := ht.NewNode( + "wumbo", []string{"--protocol.wumbo-channels"}, ) - defer shutdownAndAssert(net, t, wumboNode) - - wumboNode2 := net.NewNode( - t.t, "wumbo2", []string{"--protocol.wumbo-channels"}, + wumboNode2 := ht.NewNode( + "wumbo2", []string{"--protocol.wumbo-channels"}, ) - defer shutdownAndAssert(net, t, wumboNode2) - // We'll send 11 BTC to the wumbo node so it can test the wumbo soft limit. - net.SendCoins(t.t, 11*btcutil.SatoshiPerBitcoin, wumboNode) + // We'll send 11 BTC to the wumbo node so it can test the wumbo soft + // limit. + ht.FundCoins(11*btcutil.SatoshiPerBitcoin, wumboNode) // Next we'll connect both nodes, then attempt to make a wumbo channel // funding request, which should fail as it exceeds the default wumbo // soft limit of 10 BTC. - net.EnsureConnected(t.t, wumboNode, wumboNode2) + ht.EnsureConnected(wumboNode, wumboNode2) chanAmt := funding.MaxBtcFundingAmountWumbo + 1 - _, err := net.OpenChannel( - wumboNode, wumboNode2, lntest.OpenChannelParams{ - Amt: chanAmt, - }, + // The test should show failure due to the channel exceeding our max + // size. + expectedErr := lnwallet.ErrChanTooLarge( + chanAmt, funding.MaxBtcFundingAmountWumbo, ) - if err == nil { - t.Fatalf("expected channel funding to fail as it exceeds 10 BTC limit") - } - - // The test should show failure due to the channel exceeding our max size. - if !strings.Contains(err.Error(), "exceeds maximum chan size") { - t.Fatalf("channel should be rejected due to size, instead "+ - "error was: %v", err) - } - - // Next we'll create a non-wumbo node to verify that it enforces the - // BOLT-02 channel size limit and rejects our funding request. - miniNode := net.NewNode(t.t, "mini", nil) - defer shutdownAndAssert(net, t, miniNode) - - net.EnsureConnected(t.t, wumboNode, miniNode) - - _, err = net.OpenChannel( - wumboNode, miniNode, lntest.OpenChannelParams{ - Amt: chanAmt, - }, + ht.OpenChannelAssertErr( + wumboNode, wumboNode2, + lntemp.OpenChannelParams{Amt: chanAmt}, expectedErr, ) - if err == nil { - t.Fatalf("expected channel funding to fail as it exceeds 0.16 BTC limit") - } - // The test should show failure due to the channel exceeding our max size. - if !strings.Contains(err.Error(), "exceeds maximum chan size") { - t.Fatalf("channel should be rejected due to size, instead "+ - "error was: %v", err) - } - - // We'll now make another wumbo node with appropriate maximum channel size - // to accept our wumbo channel funding. - wumboNode3 := net.NewNode( - t.t, "wumbo3", []string{ + // We'll now make another wumbo node with appropriate maximum channel + // size to accept our wumbo channel funding. + wumboNode3 := ht.NewNode( + "wumbo3", []string{ "--protocol.wumbo-channels", fmt.Sprintf( "--maxchansize=%v", @@ -85,16 +56,12 @@ func testMaxChannelSize(net *lntest.NetworkHarness, t *harnessTest) { ), }, ) - defer shutdownAndAssert(net, t, wumboNode3) // Creating a wumbo channel between these two nodes should succeed. - net.EnsureConnected(t.t, wumboNode, wumboNode3) - chanPoint := openChannelAndAssert( - t, net, wumboNode, wumboNode3, - lntest.OpenChannelParams{ - Amt: chanAmt, - }, + ht.EnsureConnected(wumboNode, wumboNode3) + chanPoint := ht.OpenChannel( + wumboNode, wumboNode3, lntemp.OpenChannelParams{Amt: chanAmt}, ) - closeChannelAndAssert(t, net, wumboNode, chanPoint, false) + ht.CloseChannel(wumboNode, chanPoint) } diff --git a/lntest/itest/lnd_test_list_on_test.go b/lntest/itest/lnd_test_list_on_test.go index 4e81f52fd..aefdb7b9b 100644 --- a/lntest/itest/lnd_test_list_on_test.go +++ b/lntest/itest/lnd_test_list_on_test.go @@ -186,10 +186,6 @@ var allTestCases = []*testCase{ name: "wumbo channels", test: testWumboChannels, }, - { - name: "maximum channel size", - test: testMaxChannelSize, - }, { name: "stateless init", test: testStatelessInit, From ec3e1561b64c2d5a47a0240260218665cc012dd3 Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Fri, 5 Aug 2022 18:15:48 +0800 Subject: [PATCH 05/44] itest: refactor `testWumboChannels` --- lntest/itest/list_on_test.go | 4 ++ lntest/itest/lnd_test_list_on_test.go | 4 -- lntest/itest/lnd_wumbo_channels_test.go | 52 ++++++++----------------- 3 files changed, 21 insertions(+), 39 deletions(-) diff --git a/lntest/itest/list_on_test.go b/lntest/itest/list_on_test.go index baa553f21..2bb5dcee6 100644 --- a/lntest/itest/list_on_test.go +++ b/lntest/itest/list_on_test.go @@ -247,4 +247,8 @@ var allTestCasesTemp = []*lntemp.TestCase{ Name: "maximum channel size", TestFunc: testMaxChannelSize, }, + { + Name: "wumbo channels", + TestFunc: testWumboChannels, + }, } diff --git a/lntest/itest/lnd_test_list_on_test.go b/lntest/itest/lnd_test_list_on_test.go index aefdb7b9b..dea3d20ef 100644 --- a/lntest/itest/lnd_test_list_on_test.go +++ b/lntest/itest/lnd_test_list_on_test.go @@ -182,10 +182,6 @@ var allTestCases = []*testCase{ name: "forward interceptor dedup htlcs", test: testForwardInterceptorDedupHtlc, }, - { - name: "wumbo channels", - test: testWumboChannels, - }, { name: "stateless init", test: testStatelessInit, diff --git a/lntest/itest/lnd_wumbo_channels_test.go b/lntest/itest/lnd_wumbo_channels_test.go index ddfd6d677..5a5ba6627 100644 --- a/lntest/itest/lnd_wumbo_channels_test.go +++ b/lntest/itest/lnd_wumbo_channels_test.go @@ -1,70 +1,52 @@ package itest import ( - "strings" - "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/funding" - "github.com/lightningnetwork/lnd/lntest" + "github.com/lightningnetwork/lnd/lntemp" + "github.com/lightningnetwork/lnd/lnwallet" ) // testWumboChannels tests that only a node that signals wumbo channel // acceptances will allow a wumbo channel to be created. Additionally, if a // node is running with mini channels only enabled, then they should reject any // inbound wumbo channel requests. -func testWumboChannels(net *lntest.NetworkHarness, t *harnessTest) { +func testWumboChannels(ht *lntemp.HarnessTest) { // With all the channel types exercised, we'll now make sure the wumbo // signalling support works properly. // // We'll make two new nodes, with one of them signalling support for // wumbo channels while the other doesn't. - wumboNode := net.NewNode( - t.t, "wumbo", []string{"--protocol.wumbo-channels"}, - ) - defer shutdownAndAssert(net, t, wumboNode) - miniNode := net.NewNode(t.t, "mini", nil) - defer shutdownAndAssert(net, t, miniNode) + wumboNode := ht.NewNode("wumbo", []string{"--protocol.wumbo-channels"}) + miniNode := ht.NewNode("mini", nil) // We'll send coins to the wumbo node, as it'll be the one imitating // the channel funding. - net.SendCoins(t.t, btcutil.SatoshiPerBitcoin, wumboNode) + ht.FundCoins(btcutil.SatoshiPerBitcoin, wumboNode) // Next we'll connect both nodes, then attempt to make a wumbo channel // funding request to the mini node we created above. The wumbo request // should fail as the node isn't advertising wumbo channels. - net.EnsureConnected(t.t, wumboNode, miniNode) + ht.EnsureConnected(wumboNode, miniNode) chanAmt := funding.MaxBtcFundingAmount + 1 - _, err := net.OpenChannel( - wumboNode, miniNode, lntest.OpenChannelParams{ - Amt: chanAmt, - }, - ) - if err == nil { - t.Fatalf("expected wumbo channel funding to fail") - } - // The test should indicate a failure due to the channel being too // large. - if !strings.Contains(err.Error(), "exceeds maximum chan size") { - t.Fatalf("channel should be rejected due to size, instead "+ - "error was: %v", err) - } + ht.OpenChannelAssertErr( + wumboNode, miniNode, lntemp.OpenChannelParams{Amt: chanAmt}, + lnwallet.ErrChanTooLarge(chanAmt, funding.MaxBtcFundingAmount), + ) // We'll now make another wumbo node to accept our wumbo channel // funding. - wumboNode2 := net.NewNode( - t.t, "wumbo2", []string{"--protocol.wumbo-channels"}, + wumboNode2 := ht.NewNode( + "wumbo2", []string{"--protocol.wumbo-channels"}, ) - defer shutdownAndAssert(net, t, wumboNode2) // Creating a wumbo channel between these two nodes should succeed. - net.EnsureConnected(t.t, wumboNode, wumboNode2) - chanPoint := openChannelAndAssert( - t, net, wumboNode, wumboNode2, - lntest.OpenChannelParams{ - Amt: chanAmt, - }, + ht.EnsureConnected(wumboNode, wumboNode2) + chanPoint := ht.OpenChannel( + wumboNode, wumboNode2, lntemp.OpenChannelParams{Amt: chanAmt}, ) - closeChannelAndAssert(t, net, wumboNode, chanPoint, false) + ht.CloseChannel(wumboNode, chanPoint) } From 2a7830d442ddda447957413ba25c05733e405fe2 Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Fri, 5 Aug 2022 18:22:19 +0800 Subject: [PATCH 06/44] itest: refactor `testMaxHtlcPathfind` --- lntest/itest/list_on_test.go | 4 + lntest/itest/lnd_max_htlcs_test.go | 207 +++++++++----------------- lntest/itest/lnd_test_list_on_test.go | 4 - 3 files changed, 74 insertions(+), 141 deletions(-) diff --git a/lntest/itest/list_on_test.go b/lntest/itest/list_on_test.go index 2bb5dcee6..7671663ec 100644 --- a/lntest/itest/list_on_test.go +++ b/lntest/itest/list_on_test.go @@ -251,4 +251,8 @@ var allTestCasesTemp = []*lntemp.TestCase{ Name: "wumbo channels", TestFunc: testWumboChannels, }, + { + Name: "max htlc pathfind", + TestFunc: testMaxHtlcPathfind, + }, } diff --git a/lntest/itest/lnd_max_htlcs_test.go b/lntest/itest/lnd_max_htlcs_test.go index 9f5218cac..d457e452f 100644 --- a/lntest/itest/lnd_max_htlcs_test.go +++ b/lntest/itest/lnd_max_htlcs_test.go @@ -1,13 +1,11 @@ package itest import ( - "context" - "testing" - "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/invoicesrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" - "github.com/lightningnetwork/lnd/lntest" + "github.com/lightningnetwork/lnd/lntemp" + "github.com/lightningnetwork/lnd/lntemp/node" "github.com/lightningnetwork/lnd/lntypes" "github.com/stretchr/testify/require" ) @@ -16,118 +14,69 @@ import ( // channel where we have already reached the limit of the number of htlcs that // we may add to the remote party's commitment. This test asserts that we do // not attempt to use the full channel at all in our pathfinding. -func testMaxHtlcPathfind(net *lntest.NetworkHarness, t *harnessTest) { - ctxb := context.Background() - +func testMaxHtlcPathfind(ht *lntemp.HarnessTest) { // Setup a channel between Alice and Bob where Alice will only allow // Bob to add a maximum of 5 htlcs to her commitment. maxHtlcs := 5 - chanPoint := openChannelAndAssert( - t, net, net.Alice, net.Bob, - lntest.OpenChannelParams{ + alice, bob := ht.Alice, ht.Bob + chanPoint := ht.OpenChannel( + alice, bob, lntemp.OpenChannelParams{ Amt: 1000000, PushAmt: 800000, RemoteMaxHtlcs: uint16(maxHtlcs), }, ) - // Wait for Alice and Bob to receive the channel edge from the - // funding manager. - err := net.Alice.WaitForNetworkChannelOpen(chanPoint) - require.NoError(t.t, err, "alice does not have open channel") - - err = net.Bob.WaitForNetworkChannelOpen(chanPoint) - require.NoError(t.t, err, "bob does not have open channel") - // Alice and bob should have one channel open with each other now. - assertNodeNumChannels(t, net.Alice, 1) - assertNodeNumChannels(t, net.Bob, 1) + ht.AssertNodeNumChannels(alice, 1) + ht.AssertNodeNumChannels(bob, 1) // Send our maximum number of htlcs from Bob -> Alice so that we get // to a point where Alice won't accept any more htlcs on the channel. subscriptions := make([]*holdSubscription, maxHtlcs) - cancelCtxs := make([]func(), maxHtlcs) for i := 0; i < maxHtlcs; i++ { - subCtx, cancel := context.WithTimeout(ctxb, defaultTimeout) - cancelCtxs[i] = cancel - - subscriptions[i] = acceptHoldInvoice( - subCtx, t.t, i, net.Bob, net.Alice, - ) + subscriptions[i] = acceptHoldInvoice(ht, i, bob, alice) } - // Cancel all of our subscriptions on exit. - defer func() { - for _, cancel := range cancelCtxs { - cancel() - } - }() - - err = assertNumActiveHtlcs([]*lntest.HarnessNode{ - net.Alice, net.Bob, - }, maxHtlcs) - require.NoError(t.t, err, "htlcs not active") + ht.AssertNumActiveHtlcs(alice, maxHtlcs) + ht.AssertNumActiveHtlcs(bob, maxHtlcs) // Now we send a payment from Alice -> Bob to sanity check that our // commitment limit is not applied in the opposite direction. - subCtx, cancel := context.WithTimeout(ctxb, defaultTimeout) - defer cancel() - aliceBobSub := acceptHoldInvoice( - subCtx, t.t, maxHtlcs, net.Alice, net.Bob, - ) - err = assertNumActiveHtlcs([]*lntest.HarnessNode{ - net.Alice, net.Bob, - }, maxHtlcs+1) - require.NoError(t.t, err, "htlcs not active") + aliceBobSub := acceptHoldInvoice(ht, maxHtlcs, alice, bob) + ht.AssertNumActiveHtlcs(alice, maxHtlcs+1) + ht.AssertNumActiveHtlcs(bob, maxHtlcs+1) // Now, we're going to try to send another payment from Bob -> Alice. // We've hit our max remote htlcs, so we expect this payment to spin // out dramatically with pathfinding. - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - payment, err := net.Bob.RouterClient.SendPaymentV2( - ctxt, &routerrpc.SendPaymentRequest{ - Amt: 1000, - Dest: net.Alice.PubKey[:], - TimeoutSeconds: 60, - FeeLimitSat: 1000000, - MaxParts: 10, - Amp: true, - }, - ) - require.NoError(t.t, err, "send payment failed") - - update, err := payment.Recv() - require.NoError(t.t, err, "no payment in flight update") - require.Equal(t.t, lnrpc.Payment_IN_FLIGHT, update.Status, - "payment not inflight") - - update, err = payment.Recv() - require.NoError(t.t, err, "no payment failed update") - require.Equal(t.t, lnrpc.Payment_FAILED, update.Status) - require.Len(t.t, update.Htlcs, 0, "expected no htlcs dispatched") + sendReq := &routerrpc.SendPaymentRequest{ + Amt: 1000, + Dest: alice.PubKey[:], + TimeoutSeconds: 60, + FeeLimitSat: 1000000, + MaxParts: 10, + Amp: true, + } + ht.SendPaymentAndAssertStatus(bob, sendReq, lnrpc.Payment_FAILED) // Now that we're done, we cancel all our pending htlcs so that we // can cleanup the channel with a coop close. for _, sub := range subscriptions { - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - sub.cancel(ctxt, t.t) + sub.cancel(ht) } + aliceBobSub.cancel(ht) - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - aliceBobSub.cancel(ctxt, t.t) + ht.AssertNumActiveHtlcs(alice, 0) + ht.AssertNumActiveHtlcs(bob, 0) - err = assertNumActiveHtlcs([]*lntest.HarnessNode{ - net.Alice, net.Bob, - }, 0) - require.NoError(t.t, err, "expected all htlcs canceled") - - closeChannelAndAssert(t, net, net.Alice, chanPoint, false) + ht.CloseChannel(alice, chanPoint) } type holdSubscription struct { - recipient invoicesrpc.InvoicesClient + recipient *node.HarnessNode hash lntypes.Hash invSubscription invoicesrpc.Invoices_SubscribeSingleInvoiceClient paymentSubscription routerrpc.Router_SendPaymentV2Client @@ -135,89 +84,73 @@ type holdSubscription struct { // cancel updates a hold invoice to cancel from the recipient and consumes // updates from the payer until it has reached a final, failed state. -func (h *holdSubscription) cancel(ctx context.Context, t *testing.T) { - _, err := h.recipient.CancelInvoice(ctx, &invoicesrpc.CancelInvoiceMsg{ - PaymentHash: h.hash[:], - }) - require.NoError(t, err, "invoice cancel failed") +func (h *holdSubscription) cancel(ht *lntemp.HarnessTest) { + h.recipient.RPC.CancelInvoice(h.hash[:]) - invUpdate, err := h.invSubscription.Recv() - require.NoError(t, err, "cancel invoice subscribe failed") - require.Equal(t, lnrpc.Invoice_CANCELED, invUpdate.State, + invUpdate := ht.ReceiveSingleInvoice(h.invSubscription) + require.Equal(ht, lnrpc.Invoice_CANCELED, invUpdate.State, "expected invoice canceled") // We expect one in flight update when our htlc is canceled back, and // another when we fail the payment as a whole. - payUpdate, err := h.paymentSubscription.Recv() - require.NoError(t, err, "cancel payment subscribe failed") - require.Len(t, payUpdate.Htlcs, 1) - require.Equal(t, lnrpc.Payment_IN_FLIGHT, payUpdate.Status) + payUpdate := ht.AssertPaymentStatusFromStream( + h.paymentSubscription, lnrpc.Payment_IN_FLIGHT, + ) + require.Len(ht, payUpdate.Htlcs, 1) - payUpdate, err = h.paymentSubscription.Recv() - require.NoError(t, err, "cancel payment subscribe failed") - require.Equal(t, lnrpc.Payment_FAILED, payUpdate.Status, + payUpdate = ht.AssertPaymentStatusFromStream( + h.paymentSubscription, lnrpc.Payment_FAILED, + ) + require.Equal(ht, lnrpc.Payment_FAILED, payUpdate.Status, "expected payment failed") - require.Equal(t, lnrpc.PaymentFailureReason_FAILURE_REASON_INCORRECT_PAYMENT_DETAILS, + require.Equal(ht, lnrpc.PaymentFailureReason_FAILURE_REASON_INCORRECT_PAYMENT_DETAILS, //nolint:lll payUpdate.FailureReason, "expected unknown details") } // acceptHoldInvoice adds a hold invoice to the recipient node, pays it from // the sender and asserts that we have reached the accepted state where htlcs // are locked in for the payment. -func acceptHoldInvoice(ctx context.Context, t *testing.T, idx int, sender, - receiver *lntest.HarnessNode) *holdSubscription { +func acceptHoldInvoice(ht *lntemp.HarnessTest, idx int, sender, + receiver *node.HarnessNode) *holdSubscription { hash := [lntypes.HashSize]byte{byte(idx + 1)} - invoice, err := receiver.AddHoldInvoice( - ctx, &invoicesrpc.AddHoldInvoiceRequest{ - ValueMsat: 10000, - Hash: hash[:], - }, - ) - require.NoError(t, err, "couldn't add invoice") + req := &invoicesrpc.AddHoldInvoiceRequest{ + ValueMsat: 10000, + Hash: hash[:], + } + invoice := receiver.RPC.AddHoldInvoice(req) - invStream, err := receiver.InvoicesClient.SubscribeSingleInvoice( - ctx, &invoicesrpc.SubscribeSingleInvoiceRequest{ - RHash: hash[:], - }, - ) - require.NoError(t, err, "could not subscribe to invoice") + invStream := receiver.RPC.SubscribeSingleInvoice(hash[:]) + inv := ht.ReceiveSingleInvoice(invStream) + require.Equal(ht, lnrpc.Invoice_OPEN, inv.State, "expect open") - inv, err := invStream.Recv() - require.NoError(t, err, "invoice open stream failed") - require.Equal(t, lnrpc.Invoice_OPEN, inv.State, - "expected open") - - payStream, err := sender.RouterClient.SendPaymentV2( - ctx, &routerrpc.SendPaymentRequest{ - PaymentRequest: invoice.PaymentRequest, - TimeoutSeconds: 60, - FeeLimitSat: 1000000, - }, - ) - require.NoError(t, err, "send payment failed") + sendReq := &routerrpc.SendPaymentRequest{ + PaymentRequest: invoice.PaymentRequest, + TimeoutSeconds: 60, + FeeLimitSat: 1000000, + } + payStream := sender.RPC.SendPayment(sendReq) // Finally, assert that we progress to an accepted state. We expect // the payer to get one update for the creation of the payment, and // another when a htlc is dispatched. - payment, err := payStream.Recv() - require.NoError(t, err, "payment in flight stream failed") - require.Equal(t, lnrpc.Payment_IN_FLIGHT, payment.Status) - require.Len(t, payment.Htlcs, 0) + payment := ht.AssertPaymentStatusFromStream( + payStream, lnrpc.Payment_IN_FLIGHT, + ) + require.Empty(ht, payment.Htlcs) - payment, err = payStream.Recv() - require.NoError(t, err, "payment in flight stream failed") - require.Equal(t, lnrpc.Payment_IN_FLIGHT, payment.Status) - require.Len(t, payment.Htlcs, 1) + payment = ht.AssertPaymentStatusFromStream( + payStream, lnrpc.Payment_IN_FLIGHT, + ) + require.Len(ht, payment.Htlcs, 1) - inv, err = invStream.Recv() - require.NoError(t, err, "invoice accepted stream failed") - require.Equal(t, lnrpc.Invoice_ACCEPTED, inv.State, - "expected accepted invoice") + inv = ht.ReceiveSingleInvoice(invStream) + require.Equal(ht, lnrpc.Invoice_ACCEPTED, inv.State, + "expected accepted") return &holdSubscription{ - recipient: receiver.InvoicesClient, + recipient: receiver, hash: hash, invSubscription: invStream, paymentSubscription: payStream, diff --git a/lntest/itest/lnd_test_list_on_test.go b/lntest/itest/lnd_test_list_on_test.go index dea3d20ef..74010eb0a 100644 --- a/lntest/itest/lnd_test_list_on_test.go +++ b/lntest/itest/lnd_test_list_on_test.go @@ -194,10 +194,6 @@ var allTestCases = []*testCase{ name: "wallet import pubkey", test: testWalletImportPubKey, }, - { - name: "max htlc pathfind", - test: testMaxHtlcPathfind, - }, { name: "rpc middleware interceptor", test: testRPCMiddlewareInterceptor, From 130c4e325ad9735d8bbe82b5eb0a6353d133a7ce Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Fri, 5 Aug 2022 18:36:15 +0800 Subject: [PATCH 07/44] lntemp+itest: refactor `testHtlcErrorPropagation` --- lntemp/harness_assertion.go | 93 ++++ lntemp/rpc/router.go | 53 +++ lntest/itest/list_on_test.go | 4 + .../lnd_multi-hop-error-propagation_test.go | 419 ++++++++---------- lntest/itest/lnd_test_list_on_test.go | 4 - 5 files changed, 336 insertions(+), 237 deletions(-) diff --git a/lntemp/harness_assertion.go b/lntemp/harness_assertion.go index e92bba564..5ef7cce73 100644 --- a/lntemp/harness_assertion.go +++ b/lntemp/harness_assertion.go @@ -19,6 +19,7 @@ import ( "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/invoicesrpc" + "github.com/lightningnetwork/lnd/lnrpc/routerrpc" "github.com/lightningnetwork/lnd/lnrpc/walletrpc" "github.com/lightningnetwork/lnd/lntemp/node" "github.com/lightningnetwork/lnd/lntemp/rpc" @@ -1759,3 +1760,95 @@ func (h *HarnessTest) ReceiveTrackPayment( return nil } + +// ReceiveHtlcEvent waits until a message is received on the subscribe +// htlc event stream or the timeout is reached. +func (h *HarnessTest) ReceiveHtlcEvent( + stream rpc.HtlcEventsClient) *routerrpc.HtlcEvent { + + chanMsg := make(chan *routerrpc.HtlcEvent) + errChan := make(chan error) + go func() { + // Consume one message. This will block until the message is + // received. + resp, err := stream.Recv() + if err != nil { + errChan <- err + return + } + chanMsg <- resp + }() + + select { + case <-time.After(DefaultTimeout): + require.Fail(h, "timeout", "timeout receiving htlc "+ + "event update") + + case err := <-errChan: + require.Failf(h, "err from stream", + "received err from stream: %v", err) + + case updateMsg := <-chanMsg: + return updateMsg + } + + return nil +} + +// AssertHtlcEventType consumes one event from a client and asserts the event +// type is matched. +func (h *HarnessTest) AssertHtlcEventType(client rpc.HtlcEventsClient, + userType routerrpc.HtlcEvent_EventType) *routerrpc.HtlcEvent { + + event := h.ReceiveHtlcEvent(client) + require.Equalf(h, userType, event.EventType, "wrong event type, "+ + "want %v got %v", userType, event.EventType) + + return event +} + +// HtlcEvent maps the series of event types used in `*routerrpc.HtlcEvent_*`. +type HtlcEvent int + +const ( + HtlcEventForward HtlcEvent = iota + HtlcEventForwardFail + HtlcEventSettle + HtlcEventLinkFail + HtlcEventFinal +) + +// AssertHtlcEventType consumes one event from a client and asserts both the +// user event type the event.Event type is matched. +func (h *HarnessTest) AssertHtlcEventTypes(client rpc.HtlcEventsClient, + userType routerrpc.HtlcEvent_EventType, + eventType HtlcEvent) *routerrpc.HtlcEvent { + + event := h.ReceiveHtlcEvent(client) + require.Equalf(h, userType, event.EventType, "wrong event type, "+ + "want %v got %v", userType, event.EventType) + + var ok bool + + switch eventType { + case HtlcEventForward: + _, ok = event.Event.(*routerrpc.HtlcEvent_ForwardEvent) + + case HtlcEventForwardFail: + _, ok = event.Event.(*routerrpc.HtlcEvent_ForwardFailEvent) + + case HtlcEventSettle: + _, ok = event.Event.(*routerrpc.HtlcEvent_SettleEvent) + + case HtlcEventLinkFail: + _, ok = event.Event.(*routerrpc.HtlcEvent_LinkFailEvent) + + case HtlcEventFinal: + _, ok = event.Event.(*routerrpc.HtlcEvent_FinalHtlcEvent) + } + + require.Truef(h, ok, "wrong event type: %T, want %T", event.Event, + eventType) + + return event +} diff --git a/lntemp/rpc/router.go b/lntemp/rpc/router.go index 77c2d8f01..3a350ed76 100644 --- a/lntemp/rpc/router.go +++ b/lntemp/rpc/router.go @@ -39,3 +39,56 @@ func (h *HarnessRPC) SendPayment( return stream } + +type HtlcEventsClient routerrpc.Router_SubscribeHtlcEventsClient + +// SubscribeHtlcEvents makes a subscription to the HTLC events and returns a +// htlc event client. +func (h *HarnessRPC) SubscribeHtlcEvents() HtlcEventsClient { + // Use runCtx here to keep the client alive for the scope of the test. + client, err := h.Router.SubscribeHtlcEvents( + h.runCtx, &routerrpc.SubscribeHtlcEventsRequest{}, + ) + h.NoError(err, "SubscribeHtlcEvents") + + return client +} + +// GetMissionControlConfig makes a RPC call to the node's +// GetMissionControlConfig and asserts. +// +//nolint:lll +func (h *HarnessRPC) GetMissionControlConfig() *routerrpc.GetMissionControlConfigResponse { + ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout) + defer cancel() + + req := &routerrpc.GetMissionControlConfigRequest{} + resp, err := h.Router.GetMissionControlConfig(ctxt, req) + h.NoError(err, "GetMissionControlConfig") + + return resp +} + +// SetMissionControlConfig makes a RPC call to the node's +// SetMissionControlConfig and asserts. +func (h *HarnessRPC) SetMissionControlConfig( + config *routerrpc.MissionControlConfig) { + + ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout) + defer cancel() + + req := &routerrpc.SetMissionControlConfigRequest{Config: config} + _, err := h.Router.SetMissionControlConfig(ctxt, req) + h.NoError(err, "SetMissionControlConfig") +} + +// ResetMissionControl makes a RPC call to the node's ResetMissionControl and +// asserts. +func (h *HarnessRPC) ResetMissionControl() { + ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout) + defer cancel() + + req := &routerrpc.ResetMissionControlRequest{} + _, err := h.Router.ResetMissionControl(ctxt, req) + h.NoError(err, "ResetMissionControl") +} diff --git a/lntest/itest/list_on_test.go b/lntest/itest/list_on_test.go index 7671663ec..0af4dbc25 100644 --- a/lntest/itest/list_on_test.go +++ b/lntest/itest/list_on_test.go @@ -255,4 +255,8 @@ var allTestCasesTemp = []*lntemp.TestCase{ Name: "max htlc pathfind", TestFunc: testMaxHtlcPathfind, }, + { + Name: "multi-hop htlc error propagation", + TestFunc: testHtlcErrorPropagation, + }, } diff --git a/lntest/itest/lnd_multi-hop-error-propagation_test.go b/lntest/itest/lnd_multi-hop-error-propagation_test.go index 53ded769a..a5cca06aa 100644 --- a/lntest/itest/lnd_multi-hop-error-propagation_test.go +++ b/lntest/itest/lnd_multi-hop-error-propagation_test.go @@ -1,47 +1,65 @@ package itest import ( - "context" "math" - "strings" - "time" "github.com/lightningnetwork/lnd/funding" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" - "github.com/lightningnetwork/lnd/lntest" + "github.com/lightningnetwork/lnd/lntemp" "github.com/lightningnetwork/lnd/lnwire" + "github.com/stretchr/testify/require" ) -func testHtlcErrorPropagation(net *lntest.NetworkHarness, t *harnessTest) { - ctxb := context.Background() - +func testHtlcErrorPropagation(ht *lntemp.HarnessTest) { // In this test we wish to exercise the daemon's correct parsing, // handling, and propagation of errors that occur while processing a // multi-hop payment. const chanAmt = funding.MaxBtcFundingAmount + alice, bob := ht.Alice, ht.Bob + + // Since we'd like to test some multi-hop failure scenarios, we'll + // introduce another node into our test network: Carol. + carol := ht.NewNode("Carol", nil) + ht.ConnectNodes(bob, carol) + + // Before we start sending payments, subscribe to htlc events for each + // node. + aliceEvents := alice.RPC.SubscribeHtlcEvents() + bobEvents := bob.RPC.SubscribeHtlcEvents() + carolEvents := carol.RPC.SubscribeHtlcEvents() + + // Once subscribed, the first event will be UNKNOWN. + ht.AssertHtlcEventType(aliceEvents, routerrpc.HtlcEvent_UNKNOWN) + ht.AssertHtlcEventType(bobEvents, routerrpc.HtlcEvent_UNKNOWN) + ht.AssertHtlcEventType(carolEvents, routerrpc.HtlcEvent_UNKNOWN) + // First establish a channel with a capacity of 0.5 BTC between Alice // and Bob. - chanPointAlice := openChannelAndAssert( - t, net, net.Alice, net.Bob, - lntest.OpenChannelParams{ - Amt: chanAmt, - }, + chanPointAlice := ht.OpenChannel( + alice, bob, + lntemp.OpenChannelParams{Amt: chanAmt}, ) - if err := net.Alice.WaitForNetworkChannelOpen(chanPointAlice); err != nil { - t.Fatalf("channel not seen by alice before timeout: %v", err) - } - cType, err := channelCommitType(net.Alice, chanPointAlice) - if err != nil { - t.Fatalf("unable to get channel type: %v", err) - } + // Next, we'll create a connection from Bob to Carol, and open a + // channel between them so we have the topology: Alice -> Bob -> Carol. + // The channel created will be of lower capacity that the one created + // above. + const bobChanAmt = funding.MaxBtcFundingAmount + chanPointBob := ht.OpenChannel( + bob, carol, lntemp.OpenChannelParams{Amt: chanAmt}, + ) + // Ensure that Alice has Carol in her routing table before proceeding. + ht.AssertTopologyChannelOpen(alice, chanPointBob) + + cType := ht.GetChannelCommitType(alice, chanPointAlice) commitFee := calcStaticFee(cType, 0) + assertBaseBalance := func() { - // Alice has opened a channel with Bob with zero push amount, so - // it's remote balance is zero. + // Alice has opened a channel with Bob with zero push amount, + // so it's remote balance is zero. expBalanceAlice := &lnrpc.ChannelBalanceResponse{ LocalBalance: &lnrpc.Amount{ Sat: uint64(chanAmt - commitFee), @@ -57,7 +75,7 @@ func testHtlcErrorPropagation(net *lntest.NetworkHarness, t *harnessTest) { // Deprecated fields. Balance: int64(chanAmt - commitFee), } - assertChannelBalanceResp(t, net.Alice, expBalanceAlice) + ht.AssertChannelBalanceResp(alice, expBalanceAlice) // Bob has a channel with Alice and another with Carol, so it's // local and remote balances are both chanAmt - commitFee. @@ -81,52 +99,21 @@ func testHtlcErrorPropagation(net *lntest.NetworkHarness, t *harnessTest) { // Deprecated fields. Balance: int64(chanAmt - commitFee), } - assertChannelBalanceResp(t, net.Bob, expBalanceBob) + ht.AssertChannelBalanceResp(bob, expBalanceBob) } - // Since we'd like to test some multi-hop failure scenarios, we'll - // introduce another node into our test network: Carol. - carol := net.NewNode(t.t, "Carol", nil) + // assertLinkFailure checks that the stream provided has a single link + // failure the failure detail provided. + assertLinkFailure := func(event *routerrpc.HtlcEvent, + failureDetail routerrpc.FailureDetail) { - // Next, we'll create a connection from Bob to Carol, and open a - // channel between them so we have the topology: Alice -> Bob -> Carol. - // The channel created will be of lower capacity that the one created - // above. - net.ConnectNodes(t.t, net.Bob, carol) - const bobChanAmt = funding.MaxBtcFundingAmount - chanPointBob := openChannelAndAssert( - t, net, net.Bob, carol, - lntest.OpenChannelParams{ - Amt: chanAmt, - }, - ) + linkFail, ok := event.Event.(*routerrpc.HtlcEvent_LinkFailEvent) + require.Truef(ht, ok, "expected forwarding failure, got: %T", + linkFail) - // Ensure that Alice has Carol in her routing table before proceeding. - nodeInfoReq := &lnrpc.NodeInfoRequest{ - PubKey: carol.PubKeyStr, - } - checkTableTimeout := time.After(time.Second * 10) - checkTableTicker := time.NewTicker(100 * time.Millisecond) - defer checkTableTicker.Stop() - -out: - // TODO(roasbeef): make into async hook for node announcements - for { - select { - case <-checkTableTicker.C: - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - _, err := net.Alice.GetNodeInfo(ctxt, nodeInfoReq) - if err != nil && strings.Contains(err.Error(), - "unable to find") { - - continue - } - - break out - case <-checkTableTimeout: - t.Fatalf("carol's node announcement didn't propagate within " + - "the timeout period") - } + require.Equal(ht, failureDetail, + linkFail.LinkFailEvent.FailureDetail, + "wrong link fail detail") } // With the channels, open we can now start to test our multi-hop error @@ -137,61 +124,14 @@ out: Memo: "kek99", Value: payAmt, } - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - carolInvoice, err := carol.AddInvoice(ctxt, invoiceReq) - if err != nil { - t.Fatalf("unable to generate carol invoice: %v", err) - } - carolPayReq, err := carol.DecodePayReq(ctxb, - &lnrpc.PayReqString{ - PayReq: carolInvoice.PaymentRequest, - }) - if err != nil { - t.Fatalf("unable to decode generated payment request: %v", err) - } - - // Before we send the payment, ensure that the announcement of the new - // channel has been processed by Alice. - if err := net.Alice.WaitForNetworkChannelOpen(chanPointBob); err != nil { - t.Fatalf("channel not seen by alice before timeout: %v", err) - } - - // Before we start sending payments, subscribe to htlc events for each - // node. - ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) - defer cancel() - - aliceEvents, err := net.Alice.RouterClient.SubscribeHtlcEvents( - ctxt, &routerrpc.SubscribeHtlcEventsRequest{}, - ) - if err != nil { - t.Fatalf("could not subscribe events: %v", err) - } - assertSubscribed(t, aliceEvents) - - bobEvents, err := net.Bob.RouterClient.SubscribeHtlcEvents( - ctxt, &routerrpc.SubscribeHtlcEventsRequest{}, - ) - if err != nil { - t.Fatalf("could not subscribe events: %v", err) - } - assertSubscribed(t, bobEvents) - - carolEvents, err := carol.RouterClient.SubscribeHtlcEvents( - ctxt, &routerrpc.SubscribeHtlcEventsRequest{}, - ) - if err != nil { - t.Fatalf("could not subscribe events: %v", err) - } - assertSubscribed(t, carolEvents) + carolInvoice := carol.RPC.AddInvoice(invoiceReq) + carolPayReq := carol.RPC.DecodePayReq(carolInvoice.PaymentRequest) // For the first scenario, we'll test the cancellation of an HTLC with // an unknown payment hash. - // TODO(roasbeef): return failure response rather than failing entire - // stream on payment error. sendReq := &routerrpc.SendPaymentRequest{ - PaymentHash: makeFakePayHash(t), + PaymentHash: ht.Random32Bytes(), Dest: carol.PubKey[:], Amt: payAmt, FinalCltvDelta: int32(carolPayReq.CltvExpiry), @@ -199,25 +139,56 @@ out: FeeLimitMsat: noFeeLimitMsat, MaxParts: 1, } - sendAndAssertFailure( - t, net.Alice, - sendReq, lnrpc.PaymentFailureReason_FAILURE_REASON_INCORRECT_PAYMENT_DETAILS, + ht.SendPaymentAssertFail( + alice, sendReq, + lnrpc.PaymentFailureReason_FAILURE_REASON_INCORRECT_PAYMENT_DETAILS, //nolint:lll ) - assertLastHTLCError( - t, net.Alice, - lnrpc.Failure_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS, + ht.AssertLastHTLCError( + alice, lnrpc.Failure_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS, ) + // assertAliceAndBob is a helper closure that asserts Alice and Bob + // each has one forward and one forward fail event, and Bob has the + // final htlc fail event. + assertAliceAndBob := func() { + ht.AssertHtlcEventTypes( + aliceEvents, routerrpc.HtlcEvent_SEND, + lntemp.HtlcEventForward, + ) + ht.AssertHtlcEventTypes( + aliceEvents, routerrpc.HtlcEvent_SEND, + lntemp.HtlcEventForwardFail, + ) + + ht.AssertHtlcEventTypes( + bobEvents, routerrpc.HtlcEvent_FORWARD, + lntemp.HtlcEventForward, + ) + ht.AssertHtlcEventTypes( + bobEvents, routerrpc.HtlcEvent_FORWARD, + lntemp.HtlcEventForwardFail, + ) + ht.AssertHtlcEventTypes( + bobEvents, routerrpc.HtlcEvent_UNKNOWN, + lntemp.HtlcEventFinal, + ) + } + // We expect alice and bob to each have one forward and one forward // fail event at this stage. - assertHtlcEvents(t, 1, 1, 0, routerrpc.HtlcEvent_SEND, aliceEvents) - assertHtlcEvents(t, 1, 1, 0, routerrpc.HtlcEvent_FORWARD, bobEvents) + assertAliceAndBob() // Carol should have a link failure because the htlc failed on her // incoming link. - assertLinkFailure( - t, routerrpc.HtlcEvent_RECEIVE, - routerrpc.FailureDetail_UNKNOWN_INVOICE, carolEvents, + event := ht.AssertHtlcEventType( + carolEvents, routerrpc.HtlcEvent_RECEIVE, + ) + assertLinkFailure(event, routerrpc.FailureDetail_UNKNOWN_INVOICE) + + // There's also a final htlc event that gives the final outcome of the + // htlc. + ht.AssertHtlcEventTypes( + carolEvents, routerrpc.HtlcEvent_UNKNOWN, lntemp.HtlcEventFinal, ) // The balances of all parties should be the same as initially since @@ -228,33 +199,36 @@ out: // value on the extended HTLC. htlcAmt := lnwire.NewMSatFromSatoshis(1000) sendReq = &routerrpc.SendPaymentRequest{ - PaymentHash: carolInvoice.RHash, - Dest: carol.PubKey[:], - Amt: int64(htlcAmt.ToSatoshis()), // 10k satoshis are expected. + PaymentHash: carolInvoice.RHash, + Dest: carol.PubKey[:], + // 10k satoshis are expected. + Amt: int64(htlcAmt.ToSatoshis()), FinalCltvDelta: int32(carolPayReq.CltvExpiry), TimeoutSeconds: 60, FeeLimitMsat: noFeeLimitMsat, MaxParts: 1, } - sendAndAssertFailure( - t, net.Alice, - sendReq, lnrpc.PaymentFailureReason_FAILURE_REASON_INCORRECT_PAYMENT_DETAILS, + ht.SendPaymentAssertFail( + alice, sendReq, + lnrpc.PaymentFailureReason_FAILURE_REASON_INCORRECT_PAYMENT_DETAILS, //nolint:lll ) - assertLastHTLCError( - t, net.Alice, - lnrpc.Failure_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS, + ht.AssertLastHTLCError( + alice, lnrpc.Failure_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS, ) // We expect alice and bob to each have one forward and one forward // fail event at this stage. - assertHtlcEvents(t, 1, 1, 0, routerrpc.HtlcEvent_SEND, aliceEvents) - assertHtlcEvents(t, 1, 1, 0, routerrpc.HtlcEvent_FORWARD, bobEvents) + assertAliceAndBob() // Carol should have a link failure because the htlc failed on her // incoming link. - assertLinkFailure( - t, routerrpc.HtlcEvent_RECEIVE, - routerrpc.FailureDetail_INVOICE_UNDERPAID, carolEvents, + event = ht.AssertHtlcEventType(carolEvents, routerrpc.HtlcEvent_RECEIVE) + assertLinkFailure(event, routerrpc.FailureDetail_INVOICE_UNDERPAID) + + // There's also a final htlc event that gives the final outcome of the + // htlc. + ht.AssertHtlcEventTypes( + carolEvents, routerrpc.HtlcEvent_UNKNOWN, lntemp.HtlcEventFinal, ) // The balances of all parties should be the same as initially since @@ -284,29 +258,34 @@ out: invoiceReq = &lnrpc.Invoice{ Value: toSend, } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - carolInvoice2, err := carol.AddInvoice(ctxt, invoiceReq) - if err != nil { - t.Fatalf("unable to generate carol invoice: %v", err) - } + carolInvoice2 := carol.RPC.AddInvoice(invoiceReq) - sendAndAssertSuccess( - t, net.Bob, &routerrpc.SendPaymentRequest{ - PaymentRequest: carolInvoice2.PaymentRequest, - TimeoutSeconds: 60, - FeeLimitMsat: noFeeLimitMsat, - MaxParts: 1, - }, - ) + req := &routerrpc.SendPaymentRequest{ + PaymentRequest: carolInvoice2.PaymentRequest, + TimeoutSeconds: 60, + FeeLimitMsat: noFeeLimitMsat, + MaxParts: 1, + } + ht.SendPaymentAndAssertStatus(bob, req, lnrpc.Payment_SUCCEEDED) // For each send bob makes, we need to check that bob has a // forward and settle event for his send, and carol has a - // settle event for her receive. - assertHtlcEvents( - t, 1, 0, 1, routerrpc.HtlcEvent_SEND, bobEvents, + // settle event and a final htlc event for her receive. + ht.AssertHtlcEventTypes( + bobEvents, routerrpc.HtlcEvent_SEND, + lntemp.HtlcEventForward, ) - assertHtlcEvents( - t, 0, 0, 1, routerrpc.HtlcEvent_RECEIVE, carolEvents, + ht.AssertHtlcEventTypes( + bobEvents, routerrpc.HtlcEvent_SEND, + lntemp.HtlcEventSettle, + ) + ht.AssertHtlcEventTypes( + carolEvents, routerrpc.HtlcEvent_RECEIVE, + lntemp.HtlcEventSettle, + ) + ht.AssertHtlcEventTypes( + carolEvents, routerrpc.HtlcEvent_UNKNOWN, + lntemp.HtlcEventFinal, ) amtSent += toSend @@ -318,11 +297,7 @@ out: invoiceReq = &lnrpc.Invoice{ Value: 100000, } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - carolInvoice3, err := carol.AddInvoice(ctxt, invoiceReq) - if err != nil { - t.Fatalf("unable to generate carol invoice: %v", err) - } + carolInvoice3 := carol.RPC.AddInvoice(invoiceReq) sendReq = &routerrpc.SendPaymentRequest{ PaymentRequest: carolInvoice3.PaymentRequest, @@ -330,30 +305,37 @@ out: FeeLimitMsat: noFeeLimitMsat, MaxParts: 1, } - sendAndAssertFailure( - t, net.Alice, - sendReq, lnrpc.PaymentFailureReason_FAILURE_REASON_NO_ROUTE, + ht.SendPaymentAssertFail( + alice, sendReq, + lnrpc.PaymentFailureReason_FAILURE_REASON_NO_ROUTE, ) - assertLastHTLCError( - t, net.Alice, lnrpc.Failure_TEMPORARY_CHANNEL_FAILURE, + ht.AssertLastHTLCError( + alice, lnrpc.Failure_TEMPORARY_CHANNEL_FAILURE, ) // Alice should have a forwarding event and a forwarding failure. - assertHtlcEvents(t, 1, 1, 0, routerrpc.HtlcEvent_SEND, aliceEvents) + ht.AssertHtlcEventTypes( + aliceEvents, routerrpc.HtlcEvent_SEND, + lntemp.HtlcEventForward, + ) + ht.AssertHtlcEventTypes( + aliceEvents, routerrpc.HtlcEvent_SEND, + lntemp.HtlcEventForwardFail, + ) // Bob should have a link failure because the htlc failed on his // outgoing link. - assertLinkFailure( - t, routerrpc.HtlcEvent_FORWARD, - routerrpc.FailureDetail_INSUFFICIENT_BALANCE, bobEvents, + event = ht.AssertHtlcEventType(bobEvents, routerrpc.HtlcEvent_FORWARD) + assertLinkFailure(event, routerrpc.FailureDetail_INSUFFICIENT_BALANCE) + + // There's also a final htlc event that gives the final outcome of the + // htlc. + ht.AssertHtlcEventTypes( + bobEvents, routerrpc.HtlcEvent_UNKNOWN, lntemp.HtlcEventFinal, ) // Generate new invoice to not pay same invoice twice. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - carolInvoice, err = carol.AddInvoice(ctxt, invoiceReq) - if err != nil { - t.Fatalf("unable to generate carol invoice: %v", err) - } + carolInvoice = carol.RPC.AddInvoice(invoiceReq) // For our final test, we'll ensure that if a target link isn't // available for what ever reason then the payment fails accordingly. @@ -361,77 +343,48 @@ out: // We'll attempt to complete the original invoice we created with Carol // above, but before we do so, Carol will go offline, resulting in a // failed payment. - shutdownAndAssert(net, t, carol) + ht.Shutdown(carol) // Reset mission control to forget the temporary channel failure above. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - _, err = net.Alice.RouterClient.ResetMissionControl( - ctxt, &routerrpc.ResetMissionControlRequest{}, - ) - if err != nil { - t.Fatalf("unable to reset mission control: %v", err) - } + alice.RPC.ResetMissionControl() - sendAndAssertFailure( - t, net.Alice, - &routerrpc.SendPaymentRequest{ - PaymentRequest: carolInvoice.PaymentRequest, - TimeoutSeconds: 60, - FeeLimitMsat: noFeeLimitMsat, - MaxParts: 1, - }, - lnrpc.PaymentFailureReason_FAILURE_REASON_NO_ROUTE, + req := &routerrpc.SendPaymentRequest{ + PaymentRequest: carolInvoice.PaymentRequest, + TimeoutSeconds: 60, + FeeLimitMsat: noFeeLimitMsat, + MaxParts: 1, + } + ht.SendPaymentAssertFail( + alice, req, lnrpc.PaymentFailureReason_FAILURE_REASON_NO_ROUTE, ) - assertLastHTLCError(t, net.Alice, lnrpc.Failure_UNKNOWN_NEXT_PEER) + ht.AssertLastHTLCError(alice, lnrpc.Failure_UNKNOWN_NEXT_PEER) // Alice should have a forwarding event and subsequent fail. - assertHtlcEvents(t, 1, 1, 0, routerrpc.HtlcEvent_SEND, aliceEvents) + ht.AssertHtlcEventTypes( + aliceEvents, routerrpc.HtlcEvent_SEND, + lntemp.HtlcEventForward, + ) + ht.AssertHtlcEventTypes( + aliceEvents, routerrpc.HtlcEvent_SEND, + lntemp.HtlcEventForwardFail, + ) // Bob should have a link failure because he could not find the next // peer. - assertLinkFailure( - t, routerrpc.HtlcEvent_FORWARD, - routerrpc.FailureDetail_NO_DETAIL, bobEvents, + event = ht.AssertHtlcEventType(bobEvents, routerrpc.HtlcEvent_FORWARD) + assertLinkFailure(event, routerrpc.FailureDetail_NO_DETAIL) + + // There's also a final htlc event that gives the final outcome of the + // htlc. + ht.AssertHtlcEventTypes( + bobEvents, routerrpc.HtlcEvent_UNKNOWN, lntemp.HtlcEventFinal, ) // Finally, immediately close the channel. This function will also // block until the channel is closed and will additionally assert the // relevant channel closing post conditions. - closeChannelAndAssert(t, net, net.Alice, chanPointAlice, false) + ht.CloseChannel(alice, chanPointAlice) // Force close Bob's final channel. - closeChannelAndAssert(t, net, net.Bob, chanPointBob, true) - - // Cleanup by mining the force close and sweep transaction. - cleanupForceClose(t, net, net.Bob, chanPointBob) -} - -// assertLinkFailure checks that the stream provided has a single link failure -// the the failure detail provided. -func assertLinkFailure(t *harnessTest, - eventType routerrpc.HtlcEvent_EventType, - failureDetail routerrpc.FailureDetail, - client routerrpc.Router_SubscribeHtlcEventsClient) { - - event := assertEventAndType(t, eventType, client) - - linkFail, ok := event.Event.(*routerrpc.HtlcEvent_LinkFailEvent) - if !ok { - t.Fatalf("expected forwarding failure, got: %T", linkFail) - } - - if linkFail.LinkFailEvent.FailureDetail != failureDetail { - t.Fatalf("expected: %v, got: %v", failureDetail, - linkFail.LinkFailEvent.FailureDetail) - } - - event = assertEventAndType(t, routerrpc.HtlcEvent_UNKNOWN, client) - finalHtlc, ok := event.Event.(*routerrpc.HtlcEvent_FinalHtlcEvent) - if !ok { - t.Fatalf("expected final htlc, got: %T", event.Event) - } - - if finalHtlc.FinalHtlcEvent.Settled { - t.Fatalf("expected final fail") - } + ht.ForceCloseChannel(bob, chanPointBob) } diff --git a/lntest/itest/lnd_test_list_on_test.go b/lntest/itest/lnd_test_list_on_test.go index 74010eb0a..10fc94870 100644 --- a/lntest/itest/lnd_test_list_on_test.go +++ b/lntest/itest/lnd_test_list_on_test.go @@ -44,10 +44,6 @@ var allTestCases = []*testCase{ name: "multiple channel creation and update subscription", test: testBasicChannelCreationAndUpdates, }, - { - name: "multi-hop htlc error propagation", - test: testHtlcErrorPropagation, - }, { name: "derive shared key", test: testDeriveSharedKey, From f1f3d22a813223bd4a06403abb77aaeaf94e8fb0 Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Fri, 5 Aug 2022 18:44:52 +0800 Subject: [PATCH 08/44] lntemp+itest: refactor `testMultiHopPayments` --- lntemp/harness_assertion.go | 75 ++++ lntemp/rpc/lnd.go | 18 + lntest/itest/list_on_test.go | 4 + lntest/itest/lnd_multi-hop-payments_test.go | 430 +++++--------------- lntest/itest/lnd_test_list_on_test.go | 4 - 5 files changed, 195 insertions(+), 336 deletions(-) diff --git a/lntemp/harness_assertion.go b/lntemp/harness_assertion.go index 5ef7cce73..d4c645d79 100644 --- a/lntemp/harness_assertion.go +++ b/lntemp/harness_assertion.go @@ -1852,3 +1852,78 @@ func (h *HarnessTest) AssertHtlcEventTypes(client rpc.HtlcEventsClient, return event } + +// AssertFeeReport checks that the fee report from the given node has the +// desired day, week, and month sum values. +func (h *HarnessTest) AssertFeeReport(hn *node.HarnessNode, + day, week, month int) { + + ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout) + defer cancel() + + feeReport, err := hn.RPC.LN.FeeReport(ctxt, &lnrpc.FeeReportRequest{}) + require.NoError(h, err, "unable to query for fee report") + + require.EqualValues(h, day, feeReport.DayFeeSum, "day fee mismatch") + require.EqualValues(h, week, feeReport.WeekFeeSum, "day week mismatch") + require.EqualValues(h, month, feeReport.MonthFeeSum, + "day month mismatch") +} + +// AssertHtlcEvents consumes events from a client and ensures that they are of +// the expected type and contain the expected number of forwards, forward +// failures and settles. +// +// TODO(yy): needs refactor to reduce its complexity. +func (h *HarnessTest) AssertHtlcEvents(client rpc.HtlcEventsClient, + fwdCount, fwdFailCount, settleCount int, + userType routerrpc.HtlcEvent_EventType) []*routerrpc.HtlcEvent { + + var forwards, forwardFails, settles int + + numEvents := fwdCount + fwdFailCount + settleCount + events := make([]*routerrpc.HtlcEvent, 0) + + // It's either the userType or the unknown type. + // + // TODO(yy): maybe the FinalHtlcEvent shouldn't be in UNKNOWN type? + eventTypes := []routerrpc.HtlcEvent_EventType{ + userType, routerrpc.HtlcEvent_UNKNOWN, + } + + for i := 0; i < numEvents; i++ { + event := h.ReceiveHtlcEvent(client) + + require.Containsf(h, eventTypes, event.EventType, + "wrong event type, got %v", userType, event.EventType) + + events = append(events, event) + + switch e := event.Event.(type) { + case *routerrpc.HtlcEvent_ForwardEvent: + forwards++ + + case *routerrpc.HtlcEvent_ForwardFailEvent: + forwardFails++ + + case *routerrpc.HtlcEvent_SettleEvent: + settles++ + + case *routerrpc.HtlcEvent_FinalHtlcEvent: + if e.FinalHtlcEvent.Settled { + settles++ + } + + default: + require.Fail(h, "assert event fail", + "unexpected event: %T", event.Event) + } + } + + require.Equal(h, fwdCount, forwards, "num of forwards mismatch") + require.Equal(h, fwdFailCount, forwardFails, + "num of forward fails mismatch") + require.Equal(h, settleCount, settles, "num of settles mismatch") + + return events +} diff --git a/lntemp/rpc/lnd.go b/lntemp/rpc/lnd.go index a8a21e5ae..7a451e3c2 100644 --- a/lntemp/rpc/lnd.go +++ b/lntemp/rpc/lnd.go @@ -591,3 +591,21 @@ func (h *HarnessRPC) DecodePayReq(req string) *lnrpc.PayReq { return resp } + +// ForwardingHistory makes a RPC call to the node's ForwardingHistory and +// asserts. +func (h *HarnessRPC) ForwardingHistory( + req *lnrpc.ForwardingHistoryRequest) *lnrpc.ForwardingHistoryResponse { + + ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout) + defer cancel() + + if req == nil { + req = &lnrpc.ForwardingHistoryRequest{} + } + + resp, err := h.LN.ForwardingHistory(ctxt, req) + h.NoError(err, "ForwardingHistory") + + return resp +} diff --git a/lntest/itest/list_on_test.go b/lntest/itest/list_on_test.go index 0af4dbc25..9005fd6df 100644 --- a/lntest/itest/list_on_test.go +++ b/lntest/itest/list_on_test.go @@ -259,4 +259,8 @@ var allTestCasesTemp = []*lntemp.TestCase{ Name: "multi-hop htlc error propagation", TestFunc: testHtlcErrorPropagation, }, + { + Name: "multi-hop payments", + TestFunc: testMultiHopPayments, + }, } diff --git a/lntest/itest/lnd_multi-hop-payments_test.go b/lntest/itest/lnd_multi-hop-payments_test.go index 09a5f6b78..28c032061 100644 --- a/lntest/itest/lnd_multi-hop-payments_test.go +++ b/lntest/itest/lnd_multi-hop-payments_test.go @@ -1,146 +1,70 @@ package itest import ( - "context" - "time" - "github.com/btcsuite/btcd/btcutil" - "github.com/btcsuite/btcd/wire" "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/lntemp" + "github.com/lightningnetwork/lnd/lntemp/node" "github.com/stretchr/testify/require" ) -func testMultiHopPayments(net *lntest.NetworkHarness, t *harnessTest) { - ctxb := context.Background() - +func testMultiHopPayments(ht *lntemp.HarnessTest) { 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. - chanPointAlice := openChannelAndAssert( - 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, - } // As preliminary setup, we'll create two new nodes: Carol and Dave, // such that we now have a 4 node, 3 channel topology. Dave will make a // channel with Alice, and Carol with Dave. After this setup, the // network topology should now look like: // Carol -> Dave -> Alice -> Bob - // - // First, we'll create Dave and establish a channel to Alice. Dave will - // be running an older node that requires the legacy onion payload. + alice, bob := ht.Alice, ht.Bob + daveArgs := []string{"--protocol.legacy.onion"} - dave := net.NewNode(t.t, "Dave", daveArgs) - defer shutdownAndAssert(net, t, dave) + dave := ht.NewNode("Dave", daveArgs) + carol := ht.NewNode("Carol", nil) - net.ConnectNodes(t.t, dave, net.Alice) - net.SendCoins(t.t, btcutil.SatoshiPerBitcoin, dave) + // Subscribe events early so we don't miss it out. + aliceEvents := alice.RPC.SubscribeHtlcEvents() + bobEvents := bob.RPC.SubscribeHtlcEvents() + carolEvents := carol.RPC.SubscribeHtlcEvents() + daveEvents := dave.RPC.SubscribeHtlcEvents() - chanPointDave := openChannelAndAssert( - t, net, dave, net.Alice, - lntest.OpenChannelParams{ - Amt: chanAmt, - }, + // Once subscribed, the first event will be UNKNOWN. + ht.AssertHtlcEventType(aliceEvents, routerrpc.HtlcEvent_UNKNOWN) + ht.AssertHtlcEventType(bobEvents, routerrpc.HtlcEvent_UNKNOWN) + ht.AssertHtlcEventType(carolEvents, routerrpc.HtlcEvent_UNKNOWN) + ht.AssertHtlcEventType(daveEvents, routerrpc.HtlcEvent_UNKNOWN) + + // Connect the nodes. + ht.ConnectNodes(dave, alice) + ht.ConnectNodes(carol, dave) + + // Open a channel with 100k satoshis between Alice and Bob with Alice + // being the sole funder of the channel. + chanPointAlice := ht.OpenChannel( + alice, bob, lntemp.OpenChannelParams{Amt: chanAmt}, + ) + + // We'll create Dave and establish a channel to Alice. Dave will be + // running an older node that requires the legacy onion payload. + ht.FundCoins(btcutil.SatoshiPerBitcoin, dave) + chanPointDave := ht.OpenChannel( + dave, alice, lntemp.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 to from her to // Dave. - carol := net.NewNode(t.t, "Carol", nil) - defer shutdownAndAssert(net, t, carol) - - net.ConnectNodes(t.t, carol, dave) - net.SendCoins(t.t, btcutil.SatoshiPerBitcoin, carol) - - chanPointCarol := openChannelAndAssert( - t, net, carol, dave, - lntest.OpenChannelParams{ - Amt: chanAmt, - }, + ht.FundCoins(btcutil.SatoshiPerBitcoin, carol) + chanPointCarol := ht.OpenChannel( + carol, dave, lntemp.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{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, - } - - err = node.WaitForNetworkChannelOpen(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 Bob, which expect a payment from Carol for 1k // satoshis with a different preimage each time. const numPayments = 5 const paymentAmt = 1000 - payReqs, _, _, err := createPayReqs( - net.Bob, paymentAmt, numPayments, - ) - if err != nil { - t.Fatalf("unable to create pay reqs: %v", err) - } - - // We'll wait for all parties to recognize the new channels within the - // network. - err = dave.WaitForNetworkChannelOpen(chanPointDave) - if err != nil { - t.Fatalf("dave didn't advertise his channel: %v", err) - } - err = carol.WaitForNetworkChannelOpen(chanPointCarol) - if err != nil { - t.Fatalf("carol didn't advertise her channel in time: %v", - err) - } - - time.Sleep(time.Millisecond * 50) + payReqs, _, _ := ht.CreatePayReqs(bob, paymentAmt, numPayments) // Set the fee policies of the Alice -> Bob and the Dave -> Alice // channel edges to relatively large non default values. This makes it @@ -149,61 +73,21 @@ func testMultiHopPayments(net *lntest.NetworkHarness, t *harnessTest) { const aliceBaseFeeSat = 1 const aliceFeeRatePPM = 100000 updateChannelPolicy( - t, net.Alice, chanPointAlice, aliceBaseFeeSat*1000, - aliceFeeRatePPM, chainreg.DefaultBitcoinTimeLockDelta, maxHtlc, - carol, + ht, alice, chanPointAlice, aliceBaseFeeSat*1000, + aliceFeeRatePPM, chainreg.DefaultBitcoinTimeLockDelta, + maxHtlc, carol, ) const daveBaseFeeSat = 5 const daveFeeRatePPM = 150000 updateChannelPolicy( - t, dave, chanPointDave, daveBaseFeeSat*1000, daveFeeRatePPM, + ht, dave, chanPointDave, daveBaseFeeSat*1000, daveFeeRatePPM, chainreg.DefaultBitcoinTimeLockDelta, maxHtlc, carol, ) - // Before we start sending payments, subscribe to htlc events for each - // node. - ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) - defer cancel() - - aliceEvents, err := net.Alice.RouterClient.SubscribeHtlcEvents( - ctxt, &routerrpc.SubscribeHtlcEventsRequest{}, - ) - if err != nil { - t.Fatalf("could not subscribe events: %v", err) - } - assertSubscribed(t, aliceEvents) - - bobEvents, err := net.Bob.RouterClient.SubscribeHtlcEvents( - ctxt, &routerrpc.SubscribeHtlcEventsRequest{}, - ) - if err != nil { - t.Fatalf("could not subscribe events: %v", err) - } - assertSubscribed(t, bobEvents) - - carolEvents, err := carol.RouterClient.SubscribeHtlcEvents( - ctxt, &routerrpc.SubscribeHtlcEventsRequest{}, - ) - if err != nil { - t.Fatalf("could not subscribe events: %v", err) - } - assertSubscribed(t, carolEvents) - - daveEvents, err := dave.RouterClient.SubscribeHtlcEvents( - ctxt, &routerrpc.SubscribeHtlcEventsRequest{}, - ) - if err != nil { - t.Fatalf("could not subscribe events: %v", err) - } - assertSubscribed(t, daveEvents) - // Using Carol as the source, pay to the 5 invoices from Bob created // above. - err = completePaymentRequests(carol, carol.RouterClient, payReqs, true) - if err != nil { - t.Fatalf("unable to send payments: %v", err) - } + ht.CompletePaymentRequests(carol, payReqs) // At this point all the channels within our proto network should be // shifted by 5k satoshis in the direction of Bob, the sink within the @@ -215,10 +99,10 @@ func testMultiHopPayments(net *lntest.NetworkHarness, t *harnessTest) { // The final node bob expects to get paid five times 1000 sat. expectedAmountPaidAtoB := int64(numPayments * paymentAmt) - assertAmountPaid(t, "Alice(local) => Bob(remote)", net.Bob, - aliceFundPoint, int64(0), expectedAmountPaidAtoB) - assertAmountPaid(t, "Alice(local) => Bob(remote)", net.Alice, - aliceFundPoint, expectedAmountPaidAtoB, int64(0)) + ht.AssertAmountPaid("Alice(local) => Bob(remote)", bob, + chanPointAlice, int64(0), expectedAmountPaidAtoB) + ht.AssertAmountPaid("Alice(local) => Bob(remote)", alice, + chanPointAlice, expectedAmountPaidAtoB, int64(0)) // To forward a payment of 1000 sat, Alice is charging a fee of // 1 sat + 10% = 101 sat. @@ -229,10 +113,10 @@ func testMultiHopPayments(net *lntest.NetworkHarness, t *harnessTest) { // Dave needs to pay what Alice pays plus Alice's fee. expectedAmountPaidDtoA := expectedAmountPaidAtoB + expectedFeeAlice - assertAmountPaid(t, "Dave(local) => Alice(remote)", net.Alice, - daveFundPoint, int64(0), expectedAmountPaidDtoA) - assertAmountPaid(t, "Dave(local) => Alice(remote)", dave, - daveFundPoint, expectedAmountPaidDtoA, int64(0)) + ht.AssertAmountPaid("Dave(local) => Alice(remote)", alice, + chanPointDave, int64(0), expectedAmountPaidDtoA) + ht.AssertAmountPaid("Dave(local) => Alice(remote)", dave, + chanPointDave, expectedAmountPaidDtoA, int64(0)) // To forward a payment of 1101 sat, Dave is charging a fee of // 5 sat + 15% = 170.15 sat. This is rounded down in rpcserver to 170. @@ -244,10 +128,10 @@ func testMultiHopPayments(net *lntest.NetworkHarness, t *harnessTest) { // Carol needs to pay what Dave pays plus Dave's fee. expectedAmountPaidCtoD := expectedAmountPaidDtoA + expectedFeeDave - assertAmountPaid(t, "Carol(local) => Dave(remote)", dave, - carolFundPoint, int64(0), expectedAmountPaidCtoD) - assertAmountPaid(t, "Carol(local) => Dave(remote)", carol, - carolFundPoint, expectedAmountPaidCtoD, int64(0)) + ht.AssertAmountPaid("Carol(local) => Dave(remote)", dave, + chanPointCarol, int64(0), expectedAmountPaidCtoD) + ht.AssertAmountPaid("Carol(local) => Dave(remote)", carol, + chanPointCarol, expectedAmountPaidCtoD, int64(0)) // Now that we know all the balances have been settled out properly, // we'll ensure that our internal record keeping for completed circuits @@ -256,206 +140,92 @@ func testMultiHopPayments(net *lntest.NetworkHarness, t *harnessTest) { // First, check that the FeeReport response shows the proper fees // accrued over each time range. Dave should've earned 170 satoshi for // each of the forwarded payments. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - feeReport, err := dave.FeeReport(ctxt, &lnrpc.FeeReportRequest{}) - require.NoError(t.t, err) - require.EqualValues(t.t, expectedFeeDave, feeReport.DayFeeSum) - require.EqualValues(t.t, expectedFeeDave, feeReport.WeekFeeSum) - require.EqualValues(t.t, expectedFeeDave, feeReport.MonthFeeSum) + ht.AssertFeeReport( + dave, expectedFeeDave, expectedFeeDave, expectedFeeDave, + ) // Next, ensure that if we issue the vanilla query for the forwarding // history, it returns 5 values, and each entry is formatted properly. // From David's perspective he receives a payement from Carol and // forwards it to Alice. So let's ensure that the forwarding history // returns Carol's peer alias as inbound and Alice's alias as outbound. - info, err := carol.GetInfo(ctxt, &lnrpc.GetInfoRequest{}) - require.NoError(t.t, err) + info := carol.RPC.GetInfo() carolAlias := info.Alias - info, err = net.Alice.GetInfo(ctxt, &lnrpc.GetInfoRequest{}) - require.NoError(t.t, err) + info = alice.RPC.GetInfo() aliceAlias := info.Alias - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - fwdingHistory, err := dave.ForwardingHistory( - ctxt, &lnrpc.ForwardingHistoryRequest{}, - ) - require.NoError(t.t, err) - require.Len(t.t, fwdingHistory.ForwardingEvents, numPayments) + fwdingHistory := dave.RPC.ForwardingHistory(nil) + require.Len(ht, fwdingHistory.ForwardingEvents, numPayments) + expectedForwardingFee := uint64(expectedFeeDave / numPayments) for _, event := range fwdingHistory.ForwardingEvents { // Each event should show a fee of 170 satoshi. - require.Equal(t.t, expectedForwardingFee, event.Fee) + require.Equal(ht, expectedForwardingFee, event.Fee) // Check that peer aliases are empty since the // ForwardingHistoryRequest did not specify the PeerAliasLookup // flag. - require.Empty(t.t, event.PeerAliasIn) - require.Empty(t.t, event.PeerAliasOut) + require.Empty(ht, event.PeerAliasIn) + require.Empty(ht, event.PeerAliasOut) } // Lookup the forwarding history again but this time also lookup the // peers' alias names. - fwdingHistory, err = dave.ForwardingHistory( - ctxt, &lnrpc.ForwardingHistoryRequest{ + fwdingHistory = dave.RPC.ForwardingHistory( + &lnrpc.ForwardingHistoryRequest{ PeerAliasLookup: true, }, ) - require.NoError(t.t, err) - require.Len(t.t, fwdingHistory.ForwardingEvents, numPayments) + require.Len(ht, fwdingHistory.ForwardingEvents, numPayments) for _, event := range fwdingHistory.ForwardingEvents { // Each event should show a fee of 170 satoshi. - require.Equal(t.t, expectedForwardingFee, event.Fee) + require.Equal(ht, expectedForwardingFee, event.Fee) // Check that peer aliases adhere to payment flow, namely // Carol->Dave->Alice. - require.Equal(t.t, carolAlias, event.PeerAliasIn) - require.Equal(t.t, aliceAlias, event.PeerAliasOut) + require.Equal(ht, carolAlias, event.PeerAliasIn) + require.Equal(ht, aliceAlias, event.PeerAliasOut) } // We expect Carol to have successful forwards and settles for // her sends. - assertHtlcEvents( - t, numPayments, 0, numPayments, routerrpc.HtlcEvent_SEND, - carolEvents, + ht.AssertHtlcEvents( + carolEvents, numPayments, 0, numPayments, + routerrpc.HtlcEvent_SEND, ) // Dave and Alice should both have forwards and settles for // their role as forwarding nodes. - assertHtlcEvents( - t, numPayments, 0, numPayments, routerrpc.HtlcEvent_FORWARD, - daveEvents, + ht.AssertHtlcEvents( + daveEvents, numPayments, 0, numPayments, + routerrpc.HtlcEvent_FORWARD, ) - assertHtlcEvents( - t, numPayments, 0, numPayments, routerrpc.HtlcEvent_FORWARD, - aliceEvents, + ht.AssertHtlcEvents( + aliceEvents, numPayments, 0, numPayments, + routerrpc.HtlcEvent_FORWARD, ) // Bob should only have settle events for his receives. - assertHtlcEvents( - t, 0, 0, numPayments, routerrpc.HtlcEvent_RECEIVE, bobEvents, + ht.AssertHtlcEvents( + bobEvents, 0, 0, numPayments, routerrpc.HtlcEvent_RECEIVE, ) - closeChannelAndAssert(t, net, net.Alice, chanPointAlice, false) - closeChannelAndAssert(t, net, dave, chanPointDave, false) - closeChannelAndAssert(t, net, carol, chanPointCarol, false) + // Finally, close all channels. + ht.CloseChannel(alice, chanPointAlice) + ht.CloseChannel(dave, chanPointDave) + ht.CloseChannel(carol, chanPointCarol) } -// assertHtlcEvents consumes events from a client and ensures that they are of -// the expected type and contain the expected number of forwards, forward -// failures and settles. -func assertHtlcEvents(t *harnessTest, fwdCount, fwdFailCount, settleCount int, - userType routerrpc.HtlcEvent_EventType, - client routerrpc.Router_SubscribeHtlcEventsClient) { - - var forwards, forwardFails, settles, finalSettles, finalFails int - - var finalFailCount, finalSettleCount int - if userType != routerrpc.HtlcEvent_SEND { - finalFailCount = fwdFailCount - finalSettleCount = settleCount - } - - numEvents := fwdCount + fwdFailCount + settleCount + - finalFailCount + finalSettleCount - - for i := 0; i < numEvents; i++ { - event, err := client.Recv() - if err != nil { - t.Fatalf("could not get event") - } - - expectedEventType := userType - - switch e := event.Event.(type) { - case *routerrpc.HtlcEvent_ForwardEvent: - forwards++ - - case *routerrpc.HtlcEvent_ForwardFailEvent: - forwardFails++ - - case *routerrpc.HtlcEvent_SettleEvent: - settles++ - - case *routerrpc.HtlcEvent_FinalHtlcEvent: - if e.FinalHtlcEvent.Settled { - finalSettles++ - } else { - finalFails++ - } - - expectedEventType = routerrpc.HtlcEvent_UNKNOWN - - default: - t.Fatalf("unexpected event: %T", event.Event) - } - - if event.EventType != expectedEventType { - t.Fatalf("expected: %v, got: %v", expectedEventType, - event.EventType) - } - } - - if forwards != fwdCount { - t.Fatalf("expected: %v forwards, got: %v", fwdCount, forwards) - } - - if forwardFails != fwdFailCount { - t.Fatalf("expected: %v forward fails, got: %v", fwdFailCount, - forwardFails) - } - - if finalFails != finalFailCount { - t.Fatalf("expected: %v final fails, got: %v", finalFailCount, - finalFails) - } - - if settles != settleCount { - t.Fatalf("expected: %v settles, got: %v", settleCount, settles) - } - - if finalSettles != finalSettleCount { - t.Fatalf("expected: %v settles, got: %v", finalSettleCount, - finalSettles) - } -} - -// assertEventAndType reads an event from the stream provided and ensures that -// it is associated with the correct user related type - a user initiated send, -// a receive to our node or a forward through our node. Note that this event -// type is different from the htlc event type (forward, link failure etc). -func assertEventAndType(t *harnessTest, eventType routerrpc.HtlcEvent_EventType, - client routerrpc.Router_SubscribeHtlcEventsClient) *routerrpc.HtlcEvent { - - event, err := client.Recv() - if err != nil { - t.Fatalf("could not get event") - } - - if event.EventType != eventType { - t.Fatalf("expected: %v, got: %v", eventType, - event.EventType) - } - - return event -} - -func assertSubscribed(t *harnessTest, - client routerrpc.Router_SubscribeHtlcEventsClient) { - - event, err := client.Recv() - require.NoError(t.t, err) - require.NotNil(t.t, event.GetSubscribedEvent()) -} - -// updateChannelPolicy updates the channel policy of node to the -// given fees and timelock delta. This function blocks until -// listenerNode has received the policy update. -func updateChannelPolicy(t *harnessTest, node *lntest.HarnessNode, - chanPoint *lnrpc.ChannelPoint, baseFee int64, feeRate int64, - timeLockDelta uint32, maxHtlc uint64, listenerNode *lntest.HarnessNode) { - - ctxb := context.Background() +// updateChannelPolicy updates the channel policy of node to the given fees and +// timelock delta. This function blocks until listenerNode has received the +// policy update. +// +// NOTE: only used in current test. +func updateChannelPolicy(ht *lntemp.HarnessTest, hn *node.HarnessNode, + chanPoint *lnrpc.ChannelPoint, baseFee int64, + feeRate int64, timeLockDelta uint32, + maxHtlc uint64, listenerNode *node.HarnessNode) { expectedPolicy := &lnrpc.RoutingPolicy{ FeeBaseMsat: baseFee, @@ -475,14 +245,10 @@ func updateChannelPolicy(t *harnessTest, node *lntest.HarnessNode, MaxHtlcMsat: maxHtlc, } - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - if _, err := node.UpdateChannelPolicy(ctxt, updateFeeReq); err != nil { - t.Fatalf("unable to update chan policy: %v", err) - } + hn.RPC.UpdateChannelPolicy(updateFeeReq) // Wait for listener node to receive the channel update from node. - assertChannelPolicyUpdate( - t.t, listenerNode, node.PubKeyStr, - expectedPolicy, chanPoint, false, + ht.AssertChannelPolicyUpdate( + listenerNode, hn, expectedPolicy, chanPoint, false, ) } diff --git a/lntest/itest/lnd_test_list_on_test.go b/lntest/itest/lnd_test_list_on_test.go index 10fc94870..1b4deda3a 100644 --- a/lntest/itest/lnd_test_list_on_test.go +++ b/lntest/itest/lnd_test_list_on_test.go @@ -12,10 +12,6 @@ var allTestCases = []*testCase{ name: "single hop invoice", test: testSingleHopInvoice, }, - { - name: "multi-hop payments", - test: testMultiHopPayments, - }, { name: "single-hop send to route", test: testSingleHopSendToRoute, From 211dad1574e7f0ad1c71349754292c4e64f6a619 Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Sun, 7 Aug 2022 05:50:49 +0800 Subject: [PATCH 09/44] itest: refactor `testAnchorReservedValue` --- lntest/itest/list_on_test.go | 4 + lntest/itest/lnd_onchain_test.go | 179 ++++++++------------------ lntest/itest/lnd_test_list_on_test.go | 4 - 3 files changed, 55 insertions(+), 132 deletions(-) diff --git a/lntest/itest/list_on_test.go b/lntest/itest/list_on_test.go index 9005fd6df..2c1373c9c 100644 --- a/lntest/itest/list_on_test.go +++ b/lntest/itest/list_on_test.go @@ -263,4 +263,8 @@ var allTestCasesTemp = []*lntemp.TestCase{ Name: "multi-hop payments", TestFunc: testMultiHopPayments, }, + { + Name: "anchors reserved value", + TestFunc: testAnchorReservedValue, + }, } diff --git a/lntest/itest/lnd_onchain_test.go b/lntest/itest/lnd_onchain_test.go index 4f94b7e0f..4444caa70 100644 --- a/lntest/itest/lnd_onchain_test.go +++ b/lntest/itest/lnd_onchain_test.go @@ -4,7 +4,6 @@ import ( "bytes" "context" "fmt" - "strings" "time" "github.com/btcsuite/btcd/btcutil" @@ -233,17 +232,17 @@ func runCPFP(net *lntest.NetworkHarness, t *harnessTest, // testAnchorReservedValue tests that we won't allow sending transactions when // that would take the value we reserve for anchor fee bumping out of our // wallet. -func testAnchorReservedValue(net *lntest.NetworkHarness, t *harnessTest) { +func testAnchorReservedValue(ht *lntemp.HarnessTest) { // Start two nodes supporting anchor channels. args := nodeArgsForCommitType(lnrpc.CommitmentType_ANCHORS) - alice := net.NewNode(t.t, "Alice", args) - defer shutdownAndAssert(net, t, alice) - bob := net.NewNode(t.t, "Bob", args) - defer shutdownAndAssert(net, t, bob) + // NOTE: we cannot reuse the standby node here as the test requires the + // node to start with no UTXOs. + alice := ht.NewNode("Alice", args) + bob := ht.Bob + ht.RestartNodeWithExtraArgs(bob, args) - ctxb := context.Background() - net.ConnectNodes(t.t, alice, bob) + ht.ConnectNodes(alice, bob) // Send just enough coins for Alice to open a channel without a change // output. @@ -252,100 +251,73 @@ func testAnchorReservedValue(net *lntest.NetworkHarness, t *harnessTest) { feeEst = 8000 ) - net.SendCoins(t.t, chanAmt+feeEst, alice) + ht.FundCoins(chanAmt+feeEst, alice) // wallet, without a change output. This should not be allowed. - resErr := lnwallet.ErrReservedValueInvalidated.Error() - - _, err := net.OpenChannel( - alice, bob, lntest.OpenChannelParams{ + ht.OpenChannelAssertErr( + alice, bob, lntemp.OpenChannelParams{ Amt: chanAmt, - }, + }, lnwallet.ErrReservedValueInvalidated, ) - if err == nil || !strings.Contains(err.Error(), resErr) { - t.Fatalf("expected failure, got: %v", err) - } // Alice opens a smaller channel. This works since it will have a // change output. - aliceChanPoint1 := openChannelAndAssert( - t, net, alice, bob, lntest.OpenChannelParams{ - Amt: chanAmt / 4, - }, + chanPoint1 := ht.OpenChannel( + alice, bob, lntemp.OpenChannelParams{Amt: chanAmt / 4}, ) // If Alice tries to open another anchor channel to Bob, Bob should not // reject it as he is not contributing any funds. - aliceChanPoint2 := openChannelAndAssert( - t, net, alice, bob, lntest.OpenChannelParams{ - Amt: chanAmt / 4, - }, + chanPoint2 := ht.OpenChannel( + alice, bob, lntemp.OpenChannelParams{Amt: chanAmt / 4}, ) - // Similarly, if Alice tries to open a legacy channel to Bob, Bob should - // not reject it as he is not contributing any funds. We'll restart Bob - // to remove his support for anchors. - err = net.RestartNode(bob, nil) - require.NoError(t.t, err) - aliceChanPoint3 := openChannelAndAssert( - t, net, alice, bob, lntest.OpenChannelParams{ - Amt: chanAmt / 4, - }, + // Similarly, if Alice tries to open a legacy channel to Bob, Bob + // should not reject it as he is not contributing any funds. We'll + // restart Bob to remove his support for anchors. + ht.RestartNode(bob) + + // Before opening the channel, make sure the nodes are connected. + ht.EnsureConnected(alice, bob) + + chanPoint3 := ht.OpenChannel( + alice, bob, lntemp.OpenChannelParams{Amt: chanAmt / 4}, ) - - chanPoints := []*lnrpc.ChannelPoint{ - aliceChanPoint1, aliceChanPoint2, aliceChanPoint3, - } - for _, chanPoint := range chanPoints { - err = alice.WaitForNetworkChannelOpen(chanPoint) - require.NoError(t.t, err) - - err = bob.WaitForNetworkChannelOpen(chanPoint) - require.NoError(t.t, err) - } + chanPoints := []*lnrpc.ChannelPoint{chanPoint1, chanPoint2, chanPoint3} // Alice tries to send all coins to an internal address. This is // allowed, since the final wallet balance will still be above the // reserved value. - addrReq := &lnrpc.NewAddressRequest{ + req := &lnrpc.NewAddressRequest{ Type: lnrpc.AddressType_WITNESS_PUBKEY_HASH, } - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - resp, err := alice.NewAddress(ctxt, addrReq) - require.NoError(t.t, err) + resp := alice.RPC.NewAddress(req) sweepReq := &lnrpc.SendCoinsRequest{ Addr: resp.Address, SendAll: true, } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - _, err = alice.SendCoins(ctxt, sweepReq) - require.NoError(t.t, err) + alice.RPC.SendCoins(sweepReq) - block := mineBlocks(t, net, 1, 1)[0] + block := ht.MineBlocksAndAssertNumTxes(1, 1)[0] + + assertNumTxInAndTxOut := func(tx *wire.MsgTx, in, out int) { + require.Len(ht, tx.TxIn, in, "num inputs not matched") + require.Len(ht, tx.TxOut, out, "num outputs not matched") + } // The sweep transaction should have exactly one input, the change from // the previous SendCoins call. sweepTx := block.Transactions[1] - if len(sweepTx.TxIn) != 1 { - t.Fatalf("expected 1 inputs instead have %v", len(sweepTx.TxIn)) - } // It should have a single output. - if len(sweepTx.TxOut) != 1 { - t.Fatalf("expected 1 output instead have %v", len(sweepTx.TxOut)) - } + assertNumTxInAndTxOut(sweepTx, 1, 1) // Wait for Alice to see her balance as confirmed. waitForConfirmedBalance := func() int64 { var balance int64 err := wait.NoError(func() error { - req := &lnrpc.WalletBalanceRequest{} - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - resp, err := alice.WalletBalance(ctxt, req) - if err != nil { - return err - } + resp := alice.RPC.WalletBalance() if resp.TotalBalance == 0 { return fmt.Errorf("no balance") @@ -358,93 +330,51 @@ func testAnchorReservedValue(net *lntest.NetworkHarness, t *harnessTest) { balance = resp.TotalBalance return nil }, defaultTimeout) - require.NoError(t.t, err) + require.NoError(ht, err, "timeout checking alice's balance") return balance } - _ = waitForConfirmedBalance() + waitForConfirmedBalance() // Alice tries to send all funds to an external address, the reserved // value must stay in her wallet. - minerAddr, err := net.Miner.NewAddress() - require.NoError(t.t, err) + minerAddr := ht.Miner.NewMinerAddress() sweepReq = &lnrpc.SendCoinsRequest{ Addr: minerAddr.String(), SendAll: true, } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - _, err = alice.SendCoins(ctxt, sweepReq) - require.NoError(t.t, err) + alice.RPC.SendCoins(sweepReq) // We'll mine a block which should include the sweep transaction we // generated above. - block = mineBlocks(t, net, 1, 1)[0] + block = ht.MineBlocksAndAssertNumTxes(1, 1)[0] // The sweep transaction should have exactly one inputs as we only had // the single output from above in the wallet. sweepTx = block.Transactions[1] - if len(sweepTx.TxIn) != 1 { - t.Fatalf("expected 1 inputs instead have %v", len(sweepTx.TxIn)) - } // It should have two outputs, one being the miner address, the other // one being the reserve going back to our wallet. - if len(sweepTx.TxOut) != 2 { - t.Fatalf("expected 2 outputs instead have %v", len(sweepTx.TxOut)) - } + assertNumTxInAndTxOut(sweepTx, 1, 2) // The reserved value is now back in Alice's wallet. aliceBalance := waitForConfirmedBalance() - // The reserved value should be equal to the required reserve for anchor - // channels. - walletBalanceResp, err := alice.WalletBalance( - ctxb, &lnrpc.WalletBalanceRequest{}, - ) - require.NoError(t.t, err) - require.Equal( - t.t, aliceBalance, walletBalanceResp.ReservedBalanceAnchorChan, - ) - - additionalChannels := int64(1) - - // Required reserve when additional channels are provided. - requiredReserveResp, err := alice.WalletKitClient.RequiredReserve( - ctxb, &walletrpc.RequiredReserveRequest{ - AdditionalPublicChannels: uint32(additionalChannels), - }, - ) - require.NoError(t.t, err) - - additionalReservedValue := btcutil.Amount(additionalChannels * - int64(lnwallet.AnchorChanReservedValue)) - totalReserved := btcutil.Amount(aliceBalance) + additionalReservedValue - - // The total reserved value should not exceed the maximum value reserved - // for anchor channels. - if totalReserved > lnwallet.MaxAnchorChanReservedValue { - totalReserved = lnwallet.MaxAnchorChanReservedValue - } - require.Equal( - t.t, int64(totalReserved), requiredReserveResp.RequiredReserve, - ) - // Alice closes channel, should now be allowed to send everything to an // external address. for _, chanPoint := range chanPoints { - closeChannelAndAssert(t, net, alice, chanPoint, false) + ht.CloseChannel(alice, chanPoint) } newBalance := waitForConfirmedBalance() - if newBalance <= aliceBalance { - t.Fatalf("Alice's balance did not increase after channel close") - } + require.Greater(ht, newBalance, aliceBalance, + "Alice's balance did not increase after channel close") // Assert there are no open or pending channels anymore. - assertNumPendingChannels(t, alice, 0, 0) - assertNodeNumChannels(t, alice, 0) + ht.AssertNumWaitingClose(alice, 0) + ht.AssertNodeNumChannels(alice, 0) // We'll wait for the balance to reflect that the channel has been // closed and the funds are in the wallet. @@ -452,25 +382,18 @@ func testAnchorReservedValue(net *lntest.NetworkHarness, t *harnessTest) { Addr: minerAddr.String(), SendAll: true, } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - _, err = alice.SendCoins(ctxt, sweepReq) - require.NoError(t.t, err) + alice.RPC.SendCoins(sweepReq) // We'll mine a block which should include the sweep transaction we // generated above. - block = mineBlocks(t, net, 1, 1)[0] + block = ht.MineBlocksAndAssertNumTxes(1, 1)[0] // The sweep transaction should have four inputs, the change output from // the previous sweep, and the outputs from the coop closed channels. sweepTx = block.Transactions[1] - if len(sweepTx.TxIn) != 4 { - t.Fatalf("expected 4 inputs instead have %v", len(sweepTx.TxIn)) - } // It should have a single output. - if len(sweepTx.TxOut) != 1 { - t.Fatalf("expected 1 output instead have %v", len(sweepTx.TxOut)) - } + assertNumTxInAndTxOut(sweepTx, 4, 1) } // genAnchorSweep generates a "3rd party" anchor sweeping from an existing one. diff --git a/lntest/itest/lnd_test_list_on_test.go b/lntest/itest/lnd_test_list_on_test.go index 1b4deda3a..c2dde1c9c 100644 --- a/lntest/itest/lnd_test_list_on_test.go +++ b/lntest/itest/lnd_test_list_on_test.go @@ -106,10 +106,6 @@ var allTestCases = []*testCase{ name: "cpfp", test: testCPFP, }, - { - name: "anchors reserved value", - test: testAnchorReservedValue, - }, { name: "macaroon authentication", test: testMacaroonAuthentication, From 9cb8b120ba8b78cf3223f4c1e39e1d19fc3a2395 Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Sun, 7 Aug 2022 06:06:02 +0800 Subject: [PATCH 10/44] lntemp+itest: refactor `testAnchorThirdPartySpend` --- lntemp/harness.go | 13 + lntemp/harness_assertion.go | 42 ++ lntemp/harness_miner.go | 32 ++ lntest/itest/assertions.go | 24 -- lntest/itest/list_on_test.go | 4 + lntest/itest/lnd_channel_force_close_test.go | 52 --- lntest/itest/lnd_onchain_test.go | 386 +++++++++---------- lntest/itest/lnd_test_list_on_test.go | 4 - 8 files changed, 278 insertions(+), 279 deletions(-) diff --git a/lntemp/harness.go b/lntemp/harness.go index d014aec07..06c9922fc 100644 --- a/lntemp/harness.go +++ b/lntemp/harness.go @@ -1391,6 +1391,19 @@ func (h *HarnessTest) MineBlocksAndAssertNumTxes(num uint32, return blocks } +// MineEmptyBlocks mines a given number of empty blocks. +// +// NOTE: this differs from miner's `MineEmptyBlocks` as it requires the nodes +// to be synced. +func (h *HarnessTest) MineEmptyBlocks(num int) []*wire.MsgBlock { + blocks := h.Miner.MineEmptyBlocks(num) + + // Finally, make sure all the active nodes are synced. + h.AssertActiveNodesSynced() + + return blocks +} + // QueryChannelByChanPoint tries to find a channel matching the channel point // and asserts. It returns the channel found. func (h *HarnessTest) QueryChannelByChanPoint(hn *node.HarnessNode, diff --git a/lntemp/harness_assertion.go b/lntemp/harness_assertion.go index d4c645d79..8a3c47e31 100644 --- a/lntemp/harness_assertion.go +++ b/lntemp/harness_assertion.go @@ -1927,3 +1927,45 @@ func (h *HarnessTest) AssertHtlcEvents(client rpc.HtlcEventsClient, return events } + +// AssertTransactionInWallet asserts a given txid can be found in the node's +// wallet. +func (h *HarnessTest) AssertTransactionInWallet(hn *node.HarnessNode, + txid chainhash.Hash) { + + req := &lnrpc.GetTransactionsRequest{} + err := wait.NoError(func() error { + txResp := hn.RPC.GetTransactions(req) + for _, txn := range txResp.Transactions { + if txn.TxHash == txid.String() { + return nil + } + } + + return fmt.Errorf("%s: expected txid=%v not found in wallet", + hn.Name(), txid) + }, DefaultTimeout) + + require.NoError(h, err, "failed to find tx") +} + +// AssertTransactionNotInWallet asserts a given txid can NOT be found in the +// node's wallet. +func (h *HarnessTest) AssertTransactionNotInWallet(hn *node.HarnessNode, + txid chainhash.Hash) { + + req := &lnrpc.GetTransactionsRequest{} + err := wait.NoError(func() error { + txResp := hn.RPC.GetTransactions(req) + for _, txn := range txResp.Transactions { + if txn.TxHash == txid.String() { + return fmt.Errorf("expected txid=%v to be "+ + "not found", txid) + } + } + + return nil + }, DefaultTimeout) + + require.NoErrorf(h, err, "%s: failed to assert tx not found", hn.Name()) +} diff --git a/lntemp/harness_miner.go b/lntemp/harness_miner.go index bcccb8656..430779897 100644 --- a/lntemp/harness_miner.go +++ b/lntemp/harness_miner.go @@ -383,3 +383,35 @@ func (h *HarnessMiner) NewMinerAddress() btcutil.Address { require.NoError(h, err, "failed to create new miner address") return addr } + +// MineBlocksWithTxes mines a single block to include the specifies +// transactions only. +func (h *HarnessMiner) MineBlockWithTxes(txes []*btcutil.Tx) *wire.MsgBlock { + var emptyTime time.Time + + // Generate a block. + b, err := h.GenerateAndSubmitBlock(txes, -1, emptyTime) + require.NoError(h, err, "unable to mine block") + + block, err := h.Client.GetBlock(b.Hash()) + require.NoError(h, err, "unable to get block") + + return block +} + +// MineEmptyBlocks mines a given number of empty blocks. +func (h *HarnessMiner) MineEmptyBlocks(num int) []*wire.MsgBlock { + var emptyTime time.Time + + blocks := make([]*wire.MsgBlock, num) + for i := 0; i < num; i++ { + // Generate an empty block. + b, err := h.GenerateAndSubmitBlock(nil, -1, emptyTime) + require.NoError(h, err, "unable to mine empty block") + + block := h.GetBlock(b.Hash()) + blocks[i] = block + } + + return blocks +} diff --git a/lntest/itest/assertions.go b/lntest/itest/assertions.go index 0be40d324..f7eb35e5b 100644 --- a/lntest/itest/assertions.go +++ b/lntest/itest/assertions.go @@ -1418,30 +1418,6 @@ func assertTransactionNotInWallet(t *testing.T, node *lntest.HarnessNode, ) } -func assertAnchorOutputLost(t *harnessTest, node *lntest.HarnessNode, - chanPoint wire.OutPoint) { - - pendingChansRequest := &lnrpc.PendingChannelsRequest{} - err := wait.Predicate(func() bool { - resp, pErr := node.PendingChannels( - context.Background(), pendingChansRequest, - ) - if pErr != nil { - return false - } - - for _, pendingChan := range resp.PendingForceClosingChannels { - if pendingChan.Channel.ChannelPoint == chanPoint.String() { - return (pendingChan.Anchor == - lnrpc.PendingChannelsResponse_ForceClosedChannel_LOST) - } - } - - return false - }, defaultTimeout) - require.NoError(t.t, err, "anchor doesn't show as being lost") -} - // assertNodeAnnouncement compares that two node announcements match. func assertNodeAnnouncement(t *harnessTest, n1, n2 *lnrpc.NodeUpdate) { // Alias should match. diff --git a/lntest/itest/list_on_test.go b/lntest/itest/list_on_test.go index 2c1373c9c..0c53a09ee 100644 --- a/lntest/itest/list_on_test.go +++ b/lntest/itest/list_on_test.go @@ -267,4 +267,8 @@ var allTestCasesTemp = []*lntemp.TestCase{ Name: "anchors reserved value", TestFunc: testAnchorReservedValue, }, + { + Name: "3rd party anchor spend", + TestFunc: testAnchorThirdPartySpend, + }, } diff --git a/lntest/itest/lnd_channel_force_close_test.go b/lntest/itest/lnd_channel_force_close_test.go index 4c87ef973..0857751d4 100644 --- a/lntest/itest/lnd_channel_force_close_test.go +++ b/lntest/itest/lnd_channel_force_close_test.go @@ -15,7 +15,6 @@ import ( "github.com/lightningnetwork/lnd/lnrpc/routerrpc" "github.com/lightningnetwork/lnd/lntemp" "github.com/lightningnetwork/lnd/lntemp/node" - "github.com/lightningnetwork/lnd/lntest" "github.com/lightningnetwork/lnd/lntest/wait" "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet/chainfee" @@ -984,57 +983,6 @@ func padCLTV(cltv uint32) uint32 { return cltv + uint32(routing.BlockPadding) } -type sweptOutput struct { - OutPoint wire.OutPoint - SweepTx *wire.MsgTx -} - -// findCommitAndAnchor looks for a commitment sweep and anchor sweep in the -// mempool. Our anchor output is identified by having multiple inputs, because -// we have to bring another input to add fees to the anchor. Note that the -// anchor swept output may be nil if the channel did not have anchors. -// TODO(yy): delete. -func findCommitAndAnchor(t *harnessTest, net *lntest.NetworkHarness, - sweepTxns []*wire.MsgTx, closeTx string) (*sweptOutput, *sweptOutput) { - - var commitSweep, anchorSweep *sweptOutput - - for _, tx := range sweepTxns { - txHash := tx.TxHash() - sweepTx, err := net.Miner.Client.GetRawTransaction(&txHash) - require.NoError(t.t, err) - - // We expect our commitment sweep to have a single input, and, - // our anchor sweep to have more inputs (because the wallet - // needs to add balance to the anchor amount). We find their - // sweep txids here to setup appropriate resolutions. We also - // need to find the outpoint for our resolution, which we do by - // matching the inputs to the sweep to the close transaction. - inputs := sweepTx.MsgTx().TxIn - if len(inputs) == 1 { - commitSweep = &sweptOutput{ - OutPoint: inputs[0].PreviousOutPoint, - SweepTx: tx, - } - } else { - // Since we have more than one input, we run through - // them to find the outpoint that spends from the close - // tx. This will be our anchor output. - for _, txin := range inputs { - outpointStr := txin.PreviousOutPoint.Hash.String() - if outpointStr == closeTx { - anchorSweep = &sweptOutput{ - OutPoint: txin.PreviousOutPoint, - SweepTx: tx, - } - } - } - } - } - - return commitSweep, anchorSweep -} - // testFailingChannel tests that we will fail the channel by force closing it // in the case where a counterparty tries to settle an HTLC with the wrong // preimage. diff --git a/lntest/itest/lnd_onchain_test.go b/lntest/itest/lnd_onchain_test.go index 4444caa70..a2d024aa6 100644 --- a/lntest/itest/lnd_onchain_test.go +++ b/lntest/itest/lnd_onchain_test.go @@ -4,7 +4,6 @@ import ( "bytes" "context" "fmt" - "time" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" @@ -14,6 +13,7 @@ import ( "github.com/lightningnetwork/lnd/lnrpc/chainrpc" "github.com/lightningnetwork/lnd/lnrpc/walletrpc" "github.com/lightningnetwork/lnd/lntemp" + "github.com/lightningnetwork/lnd/lntemp/node" "github.com/lightningnetwork/lnd/lntest" "github.com/lightningnetwork/lnd/lntest/wait" "github.com/lightningnetwork/lnd/lnwallet" @@ -396,11 +396,191 @@ func testAnchorReservedValue(ht *lntemp.HarnessTest) { assertNumTxInAndTxOut(sweepTx, 4, 1) } +// testAnchorThirdPartySpend tests that if we force close a channel, but then +// don't sweep the anchor in time and a 3rd party spends it, that we remove any +// transactions that are a descendent of that sweep. +func testAnchorThirdPartySpend(ht *lntemp.HarnessTest) { + // First, we'll create two new nodes that both default to anchor + // channels. + // + // NOTE: The itests differ here as anchors is default off vs the normal + // lnd binary. + args := nodeArgsForCommitType(lnrpc.CommitmentType_ANCHORS) + alice := ht.NewNode("Alice", args) + defer ht.Shutdown(alice) + + bob := ht.NewNode("Bob", args) + defer ht.Shutdown(bob) + + ht.EnsureConnected(alice, bob) + + // We'll fund our Alice with coins, as she'll be opening the channel. + // We'll fund her with *just* enough coins to open the channel and + // sweep the anchor. + const ( + firstChanSize = 1_000_000 + anchorFeeBuffer = 500_000 + ) + ht.FundCoins(firstChanSize+anchorFeeBuffer, alice) + + // Open the channel between the two nodes and wait for it to confirm + // fully. + aliceChanPoint1 := ht.OpenChannel( + alice, bob, lntemp.OpenChannelParams{ + Amt: firstChanSize, + }, + ) + + // Send another UTXO if this is a neutrino backend. When sweeping + // anchors, there are two transactions created, `local_sweep_tx` for + // sweeping Alice's anchor on the local commitment, `remote_sweep_tx` + // for sweeping her anchor on the remote commitment. Whenever the force + // close transaction is published, Alice will always create these two + // transactions to sweep her anchor. + // On the other hand, when creating the sweep txes, the anchor itself + // is not able to cover the fee, so another wallet UTXO is needed. In + // our test case, there's a change output that can be used from the + // above funding process. And it's used by both sweep txes - when `lnd` + // happens to create the `remote_sweep_tx` first, it will receive an + // error since its parent tx, the remote commitment, is not known, + // hence freeing the change output to be used by `local_sweep_tx`. + // For neutrino client, however, it will consider the transaction which + // sweeps the remote anchor as an orphan tx, and it will neither send + // it to the mempool nor return an error to free the change output. + // Thus, if the change output is already used in `remote_sweep_tx`, we + // won't have UTXO to create `local_sweep_tx`. + // + // NOTE: the order of the sweep requests for the two anchors cannot be + // guaranteed. If the sweeper happens to sweep the remote anchor first, + // then the test won't pass without the extra UTXO, which is the source + // of the flakeness. + // + // TODO(yy): make a RPC server for sweeper so we can explicitly check + // and control its state. + if ht.IsNeutrinoBackend() { + ht.FundCoins(anchorFeeBuffer, alice) + } + + // With the channel open, we'll actually immediately force close it. We + // don't care about network announcements here since there's no routing + // in this test. + ht.CloseChannelAssertPending(alice, aliceChanPoint1, true) + + // Now that the channel has been force closed, it should show up in the + // PendingChannels RPC under the waiting close section. + waitingClose := ht.AssertChannelWaitingClose(alice, aliceChanPoint1) + + // At this point, the channel is waiting close, and we have both the + // commitment transaction and anchor sweep in the mempool. + const expectedTxns = 2 + sweepTxns := ht.Miner.GetNumTxsFromMempool(expectedTxns) + aliceCloseTx := waitingClose.Commitments.LocalTxid + _, aliceAnchor := ht.FindCommitAndAnchor(sweepTxns, aliceCloseTx) + + // We'll now mine _only_ the commitment force close transaction, as we + // want the anchor sweep to stay unconfirmed. + forceCloseTxID, _ := chainhash.NewHashFromStr(aliceCloseTx) + commitTxn := ht.Miner.GetRawTransaction(forceCloseTxID) + ht.Miner.MineBlockWithTxes([]*btcutil.Tx{commitTxn}) + + // With the anchor output located, and the main commitment mined we'll + // instruct the wallet to send all coins in the wallet to a new address + // (to the miner), including unconfirmed change. + minerAddr := ht.Miner.NewMinerAddress() + sweepReq := &lnrpc.SendCoinsRequest{ + Addr: minerAddr.String(), + SendAll: true, + MinConfs: 0, + SpendUnconfirmed: true, + } + sweepAllResp := alice.RPC.SendCoins(sweepReq) + + // Both the original anchor sweep transaction, as well as the + // transaction we created to sweep all the coins from Alice's wallet + // should be found in her transaction store. + sweepAllTxID, _ := chainhash.NewHashFromStr(sweepAllResp.Txid) + ht.AssertTransactionInWallet(alice, aliceAnchor.SweepTx.TxHash()) + ht.AssertTransactionInWallet(alice, *sweepAllTxID) + + // Next, we'll shutdown Alice, and allow 16 blocks to pass so that the + // anchor output can be swept by anyone. Rather than use the normal API + // call, we'll generate a series of _empty_ blocks here. + aliceRestart := ht.SuspendNode(alice) + const anchorCsv = 16 + ht.MineEmptyBlocks(anchorCsv) + + // Before we sweep the anchor, we'll restart Alice. + require.NoErrorf(ht, aliceRestart(), "unable to restart alice") + + // Now that the channel has been closed, and Alice has an unconfirmed + // transaction spending the output produced by her anchor sweep, we'll + // mine a transaction that double spends the output. + thirdPartyAnchorSweep := genAnchorSweep(ht, aliceAnchor, anchorCsv) + ht.Miner.MineBlockWithTxes([]*btcutil.Tx{thirdPartyAnchorSweep}) + + // At this point, we should no longer find Alice's transaction that + // tried to sweep the anchor in her wallet. + ht.AssertTransactionNotInWallet(alice, aliceAnchor.SweepTx.TxHash()) + + // In addition, the transaction she sent to sweep all her coins to the + // miner also should no longer be found. + ht.AssertTransactionNotInWallet(alice, *sweepAllTxID) + + // The anchor should now show as being "lost", while the force close + // response is still present. + assertAnchorOutputLost(ht, alice, aliceChanPoint1) + + // At this point Alice's CSV output should already be fully spent and + // the channel marked as being resolved. We mine a block first, as so + // far we've been generating custom blocks this whole time. + commitSweepOp := wire.OutPoint{ + Hash: *forceCloseTxID, + Index: 1, + } + ht.Miner.AssertOutpointInMempool(commitSweepOp) + ht.MineBlocks(1) + + ht.AssertNumWaitingClose(alice, 0) +} + +// assertAnchorOutputLost asserts that the anchor output for the given channel +// has the state of being lost. +func assertAnchorOutputLost(ht *lntemp.HarnessTest, hn *node.HarnessNode, + chanPoint *lnrpc.ChannelPoint) { + + cp := ht.OutPointFromChannelPoint(chanPoint) + + expected := lnrpc.PendingChannelsResponse_ForceClosedChannel_LOST + + err := wait.NoError(func() error { + resp := hn.RPC.PendingChannels() + channels := resp.PendingForceClosingChannels + + for _, c := range channels { + // Not the wanted channel, skipped. + if c.Channel.ChannelPoint != cp.String() { + continue + } + + // Found the channel, check the anchor state. + if c.Anchor == expected { + return nil + } + + return fmt.Errorf("unexpected anchor state, want %v, "+ + "got %v", expected, c.Anchor) + } + + return fmt.Errorf("channel not found using cp=%v", cp) + }, defaultTimeout) + require.NoError(ht, err, "anchor doesn't show as being lost") +} + // genAnchorSweep generates a "3rd party" anchor sweeping from an existing one. // In practice, we just re-use the existing witness, and track on our own // output producing a 1-in-1-out transaction. -func genAnchorSweep(t *harnessTest, net *lntest.NetworkHarness, - aliceAnchor *sweptOutput, anchorCsv uint32) *btcutil.Tx { +func genAnchorSweep(ht *lntemp.HarnessTest, + aliceAnchor *lntemp.SweptOutput, anchorCsv uint32) *btcutil.Tx { // At this point, we have the transaction that Alice used to try to // sweep her anchor. As this is actually just something anyone can @@ -414,7 +594,8 @@ func genAnchorSweep(t *harnessTest, net *lntest.NetworkHarness, } } - t.Fatalf("anchor op not found") + require.FailNow(ht, "anchor op not found") + return wire.TxIn{} }() @@ -423,14 +604,9 @@ func genAnchorSweep(t *harnessTest, net *lntest.NetworkHarness, aliceAnchorTxIn.Witness[0] = nil aliceAnchorTxIn.Sequence = anchorCsv - minerAddr, err := net.Miner.NewAddress() - if err != nil { - t.Fatalf("unable to get miner addr: %v", err) - } + minerAddr := ht.Miner.NewMinerAddress() addrScript, err := txscript.PayToAddrScript(minerAddr) - if err != nil { - t.Fatalf("unable to gen addr script: %v", err) - } + require.NoError(ht, err, "unable to gen addr script") // Now that we have the txIn, we can just make a new transaction that // uses a different script for the output. @@ -443,191 +619,3 @@ func genAnchorSweep(t *harnessTest, net *lntest.NetworkHarness, return btcutil.NewTx(tx) } - -// testAnchorThirdPartySpend tests that if we force close a channel, but then -// don't sweep the anchor in time and a 3rd party spends it, that we remove any -// transactions that are a descendent of that sweep. -func testAnchorThirdPartySpend(net *lntest.NetworkHarness, t *harnessTest) { - // First, we'll create two new nodes that both default to anchor - // channels. - // - // NOTE: The itests differ here as anchors is default off vs the normal - // lnd binary. - args := nodeArgsForCommitType(lnrpc.CommitmentType_ANCHORS) - alice := net.NewNode(t.t, "Alice", args) - defer shutdownAndAssert(net, t, alice) - - bob := net.NewNode(t.t, "Bob", args) - defer shutdownAndAssert(net, t, bob) - - ctxb := context.Background() - net.ConnectNodes(t.t, alice, bob) - - // We'll fund our Alice with coins, as she'll be opening the channel. - // We'll fund her with *just* enough coins to open the channel. - const ( - firstChanSize = 1_000_000 - anchorFeeBuffer = 500_000 - ) - net.SendCoins(t.t, firstChanSize, alice) - - // We'll give Alice another spare UTXO as well so she can use it to - // help sweep all coins. - net.SendCoins(t.t, anchorFeeBuffer, alice) - - // Open the channel between the two nodes and wait for it to confirm - // fully. - aliceChanPoint1 := openChannelAndAssert( - t, net, alice, bob, lntest.OpenChannelParams{ - Amt: firstChanSize, - }, - ) - - // With the channel open, we'll actually immediately force close it. We - // don't care about network announcements here since there's no routing - // in this test. - _, _, err := net.CloseChannel(alice, aliceChanPoint1, true) - if err != nil { - t.Fatalf("unable to execute force channel closure: %v", err) - } - - // Now that the channel has been force closed, it should show up in the - // PendingChannels RPC under the waiting close section. - pendingChansRequest := &lnrpc.PendingChannelsRequest{} - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - pendingChanResp, err := alice.PendingChannels(ctxt, pendingChansRequest) - if err != nil { - t.Fatalf("unable to query for pending channels: %v", err) - } - err = checkNumWaitingCloseChannels(pendingChanResp, 1) - if err != nil { - t.Fatalf(err.Error()) - } - - // Get the normal channel outpoint so we can track it in the set of - // channels that are waiting to be closed. - fundingTxID, err := lnrpc.GetChanPointFundingTxid(aliceChanPoint1) - if err != nil { - t.Fatalf("unable to get txid: %v", err) - } - chanPoint := wire.OutPoint{ - Hash: *fundingTxID, - Index: aliceChanPoint1.OutputIndex, - } - waitingClose, err := findWaitingCloseChannel(pendingChanResp, &chanPoint) - if err != nil { - t.Fatalf(err.Error()) - } - - // At this point, the channel is waiting close, and we have both the - // commitment transaction and anchor sweep in the mempool. - const expectedTxns = 2 - sweepTxns, err := getNTxsFromMempool( - net.Miner.Client, expectedTxns, minerMempoolTimeout, - ) - require.NoError(t.t, err, "no sweep txns in miner mempool") - aliceCloseTx := waitingClose.Commitments.LocalTxid - _, aliceAnchor := findCommitAndAnchor(t, net, sweepTxns, aliceCloseTx) - - // We'll now mine _only_ the commitment force close transaction, as we - // want the anchor sweep to stay unconfirmed. - var emptyTime time.Time - forceCloseTxID, _ := chainhash.NewHashFromStr(aliceCloseTx) - commitTxn, err := net.Miner.Client.GetRawTransaction( - forceCloseTxID, - ) - if err != nil { - t.Fatalf("unable to get transaction: %v", err) - } - _, err = net.Miner.GenerateAndSubmitBlock( - []*btcutil.Tx{commitTxn}, -1, emptyTime, - ) - if err != nil { - t.Fatalf("unable to generate block: %v", err) - } - - // With the anchor output located, and the main commitment mined we'll - // instruct the wallet to send all coins in the wallet to a new address - // (to the miner), including unconfirmed change. - minerAddr, err := net.Miner.NewAddress() - if err != nil { - t.Fatalf("unable to create new miner addr: %v", err) - } - sweepReq := &lnrpc.SendCoinsRequest{ - Addr: minerAddr.String(), - SendAll: true, - MinConfs: 0, - SpendUnconfirmed: true, - } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - sweepAllResp, err := alice.SendCoins(ctxt, sweepReq) - if err != nil { - t.Fatalf("unable to sweep coins: %v", err) - } - - // Both the original anchor sweep transaction, as well as the - // transaction we created to sweep all the coins from Alice's wallet - // should be found in her transaction store. - sweepAllTxID, _ := chainhash.NewHashFromStr(sweepAllResp.Txid) - assertTransactionInWallet(t.t, alice, aliceAnchor.SweepTx.TxHash()) - assertTransactionInWallet(t.t, alice, *sweepAllTxID) - - // Next, we'll shutdown Alice, and allow 16 blocks to pass so that the - // anchor output can be swept by anyone. Rather than use the normal API - // call, we'll generate a series of _empty_ blocks here. - aliceRestart, err := net.SuspendNode(alice) - if err != nil { - t.Fatalf("unable to shutdown alice: %v", err) - } - const anchorCsv = 16 - for i := 0; i < anchorCsv; i++ { - _, err := net.Miner.GenerateAndSubmitBlock(nil, -1, emptyTime) - if err != nil { - t.Fatalf("unable to generate block: %v", err) - } - } - - // Before we sweep the anchor, we'll restart Alice. - if err := aliceRestart(); err != nil { - t.Fatalf("unable to restart alice: %v", err) - } - - // Now that the channel has been closed, and Alice has an unconfirmed - // transaction spending the output produced by her anchor sweep, we'll - // mine a transaction that double spends the output. - thirdPartyAnchorSweep := genAnchorSweep(t, net, aliceAnchor, anchorCsv) - _, err = net.Miner.GenerateAndSubmitBlock( - []*btcutil.Tx{thirdPartyAnchorSweep}, -1, emptyTime, - ) - if err != nil { - t.Fatalf("unable to generate block: %v", err) - } - - // At this point, we should no longer find Alice's transaction that - // tried to sweep the anchor in her wallet. - assertTransactionNotInWallet(t.t, alice, aliceAnchor.SweepTx.TxHash()) - - // In addition, the transaction she sent to sweep all her coins to the - // miner also should no longer be found. - assertTransactionNotInWallet(t.t, alice, *sweepAllTxID) - - // The anchor should now show as being "lost", while the force close - // response is still present. - assertAnchorOutputLost(t, alice, chanPoint) - - // At this point Alice's CSV output should already be fully spent and - // the channel marked as being resolved. We mine a block first, as so - // far we've been generating custom blocks this whole time.. - commitSweepOp := wire.OutPoint{ - Hash: *forceCloseTxID, - Index: 1, - } - assertSpendingTxInMempool( - t, net.Miner.Client, minerMempoolTimeout, commitSweepOp, - ) - _, err = net.Miner.Client.Generate(1) - if err != nil { - t.Fatalf("unable to generate block: %v", err) - } - assertNumPendingChannels(t, alice, 0, 0) -} diff --git a/lntest/itest/lnd_test_list_on_test.go b/lntest/itest/lnd_test_list_on_test.go index c2dde1c9c..589b576d9 100644 --- a/lntest/itest/lnd_test_list_on_test.go +++ b/lntest/itest/lnd_test_list_on_test.go @@ -194,10 +194,6 @@ var allTestCases = []*testCase{ name: "remote signer", test: testRemoteSigner, }, - { - name: "3rd party anchor spend", - test: testAnchorThirdPartySpend, - }, { name: "taproot", test: testTaproot, From 68016c0b51847ee616b3bcab1964f388181f4b2d Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Sun, 7 Aug 2022 07:53:43 +0800 Subject: [PATCH 11/44] lntemp+itest: refactor `testOpenChannelAfterReorg` --- lntemp/harness.go | 13 ++ lntemp/harness_assertion.go | 31 +++- lntemp/harness_miner.go | 10 ++ lntest/itest/assertions.go | 35 ---- lntest/itest/list_on_test.go | 4 + lntest/itest/lnd_open_channel_test.go | 233 ++++++++++---------------- lntest/itest/lnd_test_list_on_test.go | 4 - 7 files changed, 139 insertions(+), 191 deletions(-) diff --git a/lntemp/harness.go b/lntemp/harness.go index 06c9922fc..485c076ce 100644 --- a/lntemp/harness.go +++ b/lntemp/harness.go @@ -1709,3 +1709,16 @@ func findSweepInDetails(ht *HarnessTest, sweepTxid string, return false } + +// ConnectMiner connects the miner with the chain backend in the network. +func (h *HarnessTest) ConnectMiner() { + err := h.manager.chainBackend.ConnectMiner() + require.NoError(h, err, "failed to connect miner") +} + +// DisconnectMiner removes the connection between the miner and the chain +// backend in the network. +func (h *HarnessTest) DisconnectMiner() { + err := h.manager.chainBackend.DisconnectMiner() + require.NoError(h, err, "failed to disconnect miner") +} diff --git a/lntemp/harness_assertion.go b/lntemp/harness_assertion.go index 8a3c47e31..1378bb9a7 100644 --- a/lntemp/harness_assertion.go +++ b/lntemp/harness_assertion.go @@ -565,8 +565,10 @@ func (h *HarnessTest) AssertStreamChannelCoopClosed(hn *node.HarnessNode, h.AssertNumWaitingClose(hn, 0) // Finally, check that the node's topology graph has seen this channel - // closed. - h.AssertTopologyChannelClosed(hn, cp) + // closed if it's a public channel. + if !resp.Channel.Private { + h.AssertTopologyChannelClosed(hn, cp) + } return closingTxid } @@ -611,8 +613,10 @@ func (h *HarnessTest) AssertStreamChannelForceClosed(hn *node.HarnessNode, h.AssertNumPendingForceClose(hn, 1) // Finally, check that the node's topology graph has seen this channel - // closed. - h.AssertTopologyChannelClosed(hn, cp) + // closed if it's a public channel. + if !resp.Channel.Private { + h.AssertTopologyChannelClosed(hn, cp) + } return closingTxid } @@ -1969,3 +1973,22 @@ func (h *HarnessTest) AssertTransactionNotInWallet(hn *node.HarnessNode, require.NoErrorf(h, err, "%s: failed to assert tx not found", hn.Name()) } + +// WaitForNodeBlockHeight queries the node for its current block height until +// it reaches the passed height. +func (h *HarnessTest) WaitForNodeBlockHeight(hn *node.HarnessNode, + height int32) { + + err := wait.NoError(func() error { + info := hn.RPC.GetInfo() + if int32(info.BlockHeight) != height { + return fmt.Errorf("expected block height to "+ + "be %v, was %v", height, info.BlockHeight) + } + + return nil + }, DefaultTimeout) + + require.NoErrorf(h, err, "%s: timeout while waiting for height", + hn.Name()) +} diff --git a/lntemp/harness_miner.go b/lntemp/harness_miner.go index 430779897..916b57500 100644 --- a/lntemp/harness_miner.go +++ b/lntemp/harness_miner.go @@ -57,6 +57,16 @@ func NewMiner(ctxt context.Context, t *testing.T) *HarnessMiner { return newMiner(ctxt, t, minerLogDir, minerLogFilename) } +// NewTempMiner creates a new miner using btcd backend with the specified log +// file dir and name. +func NewTempMiner(ctxt context.Context, t *testing.T, + tempDir, tempLogFilename string) *HarnessMiner { + + t.Helper() + + return newMiner(ctxt, t, tempDir, tempLogFilename) +} + // newMiner creates a new miner using btcd's rpctest. func newMiner(ctxb context.Context, t *testing.T, minerDirName, logFilename string) *HarnessMiner { diff --git a/lntest/itest/assertions.go b/lntest/itest/assertions.go index f7eb35e5b..feae9e160 100644 --- a/lntest/itest/assertions.go +++ b/lntest/itest/assertions.go @@ -690,41 +690,6 @@ func assertChannelPolicy(t *harnessTest, node *lntest.HarnessNode, } } -// assertMinerBlockHeightDelta ensures that tempMiner is 'delta' blocks ahead -// of miner. -func assertMinerBlockHeightDelta(t *harnessTest, - miner, tempMiner *lntest.HarnessMiner, delta int32) { - - // Ensure the chain lengths are what we expect. - var predErr error - err := wait.Predicate(func() bool { - _, tempMinerHeight, err := tempMiner.Client.GetBestBlock() - if err != nil { - predErr = fmt.Errorf("unable to get current "+ - "blockheight %v", err) - return false - } - - _, minerHeight, err := miner.Client.GetBestBlock() - if err != nil { - predErr = fmt.Errorf("unable to get current "+ - "blockheight %v", err) - return false - } - - if tempMinerHeight != minerHeight+delta { - predErr = fmt.Errorf("expected new miner(%d) to be %d "+ - "blocks ahead of original miner(%d)", - tempMinerHeight, delta, minerHeight) - return false - } - return true - }, defaultTimeout) - if err != nil { - t.Fatalf(predErr.Error()) - } -} - func checkCommitmentMaturity( forceClose *lnrpc.PendingChannelsResponse_ForceClosedChannel, maturityHeight uint32, blocksTilMaturity int32) error { diff --git a/lntest/itest/list_on_test.go b/lntest/itest/list_on_test.go index 0c53a09ee..a814ec8cf 100644 --- a/lntest/itest/list_on_test.go +++ b/lntest/itest/list_on_test.go @@ -271,4 +271,8 @@ var allTestCasesTemp = []*lntemp.TestCase{ Name: "3rd party anchor spend", TestFunc: testAnchorThirdPartySpend, }, + { + Name: "open channel reorg test", + TestFunc: testOpenChannelAfterReorg, + }, } diff --git a/lntest/itest/lnd_open_channel_test.go b/lntest/itest/lnd_open_channel_test.go index 0a2d0b90b..51e9a02d3 100644 --- a/lntest/itest/lnd_open_channel_test.go +++ b/lntest/itest/lnd_open_channel_test.go @@ -6,12 +6,12 @@ import ( "time" "github.com/btcsuite/btcd/btcjson" - "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/integration/rpctest" "github.com/lightningnetwork/lnd/chainreg" "github.com/lightningnetwork/lnd/funding" "github.com/lightningnetwork/lnd/lnrpc" + "github.com/lightningnetwork/lnd/lntemp" "github.com/lightningnetwork/lnd/lntest" "github.com/lightningnetwork/lnd/lntest/wait" "github.com/stretchr/testify/require" @@ -20,112 +20,83 @@ import ( // testOpenChannelAfterReorg tests that in the case where we have an open // channel where the funding tx gets reorged out, the channel will no // longer be present in the node's routing table. -func testOpenChannelAfterReorg(net *lntest.NetworkHarness, t *harnessTest) { +func testOpenChannelAfterReorg(ht *lntemp.HarnessTest) { // Skip test for neutrino, as we cannot disconnect the miner at will. // TODO(halseth): remove when either can disconnect at will, or restart // node with connection to new miner. - if net.BackendCfg.Name() == lntest.NeutrinoBackendName { - t.Skipf("skipping reorg test for neutrino backend") + if ht.IsNeutrinoBackend() { + ht.Skipf("skipping reorg test for neutrino backend") } - var ( - ctxb = context.Background() - temp = "temp" - ) + temp := "temp" // Set up a new miner that we can use to cause a reorg. tempLogDir := ".tempminerlogs" logFilename := "output-open_channel_reorg-temp_miner.log" - tempMiner, err := lntest.NewTempMiner(tempLogDir, logFilename) - require.NoError(t.t, err, "failed to create temp miner") - defer func() { - require.NoError( - t.t, tempMiner.Stop(), - "failed to clean up temp miner", - ) - }() + tempMiner := lntemp.NewTempMiner( + ht.Context(), ht.T, tempLogDir, logFilename, + ) + defer tempMiner.Stop() // Setup the temp miner - require.NoError( - t.t, tempMiner.SetUp(false, 0), "unable to set up mining node", - ) + require.NoError(ht, tempMiner.SetUp(false, 0), + "unable to set up mining node") + + miner := ht.Miner + alice, bob := ht.Alice, ht.Bob // We start by connecting the new miner to our original miner, // such that it will sync to our original chain. - err = net.Miner.Client.Node( + err := miner.Client.Node( btcjson.NConnect, tempMiner.P2PAddress(), &temp, ) - if err != nil { - t.Fatalf("unable to remove node: %v", err) - } - nodeSlice := []*rpctest.Harness{net.Miner.Harness, tempMiner.Harness} - if err := rpctest.JoinNodes(nodeSlice, rpctest.Blocks); err != nil { - t.Fatalf("unable to join node on blocks: %v", err) - } + require.NoError(ht, err, "unable to connect miners") + + nodeSlice := []*rpctest.Harness{miner.Harness, tempMiner.Harness} + err = rpctest.JoinNodes(nodeSlice, rpctest.Blocks) + require.NoError(ht, err, "unable to join node on blocks") // The two miners should be on the same blockheight. - assertMinerBlockHeightDelta(t, net.Miner, tempMiner, 0) + assertMinerBlockHeightDelta(ht, miner, tempMiner, 0) // We disconnect the two miners, such that we can mine two different // chains and can cause a reorg later. - err = net.Miner.Client.Node( + err = miner.Client.Node( btcjson.NDisconnect, tempMiner.P2PAddress(), &temp, ) - if err != nil { - t.Fatalf("unable to remove node: %v", err) - } + require.NoError(ht, err, "unable to disconnect miners") // Create a new channel that requires 1 confs before it's considered // open, then broadcast the funding transaction - chanAmt := funding.MaxBtcFundingAmount - pushAmt := btcutil.Amount(0) - pendingUpdate, err := net.OpenPendingChannel( - net.Alice, net.Bob, chanAmt, pushAmt, - ) - if err != nil { - t.Fatalf("unable to open channel: %v", err) + params := lntemp.OpenChannelParams{ + Amt: funding.MaxBtcFundingAmount, + Private: true, } + pendingUpdate := ht.OpenChannelAssertPending(alice, bob, params) // Wait for miner to have seen the funding tx. The temporary miner is // disconnected, and won't see the transaction. - _, err = waitForTxInMempool(net.Miner.Client, minerMempoolTimeout) - if err != nil { - t.Fatalf("failed to find funding tx in mempool: %v", err) - } + ht.Miner.AssertNumTxsInMempool(1) // At this point, the channel's funding transaction will have been // broadcast, but not confirmed, and the channel should be pending. - assertNumOpenChannelsPending(t, net.Alice, net.Bob, 1) + ht.AssertNodesNumPendingOpenChannels(alice, bob, 1) fundingTxID, err := chainhash.NewHash(pendingUpdate.Txid) - if err != nil { - t.Fatalf("unable to convert funding txid into chainhash.Hash:"+ - " %v", err) - } + require.NoError(ht, err, "convert funding txid into chainhash failed") // We now cause a fork, by letting our original miner mine 10 blocks, // and our new miner mine 15. This will also confirm our pending // channel on the original miner's chain, which should be considered // open. - block := mineBlocks(t, net, 10, 1)[0] - assertTxInBlock(t, block, fundingTxID) - if _, err := tempMiner.Client.Generate(15); err != nil { - t.Fatalf("unable to generate blocks: %v", err) - } + block := ht.MineBlocks(10)[0] + ht.Miner.AssertTxInBlock(block, fundingTxID) + _, err = tempMiner.Client.Generate(15) + require.NoError(ht, err, "unable to generate blocks") // Ensure the chain lengths are what we expect, with the temp miner // being 5 blocks ahead. - assertMinerBlockHeightDelta(t, net.Miner, tempMiner, 5) - - // Wait for Alice to sync to the original miner's chain. - _, minerHeight, err := net.Miner.Client.GetBestBlock() - if err != nil { - t.Fatalf("unable to get current blockheight %v", err) - } - err = waitForNodeBlockHeight(net.Alice, minerHeight) - if err != nil { - t.Fatalf("unable to sync to chain: %v", err) - } + assertMinerBlockHeightDelta(ht, miner, tempMiner, 5) chanPoint := &lnrpc.ChannelPoint{ FundingTxid: &lnrpc.ChannelPoint_FundingTxidBytes{ @@ -135,121 +106,57 @@ func testOpenChannelAfterReorg(net *lntest.NetworkHarness, t *harnessTest) { } // Ensure channel is no longer pending. - assertNumOpenChannelsPending(t, net.Alice, net.Bob, 0) + ht.AssertNodesNumPendingOpenChannels(alice, bob, 0) // Wait for Alice and Bob to recognize and advertise the new channel // generated above. - err = net.Alice.WaitForNetworkChannelOpen(chanPoint) - if err != nil { - t.Fatalf("alice didn't advertise channel before "+ - "timeout: %v", err) - } - err = net.Bob.WaitForNetworkChannelOpen(chanPoint) - if err != nil { - t.Fatalf("bob didn't advertise channel before "+ - "timeout: %v", err) - } + ht.AssertTopologyChannelOpen(alice, chanPoint) + ht.AssertTopologyChannelOpen(bob, chanPoint) // Alice should now have 1 edge in her graph. - req := &lnrpc.ChannelGraphRequest{ - IncludeUnannounced: true, - } - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - chanGraph, err := net.Alice.DescribeGraph(ctxt, req) - if err != nil { - t.Fatalf("unable to query for alice's routing table: %v", err) - } - - numEdges := len(chanGraph.Edges) - if numEdges != 1 { - t.Fatalf("expected to find one edge in the graph, found %d", - numEdges) - } + ht.AssertNumEdges(alice, 1, true) // Now we disconnect Alice's chain backend from the original miner, and // connect the two miners together. Since the temporary miner knows // about a longer chain, both miners should sync to that chain. - err = net.BackendCfg.DisconnectMiner() - if err != nil { - t.Fatalf("unable to remove node: %v", err) - } + ht.DisconnectMiner() // Connecting to the temporary miner should now cause our original // chain to be re-orged out. - err = net.Miner.Client.Node( - btcjson.NConnect, tempMiner.P2PAddress(), &temp, - ) - if err != nil { - t.Fatalf("unable to remove node: %v", err) - } + err = miner.Client.Node(btcjson.NConnect, tempMiner.P2PAddress(), &temp) + require.NoError(ht, err, "unable to connect temp miner") - nodes := []*rpctest.Harness{tempMiner.Harness, net.Miner.Harness} - if err := rpctest.JoinNodes(nodes, rpctest.Blocks); err != nil { - t.Fatalf("unable to join node on blocks: %v", err) - } + nodes := []*rpctest.Harness{tempMiner.Harness, miner.Harness} + err = rpctest.JoinNodes(nodes, rpctest.Blocks) + require.NoError(ht, err, "unable to join node on blocks") // Once again they should be on the same chain. - assertMinerBlockHeightDelta(t, net.Miner, tempMiner, 0) + assertMinerBlockHeightDelta(ht, miner, tempMiner, 0) // Now we disconnect the two miners, and connect our original miner to // our chain backend once again. - err = net.Miner.Client.Node( + err = miner.Client.Node( btcjson.NDisconnect, tempMiner.P2PAddress(), &temp, ) - if err != nil { - t.Fatalf("unable to remove node: %v", err) - } + require.NoError(ht, err, "unable to disconnect temp miner") - err = net.BackendCfg.ConnectMiner() - if err != nil { - t.Fatalf("unable to remove node: %v", err) - } + ht.ConnectMiner() // This should have caused a reorg, and Alice should sync to the longer // chain, where the funding transaction is not confirmed. _, tempMinerHeight, err := tempMiner.Client.GetBestBlock() - if err != nil { - t.Fatalf("unable to get current blockheight %v", err) - } - err = waitForNodeBlockHeight(net.Alice, tempMinerHeight) - if err != nil { - t.Fatalf("unable to sync to chain: %v", err) - } + require.NoError(ht, err, "unable to get current blockheight") + ht.WaitForNodeBlockHeight(alice, tempMinerHeight) // Since the fundingtx was reorged out, Alice should now have no edges // in her graph. - req = &lnrpc.ChannelGraphRequest{ - IncludeUnannounced: true, - } - - var predErr error - err = wait.Predicate(func() bool { - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - chanGraph, err = net.Alice.DescribeGraph(ctxt, req) - if err != nil { - predErr = fmt.Errorf("unable to query for "+ - "alice's routing table: %v", err) - return false - } - - numEdges = len(chanGraph.Edges) - if numEdges != 0 { - predErr = fmt.Errorf("expected to find "+ - "no edge in the graph, found %d", - numEdges) - return false - } - return true - }, defaultTimeout) - if err != nil { - t.Fatalf(predErr.Error()) - } + ht.AssertNumEdges(alice, 0, true) // Cleanup by mining the funding tx again, then closing the channel. - block = mineBlocks(t, net, 1, 1)[0] - assertTxInBlock(t, block, fundingTxID) + block = ht.MineBlocksAndAssertNumTxes(1, 1)[0] + ht.Miner.AssertTxInBlock(block, fundingTxID) - closeReorgedChannelAndAssert(t, net, net.Alice, chanPoint, false) + ht.CloseChannel(alice, chanPoint) } // testOpenChannelFeePolicy checks if different channel fee scenarios @@ -579,3 +486,33 @@ func runBasicChannelCreationAndUpdates(net *lntest.NetworkHarness, ), "verifying alice close updates", ) } + +// assertMinerBlockHeightDelta ensures that tempMiner is 'delta' blocks ahead +// of miner. +func assertMinerBlockHeightDelta(ht *lntemp.HarnessTest, + miner, tempMiner *lntemp.HarnessMiner, delta int32) { + + // Ensure the chain lengths are what we expect. + err := wait.NoError(func() error { + _, tempMinerHeight, err := tempMiner.Client.GetBestBlock() + if err != nil { + return fmt.Errorf("unable to get current "+ + "blockheight %v", err) + } + + _, minerHeight, err := miner.Client.GetBestBlock() + if err != nil { + return fmt.Errorf("unable to get current "+ + "blockheight %v", err) + } + + if tempMinerHeight != minerHeight+delta { + return fmt.Errorf("expected new miner(%d) to be %d "+ + "blocks ahead of original miner(%d)", + tempMinerHeight, delta, minerHeight) + } + + return nil + }, defaultTimeout) + require.NoError(ht, err, "failed to assert block height delta") +} diff --git a/lntest/itest/lnd_test_list_on_test.go b/lntest/itest/lnd_test_list_on_test.go index 589b576d9..432cd4cdc 100644 --- a/lntest/itest/lnd_test_list_on_test.go +++ b/lntest/itest/lnd_test_list_on_test.go @@ -4,10 +4,6 @@ package itest var allTestCases = []*testCase{ - { - name: "open channel reorg test", - test: testOpenChannelAfterReorg, - }, { name: "single hop invoice", test: testSingleHopInvoice, From 4a8cf91e53aedbbee7642ab875a5bd1017a45e88 Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Mon, 8 Aug 2022 10:18:23 +0800 Subject: [PATCH 12/44] itest: refactor `testPsbtChanFundingExternal` --- lntest/itest/list_on_test.go | 4 + lntest/itest/lnd_psbt_test.go | 125 +++++++++++--------------- lntest/itest/lnd_test_list_on_test.go | 4 - 3 files changed, 55 insertions(+), 78 deletions(-) diff --git a/lntest/itest/list_on_test.go b/lntest/itest/list_on_test.go index a814ec8cf..de3310778 100644 --- a/lntest/itest/list_on_test.go +++ b/lntest/itest/list_on_test.go @@ -275,4 +275,8 @@ var allTestCasesTemp = []*lntemp.TestCase{ Name: "open channel reorg test", TestFunc: testOpenChannelAfterReorg, }, + { + Name: "psbt channel funding external", + TestFunc: testPsbtChanFundingExternal, + }, } diff --git a/lntest/itest/lnd_psbt_test.go b/lntest/itest/lnd_psbt_test.go index 4644d54bc..6ec45ea19 100644 --- a/lntest/itest/lnd_psbt_test.go +++ b/lntest/itest/lnd_psbt_test.go @@ -5,6 +5,7 @@ import ( "context" "crypto/rand" "fmt" + "time" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/ecdsa" @@ -20,6 +21,7 @@ import ( "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/signrpc" "github.com/lightningnetwork/lnd/lnrpc/walletrpc" + "github.com/lightningnetwork/lnd/lntemp" "github.com/lightningnetwork/lnd/lntest" "github.com/stretchr/testify/require" ) @@ -255,69 +257,56 @@ func runPsbtChanFunding(net *lntest.NetworkHarness, t *harnessTest, carol, // and dave by using a Partially Signed Bitcoin Transaction that funds the // channel multisig funding output and is fully funded by an external third // party. -func testPsbtChanFundingExternal(net *lntest.NetworkHarness, t *harnessTest) { - ctxb := context.Background() +func testPsbtChanFundingExternal(ht *lntemp.HarnessTest) { const chanSize = funding.MaxBtcFundingAmount - // Everything we do here should be done within a second or two, so we - // can just keep a single timeout context around for all calls. - ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) - defer cancel() - // First, we'll create two new nodes that we'll use to open channels // between for this test. Both these nodes have an empty wallet as Alice // will be funding the channel. - carol := net.NewNode(t.t, "carol", nil) - defer shutdownAndAssert(net, t, carol) - - dave := net.NewNode(t.t, "dave", nil) - defer shutdownAndAssert(net, t, dave) + carol := ht.NewNode("carol", nil) + dave := ht.NewNode("dave", nil) // Before we start the test, we'll ensure both sides are connected so // the funding flow can be properly executed. - net.EnsureConnected(t.t, carol, dave) - net.EnsureConnected(t.t, carol, net.Alice) + alice := ht.Alice + ht.EnsureConnected(carol, dave) + ht.EnsureConnected(carol, alice) // At this point, we can begin our PSBT channel funding workflow. We'll // start by generating a pending channel ID externally that will be used // to track this new funding type. - var pendingChanID [32]byte - _, err := rand.Read(pendingChanID[:]) - require.NoError(t.t, err) + pendingChanID := ht.Random32Bytes() // We'll also test batch funding of two channels so we need another ID. - var pendingChanID2 [32]byte - _, err = rand.Read(pendingChanID2[:]) - require.NoError(t.t, err) + pendingChanID2 := ht.Random32Bytes() // Now that we have the pending channel ID, Carol will open the channel // by specifying a PSBT shim. We use the NoPublish flag here to avoid // publishing the whole batch TX too early. - chanUpdates, tempPsbt, err := openChannelPsbt( - ctxt, carol, dave, lntest.OpenChannelParams{ + chanUpdates, tempPsbt := ht.OpenChannelPsbt( + carol, dave, lntemp.OpenChannelParams{ Amt: chanSize, FundingShim: &lnrpc.FundingShim{ Shim: &lnrpc.FundingShim_PsbtShim{ PsbtShim: &lnrpc.PsbtShim{ - PendingChanId: pendingChanID[:], + PendingChanId: pendingChanID, NoPublish: true, }, }, }, }, ) - require.NoError(t.t, err) // Let's add a second channel to the batch. This time between Carol and // Alice. We will publish the batch TX once this channel funding is // complete. - chanUpdates2, psbtBytes2, err := openChannelPsbt( - ctxt, carol, net.Alice, lntest.OpenChannelParams{ + chanUpdates2, psbtBytes2 := ht.OpenChannelPsbt( + carol, alice, lntemp.OpenChannelParams{ Amt: chanSize, FundingShim: &lnrpc.FundingShim{ Shim: &lnrpc.FundingShim_PsbtShim{ PsbtShim: &lnrpc.PsbtShim{ - PendingChanId: pendingChanID2[:], + PendingChanId: pendingChanID2, NoPublish: true, BasePsbt: tempPsbt, }, @@ -325,7 +314,6 @@ func testPsbtChanFundingExternal(net *lntest.NetworkHarness, t *harnessTest) { }, }, ) - require.NoError(t.t, err) // We'll now ask Alice's wallet to fund the PSBT for us. This will // return a packet with inputs and outputs set but without any witness @@ -338,8 +326,7 @@ func testPsbtChanFundingExternal(net *lntest.NetworkHarness, t *harnessTest) { SatPerVbyte: 2, }, } - fundResp, err := net.Alice.WalletKitClient.FundPsbt(ctxt, fundReq) - require.NoError(t.t, err) + fundResp := alice.RPC.FundPsbt(fundReq) // We have a PSBT that has no witness data yet, which is exactly what we // need for the next step: Verify the PSBT with the funding intents. @@ -349,92 +336,77 @@ func testPsbtChanFundingExternal(net *lntest.NetworkHarness, t *harnessTest) { // direct communication with Carol and won't send the signed TX to her // before broadcasting it. So we cannot call the finalize step but // instead just tell lnd to wait for a TX to be published/confirmed. - _, err = carol.FundingStateStep(ctxb, &lnrpc.FundingTransitionMsg{ + carol.RPC.FundingStateStep(&lnrpc.FundingTransitionMsg{ Trigger: &lnrpc.FundingTransitionMsg_PsbtVerify{ PsbtVerify: &lnrpc.FundingPsbtVerify{ - PendingChanId: pendingChanID[:], + PendingChanId: pendingChanID, FundedPsbt: fundResp.FundedPsbt, SkipFinalize: true, }, }, }) - require.NoError(t.t, err) - _, err = carol.FundingStateStep(ctxb, &lnrpc.FundingTransitionMsg{ + carol.RPC.FundingStateStep(&lnrpc.FundingTransitionMsg{ Trigger: &lnrpc.FundingTransitionMsg_PsbtVerify{ PsbtVerify: &lnrpc.FundingPsbtVerify{ - PendingChanId: pendingChanID2[:], + PendingChanId: pendingChanID2, FundedPsbt: fundResp.FundedPsbt, SkipFinalize: true, }, }, }) - require.NoError(t.t, err) // Consume the "channel pending" update. This waits until the funding // transaction was fully compiled for both channels. - updateResp, err := receiveChanUpdate(ctxt, chanUpdates) - require.NoError(t.t, err) + updateResp := ht.ReceiveOpenChannelUpdate(chanUpdates) upd, ok := updateResp.Update.(*lnrpc.OpenStatusUpdate_ChanPending) - require.True(t.t, ok) + require.True(ht, ok) chanPoint := &lnrpc.ChannelPoint{ FundingTxid: &lnrpc.ChannelPoint_FundingTxidBytes{ FundingTxidBytes: upd.ChanPending.Txid, }, OutputIndex: upd.ChanPending.OutputIndex, } - updateResp2, err := receiveChanUpdate(ctxt, chanUpdates2) - require.NoError(t.t, err) + updateResp2 := ht.ReceiveOpenChannelUpdate(chanUpdates2) upd2, ok := updateResp2.Update.(*lnrpc.OpenStatusUpdate_ChanPending) - require.True(t.t, ok) + require.True(ht, ok) chanPoint2 := &lnrpc.ChannelPoint{ FundingTxid: &lnrpc.ChannelPoint_FundingTxidBytes{ FundingTxidBytes: upd2.ChanPending.Txid, }, OutputIndex: upd2.ChanPending.OutputIndex, } - numPending, err := numOpenChannelsPending(ctxt, carol) - require.NoError(t.t, err) - require.Equal(t.t, 2, numPending) + ht.AssertNumPendingOpenChannels(carol, 2) // Now we'll ask Alice's wallet to sign the PSBT so we can finish the // funding flow. finalizeReq := &walletrpc.FinalizePsbtRequest{ FundedPsbt: fundResp.FundedPsbt, } - finalizeRes, err := net.Alice.WalletKitClient.FinalizePsbt( - ctxt, finalizeReq, - ) - require.NoError(t.t, err) + finalizeRes := alice.RPC.FinalizePsbt(finalizeReq) // No transaction should have been published yet. - mempool, err := net.Miner.Client.GetRawMempool() - require.NoError(t.t, err) - require.Equal(t.t, 0, len(mempool)) + ht.Miner.AssertNumTxsInMempool(0) // Great, now let's publish the final raw transaction. var finalTx wire.MsgTx - err = finalTx.Deserialize(bytes.NewReader(finalizeRes.RawFinalTx)) - require.NoError(t.t, err) + err := finalTx.Deserialize(bytes.NewReader(finalizeRes.RawFinalTx)) + require.NoError(ht, err) txHash := finalTx.TxHash() - _, err = net.Miner.Client.SendRawTransaction(&finalTx, false) - require.NoError(t.t, err) + _, err = ht.Miner.Client.SendRawTransaction(&finalTx, false) + require.NoError(ht, err) // Now we can mine a block to get the transaction confirmed, then wait // for the new channel to be propagated through the network. - block := mineBlocks(t, net, 6, 1)[0] - assertTxInBlock(t, block, &txHash) - err = carol.WaitForNetworkChannelOpen(chanPoint) - require.NoError(t.t, err) - err = carol.WaitForNetworkChannelOpen(chanPoint2) - require.NoError(t.t, err) + block := ht.MineBlocksAndAssertNumTxes(6, 1)[0] + ht.Miner.AssertTxInBlock(block, &txHash) + ht.AssertTopologyChannelOpen(carol, chanPoint) + ht.AssertTopologyChannelOpen(carol, chanPoint2) // With the channel open, ensure that it is counted towards Carol's // total channel balance. - balReq := &lnrpc.ChannelBalanceRequest{} - balRes, err := carol.ChannelBalance(ctxt, balReq) - require.NoError(t.t, err) - require.NotEqual(t.t, int64(0), balRes.LocalBalance.Sat) + balRes := carol.RPC.ChannelBalance() + require.NotZero(ht, balRes.LocalBalance.Sat) // Next, to make sure the channel functions as normal, we'll make some // payments within the channel. @@ -443,19 +415,24 @@ func testPsbtChanFundingExternal(net *lntest.NetworkHarness, t *harnessTest) { Memo: "new chans", Value: int64(payAmt), } - resp, err := dave.AddInvoice(ctxt, invoice) - require.NoError(t.t, err) - err = completePaymentRequests( - carol, carol.RouterClient, []string{resp.PaymentRequest}, true, - ) - require.NoError(t.t, err) + resp := dave.RPC.AddInvoice(invoice) + ht.CompletePaymentRequests(carol, []string{resp.PaymentRequest}) + + // TODO(yy): remove the sleep once the following bug is fixed. When the + // payment is reported as settled by Carol, it's expected the + // commitment dance is finished and all subsequent states have been + // updated. Yet we'd receive the error `cannot co-op close channel with + // active htlcs` or `link failed to shutdown` if we close the channel. + // We need to investigate the order of settling the payments and + // updating commitments to understand and fix . + time.Sleep(2 * time.Second) // To conclude, we'll close the newly created channel between Carol and // Dave. This function will also block until the channels are closed and // will additionally assert the relevant channel closing post // conditions. - closeChannelAndAssert(t, net, carol, chanPoint, false) - closeChannelAndAssert(t, net, carol, chanPoint2, false) + ht.CloseChannel(carol, chanPoint) + ht.CloseChannel(carol, chanPoint2) } // testPsbtChanFundingSingleStep checks whether PSBT funding works also when the diff --git a/lntest/itest/lnd_test_list_on_test.go b/lntest/itest/lnd_test_list_on_test.go index 432cd4cdc..1c75dabfa 100644 --- a/lntest/itest/lnd_test_list_on_test.go +++ b/lntest/itest/lnd_test_list_on_test.go @@ -118,10 +118,6 @@ var allTestCases = []*testCase{ name: "psbt channel funding", test: testPsbtChanFunding, }, - { - name: "psbt channel funding external", - test: testPsbtChanFundingExternal, - }, { name: "sign psbt", test: testSignPsbt, From 83ecc6285c5bc9d349f80c887ac0813c37c6bf68 Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Mon, 8 Aug 2022 10:25:38 +0800 Subject: [PATCH 13/44] itest: refactor `testPsbtChanFundingSingleStep` --- lntest/itest/list_on_test.go | 4 + lntest/itest/lnd_psbt_test.go | 116 ++++++++++++-------------- lntest/itest/lnd_test_list_on_test.go | 4 - 3 files changed, 56 insertions(+), 68 deletions(-) diff --git a/lntest/itest/list_on_test.go b/lntest/itest/list_on_test.go index de3310778..f5a411490 100644 --- a/lntest/itest/list_on_test.go +++ b/lntest/itest/list_on_test.go @@ -279,4 +279,8 @@ var allTestCasesTemp = []*lntemp.TestCase{ Name: "psbt channel funding external", TestFunc: testPsbtChanFundingExternal, }, + { + Name: "psbt channel funding single step", + TestFunc: testPsbtChanFundingSingleStep, + }, } diff --git a/lntest/itest/lnd_psbt_test.go b/lntest/itest/lnd_psbt_test.go index 6ec45ea19..656a7361c 100644 --- a/lntest/itest/lnd_psbt_test.go +++ b/lntest/itest/lnd_psbt_test.go @@ -435,73 +435,68 @@ func testPsbtChanFundingExternal(ht *lntemp.HarnessTest) { ht.CloseChannel(carol, chanPoint2) } -// testPsbtChanFundingSingleStep checks whether PSBT funding works also when the -// wallet of both nodes are empty and one of them uses PSBT and an external +// testPsbtChanFundingSingleStep checks whether PSBT funding works also when +// the wallet of both nodes are empty and one of them uses PSBT and an external // wallet to fund the channel while creating reserve output in the same // transaction. -func testPsbtChanFundingSingleStep(net *lntest.NetworkHarness, t *harnessTest) { - ctxb := context.Background() +func testPsbtChanFundingSingleStep(ht *lntemp.HarnessTest) { const chanSize = funding.MaxBtcFundingAmount - // Everything we do here should be done within a second or two, so we - // can just keep a single timeout context around for all calls. - ctxt, cancel := context.WithTimeout(ctxb, 2*defaultTimeout) - defer cancel() - args := nodeArgsForCommitType(lnrpc.CommitmentType_ANCHORS) // First, we'll create two new nodes that we'll use to open channels // between for this test. But in this case both nodes have an empty // wallet. - carol := net.NewNode(t.t, "carol", args) - defer shutdownAndAssert(net, t, carol) + carol := ht.NewNode("carol", args) + defer ht.Shutdown(carol) - dave := net.NewNode(t.t, "dave", args) - defer shutdownAndAssert(net, t, dave) + dave := ht.NewNode("dave", args) + defer ht.Shutdown(dave) - net.SendCoins(t.t, btcutil.SatoshiPerBitcoin, net.Alice) + alice := ht.Alice + ht.FundCoins(btcutil.SatoshiPerBitcoin, alice) // Get new address for anchor reserve. - reserveAddrReq := &lnrpc.NewAddressRequest{ + req := &lnrpc.NewAddressRequest{ Type: lnrpc.AddressType_WITNESS_PUBKEY_HASH, } - addrResp, err := carol.NewAddress(ctxb, reserveAddrReq) - require.NoError(t.t, err) - reserveAddr, err := btcutil.DecodeAddress(addrResp.Address, harnessNetParams) - require.NoError(t.t, err) + addrResp := carol.RPC.NewAddress(req) + reserveAddr, err := btcutil.DecodeAddress( + addrResp.Address, harnessNetParams, + ) + require.NoError(ht, err) reserveAddrScript, err := txscript.PayToAddrScript(reserveAddr) - require.NoError(t.t, err) + require.NoError(ht, err) // Before we start the test, we'll ensure both sides are connected so // the funding flow can be properly executed. - net.EnsureConnected(t.t, carol, dave) + ht.EnsureConnected(carol, dave) // At this point, we can begin our PSBT channel funding workflow. We'll // start by generating a pending channel ID externally that will be used // to track this new funding type. - var pendingChanID [32]byte - _, err = rand.Read(pendingChanID[:]) - require.NoError(t.t, err) + pendingChanID := ht.Random32Bytes() // Now that we have the pending channel ID, Carol will open the channel // by specifying a PSBT shim. - chanUpdates, tempPsbt, err := openChannelPsbt( - ctxt, carol, dave, lntest.OpenChannelParams{ + chanUpdates, tempPsbt := ht.OpenChannelPsbt( + carol, dave, lntemp.OpenChannelParams{ Amt: chanSize, FundingShim: &lnrpc.FundingShim{ Shim: &lnrpc.FundingShim_PsbtShim{ PsbtShim: &lnrpc.PsbtShim{ - PendingChanId: pendingChanID[:], + PendingChanId: pendingChanID, NoPublish: false, }, }, }, }, ) - require.NoError(t.t, err) - decodedPsbt, err := psbt.NewFromRawBytes(bytes.NewReader(tempPsbt), false) - require.NoError(t.t, err) + decodedPsbt, err := psbt.NewFromRawBytes( + bytes.NewReader(tempPsbt), false, + ) + require.NoError(ht, err) reserveTxOut := wire.TxOut{ Value: 10000, @@ -515,7 +510,7 @@ func testPsbtChanFundingSingleStep(net *lntest.NetworkHarness, t *harnessTest) { var psbtBytes bytes.Buffer err = decodedPsbt.Serialize(&psbtBytes) - require.NoError(t.t, err) + require.NoError(ht, err) fundReq := &walletrpc.FundPsbtRequest{ Template: &walletrpc.FundPsbtRequest_Psbt{ @@ -525,55 +520,45 @@ func testPsbtChanFundingSingleStep(net *lntest.NetworkHarness, t *harnessTest) { SatPerVbyte: 2, }, } - fundResp, err := net.Alice.WalletKitClient.FundPsbt(ctxt, fundReq) - require.NoError(t.t, err) + fundResp := alice.RPC.FundPsbt(fundReq) // Make sure the wallets are actually empty - unspentCarol, err := carol.ListUnspent(ctxb, &lnrpc.ListUnspentRequest{}) - require.NoError(t.t, err) - require.Len(t.t, unspentCarol.Utxos, 0) - - unspentDave, err := dave.ListUnspent(ctxb, &lnrpc.ListUnspentRequest{}) - require.NoError(t.t, err) - require.Len(t.t, unspentDave.Utxos, 0) + ht.AssertNumUTXOsUnconfirmed(alice, 0) + ht.AssertNumUTXOsUnconfirmed(dave, 0) // We have a PSBT that has no witness data yet, which is exactly what we // need for the next step: Verify the PSBT with the funding intents. - _, err = carol.FundingStateStep(ctxb, &lnrpc.FundingTransitionMsg{ + carol.RPC.FundingStateStep(&lnrpc.FundingTransitionMsg{ Trigger: &lnrpc.FundingTransitionMsg_PsbtVerify{ PsbtVerify: &lnrpc.FundingPsbtVerify{ - PendingChanId: pendingChanID[:], + PendingChanId: pendingChanID, FundedPsbt: fundResp.FundedPsbt, }, }, }) - require.NoError(t.t, err) // Now we'll ask Alice's wallet to sign the PSBT so we can finish the // funding flow. finalizeReq := &walletrpc.FinalizePsbtRequest{ FundedPsbt: fundResp.FundedPsbt, } - finalizeRes, err := net.Alice.WalletKitClient.FinalizePsbt(ctxt, finalizeReq) - require.NoError(t.t, err) + finalizeRes := alice.RPC.FinalizePsbt(finalizeReq) // We've signed our PSBT now, let's pass it to the intent again. - _, err = carol.FundingStateStep(ctxb, &lnrpc.FundingTransitionMsg{ + carol.RPC.FundingStateStep(&lnrpc.FundingTransitionMsg{ Trigger: &lnrpc.FundingTransitionMsg_PsbtFinalize{ PsbtFinalize: &lnrpc.FundingPsbtFinalize{ - PendingChanId: pendingChanID[:], + PendingChanId: pendingChanID, SignedPsbt: finalizeRes.SignedPsbt, }, }, }) - require.NoError(t.t, err) // Consume the "channel pending" update. This waits until the funding // transaction was fully compiled. - updateResp, err := receiveChanUpdate(ctxt, chanUpdates) - require.NoError(t.t, err) + updateResp := ht.ReceiveOpenChannelUpdate(chanUpdates) upd, ok := updateResp.Update.(*lnrpc.OpenStatusUpdate_ChanPending) - require.True(t.t, ok) + require.True(ht, ok) chanPoint := &lnrpc.ChannelPoint{ FundingTxid: &lnrpc.ChannelPoint_FundingTxidBytes{ FundingTxidBytes: upd.ChanPending.Txid, @@ -583,13 +568,12 @@ func testPsbtChanFundingSingleStep(net *lntest.NetworkHarness, t *harnessTest) { var finalTx wire.MsgTx err = finalTx.Deserialize(bytes.NewReader(finalizeRes.RawFinalTx)) - require.NoError(t.t, err) + require.NoError(ht, err) txHash := finalTx.TxHash() - block := mineBlocks(t, net, 6, 1)[0] - assertTxInBlock(t, block, &txHash) - err = carol.WaitForNetworkChannelOpen(chanPoint) - require.NoError(t.t, err) + block := ht.MineBlocksAndAssertNumTxes(6, 1)[0] + ht.Miner.AssertTxInBlock(block, &txHash) + ht.AssertTopologyChannelOpen(carol, chanPoint) // Next, to make sure the channel functions as normal, we'll make some // payments within the channel. @@ -598,19 +582,23 @@ func testPsbtChanFundingSingleStep(net *lntest.NetworkHarness, t *harnessTest) { Memo: "new chans", Value: int64(payAmt), } - resp, err := dave.AddInvoice(ctxt, invoice) - require.NoError(t.t, err) - err = completePaymentRequests( - carol, carol.RouterClient, []string{resp.PaymentRequest}, - true, - ) - require.NoError(t.t, err) + resp := dave.RPC.AddInvoice(invoice) + ht.CompletePaymentRequests(carol, []string{resp.PaymentRequest}) + + // TODO(yy): remove the sleep once the following bug is fixed. When the + // payment is reported as settled by Carol, it's expected the + // commitment dance is finished and all subsequent states have been + // updated. Yet we'd receive the error `cannot co-op close channel with + // active htlcs` or `link failed to shutdown` if we close the channel. + // We need to investigate the order of settling the payments and + // updating commitments to understand and fix . + time.Sleep(2 * time.Second) // To conclude, we'll close the newly created channel between Carol and // Dave. This function will also block until the channel is closed and // will additionally assert the relevant channel closing post // conditions. - closeChannelAndAssert(t, net, carol, chanPoint, false) + ht.CloseChannel(carol, chanPoint) } // testSignPsbt tests that the SignPsbt RPC works correctly. diff --git a/lntest/itest/lnd_test_list_on_test.go b/lntest/itest/lnd_test_list_on_test.go index 1c75dabfa..ebc4867e3 100644 --- a/lntest/itest/lnd_test_list_on_test.go +++ b/lntest/itest/lnd_test_list_on_test.go @@ -122,10 +122,6 @@ var allTestCases = []*testCase{ name: "sign psbt", test: testSignPsbt, }, - { - name: "psbt channel funding single step", - test: testPsbtChanFundingSingleStep, - }, { name: "sendtoroute multi path payment", test: testSendToRouteMultiPath, From 89727ed1d74f1c1fb82f54ba10124146ed1da5e4 Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Mon, 8 Aug 2022 12:19:48 +0800 Subject: [PATCH 14/44] lntemp+itest: refactor `testResHandoff` --- lntemp/harness.go | 7 +- lntemp/harness_assertion.go | 22 ++++ lntest/itest/list_on_test.go | 4 + lntest/itest/lnd_res_handoff_test.go | 161 +++++--------------------- lntest/itest/lnd_test_list_on_test.go | 4 - 5 files changed, 62 insertions(+), 136 deletions(-) diff --git a/lntemp/harness.go b/lntemp/harness.go index 485c076ce..1d0001d02 100644 --- a/lntemp/harness.go +++ b/lntemp/harness.go @@ -480,7 +480,12 @@ func (h *HarnessTest) SuspendNode(node *node.HarnessNode) func() error { return func() error { h.manager.registerNode(node) - return node.Start(h.runCtx) + if err := node.Start(h.runCtx); err != nil { + return err + } + h.WaitForBlockchainSync(node) + + return nil } } diff --git a/lntemp/harness_assertion.go b/lntemp/harness_assertion.go index 1378bb9a7..7c5879d92 100644 --- a/lntemp/harness_assertion.go +++ b/lntemp/harness_assertion.go @@ -1992,3 +1992,25 @@ func (h *HarnessTest) WaitForNodeBlockHeight(hn *node.HarnessNode, require.NoErrorf(h, err, "%s: timeout while waiting for height", hn.Name()) } + +// AssertChannelCommitHeight asserts the given channel for the node has the +// expected commit height(`NumUpdates`). +func (h *HarnessTest) AssertChannelCommitHeight(hn *node.HarnessNode, + cp *lnrpc.ChannelPoint, height int) { + + err := wait.NoError(func() error { + c, err := h.findChannel(hn, cp) + if err != nil { + return err + } + + if int(c.NumUpdates) == height { + return nil + } + + return fmt.Errorf("expected commit height to be %v, was %v", + height, c.NumUpdates) + }, DefaultTimeout) + + require.NoError(h, err, "timeout while waiting for commit height") +} diff --git a/lntest/itest/list_on_test.go b/lntest/itest/list_on_test.go index f5a411490..1d8d48e54 100644 --- a/lntest/itest/list_on_test.go +++ b/lntest/itest/list_on_test.go @@ -283,4 +283,8 @@ var allTestCasesTemp = []*lntemp.TestCase{ Name: "psbt channel funding single step", TestFunc: testPsbtChanFundingSingleStep, }, + { + Name: "resolution handoff", + TestFunc: testResHandoff, + }, } diff --git a/lntest/itest/lnd_res_handoff_test.go b/lntest/itest/lnd_res_handoff_test.go index 36d74527c..a18d2fce0 100644 --- a/lntest/itest/lnd_res_handoff_test.go +++ b/lntest/itest/lnd_res_handoff_test.go @@ -5,131 +5,69 @@ import ( "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/lnrpc" - "github.com/lightningnetwork/lnd/lntest" - "github.com/lightningnetwork/lnd/lntest/wait" + "github.com/lightningnetwork/lnd/lntemp" "github.com/stretchr/testify/require" - "golang.org/x/net/context" ) // testResHandoff tests that the contractcourt is able to properly hand-off // resolution messages to the switch. -func testResHandoff(net *lntest.NetworkHarness, t *harnessTest) { +func testResHandoff(ht *lntemp.HarnessTest) { const ( chanAmt = btcutil.Amount(1000000) paymentAmt = 50000 ) - ctxb := context.Background() + alice, bob := ht.Alice, ht.Bob // First we'll create a channel between Alice and Bob. - net.EnsureConnected(t.t, net.Alice, net.Bob) + ht.EnsureConnected(alice, bob) - chanPointAlice := openChannelAndAssert( - t, net, net.Alice, net.Bob, - lntest.OpenChannelParams{ - Amt: chanAmt, - }, - ) - defer closeChannelAndAssert(t, net, net.Alice, chanPointAlice, false) - - // Wait for Alice and Bob to receive the channel edge from the funding - // manager. - err := net.Alice.WaitForNetworkChannelOpen(chanPointAlice) - require.NoError(t.t, err) - - err = net.Bob.WaitForNetworkChannelOpen(chanPointAlice) - require.NoError(t.t, err) + params := lntemp.OpenChannelParams{Amt: chanAmt} + chanPointAlice := ht.OpenChannel(alice, bob, params) // Create a new node Carol that will be in hodl mode. This is used to // trigger the behavior of checkRemoteDanglingActions in the // contractcourt. This will cause Bob to fail the HTLC back to Alice. - carol := net.NewNode(t.t, "Carol", []string{"--hodl.commit"}) - defer shutdownAndAssert(net, t, carol) + carol := ht.NewNode("Carol", []string{"--hodl.commit"}) + defer ht.Shutdown(carol) // Connect Bob to Carol. - net.ConnectNodes(t.t, net.Bob, carol) + ht.ConnectNodes(bob, carol) // Open a channel between Bob and Carol. - chanPointCarol := openChannelAndAssert( - t, net, net.Bob, carol, - lntest.OpenChannelParams{ - Amt: chanAmt, - }, - ) - - // Wait for Bob and Carol to receive the channel edge from the funding - // manager. - err = net.Bob.WaitForNetworkChannelOpen(chanPointCarol) - require.NoError(t.t, err) - - err = carol.WaitForNetworkChannelOpen(chanPointCarol) - require.NoError(t.t, err) + chanPointCarol := ht.OpenChannel(bob, carol, params) // Wait for Alice to see the channel edge in the graph. - err = net.Alice.WaitForNetworkChannelOpen(chanPointCarol) - require.NoError(t.t, err) + ht.AssertTopologyChannelOpen(alice, chanPointCarol) // We'll create an invoice for Carol that Alice will attempt to pay. // Since Carol is in hodl.commit mode, she won't send back any commit // sigs. - carolPayReqs, _, _, err := createPayReqs( - carol, paymentAmt, 1, - ) - require.NoError(t.t, err) + carolPayReqs, _, _ := ht.CreatePayReqs(carol, paymentAmt, 1) // Alice will now attempt to fulfill the invoice. - err = completePaymentRequests( - net.Alice, net.Alice.RouterClient, carolPayReqs, false, - ) - require.NoError(t.t, err) + ht.CompletePaymentRequestsNoWait(alice, carolPayReqs, chanPointAlice) // Wait until Carol has received the Add, CommitSig from Bob, and has // responded with a RevokeAndAck. We expect NumUpdates to be 1 meaning // Carol's CommitHeight is 1. - err = wait.Predicate(func() bool { - carolInfo, err := getChanInfo(carol) - if err != nil { - return false - } - - return carolInfo.NumUpdates == 1 - }, defaultTimeout) - require.NoError(t.t, err) + ht.AssertChannelCommitHeight(carol, chanPointCarol, 1) // Before we shutdown Alice, we'll assert that she only has 1 update. - err = wait.Predicate(func() bool { - aliceInfo, err := getChanInfo(net.Alice) - if err != nil { - return false - } - - return aliceInfo.NumUpdates == 1 - }, defaultTimeout) - require.NoError(t.t, err) + ht.AssertChannelCommitHeight(alice, chanPointAlice, 1) // We'll shutdown Alice so that Bob can't connect to her. - restartAlice, err := net.SuspendNode(net.Alice) - require.NoError(t.t, err) + restartAlice := ht.SuspendNode(alice) // Bob will now force close his channel with Carol such that resolution // messages are created and forwarded backwards to Alice. - _, _, err = net.CloseChannel(net.Bob, chanPointCarol, true) - require.NoError(t.t, err) + ht.CloseChannelAssertPending(bob, chanPointCarol, true) // The channel should be listed in the PendingChannels result. - ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) - defer cancel() + ht.AssertNumWaitingClose(bob, 1) - pendingChansRequest := &lnrpc.PendingChannelsRequest{} - pendingChanResp, err := net.Bob.PendingChannels( - ctxt, pendingChansRequest, - ) - require.NoError(t.t, err) - require.NoError(t.t, checkNumWaitingCloseChannels(pendingChanResp, 1)) - - // We'll mine a block to confirm the force close transaction and to - // advance Bob's contract state with Carol to StateContractClosed. - mineBlocks(t, net, 1, 1) + // Mine a block to confirm the closing tx. + ht.MineBlocks(1) // We sleep here so we can be sure that the hand-off has occurred from // Bob's contractcourt to Bob's htlcswitch. This sleep could be removed @@ -137,64 +75,25 @@ func testResHandoff(net *lntest.NetworkHarness, t *harnessTest) { // querying the state of resolution messages. time.Sleep(10 * time.Second) - // Mine blocks until Bob has no waiting close channels. This tests - // that the circuit-deletion logic is skipped if a resolution message + // Mine blocks until Bob has no waiting close channels. This tests that + // the circuit-deletion logic is skipped if a resolution message // exists. - for { - _, err = net.Miner.Client.Generate(1) - require.NoError(t.t, err) - - pendingChanResp, err = net.Bob.PendingChannels( - ctxt, pendingChansRequest, - ) - require.NoError(t.t, err) - - isErr := checkNumForceClosedChannels(pendingChanResp, 0) - if isErr == nil { - break - } - - time.Sleep(150 * time.Millisecond) - } + ht.CleanupForceClose(bob, chanPointCarol) // We will now restart Bob so that we can test whether the resolution // messages are re-forwarded on start-up. - restartBob, err := net.SuspendNode(net.Bob) - require.NoError(t.t, err) - - err = restartBob() - require.NoError(t.t, err) + ht.RestartNode(bob) // We'll now also restart Alice and connect her with Bob. - err = restartAlice() - require.NoError(t.t, err) + require.NoError(ht, restartAlice()) - net.EnsureConnected(t.t, net.Alice, net.Bob) + ht.EnsureConnected(alice, bob) - // We'll assert that Alice has received the failure resolution - // message. - err = wait.Predicate(func() bool { - aliceInfo, err := getChanInfo(net.Alice) - if err != nil { - return false - } - - return aliceInfo.NumUpdates == 2 - }, defaultTimeout) - require.NoError(t.t, err) + // We'll assert that Alice has received the failure resolution message. + ht.AssertChannelCommitHeight(alice, chanPointAlice, 2) // Assert that Alice's payment failed. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - paymentsResp, err := net.Alice.ListPayments( - ctxt, &lnrpc.ListPaymentsRequest{ - IncludeIncomplete: true, - }, - ) - require.NoError(t.t, err) - require.Equal(t.t, 1, len(paymentsResp.Payments)) + ht.AssertFirstHTLCError(alice, lnrpc.Failure_PERMANENT_CHANNEL_FAILURE) - htlcs := paymentsResp.Payments[0].Htlcs - - require.Equal(t.t, 1, len(htlcs)) - require.Equal(t.t, lnrpc.HTLCAttempt_FAILED, htlcs[0].Status) + ht.CloseChannel(alice, chanPointAlice) } diff --git a/lntest/itest/lnd_test_list_on_test.go b/lntest/itest/lnd_test_list_on_test.go index ebc4867e3..2f0d0cf42 100644 --- a/lntest/itest/lnd_test_list_on_test.go +++ b/lntest/itest/lnd_test_list_on_test.go @@ -186,10 +186,6 @@ var allTestCases = []*testCase{ name: "taproot", test: testTaproot, }, - { - name: "resolution handoff", - test: testResHandoff, - }, { name: "zero conf channel open", test: testZeroConfChannelOpen, From f254851e0191adfc170a9d1eeebf511d22e965f6 Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Mon, 8 Aug 2022 12:30:01 +0800 Subject: [PATCH 15/44] itest: refactor `testRestAPI` --- lntest/itest/list_on_test.go | 4 + lntest/itest/lnd_rest_api_test.go | 229 +++++++++++++------------- lntest/itest/lnd_test_list_on_test.go | 4 - 3 files changed, 116 insertions(+), 121 deletions(-) diff --git a/lntest/itest/list_on_test.go b/lntest/itest/list_on_test.go index 1d8d48e54..23446bd12 100644 --- a/lntest/itest/list_on_test.go +++ b/lntest/itest/list_on_test.go @@ -287,4 +287,8 @@ var allTestCasesTemp = []*lntemp.TestCase{ Name: "resolution handoff", TestFunc: testResHandoff, }, + { + Name: "REST API", + TestFunc: testRestAPI, + }, } diff --git a/lntest/itest/lnd_rest_api_test.go b/lntest/itest/lnd_rest_api_test.go index 16f7c6c17..43aff6110 100644 --- a/lntest/itest/lnd_rest_api_test.go +++ b/lntest/itest/lnd_rest_api_test.go @@ -2,7 +2,6 @@ package itest import ( "bytes" - "context" "crypto/tls" "encoding/base64" "encoding/hex" @@ -24,8 +23,8 @@ import ( "github.com/lightningnetwork/lnd/lnrpc/routerrpc" "github.com/lightningnetwork/lnd/lnrpc/verrpc" "github.com/lightningnetwork/lnd/lnrpc/walletrpc" - "github.com/lightningnetwork/lnd/lntest" - "github.com/stretchr/testify/assert" + "github.com/lightningnetwork/lnd/lntemp" + "github.com/lightningnetwork/lnd/lntemp/node" "github.com/stretchr/testify/require" ) @@ -57,19 +56,21 @@ var ( // testRestAPI tests that the most important features of the REST API work // correctly. -func testRestAPI(net *lntest.NetworkHarness, ht *harnessTest) { +func testRestAPI(ht *lntemp.HarnessTest) { testCases := []struct { name string - run func(*testing.T, *lntest.HarnessNode, *lntest.HarnessNode) + run func(*testing.T, *node.HarnessNode, *node.HarnessNode) }{{ name: "simple GET", - run: func(t *testing.T, a, b *lntest.HarnessNode) { + run: func(t *testing.T, a, b *node.HarnessNode) { + t.Helper() + // Check that the parsing into the response proto // message works. resp := &lnrpc.GetInfoResponse{} err := invokeGET(a, "/v1/getinfo", resp) require.Nil(t, err, "getinfo") - assert.Equal(t, "#3399ff", resp.Color, "node color") + require.Equal(t, "#3399ff", resp.Color, "node color") // Make sure we get the correct field names (snake // case). @@ -77,20 +78,22 @@ func testRestAPI(net *lntest.NetworkHarness, ht *harnessTest) { a, "/v1/getinfo", "GET", nil, nil, ) require.Nil(t, err, "getinfo") - assert.Contains( + require.Contains( t, string(resp2), "best_header_timestamp", "getinfo", ) }, }, { name: "simple POST and GET with query param", - run: func(t *testing.T, a, b *lntest.HarnessNode) { + run: func(t *testing.T, a, b *node.HarnessNode) { + t.Helper() + // Add an invoice, testing POST in the process. req := &lnrpc.Invoice{Value: 1234} resp := &lnrpc.AddInvoiceResponse{} err := invokePOST(a, "/v1/invoices", req, resp) require.Nil(t, err, "add invoice") - assert.Equal(t, 32, len(resp.RHash), "invoice rhash") + require.Equal(t, 32, len(resp.RHash), "invoice rhash") // Make sure we can call a GET endpoint with a hex // encoded URL part. @@ -98,11 +101,14 @@ func testRestAPI(net *lntest.NetworkHarness, ht *harnessTest) { resp2 := &lnrpc.Invoice{} err = invokeGET(a, url, resp2) require.Nil(t, err, "query invoice") - assert.Equal(t, int64(1234), resp2.Value, "invoice amt") + require.Equal(t, int64(1234), resp2.Value, + "invoice amt") }, }, { name: "GET with base64 encoded byte slice in path", - run: func(t *testing.T, a, b *lntest.HarnessNode) { + run: func(t *testing.T, a, b *node.HarnessNode) { + t.Helper() + url := "/v2/router/mc/probability/%s/%s/%d" url = fmt.Sprintf( url, urlEnc.EncodeToString(a.PubKey[:]), @@ -115,38 +121,37 @@ func testRestAPI(net *lntest.NetworkHarness, ht *harnessTest) { }, }, { name: "GET with map type query param", - run: func(t *testing.T, a, b *lntest.HarnessNode) { - // Get a new wallet address from Alice. - ctxb := context.Background() - newAddrReq := &lnrpc.NewAddressRequest{ - Type: lnrpc.AddressType_WITNESS_PUBKEY_HASH, - } - addrRes, err := a.NewAddress(ctxb, newAddrReq) - require.Nil(t, err, "get address") + run: func(t *testing.T, a, b *node.HarnessNode) { + t.Helper() + + // Use a fake address. + addr := "bcrt1qlutnwklt4u2548cufrjmsjclewugr9lcpnkzag" // Create the full URL with the map query param. url := "/v1/transactions/fee?target_conf=%d&" + "AddrToAmount[%s]=%d" - url = fmt.Sprintf(url, 2, addrRes.Address, 50000) + url = fmt.Sprintf(url, 2, addr, 50000) resp := &lnrpc.EstimateFeeResponse{} - err = invokeGET(a, url, resp) + err := invokeGET(a, url, resp) require.Nil(t, err, "estimate fee") - assert.Greater(t, resp.FeeSat, int64(253), "fee") + require.Greater(t, resp.FeeSat, int64(253), "fee") }, }, { name: "sub RPC servers REST support", - run: func(t *testing.T, a, b *lntest.HarnessNode) { + run: func(t *testing.T, a, b *node.HarnessNode) { + t.Helper() + // Query autopilot status. res1 := &autopilotrpc.StatusResponse{} err := invokeGET(a, "/v2/autopilot/status", res1) require.Nil(t, err, "autopilot status") - assert.Equal(t, false, res1.Active, "autopilot status") + require.Equal(t, false, res1.Active, "autopilot status") // Query the version RPC. res2 := &verrpc.Version{} err = invokeGET(a, "/v2/versioner/version", res2) require.Nil(t, err, "version") - assert.Greater( + require.Greater( t, res2.AppMinor, uint32(0), "lnd minor version", ) @@ -157,11 +162,13 @@ func testRestAPI(net *lntest.NetworkHarness, ht *harnessTest) { a, "/v2/wallet/address/next", req1, res3, ) require.Nil(t, err, "address") - assert.NotEmpty(t, res3.Addr, "address") + require.NotEmpty(t, res3.Addr, "address") }, }, { name: "CORS headers", - run: func(t *testing.T, a, b *lntest.HarnessNode) { + run: func(t *testing.T, a, b *node.HarnessNode) { + t.Helper() + // Alice allows all origins. Make sure we get the same // value back in the CORS header that we send in the // Origin header. @@ -171,12 +178,12 @@ func testRestAPI(net *lntest.NetworkHarness, ht *harnessTest) { a, "/v1/getinfo", "OPTIONS", nil, reqHeaders, ) require.Nil(t, err, "getinfo") - assert.Equal( + require.Equal( t, "https://foo.bar:9999", resHeaders.Get("Access-Control-Allow-Origin"), "CORS header", ) - assert.Equal(t, 0, len(body)) + require.Equal(t, 0, len(body)) // Make sure that we don't get a value set for Bob which // doesn't allow any CORS origin. @@ -184,17 +191,17 @@ func testRestAPI(net *lntest.NetworkHarness, ht *harnessTest) { b, "/v1/getinfo", "OPTIONS", nil, reqHeaders, ) require.Nil(t, err, "getinfo") - assert.Equal( + require.Equal( t, "", resHeaders.Get("Access-Control-Allow-Origin"), "CORS header", ) - assert.Equal(t, 0, len(body)) + require.Equal(t, 0, len(body)) }, }} wsTestCases := []struct { name string - run func(ht *harnessTest, net *lntest.NetworkHarness) + run func(ht *lntemp.HarnessTest) }{{ name: "websocket subscription", run: wsTestCaseSubscription, @@ -212,39 +219,33 @@ func testRestAPI(net *lntest.NetworkHarness, ht *harnessTest) { // Make sure Alice allows all CORS origins. Bob will keep the default. // We also make sure the ping/pong messages are sent very often, so we // can test them without waiting half a minute. - net.Alice.Cfg.ExtraArgs = append( - net.Alice.Cfg.ExtraArgs, "--restcors=\"*\"", + alice, bob := ht.Alice, ht.Bob + alice.Cfg.ExtraArgs = append( + alice.Cfg.ExtraArgs, "--restcors=\"*\"", fmt.Sprintf("--ws-ping-interval=%s", pingInterval), fmt.Sprintf("--ws-pong-wait=%s", pongWait), ) - err := net.RestartNode(net.Alice, nil) - if err != nil { - ht.t.Fatalf("Could not restart Alice to set CORS config: %v", - err) - } + ht.RestartNode(alice) for _, tc := range testCases { tc := tc - ht.t.Run(tc.name, func(t *testing.T) { - tc.run(t, net.Alice, net.Bob) + ht.Run(tc.name, func(t *testing.T) { + tc.run(t, alice, bob) }) } for _, tc := range wsTestCases { tc := tc - ht.t.Run(tc.name, func(t *testing.T) { - ht := &harnessTest{ - t: t, testCase: ht.testCase, lndHarness: net, - } - tc.run(ht, net) + ht.Run(tc.name, func(t *testing.T) { + st := ht.Subtest(t) + tc.run(st) }) } } -func wsTestCaseSubscription(ht *harnessTest, net *lntest.NetworkHarness) { +func wsTestCaseSubscription(ht *lntemp.HarnessTest) { // Find out the current best block so we can subscribe to the next one. - hash, height, err := net.Miner.Client.GetBestBlock() - require.Nil(ht.t, err, "get best block") + hash, height := ht.Miner.GetBestBlock() // Create a new subscription to get block epoch events. req := &chainrpc.BlockEpoch{ @@ -252,11 +253,11 @@ func wsTestCaseSubscription(ht *harnessTest, net *lntest.NetworkHarness) { Height: uint32(height), } url := "/v2/chainnotifier/register/blocks" - c, err := openWebSocket(net.Alice, url, "POST", req, nil) - require.Nil(ht.t, err, "websocket") + c, err := openWebSocket(ht.Alice, url, "POST", req, nil) + require.NoError(ht, err, "websocket") defer func() { err := c.WriteMessage(websocket.CloseMessage, closeMsg) - require.NoError(ht.t, err) + require.NoError(ht, err) _ = c.Close() }() @@ -300,30 +301,25 @@ func wsTestCaseSubscription(ht *harnessTest, net *lntest.NetworkHarness) { }() // Mine a block and make sure we get a message for it. - blockHashes, err := net.Miner.Client.Generate(1) - require.Nil(ht.t, err, "generate blocks") - assert.Equal(ht.t, 1, len(blockHashes), "num blocks") + blockHashes := ht.Miner.GenerateBlocks(1) select { case msg := <-msgChan: - assert.Equal( - ht.t, blockHashes[0].CloneBytes(), msg.Hash, + require.Equal( + ht, blockHashes[0].CloneBytes(), msg.Hash, "block hash", ) case err := <-errChan: - ht.t.Fatalf("Received error from WS: %v", err) + ht.Fatalf("Received error from WS: %v", err) case <-timeout: - ht.t.Fatalf("Timeout before message was received") + ht.Fatalf("Timeout before message was received") } } -func wsTestCaseSubscriptionMacaroon(ht *harnessTest, - net *lntest.NetworkHarness) { - +func wsTestCaseSubscriptionMacaroon(ht *lntemp.HarnessTest) { // Find out the current best block so we can subscribe to the next one. - hash, height, err := net.Miner.Client.GetBestBlock() - require.Nil(ht.t, err, "get best block") + hash, height := ht.Miner.GetBestBlock() // Create a new subscription to get block epoch events. req := &chainrpc.BlockEpoch{ @@ -335,22 +331,23 @@ func wsTestCaseSubscriptionMacaroon(ht *harnessTest, // This time we send the macaroon in the special header // Sec-Websocket-Protocol which is the only header field available to // browsers when opening a WebSocket. - mac, err := net.Alice.ReadMacaroon( - net.Alice.AdminMacPath(), defaultTimeout, + alice := ht.Alice + mac, err := alice.ReadMacaroon( + alice.Cfg.AdminMacPath, defaultTimeout, ) - require.NoError(ht.t, err, "read admin mac") + require.NoError(ht, err, "read admin mac") macBytes, err := mac.MarshalBinary() - require.NoError(ht.t, err, "marshal admin mac") + require.NoError(ht, err, "marshal admin mac") customHeader := make(http.Header) customHeader.Set(lnrpc.HeaderWebSocketProtocol, fmt.Sprintf( "Grpc-Metadata-Macaroon+%s", hex.EncodeToString(macBytes), )) - c, err := openWebSocket(net.Alice, url, "POST", req, customHeader) - require.Nil(ht.t, err, "websocket") + c, err := openWebSocket(alice, url, "POST", req, customHeader) + require.Nil(ht, err, "websocket") defer func() { err := c.WriteMessage(websocket.CloseMessage, closeMsg) - require.NoError(ht.t, err) + require.NoError(ht, err) _ = c.Close() }() @@ -394,52 +391,49 @@ func wsTestCaseSubscriptionMacaroon(ht *harnessTest, }() // Mine a block and make sure we get a message for it. - blockHashes, err := net.Miner.Client.Generate(1) - require.Nil(ht.t, err, "generate blocks") - assert.Equal(ht.t, 1, len(blockHashes), "num blocks") + blockHashes := ht.Miner.GenerateBlocks(1) select { case msg := <-msgChan: - assert.Equal( - ht.t, blockHashes[0].CloneBytes(), msg.Hash, + require.Equal( + ht, blockHashes[0].CloneBytes(), msg.Hash, "block hash", ) case err := <-errChan: - ht.t.Fatalf("Received error from WS: %v", err) + ht.Fatalf("Received error from WS: %v", err) case <-timeout: - ht.t.Fatalf("Timeout before message was received") + ht.Fatalf("Timeout before message was received") } } -func wsTestCaseBiDirectionalSubscription(ht *harnessTest, - net *lntest.NetworkHarness) { - +func wsTestCaseBiDirectionalSubscription(ht *lntemp.HarnessTest) { initialRequest := &lnrpc.ChannelAcceptResponse{} url := "/v1/channels/acceptor" // This time we send the macaroon in the special header // Sec-Websocket-Protocol which is the only header field available to // browsers when opening a WebSocket. - mac, err := net.Alice.ReadMacaroon( - net.Alice.AdminMacPath(), defaultTimeout, + alice := ht.Alice + mac, err := alice.ReadMacaroon( + alice.Cfg.AdminMacPath, defaultTimeout, ) - require.NoError(ht.t, err, "read admin mac") + require.NoError(ht, err, "read admin mac") macBytes, err := mac.MarshalBinary() - require.NoError(ht.t, err, "marshal admin mac") + require.NoError(ht, err, "marshal admin mac") customHeader := make(http.Header) customHeader.Set(lnrpc.HeaderWebSocketProtocol, fmt.Sprintf( "Grpc-Metadata-Macaroon+%s", hex.EncodeToString(macBytes), )) conn, err := openWebSocket( - net.Alice, url, "POST", initialRequest, customHeader, + alice, url, "POST", initialRequest, customHeader, ) - require.Nil(ht.t, err, "websocket") + require.Nil(ht, err, "websocket") defer func() { err := conn.WriteMessage(websocket.CloseMessage, closeMsg) _ = conn.Close() - require.NoError(ht.t, err) + require.NoError(ht, err) }() // Buffer the message channel to make sure we're always blocking on @@ -528,29 +522,30 @@ func wsTestCaseBiDirectionalSubscription(ht *harnessTest, // Before we start opening channels, make sure the two nodes are // connected. - net.EnsureConnected(ht.t, net.Alice, net.Bob) + bob := ht.Bob + ht.EnsureConnected(alice, bob) // Open 3 channels to make sure multiple requests and responses can be // sent over the web socket. const numChannels = 3 for i := 0; i < numChannels; i++ { - openChannelAndAssert( - ht, net, net.Bob, net.Alice, - lntest.OpenChannelParams{Amt: 500000}, + chanPoint := ht.OpenChannel( + bob, alice, lntemp.OpenChannelParams{Amt: 500000}, ) + defer ht.CloseChannel(bob, chanPoint) select { case <-msgChan: case err := <-errChan: - ht.t.Fatalf("Received error from WS: %v", err) + ht.Fatalf("Received error from WS: %v", err) case <-timeout: - ht.t.Fatalf("Timeout before message was received") + ht.Fatalf("Timeout before message was received") } } } -func wsTestPingPongTimeout(ht *harnessTest, net *lntest.NetworkHarness) { +func wsTestPingPongTimeout(ht *lntemp.HarnessTest) { initialRequest := &lnrpc.InvoiceSubscription{ AddIndex: 1, SettleIndex: 1, } @@ -559,25 +554,26 @@ func wsTestPingPongTimeout(ht *harnessTest, net *lntest.NetworkHarness) { // This time we send the macaroon in the special header // Sec-Websocket-Protocol which is the only header field available to // browsers when opening a WebSocket. - mac, err := net.Alice.ReadMacaroon( - net.Alice.AdminMacPath(), defaultTimeout, + alice := ht.Alice + mac, err := alice.ReadMacaroon( + alice.Cfg.AdminMacPath, defaultTimeout, ) - require.NoError(ht.t, err, "read admin mac") + require.NoError(ht, err, "read admin mac") macBytes, err := mac.MarshalBinary() - require.NoError(ht.t, err, "marshal admin mac") + require.NoError(ht, err, "marshal admin mac") customHeader := make(http.Header) customHeader.Set(lnrpc.HeaderWebSocketProtocol, fmt.Sprintf( "Grpc-Metadata-Macaroon+%s", hex.EncodeToString(macBytes), )) conn, err := openWebSocket( - net.Alice, url, "GET", initialRequest, customHeader, + alice, url, "GET", initialRequest, customHeader, ) - require.Nil(ht.t, err, "websocket") + require.Nil(ht, err, "websocket") defer func() { err := conn.WriteMessage(websocket.CloseMessage, closeMsg) _ = conn.Close() - require.NoError(ht.t, err) + require.NoError(ht, err) }() // We want to be able to read invoices for a long time, making sure we @@ -650,27 +646,26 @@ func wsTestPingPongTimeout(ht *harnessTest, net *lntest.NetworkHarness) { // Let's create five invoices and wait for them to arrive. We'll wait // for at least one ping/pong cycle between each invoice. - ctxb := context.Background() const numInvoices = 5 const value = 123 const memo = "websocket" for i := 0; i < numInvoices; i++ { - _, err := net.Alice.AddInvoice(ctxb, &lnrpc.Invoice{ + invoice := &lnrpc.Invoice{ Value: value, Memo: memo, - }) - require.NoError(ht.t, err) + } + alice.RPC.AddInvoice(invoice) select { case streamMsg := <-invoices: - require.Equal(ht.t, int64(value), streamMsg.Value) - require.Equal(ht.t, memo, streamMsg.Memo) + require.Equal(ht, int64(value), streamMsg.Value) + require.Equal(ht, memo, streamMsg.Memo) case err := <-errChan: - require.Fail(ht.t, "Error reading invoice: %v", err) + require.Fail(ht, "Error reading invoice: %v", err) case <-timeout: - require.Fail(ht.t, "No invoice msg received in time") + require.Fail(ht, "No invoice msg received in time") } // Let's wait for at least a whole ping/pong cycle to happen, so @@ -683,7 +678,7 @@ func wsTestPingPongTimeout(ht *harnessTest, net *lntest.NetworkHarness) { // invokeGET calls the given URL with the GET method and appropriate macaroon // header fields then tries to unmarshal the response into the given response // proto message. -func invokeGET(node *lntest.HarnessNode, url string, resp proto.Message) error { +func invokeGET(node *node.HarnessNode, url string, resp proto.Message) error { _, rawResp, err := makeRequest(node, url, "GET", nil, nil) if err != nil { return err @@ -695,7 +690,7 @@ func invokeGET(node *lntest.HarnessNode, url string, resp proto.Message) error { // invokePOST calls the given URL with the POST method, request body and // appropriate macaroon header fields then tries to unmarshal the response into // the given response proto message. -func invokePOST(node *lntest.HarnessNode, url string, req, +func invokePOST(node *node.HarnessNode, url string, req, resp proto.Message) error { // Marshal the request to JSON using the jsonpb marshaler to get correct @@ -715,7 +710,7 @@ func invokePOST(node *lntest.HarnessNode, url string, req, // makeRequest calls the given URL with the given method, request body and // appropriate macaroon header fields and returns the raw response body. -func makeRequest(node *lntest.HarnessNode, url, method string, +func makeRequest(node *node.HarnessNode, url, method string, request io.Reader, additionalHeaders http.Header) (http.Header, []byte, error) { @@ -748,7 +743,7 @@ func makeRequest(node *lntest.HarnessNode, url, method string, // openWebSocket opens a new WebSocket connection to the given URL with the // appropriate macaroon headers and sends the request message over the socket. -func openWebSocket(node *lntest.HarnessNode, url, method string, +func openWebSocket(node *node.HarnessNode, url, method string, req proto.Message, customHeader http.Header) (*websocket.Conn, error) { // Prepare our macaroon headers and assemble the full URL from the @@ -785,8 +780,8 @@ func openWebSocket(node *lntest.HarnessNode, url, method string, // addAdminMacaroon reads the admin macaroon from the node and appends it to // the HTTP header fields. -func addAdminMacaroon(node *lntest.HarnessNode, header http.Header) error { - mac, err := node.ReadMacaroon(node.AdminMacPath(), defaultTimeout) +func addAdminMacaroon(node *node.HarnessNode, header http.Header) error { + mac, err := node.ReadMacaroon(node.Cfg.AdminMacPath, defaultTimeout) if err != nil { return err } diff --git a/lntest/itest/lnd_test_list_on_test.go b/lntest/itest/lnd_test_list_on_test.go index 2f0d0cf42..e5040037e 100644 --- a/lntest/itest/lnd_test_list_on_test.go +++ b/lntest/itest/lnd_test_list_on_test.go @@ -146,10 +146,6 @@ var allTestCases = []*testCase{ name: "send multi path payment", test: testSendMultiPathPayment, }, - { - name: "REST API", - test: testRestAPI, - }, { name: "forward interceptor", test: testForwardInterceptorBasic, From 222c06ca48a4ba233380a210174f6c3fdf04c81c Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Mon, 8 Aug 2022 12:59:19 +0800 Subject: [PATCH 16/44] itest: refactor `testRevokedCloseRetribution` --- lntest/itest/list_on_test.go | 4 + lntest/itest/lnd_revocation_test.go | 186 +++++++------------------- lntest/itest/lnd_test_list_on_test.go | 6 - 3 files changed, 54 insertions(+), 142 deletions(-) diff --git a/lntest/itest/list_on_test.go b/lntest/itest/list_on_test.go index 23446bd12..bba41fcb4 100644 --- a/lntest/itest/list_on_test.go +++ b/lntest/itest/list_on_test.go @@ -291,4 +291,8 @@ var allTestCasesTemp = []*lntemp.TestCase{ Name: "REST API", TestFunc: testRestAPI, }, + { + Name: "revoked uncooperative close retribution", + TestFunc: testRevokedCloseRetribution, + }, } diff --git a/lntest/itest/lnd_revocation_test.go b/lntest/itest/lnd_revocation_test.go index a562c88d9..8850c10c3 100644 --- a/lntest/itest/lnd_revocation_test.go +++ b/lntest/itest/lnd_revocation_test.go @@ -15,6 +15,7 @@ import ( "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/watchtowerrpc" "github.com/lightningnetwork/lnd/lnrpc/wtclientrpc" + "github.com/lightningnetwork/lnd/lntemp" "github.com/lightningnetwork/lnd/lntest" "github.com/lightningnetwork/lnd/lntest/wait" "github.com/stretchr/testify/require" @@ -23,7 +24,7 @@ import ( // testRevokedCloseRetribution tests that Carol is able carry out // retribution in the event that she fails immediately after detecting Bob's // breach txn in the mempool. -func testRevokedCloseRetribution(net *lntest.NetworkHarness, t *harnessTest) { +func testRevokedCloseRetribution(ht *lntemp.HarnessTest) { const ( chanAmt = funding.MaxBtcFundingAmount paymentAmt = 10000 @@ -35,77 +36,38 @@ func testRevokedCloseRetribution(net *lntest.NetworkHarness, t *harnessTest) { // protection logic automatically. We also can't have Carol // automatically re-connect too early, otherwise DLP would be initiated // instead of the breach we want to provoke. - carol := net.NewNode( - t.t, "Carol", + carol := ht.NewNode( + "Carol", []string{"--hodl.exit-settle", "--nolisten", "--minbackoff=1h"}, ) - defer shutdownAndAssert(net, t, carol) // We must let Bob communicate with Carol before they are able to open // channel, so we connect Bob and Carol, - net.ConnectNodes(t.t, carol, net.Bob) + bob := ht.Bob + ht.ConnectNodes(carol, bob) // Before we make a channel, we'll load up Carol with some coins sent // directly from the miner. - net.SendCoins(t.t, btcutil.SatoshiPerBitcoin, carol) + ht.FundCoins(btcutil.SatoshiPerBitcoin, carol) // In order to test Carol's response to an uncooperative channel // closure by Bob, we'll first open up a channel between them with a // 0.5 BTC value. - chanPoint := openChannelAndAssert( - t, net, carol, net.Bob, - lntest.OpenChannelParams{ - Amt: chanAmt, - }, + chanPoint := ht.OpenChannel( + carol, bob, lntemp.OpenChannelParams{Amt: chanAmt}, ) // With the channel open, we'll create a few invoices for Bob that // Carol will pay to in order to advance the state of the channel. - bobPayReqs, _, _, err := createPayReqs( - net.Bob, paymentAmt, numInvoices, - ) - if err != nil { - t.Fatalf("unable to create pay reqs: %v", err) - } - - // Wait for Carol to receive the channel edge from the funding manager. - err = carol.WaitForNetworkChannelOpen(chanPoint) - if err != nil { - t.Fatalf("carol didn't see the carol->bob channel before "+ - "timeout: %v", err) - } + bobPayReqs, _, _ := ht.CreatePayReqs(bob, paymentAmt, numInvoices) // Send payments from Carol to Bob using 3 of Bob's payment hashes // generated above. - err = completePaymentRequests( - carol, carol.RouterClient, bobPayReqs[:numInvoices/2], true, - ) - if err != nil { - t.Fatalf("unable to send payments: %v", err) - } + ht.CompletePaymentRequests(carol, bobPayReqs[:numInvoices/2]) // Next query for Bob's channel state, as we sent 3 payments of 10k // satoshis each, Bob should now see his balance as being 30k satoshis. - var bobChan *lnrpc.Channel - var predErr error - err = wait.Predicate(func() bool { - bChan, err := getChanInfo(net.Bob) - if err != nil { - t.Fatalf("unable to get bob's channel info: %v", err) - } - if bChan.LocalBalance != 30000 { - predErr = fmt.Errorf("bob's balance is incorrect, "+ - "got %v, expected %v", bChan.LocalBalance, - 30000) - return false - } - - bobChan = bChan - return true - }, defaultTimeout) - if err != nil { - t.Fatalf("%v", predErr) - } + bobChan := ht.AssertChannelLocalBalance(bob, chanPoint, 30_000) // Grab Bob's current commitment height (update number), we'll later // revert him to this state after additional updates to force him to @@ -115,141 +77,93 @@ func testRevokedCloseRetribution(net *lntest.NetworkHarness, t *harnessTest) { // With the temporary file created, copy Bob's current state into the // temporary file we created above. Later after more updates, we'll // restore this state. - if err := net.BackupDb(net.Bob); err != nil { - t.Fatalf("unable to copy database files: %v", err) - } + ht.BackupDB(bob) // Reconnect the peers after the restart that was needed for the db // backup. - net.EnsureConnected(t.t, carol, net.Bob) + ht.EnsureConnected(carol, bob) + + // Because Bob has been restarted, we need to make sure Carol has + // remarked the channel as active after that. + ht.AssertChannelExists(carol, chanPoint) // Finally, send payments from Carol to Bob, consuming Bob's remaining // payment hashes. - err = completePaymentRequests( - carol, carol.RouterClient, bobPayReqs[numInvoices/2:], true, - ) - if err != nil { - t.Fatalf("unable to send payments: %v", err) - } - - bobChan, err = getChanInfo(net.Bob) - if err != nil { - t.Fatalf("unable to get bob chan info: %v", err) - } + ht.CompletePaymentRequests(carol, bobPayReqs[numInvoices/2:]) // Now we shutdown Bob, copying over the his temporary database state // which has the *prior* channel state over his current most up to date // state. With this, we essentially force Bob to travel back in time // within the channel's history. - if err = net.RestartNode(net.Bob, func() error { - return net.RestoreDb(net.Bob) - }); err != nil { - t.Fatalf("unable to restart node: %v", err) - } + ht.RestartNodeAndRestoreDB(bob) // Now query for Bob's channel state, it should show that he's at a // state number in the past, not the *latest* state. - bobChan, err = getChanInfo(net.Bob) - if err != nil { - t.Fatalf("unable to get bob chan info: %v", err) - } - if bobChan.NumUpdates != bobStateNumPreCopy { - t.Fatalf("db copy failed: %v", bobChan.NumUpdates) - } + ht.AssertChannelCommitHeight(bob, chanPoint, int(bobStateNumPreCopy)) // Now force Bob to execute a *force* channel closure by unilaterally // broadcasting his current channel state. This is actually the // commitment transaction of a prior *revoked* state, so he'll soon // feel the wrath of Carol's retribution. - force := true - closeUpdates, _, err := net.CloseChannel( - net.Bob, chanPoint, force, - ) - require.NoError(t.t, err, "unable to close channel") + _, breachTXID := ht.CloseChannelAssertPending(bob, chanPoint, true) - // Wait for Bob's breach transaction to show up in the mempool to ensure - // that Carol's node has started waiting for confirmations. - _, err = waitForTxInMempool(net.Miner.Client, minerMempoolTimeout) - if err != nil { - t.Fatalf("unable to find Bob's breach tx in mempool: %v", err) - } - - // Here, Carol sees Bob's breach transaction in the mempool, but is waiting - // for it to confirm before continuing her retribution. We restart Carol to - // ensure that she is persisting her retribution state and continues - // watching for the breach transaction to confirm even after her node - // restarts. - if err := net.RestartNode(carol, nil); err != nil { - t.Fatalf("unable to restart Carol's node: %v", err) - } + // Here, Carol sees Bob's breach transaction in the mempool, but is + // waiting for it to confirm before continuing her retribution. We + // restart Carol to ensure that she is persisting her retribution state + // and continues watching for the breach transaction to confirm even + // after her node restarts. + ht.RestartNode(carol) // Finally, generate a single block, wait for the final close status // update, then ensure that the closing transaction was included in the // block. - block := mineBlocks(t, net, 1, 1)[0] - - breachTXID, err := net.WaitForChannelClose(closeUpdates) - if err != nil { - t.Fatalf("error while waiting for channel close: %v", err) - } - assertTxInBlock(t, block, breachTXID) + block := ht.MineBlocksAndAssertNumTxes(1, 1)[0] + ht.Miner.AssertTxInBlock(block, breachTXID) // Query the mempool for Carol's justice transaction, this should be // broadcast as Bob's contract breaching transaction gets confirmed // above. - justiceTXID, err := waitForTxInMempool(net.Miner.Client, minerMempoolTimeout) - if err != nil { - t.Fatalf("unable to find Carol's justice tx in mempool: %v", err) - } - time.Sleep(100 * time.Millisecond) + justiceTXID := ht.Miner.AssertNumTxsInMempool(1)[0] // Query for the mempool transaction found above. Then assert that all // the inputs of this transaction are spending outputs generated by // Bob's breach transaction above. - justiceTx, err := net.Miner.Client.GetRawTransaction(justiceTXID) - if err != nil { - t.Fatalf("unable to query for justice tx: %v", err) - } + justiceTx := ht.Miner.GetRawTransaction(justiceTXID) for _, txIn := range justiceTx.MsgTx().TxIn { - if !bytes.Equal(txIn.PreviousOutPoint.Hash[:], breachTXID[:]) { - t.Fatalf("justice tx not spending commitment utxo "+ - "instead is: %v", txIn.PreviousOutPoint) - } + require.Equal(ht, breachTXID[:], txIn.PreviousOutPoint.Hash[:], + "justice tx not spending commitment utxo") } - // We restart Carol here to ensure that she persists her retribution state - // and successfully continues exacting retribution after restarting. At - // this point, Carol has broadcast the justice transaction, but it hasn't - // been confirmed yet; when Carol restarts, she should start waiting for - // the justice transaction to confirm again. - if err := net.RestartNode(carol, nil); err != nil { - t.Fatalf("unable to restart Carol's node: %v", err) - } + // We restart Carol here to ensure that she persists her retribution + // state and successfully continues exacting retribution after + // restarting. At this point, Carol has broadcast the justice + // transaction, but it hasn't been confirmed yet; when Carol restarts, + // she should start waiting for the justice transaction to confirm + // again. + ht.RestartNode(carol) // Now mine a block, this transaction should include Carol's justice // transaction which was just accepted into the mempool. - block = mineBlocks(t, net, 1, 1)[0] + block = ht.MineBlocksAndAssertNumTxes(1, 1)[0] // The block should have exactly *two* transactions, one of which is // the justice transaction. - if len(block.Transactions) != 2 { - t.Fatalf("transaction wasn't mined") - } - justiceSha := block.Transactions[1].TxHash() - if !bytes.Equal(justiceTx.Hash()[:], justiceSha[:]) { - t.Fatalf("justice tx wasn't mined") - } + require.Len(ht, block.Transactions, 2, "transaction wasn't mined") - assertNodeNumChannels(t, carol, 0) + justiceSha := block.Transactions[1].TxHash() + require.Equal(ht, justiceTx.Hash()[:], justiceSha[:], + "justice tx wasn't mined") + + ht.AssertNodeNumChannels(carol, 0) // Mine enough blocks for Bob's channel arbitrator to wrap up the // references to the breached channel. The chanarb waits for commitment // tx's confHeight+CSV-1 blocks and since we've already mined one that // included the justice tx we only need to mine extra DefaultCSV-2 // blocks to unlock it. - mineBlocks(t, net, lntest.DefaultCSV-2, 0) + ht.MineBlocks(lntest.DefaultCSV - 2) - assertNumPendingChannels(t, net.Bob, 0, 0) + ht.AssertNumPendingForceClose(bob, 0) } // testRevokedCloseRetributionZeroValueRemoteOutput tests that Dave is able diff --git a/lntest/itest/lnd_test_list_on_test.go b/lntest/itest/lnd_test_list_on_test.go index e5040037e..479d15831 100644 --- a/lntest/itest/lnd_test_list_on_test.go +++ b/lntest/itest/lnd_test_list_on_test.go @@ -72,12 +72,6 @@ var allTestCases = []*testCase{ name: "switch offline delivery outgoing offline", test: testSwitchOfflineDeliveryOutgoingOffline, }, - { - // TODO(roasbeef): test always needs to be last as Bob's state - // is borked since we trick him into attempting to cheat Alice? - name: "revoked uncooperative close retribution", - test: testRevokedCloseRetribution, - }, { name: "revoked uncooperative close retribution zero value remote output", test: testRevokedCloseRetributionZeroValueRemoteOutput, From f590a96372d84ccf9304255e44662e6b0f6ab60d Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Mon, 8 Aug 2022 20:50:03 +0800 Subject: [PATCH 17/44] itest: refactor `testRevokedCloseRetributionZeroValueRemoteOutput` --- lntest/itest/list_on_test.go | 5 + lntest/itest/lnd_revocation_test.go | 147 ++++++-------------------- lntest/itest/lnd_test_list_on_test.go | 4 - 3 files changed, 38 insertions(+), 118 deletions(-) diff --git a/lntest/itest/list_on_test.go b/lntest/itest/list_on_test.go index bba41fcb4..744090442 100644 --- a/lntest/itest/list_on_test.go +++ b/lntest/itest/list_on_test.go @@ -295,4 +295,9 @@ var allTestCasesTemp = []*lntemp.TestCase{ Name: "revoked uncooperative close retribution", TestFunc: testRevokedCloseRetribution, }, + { + Name: "revoked uncooperative close retribution zero value " + + "remote output", + TestFunc: testRevokedCloseRetributionZeroValueRemoteOutput, + }, } diff --git a/lntest/itest/lnd_revocation_test.go b/lntest/itest/lnd_revocation_test.go index 8850c10c3..03193f032 100644 --- a/lntest/itest/lnd_revocation_test.go +++ b/lntest/itest/lnd_revocation_test.go @@ -169,9 +169,7 @@ func testRevokedCloseRetribution(ht *lntemp.HarnessTest) { // testRevokedCloseRetributionZeroValueRemoteOutput tests that Dave is able // carry out retribution in the event that he fails in state where the remote // commitment output has zero-value. -func testRevokedCloseRetributionZeroValueRemoteOutput(net *lntest.NetworkHarness, - t *harnessTest) { - +func testRevokedCloseRetributionZeroValueRemoteOutput(ht *lntemp.HarnessTest) { const ( chanAmt = funding.MaxBtcFundingAmount paymentAmt = 10000 @@ -180,177 +178,103 @@ func testRevokedCloseRetributionZeroValueRemoteOutput(net *lntest.NetworkHarness // Since we'd like to test some multi-hop failure scenarios, we'll // introduce another node into our test network: Carol. - carol := net.NewNode(t.t, "Carol", []string{"--hodl.exit-settle"}) - defer shutdownAndAssert(net, t, carol) + carol := ht.NewNode("Carol", []string{"--hodl.exit-settle"}) // Dave will be the breached party. We set --nolisten to ensure Carol // won't be able to connect to him and trigger the channel data // protection logic automatically. We also can't have Dave automatically // re-connect too early, otherwise DLP would be initiated instead of the // breach we want to provoke. - dave := net.NewNode( - t.t, "Dave", + dave := ht.NewNode( + "Dave", []string{"--hodl.exit-settle", "--nolisten", "--minbackoff=1h"}, ) - defer shutdownAndAssert(net, t, dave) // We must let Dave have an open channel before he can send a node // announcement, so we open a channel with Carol, - net.ConnectNodes(t.t, dave, carol) + ht.ConnectNodes(dave, carol) // Before we make a channel, we'll load up Dave with some coins sent // directly from the miner. - net.SendCoins(t.t, btcutil.SatoshiPerBitcoin, dave) + ht.FundCoins(btcutil.SatoshiPerBitcoin, dave) // In order to test Dave's response to an uncooperative channel // closure by Carol, we'll first open up a channel between them with a // 0.5 BTC value. - chanPoint := openChannelAndAssert( - t, net, dave, carol, - lntest.OpenChannelParams{ - Amt: chanAmt, - }, + chanPoint := ht.OpenChannel( + dave, carol, lntemp.OpenChannelParams{Amt: chanAmt}, ) // With the channel open, we'll create a few invoices for Carol that // Dave will pay to in order to advance the state of the channel. - carolPayReqs, _, _, err := createPayReqs( - carol, paymentAmt, numInvoices, - ) - if err != nil { - t.Fatalf("unable to create pay reqs: %v", err) - } - - // Wait for Dave to receive the channel edge from the funding manager. - err = dave.WaitForNetworkChannelOpen(chanPoint) - if err != nil { - t.Fatalf("dave didn't see the dave->carol channel before "+ - "timeout: %v", err) - } + carolPayReqs, _, _ := ht.CreatePayReqs(carol, paymentAmt, numInvoices) // Next query for Carol's channel state, as we sent 0 payments, Carol // should now see her balance as being 0 satoshis. - carolChan, err := getChanInfo(carol) - if err != nil { - t.Fatalf("unable to get carol's channel info: %v", err) - } - if carolChan.LocalBalance != 0 { - t.Fatalf("carol's balance is incorrect, got %v, expected %v", - carolChan.LocalBalance, 0) - } + carolChan := ht.AssertChannelLocalBalance(carol, chanPoint, 0) // Grab Carol's current commitment height (update number), we'll later // revert her to this state after additional updates to force her to // broadcast this soon to be revoked state. - carolStateNumPreCopy := carolChan.NumUpdates + carolStateNumPreCopy := int(carolChan.NumUpdates) // With the temporary file created, copy Carol's current state into the // temporary file we created above. Later after more updates, we'll // restore this state. - if err := net.BackupDb(carol); err != nil { - t.Fatalf("unable to copy database files: %v", err) - } + ht.BackupDB(carol) // Reconnect the peers after the restart that was needed for the db // backup. - net.EnsureConnected(t.t, dave, carol) + ht.EnsureConnected(dave, carol) // Finally, send payments from Dave to Carol, consuming Carol's // remaining payment hashes. - err = completePaymentRequests( - dave, dave.RouterClient, carolPayReqs, false, - ) - if err != nil { - t.Fatalf("unable to send payments: %v", err) - } - - _, err = getChanInfo(carol) - if err != nil { - t.Fatalf("unable to get carol chan info: %v", err) - } + ht.CompletePaymentRequestsNoWait(dave, carolPayReqs, chanPoint) // Now we shutdown Carol, copying over the her temporary database state // which has the *prior* channel state over her current most up to date // state. With this, we essentially force Carol to travel back in time // within the channel's history. - if err = net.RestartNode(carol, func() error { - return net.RestoreDb(carol) - }); err != nil { - t.Fatalf("unable to restart node: %v", err) - } + ht.RestartNodeAndRestoreDB(carol) // Now query for Carol's channel state, it should show that she's at a // state number in the past, not the *latest* state. - carolChan, err = getChanInfo(carol) - if err != nil { - t.Fatalf("unable to get carol chan info: %v", err) - } - if carolChan.NumUpdates != carolStateNumPreCopy { - t.Fatalf("db copy failed: %v", carolChan.NumUpdates) - } + ht.AssertChannelCommitHeight(carol, chanPoint, carolStateNumPreCopy) // Now force Carol to execute a *force* channel closure by unilaterally // broadcasting her current channel state. This is actually the // commitment transaction of a prior *revoked* state, so she'll soon // feel the wrath of Dave's retribution. - force := true - closeUpdates, closeTxID, closeErr := net.CloseChannel( - carol, chanPoint, force, + stream, closeTxID := ht.CloseChannelAssertPending( + carol, chanPoint, true, ) - require.NoError(t.t, closeErr, "unable to close channel") - - // Query the mempool for the breaching closing transaction, this should - // be broadcast by Carol when she force closes the channel above. - txid, err := waitForTxInMempool(net.Miner.Client, minerMempoolTimeout) - if err != nil { - t.Fatalf("unable to find Carol's force close tx in mempool: %v", - err) - } - if *txid != *closeTxID { - t.Fatalf("expected closeTx(%v) in mempool, instead found %v", - closeTxID, txid) - } // Finally, generate a single block, wait for the final close status // update, then ensure that the closing transaction was included in the // block. - block := mineBlocks(t, net, 1, 1)[0] + ht.MineBlocksAndAssertNumTxes(1, 1) // Here, Dave receives a confirmation of Carol's breach transaction. // We restart Dave to ensure that he is persisting his retribution // state and continues exacting justice after his node restarts. - if err := net.RestartNode(dave, nil); err != nil { - t.Fatalf("unable to stop Dave's node: %v", err) - } + ht.RestartNode(dave) - breachTXID, err := net.WaitForChannelClose(closeUpdates) - if err != nil { - t.Fatalf("error while waiting for channel close: %v", err) - } - assertTxInBlock(t, block, breachTXID) + // The breachTXID should match the above closeTxID. + breachTXID := ht.WaitForChannelCloseEvent(stream) + require.EqualValues(ht, breachTXID, closeTxID) // Query the mempool for Dave's justice transaction, this should be // broadcast as Carol's contract breaching transaction gets confirmed // above. - justiceTXID, err := waitForTxInMempool(net.Miner.Client, minerMempoolTimeout) - if err != nil { - t.Fatalf("unable to find Dave's justice tx in mempool: %v", - err) - } - time.Sleep(100 * time.Millisecond) + justiceTXID := ht.Miner.AssertNumTxsInMempool(1)[0] // Query for the mempool transaction found above. Then assert that all // the inputs of this transaction are spending outputs generated by // Carol's breach transaction above. - justiceTx, err := net.Miner.Client.GetRawTransaction(justiceTXID) - if err != nil { - t.Fatalf("unable to query for justice tx: %v", err) - } + justiceTx := ht.Miner.GetRawTransaction(justiceTXID) for _, txIn := range justiceTx.MsgTx().TxIn { - if !bytes.Equal(txIn.PreviousOutPoint.Hash[:], breachTXID[:]) { - t.Fatalf("justice tx not spending commitment utxo "+ - "instead is: %v", txIn.PreviousOutPoint) - } + require.Equal(ht, breachTXID[:], txIn.PreviousOutPoint.Hash[:], + "justice tx not spending commitment utxo ") } // We restart Dave here to ensure that he persists his retribution state @@ -358,25 +282,20 @@ func testRevokedCloseRetributionZeroValueRemoteOutput(net *lntest.NetworkHarness // this point, Dave has broadcast the justice transaction, but it hasn't // been confirmed yet; when Dave restarts, he should start waiting for // the justice transaction to confirm again. - if err := net.RestartNode(dave, nil); err != nil { - t.Fatalf("unable to restart Dave's node: %v", err) - } + ht.RestartNode(dave) // Now mine a block, this transaction should include Dave's justice // transaction which was just accepted into the mempool. - block = mineBlocks(t, net, 1, 1)[0] + block := ht.MineBlocksAndAssertNumTxes(1, 1)[0] // The block should have exactly *two* transactions, one of which is // the justice transaction. - if len(block.Transactions) != 2 { - t.Fatalf("transaction wasn't mined") - } + require.Len(ht, block.Transactions, 2, "transaction wasn't mined") justiceSha := block.Transactions[1].TxHash() - if !bytes.Equal(justiceTx.Hash()[:], justiceSha[:]) { - t.Fatalf("justice tx wasn't mined") - } + require.Equal(ht, justiceTx.Hash()[:], justiceSha[:], + "justice tx wasn't mined") - assertNodeNumChannels(t, dave, 0) + ht.AssertNodeNumChannels(dave, 0) } // testRevokedCloseRetributionRemoteHodl tests that Dave properly responds to a diff --git a/lntest/itest/lnd_test_list_on_test.go b/lntest/itest/lnd_test_list_on_test.go index 479d15831..ca7ef2172 100644 --- a/lntest/itest/lnd_test_list_on_test.go +++ b/lntest/itest/lnd_test_list_on_test.go @@ -72,10 +72,6 @@ var allTestCases = []*testCase{ name: "switch offline delivery outgoing offline", test: testSwitchOfflineDeliveryOutgoingOffline, }, - { - name: "revoked uncooperative close retribution zero value remote output", - test: testRevokedCloseRetributionZeroValueRemoteOutput, - }, { name: "revoked uncooperative close retribution remote hodl", test: testRevokedCloseRetributionRemoteHodl, From 211aa4c6f2565f18a874849a6ce063935bc7cfe6 Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Tue, 9 Aug 2022 00:49:51 +0800 Subject: [PATCH 18/44] itest: refactor `testRevokedCloseRetributionRemoteHodl` --- lntest/itest/list_on_test.go | 4 + lntest/itest/lnd_revocation_test.go | 257 ++++++++------------------ lntest/itest/lnd_test_list_on_test.go | 4 - 3 files changed, 78 insertions(+), 187 deletions(-) diff --git a/lntest/itest/list_on_test.go b/lntest/itest/list_on_test.go index 744090442..6b4494f96 100644 --- a/lntest/itest/list_on_test.go +++ b/lntest/itest/list_on_test.go @@ -300,4 +300,8 @@ var allTestCasesTemp = []*lntemp.TestCase{ "remote output", TestFunc: testRevokedCloseRetributionZeroValueRemoteOutput, }, + { + Name: "revoked uncooperative close retribution remote hodl", + TestFunc: testRevokedCloseRetributionRemoteHodl, + }, } diff --git a/lntest/itest/lnd_revocation_test.go b/lntest/itest/lnd_revocation_test.go index 03193f032..24473bc17 100644 --- a/lntest/itest/lnd_revocation_test.go +++ b/lntest/itest/lnd_revocation_test.go @@ -301,9 +301,7 @@ func testRevokedCloseRetributionZeroValueRemoteOutput(ht *lntemp.HarnessTest) { // testRevokedCloseRetributionRemoteHodl tests that Dave properly responds to a // channel breach made by the remote party, specifically in the case that the // remote party breaches before settling extended HTLCs. -func testRevokedCloseRetributionRemoteHodl(net *lntest.NetworkHarness, - t *harnessTest) { - +func testRevokedCloseRetributionRemoteHodl(ht *lntemp.HarnessTest) { const ( chanAmt = funding.MaxBtcFundingAmount pushAmt = 200000 @@ -314,34 +312,31 @@ func testRevokedCloseRetributionRemoteHodl(net *lntest.NetworkHarness, // Since this test will result in the counterparty being left in a // weird state, we will introduce another node into our test network: // Carol. - carol := net.NewNode(t.t, "Carol", []string{"--hodl.exit-settle"}) - defer shutdownAndAssert(net, t, carol) + carol := ht.NewNode("Carol", []string{"--hodl.exit-settle"}) // We'll also create a new node Dave, who will have a channel with // Carol, and also use similar settings so we can broadcast a commit // with active HTLCs. Dave will be the breached party. We set // --nolisten to ensure Carol won't be able to connect to him and // trigger the channel data protection logic automatically. - dave := net.NewNode( - t.t, "Dave", + dave := ht.NewNode( + "Dave", []string{"--hodl.exit-settle", "--nolisten"}, ) - defer shutdownAndAssert(net, t, dave) // We must let Dave communicate with Carol before they are able to open // channel, so we connect Dave and Carol, - net.ConnectNodes(t.t, dave, carol) + ht.ConnectNodes(dave, carol) // Before we make a channel, we'll load up Dave with some coins sent // directly from the miner. - net.SendCoins(t.t, btcutil.SatoshiPerBitcoin, dave) + ht.FundCoins(btcutil.SatoshiPerBitcoin, dave) // In order to test Dave's response to an uncooperative channel closure // by Carol, we'll first open up a channel between them with a // funding.MaxBtcFundingAmount (2^24) satoshis value. - chanPoint := openChannelAndAssert( - t, net, dave, carol, - lntest.OpenChannelParams{ + chanPoint := ht.OpenChannel( + dave, carol, lntemp.OpenChannelParams{ Amt: chanAmt, PushAmt: pushAmt, }, @@ -349,206 +344,122 @@ func testRevokedCloseRetributionRemoteHodl(net *lntest.NetworkHarness, // With the channel open, we'll create a few invoices for Carol that // Dave will pay to in order to advance the state of the channel. - carolPayReqs, _, _, err := createPayReqs( - carol, paymentAmt, numInvoices, - ) - if err != nil { - t.Fatalf("unable to create pay reqs: %v", err) - } + carolPayReqs, _, _ := ht.CreatePayReqs(carol, paymentAmt, numInvoices) - // We'll introduce a closure to validate that Carol's current balance - // matches the given expected amount. - checkCarolBalance := func(expectedAmt int64) { - carolChan, err := getChanInfo(carol) - if err != nil { - t.Fatalf("unable to get carol's channel info: %v", err) - } - if carolChan.LocalBalance != expectedAmt { - t.Fatalf("carol's balance is incorrect, "+ - "got %v, expected %v", carolChan.LocalBalance, - expectedAmt) - } - } - - // We'll introduce another closure to validate that Carol's current + // We'll introduce an closure to validate that Carol's current // number of updates is at least as large as the provided minimum // number. - checkCarolNumUpdatesAtLeast := func(minimum uint64) { - carolChan, err := getChanInfo(carol) - if err != nil { - t.Fatalf("unable to get carol's channel info: %v", err) - } - if carolChan.NumUpdates < minimum { - t.Fatalf("carol's numupdates is incorrect, want %v "+ - "to be at least %v", carolChan.NumUpdates, - minimum) - } - } + checkCarolNumUpdatesAtLeast := func(carolChan *lnrpc.Channel, + minimum int) { - // Wait for Dave to receive the channel edge from the funding manager. - err = dave.WaitForNetworkChannelOpen(chanPoint) - if err != nil { - t.Fatalf("dave didn't see the dave->carol channel before "+ - "timeout: %v", err) + require.GreaterOrEqual(ht, int(carolChan.NumUpdates), minimum, + "carol's numupdates is incorrect") } // Ensure that carol's balance starts with the amount we pushed to her. - checkCarolBalance(pushAmt) + ht.AssertChannelLocalBalance(carol, chanPoint, pushAmt) // Send payments from Dave to Carol using 3 of Carol's payment hashes // generated above. - err = completePaymentRequests( - dave, dave.RouterClient, carolPayReqs[:numInvoices/2], false, + ht.CompletePaymentRequestsNoWait( + dave, carolPayReqs[:numInvoices/2], chanPoint, ) - if err != nil { - t.Fatalf("unable to send payments: %v", err) - } // At this point, we'll also send over a set of HTLC's from Carol to // Dave. This ensures that the final revoked transaction has HTLC's in // both directions. - davePayReqs, _, _, err := createPayReqs( - dave, paymentAmt, numInvoices, - ) - if err != nil { - t.Fatalf("unable to create pay reqs: %v", err) - } + davePayReqs, _, _ := ht.CreatePayReqs(dave, paymentAmt, numInvoices) // Send payments from Carol to Dave using 3 of Dave's payment hashes // generated above. - err = completePaymentRequests( - carol, carol.RouterClient, davePayReqs[:numInvoices/2], false, + ht.CompletePaymentRequestsNoWait( + carol, davePayReqs[:numInvoices/2], chanPoint, ) - if err != nil { - t.Fatalf("unable to send payments: %v", err) - } // Next query for Carol's channel state, as we sent 3 payments of 10k // satoshis each, however Carol should now see her balance as being // equal to the push amount in satoshis since she has not settled. - carolChan, err := getChanInfo(carol) - if err != nil { - t.Fatalf("unable to get carol's channel info: %v", err) - } + + // Ensure that carol's balance still reflects the original amount we + // pushed to her, minus the HTLCs she just sent to Dave. + carolChan := ht.AssertChannelLocalBalance( + carol, chanPoint, pushAmt-3*paymentAmt, + ) // Grab Carol's current commitment height (update number), we'll later // revert her to this state after additional updates to force her to // broadcast this soon to be revoked state. - carolStateNumPreCopy := carolChan.NumUpdates - - // Ensure that carol's balance still reflects the original amount we - // pushed to her, minus the HTLCs she just sent to Dave. - checkCarolBalance(pushAmt - 3*paymentAmt) + carolStateNumPreCopy := int(carolChan.NumUpdates) // Since Carol has not settled, she should only see at least one update // to her channel. - checkCarolNumUpdatesAtLeast(1) + checkCarolNumUpdatesAtLeast(carolChan, 1) // With the temporary file created, copy Carol's current state into the // temporary file we created above. Later after more updates, we'll // restore this state. - if err := net.BackupDb(carol); err != nil { - t.Fatalf("unable to copy database files: %v", err) - } + ht.BackupDB(carol) // Reconnect the peers after the restart that was needed for the db // backup. - net.EnsureConnected(t.t, dave, carol) + ht.EnsureConnected(dave, carol) // Finally, send payments from Dave to Carol, consuming Carol's // remaining payment hashes. - err = completePaymentRequests( - dave, dave.RouterClient, carolPayReqs[numInvoices/2:], false, + ht.CompletePaymentRequestsNoWait( + dave, carolPayReqs[numInvoices/2:], chanPoint, ) - if err != nil { - t.Fatalf("unable to send payments: %v", err) - } // Ensure that carol's balance still shows the amount we originally // pushed to her (minus the HTLCs she sent to Bob), and that at least // one more update has occurred. - time.Sleep(500 * time.Millisecond) - checkCarolBalance(pushAmt - 3*paymentAmt) - checkCarolNumUpdatesAtLeast(carolStateNumPreCopy + 1) + carolChan = ht.AssertChannelLocalBalance( + carol, chanPoint, pushAmt-3*paymentAmt, + ) + checkCarolNumUpdatesAtLeast(carolChan, carolStateNumPreCopy+1) // Suspend Dave, such that Carol won't reconnect at startup, triggering // the data loss protection. - restartDave, err := net.SuspendNode(dave) - if err != nil { - t.Fatalf("unable to suspend Dave: %v", err) - } + restartDave := ht.SuspendNode(dave) // Now we shutdown Carol, copying over the her temporary database state // which has the *prior* channel state over her current most up to date // state. With this, we essentially force Carol to travel back in time // within the channel's history. - if err = net.RestartNode(carol, func() error { - return net.RestoreDb(carol) - }); err != nil { - t.Fatalf("unable to restart node: %v", err) - } - - time.Sleep(200 * time.Millisecond) + ht.RestartNodeAndRestoreDB(carol) // Ensure that Carol's view of the channel is consistent with the state // of the channel just before it was snapshotted. - checkCarolBalance(pushAmt - 3*paymentAmt) - checkCarolNumUpdatesAtLeast(1) + carolChan = ht.AssertChannelLocalBalance( + carol, chanPoint, pushAmt-3*paymentAmt, + ) + checkCarolNumUpdatesAtLeast(carolChan, 1) // Now query for Carol's channel state, it should show that she's at a // state number in the past, *not* the latest state. - carolChan, err = getChanInfo(carol) - if err != nil { - t.Fatalf("unable to get carol chan info: %v", err) - } - if carolChan.NumUpdates != carolStateNumPreCopy { - t.Fatalf("db copy failed: %v", carolChan.NumUpdates) - } + ht.AssertChannelCommitHeight(carol, chanPoint, carolStateNumPreCopy) // Now force Carol to execute a *force* channel closure by unilaterally // broadcasting her current channel state. This is actually the // commitment transaction of a prior *revoked* state, so she'll soon // feel the wrath of Dave's retribution. - force := true - closeUpdates, closeTxID, err := net.CloseChannel( - carol, chanPoint, force, + closeUpdates, closeTxID := ht.CloseChannelAssertPending( + carol, chanPoint, true, ) - if err != nil { - t.Fatalf("unable to close channel: %v", err) - } - - // Query the mempool for the breaching closing transaction, this should - // be broadcast by Carol when she force closes the channel above. - txid, err := waitForTxInMempool(net.Miner.Client, minerMempoolTimeout) - if err != nil { - t.Fatalf("unable to find Carol's force close tx in mempool: %v", - err) - } - if *txid != *closeTxID { - t.Fatalf("expected closeTx(%v) in mempool, instead found %v", - closeTxID, txid) - } // Generate a single block to mine the breach transaction. - block := mineBlocks(t, net, 1, 1)[0] + block := ht.MineBlocksAndAssertNumTxes(1, 1)[0] // We resurrect Dave to ensure he will be exacting justice after his // node restarts. - if err := restartDave(); err != nil { - t.Fatalf("unable to stop Dave's node: %v", err) - } + require.NoError(ht, restartDave(), "unable to restart Dave's node") // Finally, wait for the final close status update, then ensure that // the closing transaction was included in the block. - breachTXID, err := net.WaitForChannelClose(closeUpdates) - if err != nil { - t.Fatalf("error while waiting for channel close: %v", err) - } - if *breachTXID != *closeTxID { - t.Fatalf("expected breach ID(%v) to be equal to close ID (%v)", - breachTXID, closeTxID) - } - assertTxInBlock(t, block, breachTXID) + breachTXID := ht.WaitForChannelCloseEvent(closeUpdates) + require.Equal(ht, closeTxID[:], breachTXID[:], + "expected breach ID to be equal to close ID") + ht.Miner.AssertTxInBlock(block, breachTXID) // Query the mempool for Dave's justice transaction, this should be // broadcast as Carol's contract breaching transaction gets confirmed @@ -556,24 +467,15 @@ func testRevokedCloseRetributionRemoteHodl(net *lntest.NetworkHarness, // outputs to the second level before Dave broadcasts his justice tx, // we'll search through the mempool for a tx that matches the number of // expected inputs in the justice tx. - var predErr error var justiceTxid *chainhash.Hash errNotFound := errors.New("justice tx not found") findJusticeTx := func() (*chainhash.Hash, error) { - mempool, err := net.Miner.Client.GetRawMempool() - if err != nil { - return nil, fmt.Errorf("unable to get mempool from "+ - "miner: %v", err) - } + mempool := ht.Miner.GetRawMempool() for _, txid := range mempool { // Check that the justice tx has the appropriate number // of inputs. - tx, err := net.Miner.Client.GetRawTransaction(txid) - if err != nil { - return nil, fmt.Errorf("unable to query for "+ - "txs: %v", err) - } + tx := ht.Miner.GetRawTransaction(txid) exNumInputs := 2 + numInvoices if len(tx.MsgTx().TxIn) == exNumInputs { @@ -583,17 +485,17 @@ func testRevokedCloseRetributionRemoteHodl(net *lntest.NetworkHarness, return nil, errNotFound } - err = wait.Predicate(func() bool { + err := wait.NoError(func() error { txid, err := findJusticeTx() if err != nil { - predErr = err - return false + return err } - justiceTxid = txid - return true + + return nil }, defaultTimeout) - if err != nil && predErr == errNotFound { + + if err != nil && errors.Is(err, errNotFound) { // If Dave is unable to broadcast his justice tx on first // attempt because of the second layer transactions, he will // wait until the next block epoch before trying again. Because @@ -602,35 +504,28 @@ func testRevokedCloseRetributionRemoteHodl(net *lntest.NetworkHarness, // transactions will be in the mempool at this point, we pass 0 // as the last argument, indicating we don't care what's in the // mempool. - mineBlocks(t, net, 1, 0) - err = wait.Predicate(func() bool { + ht.MineBlocks(1) + err = wait.NoError(func() error { txid, err := findJusticeTx() if err != nil { - predErr = err - return false + return err } justiceTxid = txid - return true + + return nil }, defaultTimeout) } - if err != nil { - t.Fatalf(predErr.Error()) - } + require.NoError(ht, err, "timeout finding justice tx") - justiceTx, err := net.Miner.Client.GetRawTransaction(justiceTxid) - if err != nil { - t.Fatalf("unable to query for justice tx: %v", err) - } + justiceTx := ht.Miner.GetRawTransaction(justiceTxid) // isSecondLevelSpend checks that the passed secondLevelTxid is a // potentitial second level spend spending from the commit tx. - isSecondLevelSpend := func(commitTxid, secondLevelTxid *chainhash.Hash) bool { - secondLevel, err := net.Miner.Client.GetRawTransaction( - secondLevelTxid) - if err != nil { - t.Fatalf("unable to query for tx: %v", err) - } + isSecondLevelSpend := func(commitTxid, + secondLevelTxid *chainhash.Hash) bool { + + secondLevel := ht.Miner.GetRawTransaction(secondLevelTxid) // A second level spend should have only one input, and one // output. @@ -661,27 +556,23 @@ func testRevokedCloseRetributionRemoteHodl(net *lntest.NetworkHarness, if isSecondLevelSpend(breachTXID, &txIn.PreviousOutPoint.Hash) { continue } - t.Fatalf("justice tx not spending commitment utxo "+ + require.Fail(ht, "justice tx not spending commitment utxo "+ "instead is: %v", txIn.PreviousOutPoint) } - time.Sleep(100 * time.Millisecond) // We restart Dave here to ensure that he persists he retribution state // and successfully continues exacting retribution after restarting. At // this point, Dave has broadcast the justice transaction, but it // hasn't been confirmed yet; when Dave restarts, he should start // waiting for the justice transaction to confirm again. - if err := net.RestartNode(dave, nil); err != nil { - t.Fatalf("unable to restart Dave's node: %v", err) - } + ht.RestartNode(dave) // Now mine a block, this transaction should include Dave's justice // transaction which was just accepted into the mempool. - block = mineBlocks(t, net, 1, 1)[0] - assertTxInBlock(t, block, justiceTxid) + ht.MineBlocksAndAssertNumTxes(1, 1) // Dave should have no open channels. - assertNodeNumChannels(t, dave, 0) + ht.AssertNodeNumChannels(dave, 0) } // testRevokedCloseRetributionAltruistWatchtower establishes a channel between diff --git a/lntest/itest/lnd_test_list_on_test.go b/lntest/itest/lnd_test_list_on_test.go index ca7ef2172..926c3fca2 100644 --- a/lntest/itest/lnd_test_list_on_test.go +++ b/lntest/itest/lnd_test_list_on_test.go @@ -72,10 +72,6 @@ var allTestCases = []*testCase{ name: "switch offline delivery outgoing offline", test: testSwitchOfflineDeliveryOutgoingOffline, }, - { - name: "revoked uncooperative close retribution remote hodl", - test: testRevokedCloseRetributionRemoteHodl, - }, { name: "revoked uncooperative close retribution altruist watchtower", test: testRevokedCloseRetributionAltruistWatchtower, From 6792ca17b418f53269f752fc053ce0d063860059 Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Tue, 9 Aug 2022 01:03:52 +0800 Subject: [PATCH 19/44] lntemp+itest: refactor `testRevokedCloseRetributionAltruistWatchtower` --- lntemp/rpc/watchtower.go | 47 ++++ lntest/itest/list_on_test.go | 4 + lntest/itest/lnd_revocation_test.go | 331 ++++++++------------------ lntest/itest/lnd_test_list_on_test.go | 4 - 4 files changed, 150 insertions(+), 236 deletions(-) diff --git a/lntemp/rpc/watchtower.go b/lntemp/rpc/watchtower.go index c18734d94..1b05d15ea 100644 --- a/lntemp/rpc/watchtower.go +++ b/lntemp/rpc/watchtower.go @@ -1,5 +1,52 @@ package rpc +import ( + "context" + + "github.com/lightningnetwork/lnd/lnrpc/watchtowerrpc" + "github.com/lightningnetwork/lnd/lnrpc/wtclientrpc" +) + // ===================== // WatchtowerClient and WatchtowerClientClient related RPCs. // ===================== + +// GetInfoWatchtower makes a RPC call to the watchtower of the given node and +// asserts. +func (h *HarnessRPC) GetInfoWatchtower() *watchtowerrpc.GetInfoResponse { + ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout) + defer cancel() + + req := &watchtowerrpc.GetInfoRequest{} + info, err := h.Watchtower.GetInfo(ctxt, req) + h.NoError(err, "GetInfo from Watchtower") + + return info +} + +// AddTower makes a RPC call to the WatchtowerClient of the given node and +// asserts. +func (h *HarnessRPC) AddTower( + req *wtclientrpc.AddTowerRequest) *wtclientrpc.AddTowerResponse { + + ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout) + defer cancel() + + resp, err := h.WatchtowerClient.AddTower(ctxt, req) + h.NoError(err, "AddTower") + + return resp +} + +// WatchtowerStats makes a RPC call to the WatchtowerClient of the given node +// and asserts. +func (h *HarnessRPC) WatchtowerStats() *wtclientrpc.StatsResponse { + ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout) + defer cancel() + + req := &wtclientrpc.StatsRequest{} + resp, err := h.WatchtowerClient.Stats(ctxt, req) + h.NoError(err, "Stats from Watchtower") + + return resp +} diff --git a/lntest/itest/list_on_test.go b/lntest/itest/list_on_test.go index 6b4494f96..2b3acbf06 100644 --- a/lntest/itest/list_on_test.go +++ b/lntest/itest/list_on_test.go @@ -304,4 +304,8 @@ var allTestCasesTemp = []*lntemp.TestCase{ Name: "revoked uncooperative close retribution remote hodl", TestFunc: testRevokedCloseRetributionRemoteHodl, }, + { + Name: "revoked uncooperative close retribution altruist watchtower", + TestFunc: testRevokedCloseRetributionAltruistWatchtower, + }, } diff --git a/lntest/itest/lnd_revocation_test.go b/lntest/itest/lnd_revocation_test.go index 24473bc17..20561c039 100644 --- a/lntest/itest/lnd_revocation_test.go +++ b/lntest/itest/lnd_revocation_test.go @@ -2,9 +2,7 @@ package itest import ( "bytes" - "context" "fmt" - "strings" "testing" "time" @@ -13,7 +11,6 @@ import ( "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/funding" "github.com/lightningnetwork/lnd/lnrpc" - "github.com/lightningnetwork/lnd/lnrpc/watchtowerrpc" "github.com/lightningnetwork/lnd/lnrpc/wtclientrpc" "github.com/lightningnetwork/lnd/lntemp" "github.com/lightningnetwork/lnd/lntest" @@ -581,9 +578,7 @@ func testRevokedCloseRetributionRemoteHodl(ht *lntemp.HarnessTest) { // trigger a breach. Carol is kept offline throughout the process and the test // asserts that Willy responds by broadcasting the justice transaction on // Carol's behalf sweeping her funds without a reward. -func testRevokedCloseRetributionAltruistWatchtower(net *lntest.NetworkHarness, - t *harnessTest) { - +func testRevokedCloseRetributionAltruistWatchtower(ht *lntemp.HarnessTest) { testCases := []struct { name string anchors bool @@ -597,23 +592,25 @@ func testRevokedCloseRetributionAltruistWatchtower(net *lntest.NetworkHarness, for _, tc := range testCases { tc := tc + testFunc := func(ht *lntemp.HarnessTest) { + testRevokedCloseRetributionAltruistWatchtowerCase( + ht, tc.anchors, + ) + } - success := t.t.Run(tc.name, func(tt *testing.T) { - ht := newHarnessTest(tt, net) - ht.RunTestCase(&testCase{ - name: tc.name, - test: func(net1 *lntest.NetworkHarness, t1 *harnessTest) { - testRevokedCloseRetributionAltruistWatchtowerCase( - net1, t1, tc.anchors, - ) - }, + success := ht.Run(tc.name, func(tt *testing.T) { + st := ht.Subtest(tt) + + st.RunTestCase(&lntemp.TestCase{ + Name: tc.name, + TestFunc: testFunc, }) }) if !success { // Log failure time to help relate the lnd logs to the // failure. - t.Logf("Failure time: %v", time.Now().Format( + ht.Logf("Failure time: %v", time.Now().Format( "2006-01-02 15:04:05.000", )) @@ -622,10 +619,9 @@ func testRevokedCloseRetributionAltruistWatchtower(net *lntest.NetworkHarness, } } -func testRevokedCloseRetributionAltruistWatchtowerCase( - net *lntest.NetworkHarness, t *harnessTest, anchors bool) { +func testRevokedCloseRetributionAltruistWatchtowerCase(ht *lntemp.HarnessTest, + anchors bool) { - ctxb := context.Background() const ( chanAmt = funding.MaxBtcFundingAmount paymentAmt = 10000 @@ -639,49 +635,32 @@ func testRevokedCloseRetributionAltruistWatchtowerCase( if anchors { carolArgs = append(carolArgs, "--protocol.anchors") } - carol := net.NewNode(t.t, "Carol", carolArgs) - defer shutdownAndAssert(net, t, carol) + carol := ht.NewNode("Carol", carolArgs) // Willy the watchtower will protect Dave from Carol's breach. He will // remain online in order to punish Carol on Dave's behalf, since the // breach will happen while Dave is offline. - willy := net.NewNode(t.t, "Willy", []string{ - "--watchtower.active", - "--watchtower.externalip=" + externalIP, - }) - defer shutdownAndAssert(net, t, willy) - - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - willyInfo, err := willy.Watchtower.GetInfo( - ctxt, &watchtowerrpc.GetInfoRequest{}, + willy := ht.NewNode( + "Willy", []string{"--watchtower.active", + "--watchtower.externalip=" + externalIP}, ) - if err != nil { - t.Fatalf("unable to getinfo from willy: %v", err) - } + + willyInfo := willy.RPC.GetInfoWatchtower() // Assert that Willy has one listener and it is 0.0.0.0:9911 or // [::]:9911. Since no listener is explicitly specified, one of these // should be the default depending on whether the host supports IPv6 or // not. - if len(willyInfo.Listeners) != 1 { - t.Fatalf("Willy should have 1 listener, has %d", - len(willyInfo.Listeners)) - } + require.Len(ht, willyInfo.Listeners, 1, "Willy should have 1 listener") listener := willyInfo.Listeners[0] if listener != "0.0.0.0:9911" && listener != "[::]:9911" { - t.Fatalf("expected listener on 0.0.0.0:9911 or [::]:9911, "+ + ht.Fatalf("expected listener on 0.0.0.0:9911 or [::]:9911, "+ "got %v", listener) } // Assert the Willy's URIs properly display the chosen external IP. - if len(willyInfo.Uris) != 1 { - t.Fatalf("Willy should have 1 uri, has %d", - len(willyInfo.Uris)) - } - if !strings.Contains(willyInfo.Uris[0], externalIP) { - t.Fatalf("expected uri with %v, got %v", - externalIP, willyInfo.Uris[0]) - } + require.Len(ht, willyInfo.Uris, 1, "Willy should have 1 uri") + require.Contains(ht, willyInfo.Uris[0], externalIP) // Dave will be the breached party. We set --nolisten to ensure Carol // won't be able to connect to him and trigger the channel data @@ -693,110 +672,67 @@ func testRevokedCloseRetributionAltruistWatchtowerCase( if anchors { daveArgs = append(daveArgs, "--protocol.anchors") } - dave := net.NewNode(t.t, "Dave", daveArgs) - defer shutdownAndAssert(net, t, dave) + dave := ht.NewNode("Dave", daveArgs) - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) addTowerReq := &wtclientrpc.AddTowerRequest{ Pubkey: willyInfo.Pubkey, Address: listener, } - if _, err := dave.WatchtowerClient.AddTower(ctxt, addTowerReq); err != nil { - t.Fatalf("unable to add willy's watchtower: %v", err) - } + dave.RPC.AddTower(addTowerReq) // We must let Dave have an open channel before she can send a node // announcement, so we open a channel with Carol, - net.ConnectNodes(t.t, dave, carol) + ht.ConnectNodes(dave, carol) // Before we make a channel, we'll load up Dave with some coins sent // directly from the miner. - net.SendCoins(t.t, btcutil.SatoshiPerBitcoin, dave) + ht.FundCoins(btcutil.SatoshiPerBitcoin, dave) // In order to test Dave's response to an uncooperative channel // closure by Carol, we'll first open up a channel between them with a // 0.5 BTC value. - chanPoint := openChannelAndAssert( - t, net, dave, carol, - lntest.OpenChannelParams{ - Amt: 3 * (chanAmt / 4), - PushAmt: chanAmt / 4, - }, - ) + params := lntemp.OpenChannelParams{ + Amt: 3 * (chanAmt / 4), + PushAmt: chanAmt / 4, + } + chanPoint := ht.OpenChannel(dave, carol, params) // With the channel open, we'll create a few invoices for Carol that // Dave will pay to in order to advance the state of the channel. - carolPayReqs, _, _, err := createPayReqs( - carol, paymentAmt, numInvoices, - ) - if err != nil { - t.Fatalf("unable to create pay reqs: %v", err) - } - - // Wait for Dave to receive the channel edge from the funding manager. - err = dave.WaitForNetworkChannelOpen(chanPoint) - if err != nil { - t.Fatalf("dave didn't see the dave->carol channel before "+ - "timeout: %v", err) - } + carolPayReqs, _, _ := ht.CreatePayReqs(carol, paymentAmt, numInvoices) // Next query for Carol's channel state, as we sent 0 payments, Carol // should still see her balance as the push amount, which is 1/4 of the // capacity. - carolChan, err := getChanInfo(carol) - if err != nil { - t.Fatalf("unable to get carol's channel info: %v", err) - } - if carolChan.LocalBalance != int64(chanAmt/4) { - t.Fatalf("carol's balance is incorrect, got %v, expected %v", - carolChan.LocalBalance, chanAmt/4) - } + carolChan := ht.AssertChannelLocalBalance( + carol, chanPoint, int64(chanAmt/4), + ) // Grab Carol's current commitment height (update number), we'll later // revert her to this state after additional updates to force him to // broadcast this soon to be revoked state. - carolStateNumPreCopy := carolChan.NumUpdates + carolStateNumPreCopy := int(carolChan.NumUpdates) // With the temporary file created, copy Carol's current state into the // temporary file we created above. Later after more updates, we'll // restore this state. - if err := net.BackupDb(carol); err != nil { - t.Fatalf("unable to copy database files: %v", err) - } + ht.BackupDB(carol) // Reconnect the peers after the restart that was needed for the db // backup. - net.EnsureConnected(t.t, dave, carol) + ht.EnsureConnected(dave, carol) - // Finally, send payments from Dave to Carol, consuming Carol's remaining - // payment hashes. - err = completePaymentRequests( - dave, dave.RouterClient, carolPayReqs, false, - ) - if err != nil { - t.Fatalf("unable to send payments: %v", err) - } - - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - daveBalReq := &lnrpc.WalletBalanceRequest{} - daveBalResp, err := dave.WalletBalance(ctxt, daveBalReq) - if err != nil { - t.Fatalf("unable to get dave's balance: %v", err) - } + // Finally, send payments from Dave to Carol, consuming Carol's + // remaining payment hashes. + ht.CompletePaymentRequestsNoWait(dave, carolPayReqs, chanPoint) + daveBalResp := dave.RPC.WalletBalance() davePreSweepBalance := daveBalResp.ConfirmedBalance // Wait until the backup has been accepted by the watchtower before // shutting down Dave. - err = wait.NoError(func() error { - ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) - defer cancel() - bkpStats, err := dave.WatchtowerClient.Stats(ctxt, - &wtclientrpc.StatsRequest{}, - ) - if err != nil { - return err - } + err := wait.NoError(func() error { + bkpStats := dave.RPC.WatchtowerStats() if bkpStats == nil { return errors.New("no active backup sessions") } @@ -806,197 +742,128 @@ func testRevokedCloseRetributionAltruistWatchtowerCase( return nil }, defaultTimeout) - if err != nil { - t.Fatalf("unable to verify backup task completed: %v", err) - } + require.NoError(ht, err, "unable to verify backup task completed") // Shutdown Dave to simulate going offline for an extended period of // time. Once he's not watching, Carol will try to breach the channel. - restart, err := net.SuspendNode(dave) - if err != nil { - t.Fatalf("unable to suspend Dave: %v", err) - } + restart := ht.SuspendNode(dave) // Now we shutdown Carol, copying over the his temporary database state // which has the *prior* channel state over his current most up to date // state. With this, we essentially force Carol to travel back in time // within the channel's history. - if err = net.RestartNode(carol, func() error { - return net.RestoreDb(carol) - }); err != nil { - t.Fatalf("unable to restart node: %v", err) - } + ht.RestartNodeAndRestoreDB(carol) // Now query for Carol's channel state, it should show that he's at a // state number in the past, not the *latest* state. - carolChan, err = getChanInfo(carol) - if err != nil { - t.Fatalf("unable to get carol chan info: %v", err) - } - if carolChan.NumUpdates != carolStateNumPreCopy { - t.Fatalf("db copy failed: %v", carolChan.NumUpdates) - } + ht.AssertChannelCommitHeight(carol, chanPoint, carolStateNumPreCopy) // Now force Carol to execute a *force* channel closure by unilaterally // broadcasting his current channel state. This is actually the // commitment transaction of a prior *revoked* state, so he'll soon // feel the wrath of Dave's retribution. - closeUpdates, closeTxID, err := net.CloseChannel(carol, chanPoint, true) - if err != nil { - t.Fatalf("unable to close channel: %v", err) - } - - // Query the mempool for the breaching closing transaction, this should - // be broadcast by Carol when she force closes the channel above. - txid, err := waitForTxInMempool(net.Miner.Client, minerMempoolTimeout) - if err != nil { - t.Fatalf("unable to find Carol's force close tx in mempool: %v", - err) - } - if *txid != *closeTxID { - t.Fatalf("expected closeTx(%v) in mempool, instead found %v", - closeTxID, txid) - } + closeUpdates, closeTxID := ht.CloseChannelAssertPending( + carol, chanPoint, true, + ) // Finally, generate a single block, wait for the final close status // update, then ensure that the closing transaction was included in the // block. - block := mineBlocks(t, net, 1, 1)[0] + block := ht.MineBlocksAndAssertNumTxes(1, 1)[0] - breachTXID, err := net.WaitForChannelClose(closeUpdates) - if err != nil { - t.Fatalf("error while waiting for channel close: %v", err) - } - assertTxInBlock(t, block, breachTXID) + breachTXID := ht.WaitForChannelCloseEvent(closeUpdates) + ht.Miner.AssertTxInBlock(block, breachTXID) + + // The breachTXID should match the above closeTxID. + require.EqualValues(ht, breachTXID, closeTxID) // Query the mempool for Dave's justice transaction, this should be // broadcast as Carol's contract breaching transaction gets confirmed // above. - justiceTXID, err := waitForTxInMempool(net.Miner.Client, minerMempoolTimeout) - if err != nil { - t.Fatalf("unable to find Dave's justice tx in mempool: %v", - err) - } - time.Sleep(100 * time.Millisecond) + justiceTXID := ht.Miner.AssertNumTxsInMempool(1)[0] // Query for the mempool transaction found above. Then assert that all // the inputs of this transaction are spending outputs generated by // Carol's breach transaction above. - justiceTx, err := net.Miner.Client.GetRawTransaction(justiceTXID) - if err != nil { - t.Fatalf("unable to query for justice tx: %v", err) - } + justiceTx := ht.Miner.GetRawTransaction(justiceTXID) for _, txIn := range justiceTx.MsgTx().TxIn { - if !bytes.Equal(txIn.PreviousOutPoint.Hash[:], breachTXID[:]) { - t.Fatalf("justice tx not spending commitment utxo "+ - "instead is: %v", txIn.PreviousOutPoint) - } + require.Equal(ht, breachTXID[:], txIn.PreviousOutPoint.Hash[:], + "justice tx not spending commitment utxo") } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - willyBalReq := &lnrpc.WalletBalanceRequest{} - willyBalResp, err := willy.WalletBalance(ctxt, willyBalReq) - if err != nil { - t.Fatalf("unable to get willy's balance: %v", err) - } - - if willyBalResp.ConfirmedBalance != 0 { - t.Fatalf("willy should have 0 balance before mining "+ - "justice transaction, instead has %d", - willyBalResp.ConfirmedBalance) - } + willyBalResp := willy.RPC.WalletBalance() + require.Zero(ht, willyBalResp.ConfirmedBalance, + "willy should have 0 balance before mining justice transaction") // Now mine a block, this transaction should include Dave's justice // transaction which was just accepted into the mempool. - block = mineBlocks(t, net, 1, 1)[0] + block = ht.MineBlocksAndAssertNumTxes(1, 1)[0] // The block should have exactly *two* transactions, one of which is // the justice transaction. - if len(block.Transactions) != 2 { - t.Fatalf("transaction wasn't mined") - } + require.Len(ht, block.Transactions, 2, "transaction wasn't mined") justiceSha := block.Transactions[1].TxHash() - if !bytes.Equal(justiceTx.Hash()[:], justiceSha[:]) { - t.Fatalf("justice tx wasn't mined") - } + require.Equal(ht, justiceTx.Hash()[:], justiceSha[:], + "justice tx wasn't mined") // Ensure that Willy doesn't get any funds, as he is acting as an // altruist watchtower. - var predErr error - err = wait.Invariant(func() bool { - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - willyBalReq := &lnrpc.WalletBalanceRequest{} - willyBalResp, err := willy.WalletBalance(ctxt, willyBalReq) - if err != nil { - t.Fatalf("unable to get willy's balance: %v", err) - } + err = wait.NoError(func() error { + willyBalResp := willy.RPC.WalletBalance() if willyBalResp.ConfirmedBalance != 0 { - predErr = fmt.Errorf("Expected Willy to have no funds "+ + return fmt.Errorf("Expected Willy to have no funds "+ "after justice transaction was mined, found %v", willyBalResp) - return false } - return true + return nil }, time.Second*5) - if err != nil { - t.Fatalf("%v", predErr) - } + require.NoError(ht, err, "timeout checking willy's balance") + + // Before restarting Dave, shutdown Carol so Dave won't sync with her. + // Otherwise, during the restart, Dave will realize Carol is falling + // behind and return `ErrCommitSyncRemoteDataLoss`, thus force closing + // the channel. Although this force close tx will be later replaced by + // the breach tx, it will create two anchor sweeping txes for neutrino + // backend, causing the confirmed wallet balance to be zero later on + // because the utxos are used in sweeping. + ht.Shutdown(carol) // Restart Dave, who will still think his channel with Carol is open. // We should him to detect the breach, but realize that the funds have // then been swept to his wallet by Willy. - err = restart() - if err != nil { - t.Fatalf("unable to restart dave: %v", err) - } - - err = wait.Predicate(func() bool { - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - daveBalReq := &lnrpc.ChannelBalanceRequest{} - daveBalResp, err := dave.ChannelBalance(ctxt, daveBalReq) - if err != nil { - t.Fatalf("unable to get dave's balance: %v", err) - } + require.NoError(ht, restart(), "unable to restart dave") + err = wait.NoError(func() error { + daveBalResp := dave.RPC.ChannelBalance() if daveBalResp.LocalBalance.Sat != 0 { - predErr = fmt.Errorf("Dave should end up with zero "+ + return fmt.Errorf("Dave should end up with zero "+ "channel balance, instead has %d", daveBalResp.LocalBalance.Sat) - return false } - return true + return nil }, defaultTimeout) - if err != nil { - t.Fatalf("%v", predErr) - } + require.NoError(ht, err, "timeout checking dave's channel balance") - assertNumPendingChannels(t, dave, 0, 0) + ht.AssertNumPendingForceClose(dave, 0) - err = wait.Predicate(func() bool { - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - daveBalReq := &lnrpc.WalletBalanceRequest{} - daveBalResp, err := dave.WalletBalance(ctxt, daveBalReq) - if err != nil { - t.Fatalf("unable to get dave's balance: %v", err) - } + // Check that Dave's wallet balance is increased. + err = wait.NoError(func() error { + daveBalResp := dave.RPC.WalletBalance() if daveBalResp.ConfirmedBalance <= davePreSweepBalance { - predErr = fmt.Errorf("Dave should have more than %d "+ + return fmt.Errorf("Dave should have more than %d "+ "after sweep, instead has %d", davePreSweepBalance, daveBalResp.ConfirmedBalance) - return false } - return true + return nil }, defaultTimeout) - if err != nil { - t.Fatalf("%v", predErr) - } + require.NoError(ht, err, "timeout checking dave's wallet balance") // Dave should have no open channels. - assertNodeNumChannels(t, dave, 0) + ht.AssertNodeNumChannels(dave, 0) } diff --git a/lntest/itest/lnd_test_list_on_test.go b/lntest/itest/lnd_test_list_on_test.go index 926c3fca2..84a629795 100644 --- a/lntest/itest/lnd_test_list_on_test.go +++ b/lntest/itest/lnd_test_list_on_test.go @@ -72,10 +72,6 @@ var allTestCases = []*testCase{ name: "switch offline delivery outgoing offline", test: testSwitchOfflineDeliveryOutgoingOffline, }, - { - name: "revoked uncooperative close retribution altruist watchtower", - test: testRevokedCloseRetributionAltruistWatchtower, - }, { name: "query routes", test: testQueryRoutes, From 8df5de3e438ea337194c80f095450ea6e1e9f03c Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Mon, 16 Jan 2023 07:28:23 +0800 Subject: [PATCH 20/44] lntest+itest: make sure mempool is cleaned in watcher tower test This commit adds the missing check for anchor sweep in test `revoked_uncooperative_close_retribution_altruist_watchtower`, also change the mempool check a bit so it's easier to track. --- lntemp/harness.go | 9 +++------ lntest/itest/lnd_revocation_test.go | 5 +++++ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/lntemp/harness.go b/lntemp/harness.go index 1d0001d02..c8d4d8c0b 100644 --- a/lntemp/harness.go +++ b/lntemp/harness.go @@ -349,10 +349,6 @@ func (h *HarnessTest) Subtest(t *testing.T) *HarnessTest { return } - // We require the mempool to be cleaned from the test. - require.Empty(st, st.Miner.GetRawMempool(), "mempool not "+ - "cleaned, please mine blocks to clean them all.") - // When we finish the test, reset the nodes' configs and take a // snapshot of each of the nodes' internal states. for _, node := range st.manager.standbyNodes { @@ -362,8 +358,9 @@ func (h *HarnessTest) Subtest(t *testing.T) *HarnessTest { // If found running nodes, shut them down. st.shutdownNonStandbyNodes() - // Assert that mempool is cleaned - st.Miner.AssertNumTxsInMempool(0) + // We require the mempool to be cleaned from the test. + require.Empty(st, st.Miner.GetRawMempool(), "mempool not "+ + "cleaned, please mine blocks to clean them all.") // Finally, cancel the run context. We have to do it here // because we need to keep the context alive for the above diff --git a/lntest/itest/lnd_revocation_test.go b/lntest/itest/lnd_revocation_test.go index 20561c039..47da07f74 100644 --- a/lntest/itest/lnd_revocation_test.go +++ b/lntest/itest/lnd_revocation_test.go @@ -849,6 +849,11 @@ func testRevokedCloseRetributionAltruistWatchtowerCase(ht *lntemp.HarnessTest, ht.AssertNumPendingForceClose(dave, 0) + // If this is an anchor channel, Dave would sweep the anchor. + if anchors { + ht.MineBlocksAndAssertNumTxes(1, 1) + } + // Check that Dave's wallet balance is increased. err = wait.NoError(func() error { daveBalResp := dave.RPC.WalletBalance() From ede86cf08f32bc9d9189c7dd9806eff116b2a74f Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Mon, 16 Jan 2023 07:33:36 +0800 Subject: [PATCH 21/44] itest: send one more UTXO for neutrino backend Send another UTXO if this is a neutrino backend. When sweeping anchors, there are two transactions created, `local_sweep_tx` for sweeping Alice's anchor on the local commitment, `remote_sweep_tx` for sweeping her anchor on the remote commitment. Whenever the force close transaction is published, Alice will always create these two transactions to sweep her anchor. On the other hand, when creating the sweep txes, the anchor itself is not able to cover the fee, so another wallet UTXO is needed. In our test case, there's a change output that can be used from the above funding process. And it's used by both sweep txes - when `lnd` happens to create the `remote_sweep_tx` first, it will receive an error since its parent tx, the remote commitment, is not known, hence freeing the change output to be used by `local_sweep_tx`. For neutrino client, however, it will consider the transaction which sweeps the remote anchor as an orphan tx, and it will neither send it to the mempool nor return an error to free the change output. Thus, if the change output is already used in `remote_sweep_tx`, we won't have UTXO to create `local_sweep_tx`. --- lntest/itest/lnd_revocation_test.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lntest/itest/lnd_revocation_test.go b/lntest/itest/lnd_revocation_test.go index 47da07f74..bff6e6760 100644 --- a/lntest/itest/lnd_revocation_test.go +++ b/lntest/itest/lnd_revocation_test.go @@ -688,6 +688,11 @@ func testRevokedCloseRetributionAltruistWatchtowerCase(ht *lntemp.HarnessTest, // directly from the miner. ht.FundCoins(btcutil.SatoshiPerBitcoin, dave) + // Send one more UTXOs if this is a neutrino backend. + if ht.IsNeutrinoBackend() { + ht.FundCoins(btcutil.SatoshiPerBitcoin, dave) + } + // In order to test Dave's response to an uncooperative channel // closure by Carol, we'll first open up a channel between them with a // 0.5 BTC value. From c5785967aad81be276c56aa5f876cd6d16567a22 Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Tue, 9 Aug 2022 01:20:00 +0800 Subject: [PATCH 22/44] lntemp+itest: refactor `testSingleHopSendToRoute` --- lntemp/harness_assertion.go | 28 +++ lntemp/rpc/router.go | 14 ++ lntest/itest/list_on_test.go | 7 +- lntest/itest/lnd_routing_test.go | 279 +++++++------------------- lntest/itest/lnd_test_list_on_test.go | 4 - 5 files changed, 116 insertions(+), 216 deletions(-) diff --git a/lntemp/harness_assertion.go b/lntemp/harness_assertion.go index 7c5879d92..a1c98be2d 100644 --- a/lntemp/harness_assertion.go +++ b/lntemp/harness_assertion.go @@ -2014,3 +2014,31 @@ func (h *HarnessTest) AssertChannelCommitHeight(hn *node.HarnessNode, require.NoError(h, err, "timeout while waiting for commit height") } + +// AssertNumInvoices asserts that the number of invoices made within the test +// scope is as expected. +func (h *HarnessTest) AssertNumInvoices(hn *node.HarnessNode, + num int) []*lnrpc.Invoice { + + have := hn.State.Invoice.Total + req := &lnrpc.ListInvoiceRequest{ + NumMaxInvoices: math.MaxUint64, + IndexOffset: hn.State.Invoice.LastIndexOffset, + } + + var invoices []*lnrpc.Invoice + err := wait.NoError(func() error { + resp := hn.RPC.ListInvoices(req) + + invoices = resp.Invoices + if len(invoices) == num { + return nil + } + + return errNumNotMatched(hn.Name(), "num of invoices", + num, len(invoices), have+len(invoices), have) + }, DefaultTimeout) + require.NoError(h, err, "timeout checking num of invoices") + + return invoices +} diff --git a/lntemp/rpc/router.go b/lntemp/rpc/router.go index 3a350ed76..2a5293eb0 100644 --- a/lntemp/rpc/router.go +++ b/lntemp/rpc/router.go @@ -3,6 +3,7 @@ package rpc import ( "context" + "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" ) @@ -92,3 +93,16 @@ func (h *HarnessRPC) ResetMissionControl() { _, err := h.Router.ResetMissionControl(ctxt, req) h.NoError(err, "ResetMissionControl") } + +// SendToRouteV2 makes a RPC call to SendToRouteV2 and asserts. +func (h *HarnessRPC) SendToRouteV2( + req *routerrpc.SendToRouteRequest) *lnrpc.HTLCAttempt { + + ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout) + defer cancel() + + resp, err := h.Router.SendToRouteV2(ctxt, req) + h.NoError(err, "SendToRouteV2") + + return resp +} diff --git a/lntest/itest/list_on_test.go b/lntest/itest/list_on_test.go index 2b3acbf06..5e7c0ae67 100644 --- a/lntest/itest/list_on_test.go +++ b/lntest/itest/list_on_test.go @@ -305,7 +305,12 @@ var allTestCasesTemp = []*lntemp.TestCase{ TestFunc: testRevokedCloseRetributionRemoteHodl, }, { - Name: "revoked uncooperative close retribution altruist watchtower", + Name: "revoked uncooperative close retribution altruist " + + "watchtower", TestFunc: testRevokedCloseRetributionAltruistWatchtower, }, + { + Name: "single-hop send to route", + TestFunc: testSingleHopSendToRoute, + }, } diff --git a/lntest/itest/lnd_routing_test.go b/lntest/itest/lnd_routing_test.go index caf5320af..6de35578a 100644 --- a/lntest/itest/lnd_routing_test.go +++ b/lntest/itest/lnd_routing_test.go @@ -1,7 +1,6 @@ package itest import ( - "bytes" "context" "encoding/hex" "fmt" @@ -14,6 +13,7 @@ import ( "github.com/lightningnetwork/lnd/chainreg" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" + "github.com/lightningnetwork/lnd/lntemp" "github.com/lightningnetwork/lnd/lntest" "github.com/lightningnetwork/lnd/lntest/wait" "github.com/lightningnetwork/lnd/lnwire" @@ -68,23 +68,18 @@ var singleHopSendToRouteCases = []singleHopSendToRouteCase{ // 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) { +func testSingleHopSendToRoute(ht *lntemp.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) - }, - }) + ht.Run(test.name, func(t1 *testing.T) { + st := ht.Subtest(t1) + testSingleHopSendToRouteCase(st, test) }) } } -func testSingleHopSendToRouteCase(net *lntest.NetworkHarness, t *harnessTest, +func testSingleHopSendToRouteCase(ht *lntemp.HarnessTest, test singleHopSendToRouteCase) { const chanAmt = btcutil.Amount(100000) @@ -92,96 +87,41 @@ func testSingleHopSendToRouteCase(net *lntest.NetworkHarness, t *harnessTest, 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) + carol := ht.NewNode("Carol", nil) + dave := ht.NewNode("Dave", nil) - dave := net.NewNode(t.t, "Dave", nil) - defer shutdownAndAssert(net, t, dave) - - net.ConnectNodes(t.t, carol, dave) - net.SendCoins(t.t, btcutil.SatoshiPerBitcoin, carol) + ht.ConnectNodes(carol, dave) + ht.FundCoins(btcutil.SatoshiPerBitcoin, carol) // Open a channel with 100k satoshis between Carol and Dave with Carol // being the sole funder of the channel. - chanPointCarol := openChannelAndAssert( - t, net, carol, dave, - lntest.OpenChannelParams{ - Amt: chanAmt, - }, + chanPointCarol := ht.OpenChannel( + carol, dave, lntemp.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, - } - - err = node.WaitForNetworkChannelOpen(chanPoint) - if err != nil { - t.Fatalf("%s(%d): timeout waiting for "+ - "channel(%s) open: %v", node.Name(), - node.NodeID, point, err) - } - } - } + defer ht.CloseChannel(carol, chanPointCarol) // Create invoices for Dave, which expect a payment from Carol. - payReqs, rHashes, _, err := createPayReqs( + payReqs, rHashes, _ := ht.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) - } + resp := dave.RPC.DecodePayReq(payReq) 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) - } - require.NoError(t.t, waitForNodeBlockHeight(carol, minerHeight)) - require.NoError(t.t, waitForNodeBlockHeight(dave, minerHeight)) + _, minerHeight := ht.Miner.GetBestBlock() + ht.WaitForNodeBlockHeight(carol, minerHeight) + ht.WaitForNodeBlockHeight(dave, minerHeight) // Query for routes to pay from Carol to Dave using the default CLTV // config. @@ -189,12 +129,7 @@ func testSingleHopSendToRouteCase(net *lntest.NetworkHarness, t *harnessTest, 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) - } + routes := carol.RPC.QueryRoutes(routesReq) // There should only be one route to try, so take the first item. r := routes.Routes[0] @@ -222,27 +157,14 @@ func testSingleHopSendToRouteCase(net *lntest.NetworkHarness, t *harnessTest, 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) - } + resp := carol.RPC.SendToRouteSync(sendReq) + require.Emptyf(ht, resp.PaymentError, + "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) - } + alicePayStream := carol.RPC.SendToRoute() for i, rHash := range rHashes { setMPPFields(i) @@ -252,19 +174,13 @@ func testSingleHopSendToRouteCase(net *lntest.NetworkHarness, t *harnessTest, Route: routes.Routes[0], } err := alicePayStream.Send(sendReq) - - if err != nil { - t.Fatalf("unable to send payment: %v", err) - } + require.NoError(ht, err, "unable to send payment") 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) - } + require.NoError(ht, err, "unable to receive stream") + require.Emptyf(ht, resp.PaymentError, + "received payment error from %s: %v", + carol.Name(), resp.PaymentError) } } sendToRouteRouterRPC := func() { @@ -275,23 +191,14 @@ func testSingleHopSendToRouteCase(net *lntest.NetworkHarness, t *harnessTest, 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) - } + resp := carol.RPC.SendToRouteV2(sendReq) + require.Nilf(ht, resp.Failure, "received payment "+ + "error from %s", carol.Name()) } } // Using Carol as the node as the source, send the payments - // synchronously via the the routerrpc's SendToRoute, or via the main RPC + // synchronously via the routerrpc's SendToRoute, or via the main RPC // server's SendToRoute streaming or sync calls. switch { case !test.routerrpc && test.streaming: @@ -301,118 +208,70 @@ func testSingleHopSendToRouteCase(net *lntest.NetworkHarness, t *harnessTest, case test.routerrpc && !test.streaming: sendToRouteRouterRPC() default: - t.Fatalf("routerrpc does not support streaming send_to_route") + require.Fail(ht, "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(ctxb, 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) - } + payments := ht.AssertNumPayments(carol, numPayments) - for i, p := range paymentsResp.Payments { + for i, p := range 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) - } + require.Equalf(ht, rHashHex, p.PaymentHash, + "incorrect payment hash for payment %d", i) - // 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 that each payment has no invoice since the payment + // was completed using SendToRoute. + require.Emptyf(ht, p.PaymentRequest, + "incorrect payment request for payment: %d", i) // 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) - } + require.EqualValues(ht, paymentAmtSat, p.ValueSat, + "incorrect payment amt for payment %d, ", i) // 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)) - } + require.Lenf(ht, p.Htlcs, 1, + "expected 1 htlc for payment %d", i) // Assert the htlc's route is populated. htlc := p.Htlcs[0] - if htlc.Route == nil { - t.Fatalf("expected route for payment %d", i) - } + require.NotNilf(ht, htlc.Route, + "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)) - } + require.Lenf(ht, htlc.Route.Hops, 1, + "expected 1 hop for payment %d", i) // 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") - } + require.NotNilf(ht, hop.MppRecord, + "expected mpp record for mpp payment %d", i) - 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) - } + require.EqualValues(ht, paymentAmtSat*1000, + hop.MppRecord.TotalAmtMsat, + "incorrect mpp total msat for payment %d", i) 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) - } + require.Equal(ht, expAddr, hop.MppRecord.PaymentAddr, + "incorrect mpp payment addr for payment %d ", i) } // Verify that the invoices's from Dave's PoV have the correct payment // hash and amount. - ctxt, _ = context.WithTimeout(ctxb, 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) - } + invoices := ht.AssertNumInvoices(dave, numPayments) - for i, inv := range invoicesResp.Invoices { + for i, inv := range 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) - } + require.Equal(ht, rHashes[i], inv.RHash, + "incorrect payment hash for invoice %d", i) // 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) - } + require.EqualValues(ht, paymentAmtSat, inv.AmtPaidSat, + "incorrect payment amt for invoice %d, ", i) } // At this point all the channels within our proto network should be @@ -420,12 +279,10 @@ func testSingleHopSendToRouteCase(net *lntest.NetworkHarness, t *harnessTest, // 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)) - - closeChannelAndAssert(t, net, carol, chanPointCarol, false) + ht.AssertAmountPaid("Carol(local) => Dave(remote)", dave, + chanPointCarol, int64(0), amountPaid) + ht.AssertAmountPaid("Carol(local) => Dave(remote)", carol, + chanPointCarol, amountPaid, int64(0)) } // testMultiHopSendToRoute tests that payments are properly processed diff --git a/lntest/itest/lnd_test_list_on_test.go b/lntest/itest/lnd_test_list_on_test.go index 84a629795..348a6c106 100644 --- a/lntest/itest/lnd_test_list_on_test.go +++ b/lntest/itest/lnd_test_list_on_test.go @@ -8,10 +8,6 @@ var allTestCases = []*testCase{ name: "single hop invoice", test: testSingleHopInvoice, }, - { - name: "single-hop send to route", - test: testSingleHopSendToRoute, - }, { name: "multi-hop send to route", test: testMultiHopSendToRoute, From 787883870b1378520f36abbc4ec65df60bb321ac Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Tue, 9 Aug 2022 01:24:05 +0800 Subject: [PATCH 23/44] itest: refactor `testMultiHopSendToRoute` --- lntest/itest/list_on_test.go | 4 + lntest/itest/lnd_routing_test.go | 165 +++++++------------------- lntest/itest/lnd_test_list_on_test.go | 4 - 3 files changed, 45 insertions(+), 128 deletions(-) diff --git a/lntest/itest/list_on_test.go b/lntest/itest/list_on_test.go index 5e7c0ae67..088897581 100644 --- a/lntest/itest/list_on_test.go +++ b/lntest/itest/list_on_test.go @@ -313,4 +313,8 @@ var allTestCasesTemp = []*lntemp.TestCase{ Name: "single-hop send to route", TestFunc: testSingleHopSendToRoute, }, + { + Name: "multi-hop send to route", + TestFunc: testMultiHopSendToRoute, + }, } diff --git a/lntest/itest/lnd_routing_test.go b/lntest/itest/lnd_routing_test.go index 6de35578a..4a08c6db6 100644 --- a/lntest/itest/lnd_routing_test.go +++ b/lntest/itest/lnd_routing_test.go @@ -292,15 +292,15 @@ func testSingleHopSendToRouteCase(ht *lntemp.HarnessTest, // // 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) { - t.t.Run("with cache", func(tt *testing.T) { - ht := newHarnessTest(tt, t.lndHarness) - runMultiHopSendToRoute(net, ht, true) +func testMultiHopSendToRoute(ht *lntemp.HarnessTest) { + ht.Run("with cache", func(tt *testing.T) { + st := ht.Subtest(tt) + runMultiHopSendToRoute(st, true) }) if *dbBackendFlag == "bbolt" { - t.t.Run("without cache", func(tt *testing.T) { - ht := newHarnessTest(tt, t.lndHarness) - runMultiHopSendToRoute(net, ht, false) + ht.Run("without cache", func(tt *testing.T) { + st := ht.Subtest(tt) + runMultiHopSendToRoute(st, false) }) } } @@ -312,104 +312,45 @@ func testMultiHopSendToRoute(net *lntest.NetworkHarness, t *harnessTest) { // // We'll query the daemon for routes from Alice to Carol and then // send payments through the routes. -func runMultiHopSendToRoute(net *lntest.NetworkHarness, t *harnessTest, - useGraphCache bool) { - - ctxb := context.Background() - +func runMultiHopSendToRoute(ht *lntemp.HarnessTest, useGraphCache bool) { var opts []string if !useGraphCache { opts = append(opts, "--db.no-graph-cache") } - alice := net.NewNode(t.t, "Alice", opts) - defer shutdownAndAssert(net, t, alice) + alice, bob := ht.Alice, ht.Bob + ht.RestartNodeWithExtraArgs(alice, opts) - net.ConnectNodes(t.t, alice, net.Bob) - net.SendCoins(t.t, btcutil.SatoshiPerBitcoin, alice) + ht.EnsureConnected(alice, bob) 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. - chanPointAlice := openChannelAndAssert( - t, net, alice, net.Bob, - lntest.OpenChannelParams{ - Amt: chanAmt, - }, + chanPointAlice := ht.OpenChannel( + alice, bob, lntemp.OpenChannelParams{Amt: chanAmt}, ) - networkChans = append(networkChans, chanPointAlice) + defer ht.CloseChannel(alice, 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: + // 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) + carol := ht.NewNode("Carol", nil) + ht.ConnectNodes(carol, bob) - net.ConnectNodes(t.t, carol, net.Bob) - net.SendCoins(t.t, btcutil.SatoshiPerBitcoin, net.Bob) - - chanPointBob := openChannelAndAssert( - t, net, net.Bob, carol, - lntest.OpenChannelParams{ - Amt: chanAmt, - }, + chanPointBob := ht.OpenChannel( + bob, carol, lntemp.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, - } + defer ht.CloseChannel(carol, chanPointBob) - // Wait for all nodes to have seen all channels. - nodes := []*lntest.HarnessNode{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, - } - - err = node.WaitForNetworkChannelOpen(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. + // 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) - } + _, rHashes, invoices := ht.CreatePayReqs(carol, paymentAmt, numPayments) // Construct a route from Alice to Carol for each of the invoices // created above. We set FinalCltvDelta to 40 since by default @@ -420,30 +361,15 @@ func runMultiHopSendToRoute(net *lntest.NetworkHarness, t *harnessTest, Amt: paymentAmt, FinalCltvDelta: chainreg.DefaultBitcoinTimeLockDelta, } - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - routes, err := 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. - err = carol.WaitForNetworkChannelOpen(chanPointBob) - if err != nil { - t.Fatalf("bob didn't advertise his channel in time: %v", err) - } - - time.Sleep(time.Millisecond * 50) + routes := alice.RPC.QueryRoutes(routesReq) // 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 := routes.Routes[0] route.Hops[len(route.Hops)-1].TlvPayload = true route.Hops[len(route.Hops)-1].MppRecord = &lnrpc.MPPRecord{ PaymentAddr: invoices[i].PaymentAddr, @@ -454,16 +380,10 @@ func runMultiHopSendToRoute(net *lntest.NetworkHarness, t *harnessTest, sendReq := &routerrpc.SendToRouteRequest{ PaymentHash: rHash, - Route: &route, - } - resp, err := 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) + Route: route, } + resp := alice.RPC.SendToRouteV2(sendReq) + require.Nil(ht, resp.Failure, "received payment error") } // When asserting the amount of satoshis moved, we'll factor in the @@ -472,23 +392,20 @@ func runMultiHopSendToRoute(net *lntest.NetworkHarness, t *harnessTest, 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 + // 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)", alice, - aliceFundPoint, amountPaid+(baseFee*numPayments), int64(0)) - - closeChannelAndAssert(t, net, alice, chanPointAlice, false) - closeChannelAndAssert(t, net, carol, chanPointBob, false) + ht.AssertAmountPaid("Bob(local) => Carol(remote)", carol, + chanPointBob, int64(0), amountPaid) + ht.AssertAmountPaid("Bob(local) => Carol(remote)", bob, + chanPointBob, amountPaid, int64(0)) + ht.AssertAmountPaid("Alice(local) => Bob(remote)", bob, + chanPointAlice, int64(0), amountPaid+(baseFee*numPayments)) + ht.AssertAmountPaid("Alice(local) => Bob(remote)", alice, + chanPointAlice, amountPaid+(baseFee*numPayments), int64(0)) } // testSendToRouteErrorPropagation tests propagation of errors that occur diff --git a/lntest/itest/lnd_test_list_on_test.go b/lntest/itest/lnd_test_list_on_test.go index 348a6c106..6a76e255a 100644 --- a/lntest/itest/lnd_test_list_on_test.go +++ b/lntest/itest/lnd_test_list_on_test.go @@ -8,10 +8,6 @@ var allTestCases = []*testCase{ name: "single hop invoice", test: testSingleHopInvoice, }, - { - name: "multi-hop send to route", - test: testMultiHopSendToRoute, - }, { name: "send to route error propagation", test: testSendToRouteErrorPropagation, From 25b6ff33366c920e61af987926f75d55d5d0a85a Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Tue, 9 Aug 2022 01:27:10 +0800 Subject: [PATCH 24/44] itest: refactor `testSendToRouteErrorPropagation` --- lntemp/harness_assertion.go | 32 ++++++++++ lntemp/rpc/lnd.go | 4 +- lntest/itest/list_on_test.go | 4 ++ lntest/itest/lnd_routing_test.go | 90 ++++++++------------------- lntest/itest/lnd_test_list_on_test.go | 4 -- 5 files changed, 64 insertions(+), 70 deletions(-) diff --git a/lntemp/harness_assertion.go b/lntemp/harness_assertion.go index a1c98be2d..e2d7c05e3 100644 --- a/lntemp/harness_assertion.go +++ b/lntemp/harness_assertion.go @@ -2042,3 +2042,35 @@ func (h *HarnessTest) AssertNumInvoices(hn *node.HarnessNode, return invoices } + +// ReceiveSendToRouteUpdate waits until a message is received on the +// SendToRoute client stream or the timeout is reached. +func (h *HarnessTest) ReceiveSendToRouteUpdate( + stream rpc.SendToRouteClient) (*lnrpc.SendResponse, error) { + + chanMsg := make(chan *lnrpc.SendResponse, 1) + errChan := make(chan error, 1) + go func() { + // Consume one message. This will block until the message is + // received. + resp, err := stream.Recv() + if err != nil { + errChan <- err + + return + } + chanMsg <- resp + }() + + select { + case <-time.After(DefaultTimeout): + require.Fail(h, "timeout", "timeout waiting for send resp") + return nil, nil + + case err := <-errChan: + return nil, err + + case updateMsg := <-chanMsg: + return updateMsg, nil + } +} diff --git a/lntemp/rpc/lnd.go b/lntemp/rpc/lnd.go index 7a451e3c2..53156dbec 100644 --- a/lntemp/rpc/lnd.go +++ b/lntemp/rpc/lnd.go @@ -481,8 +481,10 @@ func (h *HarnessRPC) QueryRoutes( return routes } +type SendToRouteClient lnrpc.Lightning_SendToRouteClient + // SendToRoute makes a RPC call to SendToRoute and asserts. -func (h *HarnessRPC) SendToRoute() lnrpc.Lightning_SendToRouteClient { +func (h *HarnessRPC) SendToRoute() SendToRouteClient { // SendToRoute needs to have the context alive for the entire test case // as the returned client will be used for send and receive payment // stream. Thus we use runCtx here instead of a timeout context. diff --git a/lntest/itest/list_on_test.go b/lntest/itest/list_on_test.go index 088897581..242d4b944 100644 --- a/lntest/itest/list_on_test.go +++ b/lntest/itest/list_on_test.go @@ -317,4 +317,8 @@ var allTestCasesTemp = []*lntemp.TestCase{ Name: "multi-hop send to route", TestFunc: testMultiHopSendToRoute, }, + { + Name: "send to route error propagation", + TestFunc: testSendToRouteErrorPropagation, + }, } diff --git a/lntest/itest/lnd_routing_test.go b/lntest/itest/lnd_routing_test.go index 4a08c6db6..da306fbe8 100644 --- a/lntest/itest/lnd_routing_test.go +++ b/lntest/itest/lnd_routing_test.go @@ -4,7 +4,6 @@ import ( "context" "encoding/hex" "fmt" - "strings" "testing" "time" @@ -410,54 +409,32 @@ func runMultiHopSendToRoute(ht *lntemp.HarnessTest, useGraphCache bool) { // 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() - +func testSendToRouteErrorPropagation(ht *lntemp.HarnessTest) { const chanAmt = btcutil.Amount(100000) // Open a channel with 100k satoshis between Alice and Bob with Alice // being the sole funder of the channel. - chanPointAlice := openChannelAndAssert( - t, net, net.Alice, net.Bob, - lntest.OpenChannelParams{ - Amt: chanAmt, - }, + alice, bob := ht.Alice, ht.Bob + chanPointAlice := ht.OpenChannel( + alice, bob, lntemp.OpenChannelParams{Amt: chanAmt}, ) - err := net.Alice.WaitForNetworkChannelOpen(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) + // The network topology should now look like: + // Alice -> Bob; Carol -> Charlie. + carol := ht.NewNode("Carol", nil) + ht.FundCoins(btcutil.SatoshiPerBitcoin, carol) - net.SendCoins(t.t, btcutil.SatoshiPerBitcoin, carol) + charlie := ht.NewNode("Charlie", nil) + ht.FundCoins(btcutil.SatoshiPerBitcoin, charlie) - charlie := net.NewNode(t.t, "Charlie", nil) - defer shutdownAndAssert(net, t, charlie) - - net.SendCoins(t.t, btcutil.SatoshiPerBitcoin, charlie) - - net.ConnectNodes(t.t, carol, charlie) - - chanPointCarol := openChannelAndAssert( - t, net, carol, charlie, - lntest.OpenChannelParams{ - Amt: chanAmt, - }, - ) - err = carol.WaitForNetworkChannelOpen(chanPointCarol) - if err != nil { - t.Fatalf("carol didn't advertise her channel: %v", err) - } + ht.ConnectNodes(carol, charlie) + ht.OpenChannel(carol, charlie, lntemp.OpenChannelParams{Amt: chanAmt}) // Query routes from Carol to Charlie which will be an invalid route // for Alice -> Bob. @@ -465,54 +442,37 @@ func testSendToRouteErrorPropagation(net *lntest.NetworkHarness, t *harnessTest) 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) - } + fakeRoute := carol.RPC.QueryRoutes(fakeReq) - // Create 1 invoices for Bob, which expect a payment from Alice for 1k - // satoshis + // Create 1 invoice 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) - } - + resp := bob.RPC.AddInvoice(invoice) 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) - } + // Using Alice as the source, pay to the invoice from Bob. + alicePayStream := alice.RPC.SendToRoute() sendReq := &lnrpc.SendToRouteRequest{ PaymentHash: rHash, Route: fakeRoute.Routes[0], } - - if err := alicePayStream.Send(sendReq); err != nil { - t.Fatalf("unable to send payment: %v", err) - } + err := alicePayStream.Send(sendReq) + require.NoError(ht, err, "unable to send payment") // At this place we should get an rpc error with notification // that edge is not found on hop(0) - _, err = alicePayStream.Recv() - if 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) - } + event, err := ht.ReceiveSendToRouteUpdate(alicePayStream) + require.NoError(ht, err, "payment stream has been closed but fake "+ + "route has consumed") + require.Contains(ht, event.PaymentError, "UnknownNextPeer") - closeChannelAndAssert(t, net, net.Alice, chanPointAlice, false) - closeChannelAndAssert(t, net, carol, chanPointCarol, false) + ht.CloseChannel(alice, chanPointAlice) } // testPrivateChannels tests that a private channel can be used for diff --git a/lntest/itest/lnd_test_list_on_test.go b/lntest/itest/lnd_test_list_on_test.go index 6a76e255a..7732632a2 100644 --- a/lntest/itest/lnd_test_list_on_test.go +++ b/lntest/itest/lnd_test_list_on_test.go @@ -8,10 +8,6 @@ var allTestCases = []*testCase{ name: "single hop invoice", test: testSingleHopInvoice, }, - { - name: "send to route error propagation", - test: testSendToRouteErrorPropagation, - }, { name: "private channels", test: testPrivateChannels, From 2ce93afd70c676d3374839857e39c3836ddb4633 Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Tue, 9 Aug 2022 01:30:45 +0800 Subject: [PATCH 25/44] itest: refactor `testPrivateChannels` --- lntest/itest/list_on_test.go | 4 + lntest/itest/lnd_routing_test.go | 277 +++++--------------------- lntest/itest/lnd_test_list_on_test.go | 4 - 3 files changed, 53 insertions(+), 232 deletions(-) diff --git a/lntest/itest/list_on_test.go b/lntest/itest/list_on_test.go index 242d4b944..4ba74f732 100644 --- a/lntest/itest/list_on_test.go +++ b/lntest/itest/list_on_test.go @@ -321,4 +321,8 @@ var allTestCasesTemp = []*lntemp.TestCase{ Name: "send to route error propagation", TestFunc: testSendToRouteErrorPropagation, }, + { + Name: "private channels", + TestFunc: testPrivateChannels, + }, } diff --git a/lntest/itest/lnd_routing_test.go b/lntest/itest/lnd_routing_test.go index da306fbe8..430b77ff1 100644 --- a/lntest/itest/lnd_routing_test.go +++ b/lntest/itest/lnd_routing_test.go @@ -478,11 +478,8 @@ func testSendToRouteErrorPropagation(ht *lntemp.HarnessTest) { // 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() - +func testPrivateChannels(ht *lntemp.HarnessTest) { const chanAmt = btcutil.Amount(100000) - var networkChans []*lnrpc.ChannelPoint // We create the following topology: // @@ -496,136 +493,40 @@ func testPrivateChannels(net *lntest.NetworkHarness, t *harnessTest) { // where the 100k channel between Carol and Alice is private. // Open a channel with 200k satoshis between Alice and Bob. - chanPointAlice := openChannelAndAssert( - t, net, net.Alice, net.Bob, - lntest.OpenChannelParams{ - Amt: chanAmt * 2, - }, + alice, bob := ht.Alice, ht.Bob + chanPointAlice := ht.OpenChannel( + alice, bob, lntemp.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) + dave := ht.NewNode("Dave", nil) + ht.ConnectNodes(dave, alice) + ht.FundCoins(btcutil.SatoshiPerBitcoin, dave) - net.ConnectNodes(t.t, dave, net.Alice) - net.SendCoins(t.t, btcutil.SatoshiPerBitcoin, dave) - - chanPointDave := openChannelAndAssert( - t, net, dave, net.Alice, - lntest.OpenChannelParams{ - Amt: chanAmt, - }, + chanPointDave := ht.OpenChannel( + dave, alice, lntemp.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) + carol := ht.NewNode("Carol", nil) + ht.ConnectNodes(carol, dave) + ht.FundCoins(btcutil.SatoshiPerBitcoin, carol) - net.ConnectNodes(t.t, carol, dave) - net.SendCoins(t.t, btcutil.SatoshiPerBitcoin, carol) - - chanPointCarol := openChannelAndAssert( - t, net, carol, dave, - lntest.OpenChannelParams{ - Amt: chanAmt, - }, + chanPointCarol := ht.OpenChannel( + carol, dave, lntemp.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, - } - - err = node.WaitForNetworkChannelOpen(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. - net.ConnectNodes(t.t, carol, net.Alice) - chanOpenUpdate := openChannelStream( - t, net, carol, net.Alice, - lntest.OpenChannelParams{ + ht.ConnectNodes(carol, alice) + chanPointPrivate := ht.OpenChannel( + carol, alice, lntemp.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] - - chanPointPrivate, err := net.WaitForChannelOpen(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, - } - err = net.AssertChannelExists(carol, &privateFundPoint) - if err != nil { - t.Fatalf("unable to assert channel existence: %v", err) - } - err = net.AssertChannelExists(net.Alice, &privateFundPoint) - if err != nil { - t.Fatalf("unable to assert channel existence: %v", err) - } + defer ht.CloseChannel(carol, chanPointPrivate) // The channel should be available for payments between Carol and Alice. // We check this by sending payments from Carol to Bob, that @@ -636,20 +537,10 @@ func testPrivateChannels(net *lntest.NetworkHarness, t *harnessTest) { // 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) + payReqs, _, _ := ht.CreatePayReqs(bob, paymentAmt, numPayments) // Let Carol pay the invoices. - err = completePaymentRequests(carol, carol.RouterClient, payReqs, true) - if err != nil { - t.Fatalf("unable to send payments: %v", err) - } + ht.CompletePaymentRequests(carol, payReqs) // When asserting the amount of satoshis moved, we'll factor in the // default base fee, as we didn't modify the fee structure when @@ -657,128 +548,58 @@ func testPrivateChannels(net *lntest.NetworkHarness, t *harnessTest) { const baseFee = 1 // Bob should have received 140k satoshis from Alice. - assertAmountPaid(t, "Alice(local) => Bob(remote)", net.Bob, - aliceFundPoint, int64(0), 2*paymentAmt) + ht.AssertAmountPaid("Alice(local) => Bob(remote)", bob, + chanPointAlice, int64(0), 2*paymentAmt) // Alice sent 140k to Bob. - assertAmountPaid(t, "Alice(local) => Bob(remote)", net.Alice, - aliceFundPoint, 2*paymentAmt, int64(0)) + ht.AssertAmountPaid("Alice(local) => Bob(remote)", alice, + chanPointAlice, 2*paymentAmt, int64(0)) // Alice received 70k + fee from Dave. - assertAmountPaid(t, "Dave(local) => Alice(remote)", net.Alice, - daveFundPoint, int64(0), paymentAmt+baseFee) + ht.AssertAmountPaid("Dave(local) => Alice(remote)", alice, + chanPointDave, int64(0), paymentAmt+baseFee) // Dave sent 70k+fee to Alice. - assertAmountPaid(t, "Dave(local) => Alice(remote)", dave, - daveFundPoint, paymentAmt+baseFee, int64(0)) + ht.AssertAmountPaid("Dave(local) => Alice(remote)", dave, + chanPointDave, 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) + ht.AssertAmountPaid("Carol(local) => Dave(remote)", dave, + chanPointCarol, 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)) + ht.AssertAmountPaid("Carol(local) => Dave(remote)", carol, + chanPointCarol, 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) + ht.AssertAmountPaid("Carol(local) [private=>] Alice(remote)", + alice, chanPointPrivate, int64(0), paymentAmt+baseFee) // Carol sent 70k+fee to Alice. - assertAmountPaid(t, "Carol(local) [private=>] Alice(remote)", - carol, privateFundPoint, paymentAmt+baseFee, int64(0)) + ht.AssertAmountPaid("Carol(local) [private=>] Alice(remote)", + carol, chanPointPrivate, 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) + payReqs, _, _ = ht.CreatePayReqs(carol, paymentAmt60k, numPayments) // Let Bob pay the invoices. - err = completePaymentRequests( - net.Alice, net.Alice.RouterClient, payReqs, true, - ) - if err != nil { - t.Fatalf("unable to send payments: %v", err) - } + ht.CompletePaymentRequests(alice, payReqs) - // 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) - } + // Carol and Alice should know about 4, while Bob and Dave should only + // know about 3, since one channel is private. + ht.AssertNumEdges(alice, 4, true) + ht.AssertNumEdges(alice, 3, false) + ht.AssertNumEdges(bob, 3, true) + ht.AssertNumEdges(carol, 4, true) + ht.AssertNumEdges(carol, 3, false) + ht.AssertNumEdges(dave, 3, true) // Close all channels. - closeChannelAndAssert(t, net, net.Alice, chanPointAlice, false) - closeChannelAndAssert(t, net, dave, chanPointDave, false) - closeChannelAndAssert(t, net, carol, chanPointCarol, false) - closeChannelAndAssert(t, net, carol, chanPointPrivate, false) + ht.CloseChannel(alice, chanPointAlice) + ht.CloseChannel(dave, chanPointDave) + ht.CloseChannel(carol, chanPointCarol) } // testInvoiceRoutingHints tests that the routing hints for an invoice are diff --git a/lntest/itest/lnd_test_list_on_test.go b/lntest/itest/lnd_test_list_on_test.go index 7732632a2..4b07cb3a5 100644 --- a/lntest/itest/lnd_test_list_on_test.go +++ b/lntest/itest/lnd_test_list_on_test.go @@ -8,10 +8,6 @@ var allTestCases = []*testCase{ name: "single hop invoice", test: testSingleHopInvoice, }, - { - name: "private channels", - test: testPrivateChannels, - }, { name: "invoice routing hints", test: testInvoiceRoutingHints, From 5536f16d4b683c8576a7be6ae98f47ac68f6e557 Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Tue, 9 Aug 2022 01:36:00 +0800 Subject: [PATCH 26/44] itest: refactor `testInvoiceRoutingHints` --- lntest/itest/list_on_test.go | 4 + lntest/itest/lnd_routing_test.go | 172 ++++++++------------------ lntest/itest/lnd_test_list_on_test.go | 4 - 3 files changed, 56 insertions(+), 124 deletions(-) diff --git a/lntest/itest/list_on_test.go b/lntest/itest/list_on_test.go index 4ba74f732..feb1441e2 100644 --- a/lntest/itest/list_on_test.go +++ b/lntest/itest/list_on_test.go @@ -325,4 +325,8 @@ var allTestCasesTemp = []*lntemp.TestCase{ Name: "private channels", TestFunc: testPrivateChannels, }, + { + Name: "invoice routing hints", + TestFunc: testInvoiceRoutingHints, + }, } diff --git a/lntest/itest/lnd_routing_test.go b/lntest/itest/lnd_routing_test.go index 430b77ff1..2932cb2b9 100644 --- a/lntest/itest/lnd_routing_test.go +++ b/lntest/itest/lnd_routing_test.go @@ -604,9 +604,7 @@ func testPrivateChannels(ht *lntemp.HarnessTest) { // testInvoiceRoutingHints tests that the routing hints for an invoice are // created properly. -func testInvoiceRoutingHints(net *lntest.NetworkHarness, t *harnessTest) { - ctxb := context.Background() - +func testInvoiceRoutingHints(ht *lntemp.HarnessTest) { const chanAmt = btcutil.Amount(100000) // Throughout this test, we'll be opening a channel between Alice and @@ -615,169 +613,106 @@ func testInvoiceRoutingHints(net *lntest.NetworkHarness, t *harnessTest) { // 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. - chanPointBob := openChannelAndAssert( - t, net, net.Alice, net.Bob, - lntest.OpenChannelParams{ + // require channels to have enough remote balance to cover the + // invoice's payment. + alice, bob := ht.Alice, ht.Bob + chanPointBob := ht.OpenChannel( + alice, bob, lntemp.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) + // 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 := ht.NewNode("Carol", nil) - net.ConnectNodes(t.t, net.Alice, carol) - chanPointCarol := openChannelAndAssert( - t, net, net.Alice, carol, - lntest.OpenChannelParams{ + ht.ConnectNodes(alice, carol) + chanPointCarol := ht.OpenChannel( + alice, carol, lntemp.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 + // 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. - net.ConnectNodes(t.t, net.Bob, carol) - chanPointBobCarol := openChannelAndAssert( - t, net, net.Bob, carol, - lntest.OpenChannelParams{ + ht.ConnectNodes(bob, carol) + chanPointBobCarol := ht.OpenChannel( + bob, carol, lntemp.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) + // 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 := ht.NewNode("Dave", nil) - net.ConnectNodes(t.t, net.Alice, dave) - chanPointDave := openChannelAndAssert( - t, net, net.Alice, dave, - lntest.OpenChannelParams{ + ht.ConnectNodes(alice, dave) + chanPointDave := ht.OpenChannel( + alice, dave, lntemp.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 + // 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) - net.ConnectNodes(t.t, net.Alice, eve) - chanPointEve := openChannelAndAssert( - t, net, net.Alice, eve, - lntest.OpenChannelParams{ + eve := ht.NewNode("Eve", nil) + ht.ConnectNodes(alice, eve) + chanPointEve := ht.OpenChannel( + alice, eve, lntemp.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 { - err := net.Alice.WaitForNetworkChannelOpen(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) + // Now that the channels are open, we'll disconnect the connection + // between Alice and Eve and then take down Eve's node. + ht.DisconnectNodes(alice, eve) + ht.Shutdown(eve) // 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) - require.NoError(t.t, err, "unable to retrieve alice's channels") - - var aliceBobChanID uint64 - for _, channel := range listResp.Channels { - if channel.RemotePubkey == net.Bob.PubKeyStr { - aliceBobChanID = channel.ChanId - } - } - - require.NotZero(t.t, aliceBobChanID, - "channel between alice and bob not found") + aliceBobChanID := ht.QueryChannelByChanPoint(alice, chanPointBob).ChanId checkInvoiceHints := func(invoice *lnrpc.Invoice) { // 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: %w", err) - return false - } + err := wait.NoError(func() error { + resp := alice.RPC.AddInvoice(invoice) // 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: %w", err) - - return false - } + decoded = alice.RPC.DecodePayReq(resp.PaymentRequest) if len(decoded.RouteHints) != 1 { - predErr = fmt.Errorf( - "expected one route hint, got %d", - len(decoded.RouteHints)) - - return false + return fmt.Errorf("expected one route hint, "+ + "got %d", len(decoded.RouteHints)) } - return true + return nil }, defaultTimeout) - if err != nil { - t.t.Fatalf(predErr.Error()) - } + require.NoError(ht, err, "timeout checking invoice hints") hops := decoded.RouteHints[0].HopHints - if len(hops) != 1 { - t.t.Fatalf("expected one hop in route hint, got %d", - len(hops)) - } - chanID := hops[0].ChanId + require.Len(ht, hops, 1, "expected one hop in route hint") - if chanID != aliceBobChanID { - t.t.Fatalf("expected channel ID %d, got %d", - aliceBobChanID, chanID) - } + chanID := hops[0].ChanId + require.Equal(ht, aliceBobChanID, chanID, "chanID mismatch") } // Create an invoice for Alice that will populate the routing hints. @@ -799,17 +734,14 @@ func testInvoiceRoutingHints(net *lntest.NetworkHarness, t *harnessTest) { // Now that we've confirmed the routing hints were added correctly, we // can close all the channels and shut down all the nodes created. - closeChannelAndAssert(t, net, net.Alice, chanPointBob, false) - closeChannelAndAssert(t, net, net.Alice, chanPointCarol, false) - closeChannelAndAssert(t, net, net.Bob, chanPointBobCarol, false) - closeChannelAndAssert(t, net, net.Alice, chanPointDave, false) + ht.CloseChannel(alice, chanPointBob) + ht.CloseChannel(alice, chanPointCarol) + ht.CloseChannel(bob, chanPointBobCarol) + ht.CloseChannel(alice, chanPointDave) // The channel between Alice and Eve should be force closed since Eve // is offline. - closeChannelAndAssert(t, net, net.Alice, chanPointEve, true) - - // Cleanup by mining the force close and sweep transaction. - cleanupForceClose(t, net, net.Alice, chanPointEve) + ht.ForceCloseChannel(alice, chanPointEve) } // testMultiHopOverPrivateChannels tests that private channels can be used as diff --git a/lntest/itest/lnd_test_list_on_test.go b/lntest/itest/lnd_test_list_on_test.go index 4b07cb3a5..26c668727 100644 --- a/lntest/itest/lnd_test_list_on_test.go +++ b/lntest/itest/lnd_test_list_on_test.go @@ -8,10 +8,6 @@ var allTestCases = []*testCase{ name: "single hop invoice", test: testSingleHopInvoice, }, - { - name: "invoice routing hints", - test: testInvoiceRoutingHints, - }, { name: "multi-hop payments over private channels", test: testMultiHopOverPrivateChannels, From ba6a722ad8ed44c647c494d51bce4de2d14fd753 Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Tue, 9 Aug 2022 01:39:19 +0800 Subject: [PATCH 27/44] itest: refactor `testMultiHopOverPrivateChannels` --- lntest/itest/list_on_test.go | 4 + lntest/itest/lnd_routing_test.go | 167 +++++++------------------- lntest/itest/lnd_test_list_on_test.go | 4 - 3 files changed, 45 insertions(+), 130 deletions(-) diff --git a/lntest/itest/list_on_test.go b/lntest/itest/list_on_test.go index feb1441e2..772b0a656 100644 --- a/lntest/itest/list_on_test.go +++ b/lntest/itest/list_on_test.go @@ -329,4 +329,8 @@ var allTestCasesTemp = []*lntemp.TestCase{ Name: "invoice routing hints", TestFunc: testInvoiceRoutingHints, }, + { + Name: "multi-hop payments over private channels", + TestFunc: testMultiHopOverPrivateChannels, + }, } diff --git a/lntest/itest/lnd_routing_test.go b/lntest/itest/lnd_routing_test.go index 2932cb2b9..c325849ee 100644 --- a/lntest/itest/lnd_routing_test.go +++ b/lntest/itest/lnd_routing_test.go @@ -746,9 +746,7 @@ func testInvoiceRoutingHints(ht *lntemp.HarnessTest) { // 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() - +func testMultiHopOverPrivateChannels(ht *lntemp.HarnessTest) { // 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 @@ -757,115 +755,42 @@ func testMultiHopOverPrivateChannels(net *lntest.NetworkHarness, t *harnessTest) // First, we'll open a private channel between Alice and Bob with Alice // being the funder. - chanPointAlice := openChannelAndAssert( - t, net, net.Alice, net.Bob, - lntest.OpenChannelParams{ + alice, bob := ht.Alice, ht.Bob + chanPointAlice := ht.OpenChannel( + alice, bob, lntemp.OpenChannelParams{ Amt: chanAmt, Private: true, }, ) - err := net.Alice.WaitForNetworkChannelOpen(chanPointAlice) - if err != nil { - t.Fatalf("alice didn't see the channel alice <-> bob before "+ - "timeout: %v", err) - } - err = net.Bob.WaitForNetworkChannelOpen(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) - - net.ConnectNodes(t.t, net.Bob, carol) - chanPointBob := openChannelAndAssert( - t, net, net.Bob, carol, - lntest.OpenChannelParams{ + carol := ht.NewNode("Carol", nil) + ht.ConnectNodes(bob, carol) + chanPointBob := ht.OpenChannel( + bob, carol, lntemp.OpenChannelParams{ Amt: chanAmt, }, ) - err = net.Bob.WaitForNetworkChannelOpen(chanPointBob) - if err != nil { - t.Fatalf("bob didn't see the channel bob <-> carol before "+ - "timeout: %v", err) - } - err = carol.WaitForNetworkChannelOpen(chanPointBob) - if err != nil { - t.Fatalf("carol didn't see the channel bob <-> carol before "+ - "timeout: %v", err) - } - err = net.Alice.WaitForNetworkChannelOpen(chanPointBob) - if err != nil { - t.Fatalf("alice didn't see the channel bob <-> carol before "+ - "timeout: %v", err) - } + // Alice should know the new channel from Bob. + ht.AssertTopologyChannelOpen(alice, chanPointBob) - // 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 := ht.NewNode("Dave", nil) + ht.ConnectNodes(carol, dave) + ht.FundCoins(btcutil.SatoshiPerBitcoin, carol) - // 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) - - net.ConnectNodes(t.t, carol, dave) - net.SendCoins(t.t, btcutil.SatoshiPerBitcoin, carol) - - chanPointCarol := openChannelAndAssert( - t, net, carol, dave, - lntest.OpenChannelParams{ + chanPointCarol := ht.OpenChannel( + carol, dave, lntemp.OpenChannelParams{ Amt: chanAmt, Private: true, }, ) - err = carol.WaitForNetworkChannelOpen(chanPointCarol) - if err != nil { - t.Fatalf("carol didn't see the channel carol <-> dave before "+ - "timeout: %v", err) - } - err = dave.WaitForNetworkChannelOpen(chanPointCarol) - if err != nil { - t.Fatalf("dave didn't see the channel carol <-> dave before "+ - "timeout: %v", err) - } - err = dave.WaitForNetworkChannelOpen(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, - } + // Dave should know the channel[Bob<->Carol] from Carol. + ht.AssertTopologyChannelOpen(dave, chanPointBob) // 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 @@ -880,21 +805,11 @@ func testMultiHopOverPrivateChannels(net *lntest.NetworkHarness, t *harnessTest) 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) - } + resp := dave.RPC.AddInvoice(invoice) // Let Alice pay the invoice. payReqs := []string{resp.PaymentRequest} - err = completePaymentRequests( - net.Alice, net.Alice.RouterClient, payReqs, true, - ) - if err != nil { - t.Fatalf("unable to send payments from alice to dave: %v", err) - } + ht.CompletePaymentRequests(alice, payReqs) // 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 @@ -902,39 +817,34 @@ func testMultiHopOverPrivateChannels(net *lntest.NetworkHarness, t *harnessTest) const baseFee = 1 // Dave should have received 20k satoshis from Carol. - assertAmountPaid(t, "Carol(local) [private=>] Dave(remote)", - dave, carolFundPoint, 0, paymentAmt) + ht.AssertAmountPaid("Carol(local) [private=>] Dave(remote)", + dave, chanPointCarol, 0, paymentAmt) // Carol should have sent 20k satoshis to Dave. - assertAmountPaid(t, "Carol(local) [private=>] Dave(remote)", - carol, carolFundPoint, paymentAmt, 0) + ht.AssertAmountPaid("Carol(local) [private=>] Dave(remote)", + carol, chanPointCarol, 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) + ht.AssertAmountPaid("Bob(local) => Carol(remote)", + carol, chanPointBob, 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) + ht.AssertAmountPaid("Bob(local) => Carol(remote)", + bob, chanPointBob, 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) + ht.AssertAmountPaid("Alice(local) [private=>] Bob(remote)", bob, + chanPointAlice, 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) + ht.AssertAmountPaid("Alice(local) [private=>] Bob(remote)", alice, + chanPointAlice, 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. - closeChannelAndAssert(t, net, net.Alice, chanPointAlice, false) - closeChannelAndAssert(t, net, net.Bob, chanPointBob, false) - closeChannelAndAssert(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 + ht.CloseChannel(alice, chanPointAlice) + ht.CloseChannel(bob, chanPointBob) + ht.CloseChannel(carol, chanPointCarol) } // testQueryRoutes checks the response of queryroutes. @@ -1460,3 +1370,8 @@ func testRouteFeeCutoff(net *lntest.NetworkHarness, t *harnessTest) { closeChannelAndAssert(t, net, net.Bob, chanPointBobDave, false) closeChannelAndAssert(t, net, carol, chanPointCarolDave, false) } + +// computeFee calculates the payment fee as specified in BOLT07. +func computeFee(baseFee, feeRate, amt lnwire.MilliSatoshi) lnwire.MilliSatoshi { + return baseFee + amt*feeRate/1000000 +} diff --git a/lntest/itest/lnd_test_list_on_test.go b/lntest/itest/lnd_test_list_on_test.go index 26c668727..7b444e26d 100644 --- a/lntest/itest/lnd_test_list_on_test.go +++ b/lntest/itest/lnd_test_list_on_test.go @@ -8,10 +8,6 @@ var allTestCases = []*testCase{ name: "single hop invoice", test: testSingleHopInvoice, }, - { - name: "multi-hop payments over private channels", - test: testMultiHopOverPrivateChannels, - }, { name: "multiple channel creation and update subscription", test: testBasicChannelCreationAndUpdates, From b2dc9d839388ca864e2462ac08b96116bae19ce0 Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Tue, 9 Aug 2022 01:47:50 +0800 Subject: [PATCH 28/44] lntemp+itest: refactor `testQueryRoutes` --- lntemp/harness.go | 35 +++++ lntemp/rpc/router.go | 42 ++++++ lntest/itest/list_on_test.go | 4 + lntest/itest/lnd_routing_test.go | 193 +++++++++----------------- lntest/itest/lnd_test_list_on_test.go | 4 - 5 files changed, 148 insertions(+), 130 deletions(-) diff --git a/lntemp/harness.go b/lntemp/harness.go index c8d4d8c0b..33134cac8 100644 --- a/lntemp/harness.go +++ b/lntemp/harness.go @@ -1724,3 +1724,38 @@ func (h *HarnessTest) DisconnectMiner() { err := h.manager.chainBackend.DisconnectMiner() require.NoError(h, err, "failed to disconnect miner") } + +// QueryRoutesAndRetry attempts to keep querying a route until timeout is +// reached. +// +// NOTE: when a channel is opened, we may need to query multiple times to get +// it in our QueryRoutes RPC. This happens even after we check the channel is +// heard by the node using ht.AssertChannelOpen. Deep down, this is because our +// GraphTopologySubscription and QueryRoutes give different results regarding a +// specific channel, with the formal reporting it being open while the latter +// not, resulting GraphTopologySubscription acting "faster" than QueryRoutes. +// TODO(yy): make sure related subsystems share the same view on a given +// channel. +func (h *HarnessTest) QueryRoutesAndRetry(hn *node.HarnessNode, + req *lnrpc.QueryRoutesRequest) *lnrpc.QueryRoutesResponse { + + var routes *lnrpc.QueryRoutesResponse + err := wait.NoError(func() error { + ctxt, cancel := context.WithCancel(h.runCtx) + defer cancel() + + resp, err := hn.RPC.LN.QueryRoutes(ctxt, req) + if err != nil { + return fmt.Errorf("%s: failed to query route: %w", + hn.Name(), err) + } + + routes = resp + + return nil + }, DefaultTimeout) + + require.NoError(h, err, "timeout querying routes") + + return routes +} diff --git a/lntemp/rpc/router.go b/lntemp/rpc/router.go index 2a5293eb0..70f09fc10 100644 --- a/lntemp/rpc/router.go +++ b/lntemp/rpc/router.go @@ -5,6 +5,7 @@ import ( "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" + "github.com/stretchr/testify/require" ) // ===================== @@ -106,3 +107,44 @@ func (h *HarnessRPC) SendToRouteV2( return resp } + +// QueryProbability makes a RPC call to the node's QueryProbability and +// asserts. +// +//nolint:lll +func (h *HarnessRPC) QueryProbability( + req *routerrpc.QueryProbabilityRequest) *routerrpc.QueryProbabilityResponse { + + ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout) + defer cancel() + + resp, err := h.Router.QueryProbability(ctxt, req) + h.NoError(err, "QueryProbability") + + return resp +} + +// XImportMissionControl makes a RPC call to the node's XImportMissionControl +// and asserts. +func (h *HarnessRPC) XImportMissionControl( + req *routerrpc.XImportMissionControlRequest) { + + ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout) + defer cancel() + + _, err := h.Router.XImportMissionControl(ctxt, req) + h.NoError(err, "XImportMissionControl") +} + +// XImportMissionControlAssertErr makes a RPC call to the node's +// XImportMissionControl +// and asserts an error occurred. +func (h *HarnessRPC) XImportMissionControlAssertErr( + req *routerrpc.XImportMissionControlRequest) { + + ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout) + defer cancel() + + _, err := h.Router.XImportMissionControl(ctxt, req) + require.Error(h, err, "expect an error from x import mission control") +} diff --git a/lntest/itest/list_on_test.go b/lntest/itest/list_on_test.go index 772b0a656..986464284 100644 --- a/lntest/itest/list_on_test.go +++ b/lntest/itest/list_on_test.go @@ -333,4 +333,8 @@ var allTestCasesTemp = []*lntemp.TestCase{ Name: "multi-hop payments over private channels", TestFunc: testMultiHopOverPrivateChannels, }, + { + Name: "query routes", + TestFunc: testQueryRoutes, + }, } diff --git a/lntest/itest/lnd_routing_test.go b/lntest/itest/lnd_routing_test.go index c325849ee..22a1d2956 100644 --- a/lntest/itest/lnd_routing_test.go +++ b/lntest/itest/lnd_routing_test.go @@ -13,6 +13,7 @@ import ( "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" "github.com/lightningnetwork/lnd/lntemp" + "github.com/lightningnetwork/lnd/lntemp/node" "github.com/lightningnetwork/lnd/lntest" "github.com/lightningnetwork/lnd/lntest/wait" "github.com/lightningnetwork/lnd/lnwire" @@ -853,73 +854,41 @@ func testMultiHopOverPrivateChannels(ht *lntemp.HarnessTest) { // Alice --> Bob --> Carol --> Dave // // and query the daemon for routes from Alice to Dave. -func testQueryRoutes(net *lntest.NetworkHarness, t *harnessTest) { - ctxb := context.Background() - +func testQueryRoutes(ht *lntemp.HarnessTest) { const chanAmt = btcutil.Amount(100000) - var networkChans []*lnrpc.ChannelPoint - // Open a channel between Alice and Bob. - chanPointAlice := openChannelAndAssert( - t, net, net.Alice, net.Bob, - lntest.OpenChannelParams{ - Amt: chanAmt, - }, - ) - networkChans = append(networkChans, chanPointAlice) + // Grab Alice and Bob from the standby nodes. + alice, bob := ht.Alice, ht.Bob - // Create Carol and establish a channel from Bob. - carol := net.NewNode(t.t, "Carol", nil) - defer shutdownAndAssert(net, t, carol) + // Create Carol and connect her to Bob. We also send her some coins for + // channel opening. + carol := ht.NewNode("Carol", nil) + ht.ConnectNodes(carol, bob) + ht.FundCoins(btcutil.SatoshiPerBitcoin, carol) - net.ConnectNodes(t.t, carol, net.Bob) - net.SendCoins(t.t, btcutil.SatoshiPerBitcoin, net.Bob) + // Create Dave and connect him to Carol. + dave := ht.NewNode("Dave", nil) + ht.ConnectNodes(dave, carol) - chanPointBob := openChannelAndAssert( - 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) - - net.ConnectNodes(t.t, dave, carol) - net.SendCoins(t.t, btcutil.SatoshiPerBitcoin, carol) - - chanPointCarol := openChannelAndAssert( - 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, - } - - err = node.WaitForNetworkChannelOpen(chanPoint) - if err != nil { - t.Fatalf("%s(%d): timeout waiting for "+ - "channel(%s) open: %v", nodeNames[i], - node.NodeID, point, err) - } - } + // We now proceed to open channels: + // Alice=>Bob, Bob=>Carol and Carol=>Dave. + p := lntemp.OpenChannelParams{Amt: chanAmt} + reqs := []*lntemp.OpenChannelRequest{ + {Local: alice, Remote: bob, Param: p}, + {Local: bob, Remote: carol, Param: p}, + {Local: carol, Remote: dave, Param: p}, } + resp := ht.OpenMultiChannelsAsync(reqs) + + // Extract channel points from the response. + chanPointAlice := resp[0] + chanPointBob := resp[1] + chanPointCarol := resp[2] + + // Before we continue, give Alice some time to catch up with the newly + // opened channels. + ht.AssertTopologyChannelOpen(alice, chanPointBob) + ht.AssertTopologyChannelOpen(alice, chanPointCarol) // Query for routes to pay from Alice to Dave. const paymentAmt = 1000 @@ -927,11 +896,7 @@ func testQueryRoutes(net *lntest.NetworkHarness, t *harnessTest) { 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) - } + routesRes := ht.QueryRoutesAndRetry(alice, routesReq) const mSat = 1000 feePerHopMSat := computeFee(1000, 1, paymentAmt*mSat) @@ -942,22 +907,22 @@ func testQueryRoutes(net *lntest.NetworkHarness, t *harnessTest) { expectedTotalAmtMSat := (paymentAmt * mSat) + expectedTotalFeesMSat if route.TotalFees != route.TotalFeesMsat/mSat { // nolint:staticcheck - t.Fatalf("route %v: total fees %v (msat) does not "+ + ht.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", + ht.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 "+ + ht.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", + ht.Fatalf("route %v: total amt in msat expected %v got %v", i, expectedTotalAmtMSat, route.TotalAmtMsat) } @@ -968,22 +933,22 @@ func testQueryRoutes(net *lntest.NetworkHarness, t *harnessTest) { expectedAmtToForwardMSat -= feePerHopMSat if hop.Fee != hop.FeeMsat/mSat { // nolint:staticcheck - t.Fatalf("route %v hop %v: fee %v (msat) does not "+ + ht.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", + ht.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 "+ + ht.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 "+ + ht.Fatalf("route %v hop %v: amt to forward in msat "+ "expected %v got %v", i, j, expectedAmtToForwardMSat, hop.AmtToForwardMsat) } @@ -993,17 +958,17 @@ func testQueryRoutes(net *lntest.NetworkHarness, t *harnessTest) { 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)", + ht.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 "+ + ht.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 "+ + ht.Fatalf("route %v hop %v: amt to forward in msat "+ "expected %v got %v", i, len(route.Hops)-1, paymentAmt*mSat, hop.AmtToForwardMsat) } @@ -1012,27 +977,23 @@ func testQueryRoutes(net *lntest.NetworkHarness, t *harnessTest) { // 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[:], - ) + testMissionControlCfg(ht.T, alice) + testMissionControlImport(ht, alice, bob.PubKey[:], carol.PubKey[:]) // We clean up the test case by closing channels that were created for // the duration of the tests. - closeChannelAndAssert(t, net, net.Alice, chanPointAlice, false) - closeChannelAndAssert(t, net, net.Bob, chanPointBob, false) - closeChannelAndAssert(t, net, carol, chanPointCarol, false) + ht.CloseChannel(alice, chanPointAlice) + ht.CloseChannel(bob, chanPointBob) + ht.CloseChannel(carol, chanPointCarol) } // 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) +func testMissionControlCfg(t *testing.T, hn *node.HarnessNode) { + t.Helper() + + startCfg := hn.RPC.GetMissionControlConfig() cfg := &routerrpc.MissionControlConfig{ HalfLifeSeconds: 8000, @@ -1042,40 +1003,25 @@ func testMissionControlCfg(t *testing.T, node *lntest.HarnessNode) { MinimumFailureRelaxInterval: 60, } - _, err = node.RouterClient.SetMissionControlConfig( - ctxb, &routerrpc.SetMissionControlConfigRequest{ - Config: cfg, - }, - ) - require.NoError(t, err) + hn.RPC.SetMissionControlConfig(cfg) - resp, err := node.RouterClient.GetMissionControlConfig( - ctxb, &routerrpc.GetMissionControlConfigRequest{}, - ) - require.NoError(t, err) + resp := hn.RPC.GetMissionControlConfig() require.True(t, proto.Equal(cfg, resp.Config)) - _, err = node.RouterClient.SetMissionControlConfig( - ctxb, &routerrpc.SetMissionControlConfigRequest{ - Config: startCfg.Config, - }, - ) - require.NoError(t, err) + hn.RPC.SetMissionControlConfig(startCfg.Config) + + resp = hn.RPC.GetMissionControlConfig() + require.True(t, proto.Equal(startCfg.Config, resp.Config)) } // testMissionControlImport tests import of mission control results from an // external source. -func testMissionControlImport(t *testing.T, node *lntest.HarnessNode, +func testMissionControlImport(ht *lntemp.HarnessTest, hn *node.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") + hn.RPC.ResetMissionControl() // Get our baseline probability for a 10 msat hop between our target // nodes. @@ -1093,10 +1039,9 @@ func testMissionControlImport(t *testing.T, node *lntest.HarnessNode, // 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) + resp1 := hn.RPC.QueryProbability(probReq) + require.Zero(ht, resp1.History.FailTime) + require.Zero(ht, resp1.History.FailAmtMsat) // Now, we import a single entry which tracks a failure of the amount // we want to query between our nodes. @@ -1109,20 +1054,16 @@ func testMissionControlImport(t *testing.T, node *lntest.HarnessNode, }, }, } + hn.RPC.XImportMissionControl(req) - _, 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) + resp2 := hn.RPC.QueryProbability(probReq) + require.Equal(ht, importHistory.FailTime, resp2.History.FailTime) + require.Equal(ht, 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") + hn.RPC.XImportMissionControlAssertErr(req) } // testRouteFeeCutoff tests that we are able to prevent querying routes and diff --git a/lntest/itest/lnd_test_list_on_test.go b/lntest/itest/lnd_test_list_on_test.go index 7b444e26d..ad207a3d7 100644 --- a/lntest/itest/lnd_test_list_on_test.go +++ b/lntest/itest/lnd_test_list_on_test.go @@ -48,10 +48,6 @@ var allTestCases = []*testCase{ name: "switch offline delivery outgoing offline", test: testSwitchOfflineDeliveryOutgoingOffline, }, - { - name: "query routes", - test: testQueryRoutes, - }, { name: "route fee cutoff", test: testRouteFeeCutoff, From 368635ef95a8c164240b1271c827928b2ba48f1a Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Tue, 9 Aug 2022 02:00:03 +0800 Subject: [PATCH 29/44] lntemp+itest: refactor `testRouteFeeCutoff` --- lntemp/harness.go | 8 ++ lntest/itest/list_on_test.go | 4 + lntest/itest/lnd_routing_test.go | 153 +++++++++----------------- lntest/itest/lnd_test_list_on_test.go | 4 - 4 files changed, 61 insertions(+), 108 deletions(-) diff --git a/lntemp/harness.go b/lntemp/harness.go index 33134cac8..3f4754417 100644 --- a/lntemp/harness.go +++ b/lntemp/harness.go @@ -1439,6 +1439,14 @@ func (h *HarnessTest) SendPaymentAssertFail(hn *node.HarnessNode, return payment } +// SendPaymentAssertSettled sends a payment from the passed node and asserts the +// payment is settled. +func (h *HarnessTest) SendPaymentAssertSettled(hn *node.HarnessNode, + req *routerrpc.SendPaymentRequest) *lnrpc.Payment { + + return h.SendPaymentAndAssertStatus(hn, req, lnrpc.Payment_SUCCEEDED) +} + // OpenChannelRequest is used to open a channel using the method // OpenMultiChannelsAsync. type OpenChannelRequest struct { diff --git a/lntest/itest/list_on_test.go b/lntest/itest/list_on_test.go index 986464284..ca6ca11e4 100644 --- a/lntest/itest/list_on_test.go +++ b/lntest/itest/list_on_test.go @@ -337,4 +337,8 @@ var allTestCasesTemp = []*lntemp.TestCase{ Name: "query routes", TestFunc: testQueryRoutes, }, + { + Name: "route fee cutoff", + TestFunc: testRouteFeeCutoff, + }, } diff --git a/lntest/itest/lnd_routing_test.go b/lntest/itest/lnd_routing_test.go index 22a1d2956..a27300337 100644 --- a/lntest/itest/lnd_routing_test.go +++ b/lntest/itest/lnd_routing_test.go @@ -1,20 +1,17 @@ package itest import ( - "context" "encoding/hex" "fmt" "testing" "time" "github.com/btcsuite/btcd/btcutil" - "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/chainreg" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" "github.com/lightningnetwork/lnd/lntemp" "github.com/lightningnetwork/lnd/lntemp/node" - "github.com/lightningnetwork/lnd/lntest" "github.com/lightningnetwork/lnd/lntest/wait" "github.com/lightningnetwork/lnd/lnwire" "github.com/stretchr/testify/require" @@ -1068,9 +1065,7 @@ func testMissionControlImport(ht *lntemp.HarnessTest, hn *node.HarnessNode, // 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() - +func testRouteFeeCutoff(ht *lntemp.HarnessTest) { // For this test, we'll create the following topology: // // --- Bob --- @@ -1085,75 +1080,45 @@ func testRouteFeeCutoff(net *lntest.NetworkHarness, t *harnessTest) { const chanAmt = btcutil.Amount(100000) // Open a channel between Alice and Bob. - chanPointAliceBob := openChannelAndAssert( - t, net, net.Alice, net.Bob, - lntest.OpenChannelParams{ - Amt: chanAmt, - }, + alice, bob := ht.Alice, ht.Bob + chanPointAliceBob := ht.OpenChannel( + alice, bob, lntemp.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) + carol := ht.NewNode("Carol", nil) + ht.ConnectNodes(carol, alice) + ht.FundCoins(btcutil.SatoshiPerBitcoin, carol) - net.ConnectNodes(t.t, carol, net.Alice) - net.SendCoins(t.t, btcutil.SatoshiPerBitcoin, carol) - - chanPointAliceCarol := openChannelAndAssert( - t, net, net.Alice, carol, - lntest.OpenChannelParams{ - Amt: chanAmt, - }, + chanPointAliceCarol := ht.OpenChannel( + alice, carol, lntemp.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) - - net.ConnectNodes(t.t, dave, net.Bob) - chanPointBobDave := openChannelAndAssert( - t, net, net.Bob, dave, - lntest.OpenChannelParams{ - Amt: chanAmt, - }, + dave := ht.NewNode("Dave", nil) + ht.ConnectNodes(dave, bob) + chanPointBobDave := ht.OpenChannel( + bob, dave, lntemp.OpenChannelParams{Amt: chanAmt}, ) // Open a channel between Carol and Dave. - net.ConnectNodes(t.t, carol, dave) - chanPointCarolDave := openChannelAndAssert( - t, net, carol, dave, - lntest.OpenChannelParams{ - Amt: chanAmt, - }, + ht.ConnectNodes(carol, dave) + chanPointCarolDave := ht.OpenChannel( + carol, dave, lntemp.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"} + nodes := []*node.HarnessNode{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, - } - - err = node.WaitForNetworkChannelOpen(chanPoint) - if err != nil { - t.Fatalf("%s(%d) timed out waiting for "+ - "channel(%s) open: %v", nodeNames[i], - node.NodeID, outpoint, err) - } + for _, node := range nodes { + ht.AssertTopologyChannelOpen(node, chanPoint) } } @@ -1184,56 +1149,35 @@ func testRouteFeeCutoff(net *lntest.NetworkHarness, t *harnessTest) { ChanPoint: chanPointCarolDave, }, } - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - if _, err := carol.UpdateChannelPolicy(ctxt, updateFeeReq); err != nil { - t.Fatalf("unable to update chan policy: %v", err) - } + carol.RPC.UpdateChannelPolicy(updateFeeReq) // Wait for Alice to receive the channel update from Carol. - assertChannelPolicyUpdate( - t.t, net.Alice, carol.PubKeyStr, - expectedPolicy, chanPointCarolDave, false, + ht.AssertChannelPolicyUpdate( + alice, carol, expectedPolicy, chanPointCarolDave, false, ) // 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) - } + channel := ht.QueryChannelByChanPoint(bob, chanPointAliceBob) + aliceBobChanID := channel.ChanId + require.NotZero(ht, aliceBobChanID, + "channel between alice and bob not found") - 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 - } - } + channel = ht.QueryChannelByChanPoint(bob, chanPointBobDave) + bobDaveChanID := channel.ChanId + require.NotZero(ht, bobDaveChanID, + "channel between bob and dave not found") - 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)) - } + require.Len(ht, route.Hops, 2, "expected two hops") for i, hop := range route.Hops { - if hop.ChanId != hopChanIDs[i] { - t.Fatalf("expected chan id %d, got %d", - hopChanIDs[i], hop.ChanId) - } + require.Equal(ht, hopChanIDs[i], hop.ChanId, + "hop chan id not match") } } @@ -1252,20 +1196,12 @@ func testRouteFeeCutoff(net *lntest.NetworkHarness, t *harnessTest) { 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) - } + routesResp := alice.RPC.QueryRoutes(queryRoutesReq) 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) - } + invoiceResp := dave.RPC.AddInvoice(invoice) sendReq := &routerrpc.SendPaymentRequest{ PaymentRequest: invoiceResp.PaymentRequest, @@ -1279,7 +1215,7 @@ func testRouteFeeCutoff(net *lntest.NetworkHarness, t *harnessTest) { sendReq.FeeLimitMsat = 1000 * paymentAmt * limit.Percent / 100 } - result := sendAndAssertSuccess(t, net.Alice, sendReq) + result := ht.SendPaymentAssertSettled(alice, sendReq) checkRoute(result.Htlcs[0].Route) } @@ -1304,12 +1240,21 @@ func testRouteFeeCutoff(net *lntest.NetworkHarness, t *harnessTest) { } testFeeCutoff(feeLimitFixed) + // TODO(yy): remove the sleep once the following bug is fixed. When the + // payment is reported as settled by Carol, it's expected the + // commitment dance is finished and all subsequent states have been + // updated. Yet we'd receive the error `cannot co-op close channel with + // active htlcs` or `link failed to shutdown` if we close the channel. + // We need to investigate the order of settling the payments and + // updating commitments to understand and fix . + time.Sleep(2 * time.Second) + // Once we're done, close the channels and shut down the nodes created // throughout this test. - closeChannelAndAssert(t, net, net.Alice, chanPointAliceBob, false) - closeChannelAndAssert(t, net, net.Alice, chanPointAliceCarol, false) - closeChannelAndAssert(t, net, net.Bob, chanPointBobDave, false) - closeChannelAndAssert(t, net, carol, chanPointCarolDave, false) + ht.CloseChannel(alice, chanPointAliceBob) + ht.CloseChannel(alice, chanPointAliceCarol) + ht.CloseChannel(bob, chanPointBobDave) + ht.CloseChannel(carol, chanPointCarolDave) } // computeFee calculates the payment fee as specified in BOLT07. diff --git a/lntest/itest/lnd_test_list_on_test.go b/lntest/itest/lnd_test_list_on_test.go index ad207a3d7..31f8217a2 100644 --- a/lntest/itest/lnd_test_list_on_test.go +++ b/lntest/itest/lnd_test_list_on_test.go @@ -48,10 +48,6 @@ var allTestCases = []*testCase{ name: "switch offline delivery outgoing offline", test: testSwitchOfflineDeliveryOutgoingOffline, }, - { - name: "route fee cutoff", - test: testRouteFeeCutoff, - }, { name: "cpfp", test: testCPFP, From ad77a45112701d5526b00ffd7be7c1db6e063aa0 Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Tue, 9 Aug 2022 12:40:27 +0800 Subject: [PATCH 30/44] lntemp+itest: refactor node restart process This commit refactors how the node restart is done. It removes the usage of `HasSeed` and replaces it with `SkipUnlock` for clarity. --- lntemp/harness.go | 35 +++++++-- lntemp/harness_node_manager.go | 97 +++++++++++-------------- lntemp/node/config.go | 6 +- lntemp/node/harness_node.go | 4 +- lntest/itest/lnd_channel_backup_test.go | 5 +- 5 files changed, 80 insertions(+), 67 deletions(-) diff --git a/lntemp/harness.go b/lntemp/harness.go index 3f4754417..8acbf4a42 100644 --- a/lntemp/harness.go +++ b/lntemp/harness.go @@ -486,13 +486,33 @@ func (h *HarnessTest) SuspendNode(node *node.HarnessNode) func() error { } } -// RestartNode restarts a given node and asserts. -func (h *HarnessTest) RestartNode(hn *node.HarnessNode, +// RestartNode restarts a given node, unlocks it and asserts it's successfully +// started. +func (h *HarnessTest) RestartNode(hn *node.HarnessNode) { + err := h.manager.restartNode(h.runCtx, hn, nil) + require.NoErrorf(h, err, "failed to restart node %s", hn.Name()) + + err = h.manager.unlockNode(hn) + require.NoErrorf(h, err, "failed to unlock node %s", hn.Name()) + + if !hn.Cfg.SkipUnlock { + // Give the node some time to catch up with the chain before we + // continue with the tests. + h.WaitForBlockchainSync(hn) + } +} + +// RestartNodeWithChanBackups restarts a given node with the specified channel +// backups. +func (h *HarnessTest) RestartNodeWithChanBackups(hn *node.HarnessNode, chanBackups ...*lnrpc.ChanBackupSnapshot) { - err := h.manager.restartNode(h.runCtx, hn, nil, chanBackups...) + err := h.manager.restartNode(h.runCtx, hn, nil) require.NoErrorf(h, err, "failed to restart node %s", hn.Name()) + err = h.manager.unlockNode(hn, chanBackups...) + require.NoErrorf(h, err, "failed to unlock node %s", hn.Name()) + // Give the node some time to catch up with the chain before we // continue with the tests. h.WaitForBlockchainSync(hn) @@ -503,7 +523,7 @@ func (h *HarnessTest) RestartNodeWithExtraArgs(hn *node.HarnessNode, extraArgs []string) { hn.SetExtraArgs(extraArgs) - h.RestartNode(hn, nil) + h.RestartNode(hn) } // NewNodeWithSeed fully initializes a new HarnessNode after creating a fresh @@ -538,7 +558,7 @@ func (h *HarnessTest) newNodeWithSeed(name string, // Start the node with seed only, which will only create the `State` // and `WalletUnlocker` clients. - err = node.StartWithSeed(h.runCtx) + err = node.StartWithNoAuth(h.runCtx) require.NoErrorf(h, err, "failed to start node %s", node.Name()) // Generate a new seed. @@ -581,7 +601,7 @@ func (h *HarnessTest) RestoreNodeWithSeed(name string, extraArgs []string, // Start the node with seed only, which will only create the `State` // and `WalletUnlocker` clients. - err = node.StartWithSeed(h.runCtx) + err = node.StartWithNoAuth(h.runCtx) require.NoErrorf(h, err, "failed to start node %s", node.Name()) // Create the wallet. @@ -1344,6 +1364,9 @@ func (h *HarnessTest) RestartNodeAndRestoreDB(hn *node.HarnessNode) { err := h.manager.restartNode(h.runCtx, hn, cb) require.NoErrorf(h, err, "failed to restart node %s", hn.Name()) + err = h.manager.unlockNode(hn) + require.NoErrorf(h, err, "failed to unlock node %s", hn.Name()) + // Give the node some time to catch up with the chain before we // continue with the tests. h.WaitForBlockchainSync(hn) diff --git a/lntemp/harness_node_manager.go b/lntemp/harness_node_manager.go index 0b6aa038c..1ee502354 100644 --- a/lntemp/harness_node_manager.go +++ b/lntemp/harness_node_manager.go @@ -71,7 +71,7 @@ func (nm *nodeManager) nextNodeID() uint32 { // node can be used immediately. Otherwise, the node will require an additional // initialization phase where the wallet is either created or restored. func (nm *nodeManager) newNode(t *testing.T, name string, extraArgs []string, - password []byte, useSeed bool) (*node.HarnessNode, error) { + password []byte, noAuth bool) (*node.HarnessNode, error) { cfg := &node.BaseNodeConfig{ Name: name, @@ -84,7 +84,7 @@ func (nm *nodeManager) newNode(t *testing.T, name string, extraArgs []string, NodeID: nm.nextNodeID(), LndBinary: nm.lndBinary, NetParams: harnessNetParams, - HasSeed: useSeed, + SkipUnlock: noAuth, } node, err := node.NewHarnessNode(t, cfg) @@ -125,56 +125,11 @@ func (nm *nodeManager) shutdownNode(node *node.HarnessNode) error { // the connection attempt is successful. If the callback parameter is non-nil, // then the function will be executed after the node shuts down, but *before* // the process has been started up again. -// -// This method can be useful when testing edge cases such as a node broadcast -// and invalidated prior state, or persistent state recovery, simulating node -// crashes, etc. Additionally, each time the node is restarted, the caller can -// pass a set of SCBs to pass in via the Unlock method allowing them to restore -// channels during restart. -func (nm *nodeManager) restartNode(ctxt context.Context, node *node.HarnessNode, - callback func() error, chanBackups ...*lnrpc.ChanBackupSnapshot) error { +func (nm *nodeManager) restartNode(ctxt context.Context, + hn *node.HarnessNode, callback func() error) error { - err := nm.restartNodeNoUnlock(ctxt, node, callback) - if err != nil { - return err - } - - // If the node doesn't have a password set, then we can exit here as we - // don't need to unlock it. - if len(node.Cfg.Password) == 0 { - return nil - } - - // Otherwise, we'll unlock the wallet, then complete the final steps - // for the node initialization process. - unlockReq := &lnrpc.UnlockWalletRequest{ - WalletPassword: node.Cfg.Password, - } - if len(chanBackups) != 0 { - unlockReq.ChannelBackups = chanBackups[0] - unlockReq.RecoveryWindow = 100 - } - - err = wait.NoError(func() error { - return node.Unlock(unlockReq) - }, DefaultTimeout) - if err != nil { - return fmt.Errorf("%s: failed to unlock: %w", node.Name(), err) - } - - return nil -} - -// restartNodeNoUnlock attempts to restart a lightning node by shutting it down -// cleanly, then restarting the process. In case the node was setup with a -// seed, it will be left in the unlocked state. This function is fully -// blocking. If the callback parameter is non-nil, then the function will be -// executed after the node shuts down, but *before* the process has been -// started up again. -func (nm *nodeManager) restartNodeNoUnlock(ctxt context.Context, - node *node.HarnessNode, callback func() error) error { - - if err := node.Stop(); err != nil { + // Stop the node. + if err := hn.Stop(); err != nil { return fmt.Errorf("restart node got error: %w", err) } @@ -184,11 +139,45 @@ func (nm *nodeManager) restartNodeNoUnlock(ctxt context.Context, } } - if node.Cfg.HasSeed { - return node.StartWithSeed(ctxt) + // Start the node without unlocking the wallet. + if hn.Cfg.SkipUnlock { + return hn.StartWithNoAuth(ctxt) } - return node.Start(ctxt) + return hn.Start(ctxt) +} + +// unlockNode unlocks the node's wallet if the password is configured. +// Additionally, each time the node is unlocked, the caller can pass a set of +// SCBs to pass in via the Unlock method allowing them to restore channels +// during restart. +func (nm *nodeManager) unlockNode(hn *node.HarnessNode, + chanBackups ...*lnrpc.ChanBackupSnapshot) error { + + // If the node doesn't have a password set, then we can exit here as we + // don't need to unlock it. + if len(hn.Cfg.Password) == 0 { + return nil + } + + // Otherwise, we'll unlock the wallet, then complete the final steps + // for the node initialization process. + unlockReq := &lnrpc.UnlockWalletRequest{ + WalletPassword: hn.Cfg.Password, + } + if len(chanBackups) != 0 { + unlockReq.ChannelBackups = chanBackups[0] + unlockReq.RecoveryWindow = 100 + } + + err := wait.NoError(func() error { + return hn.Unlock(unlockReq) + }, DefaultTimeout) + if err != nil { + return fmt.Errorf("%s: failed to unlock: %w", hn.Name(), err) + } + + return nil } // initWalletAndNode will unlock the node's wallet and finish setting up the diff --git a/lntemp/node/config.go b/lntemp/node/config.go index 84e1d9a71..c0f12d70b 100644 --- a/lntemp/node/config.go +++ b/lntemp/node/config.go @@ -63,8 +63,8 @@ type BaseNodeConfig struct { ReadMacPath string InvoiceMacPath string - HasSeed bool - Password []byte + SkipUnlock bool + Password []byte P2PPort int RPCPort int @@ -190,7 +190,7 @@ func (cfg *BaseNodeConfig) GenArgs() []string { } args = append(args, nodeArgs...) - if !cfg.HasSeed { + if cfg.Password == nil { args = append(args, "--noseedbackup") } diff --git a/lntemp/node/harness_node.go b/lntemp/node/harness_node.go index f8be79dfc..230be2a68 100644 --- a/lntemp/node/harness_node.go +++ b/lntemp/node/harness_node.go @@ -387,12 +387,12 @@ func (hn *HarnessNode) StartLndCmd(ctxb context.Context) error { return nil } -// StartWithSeed will start the lnd process, creates the grpc connection +// StartWithNoAuth will start the lnd process, creates the grpc connection // without macaroon auth, and waits until the server is reported as waiting to // start. // // NOTE: caller needs to take extra step to create and unlock the wallet. -func (hn *HarnessNode) StartWithSeed(ctxt context.Context) error { +func (hn *HarnessNode) StartWithNoAuth(ctxt context.Context) error { // Start lnd process and prepare logs. if err := hn.StartLndCmd(ctxt); err != nil { return fmt.Errorf("start lnd error: %w", err) diff --git a/lntest/itest/lnd_channel_backup_test.go b/lntest/itest/lnd_channel_backup_test.go index f99b37af1..d254bb9f2 100644 --- a/lntest/itest/lnd_channel_backup_test.go +++ b/lntest/itest/lnd_channel_backup_test.go @@ -349,8 +349,9 @@ func testChannelBackupRestoreBasic(ht *lntemp.HarnessTest) { "", revocationWindow, nil, copyPorts(oldNode), ) - - st.RestartNode(newNode, backupSnapshot) + st.RestartNodeWithChanBackups( + newNode, backupSnapshot, + ) return newNode } From 84278d6a4939ba4431a11058b9f549fd803e5e05 Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Mon, 16 Jan 2023 07:49:16 +0800 Subject: [PATCH 31/44] lntemp+itest: refactor testRPCMiddlewareInterceptor --- lntemp/rpc/lnd.go | 16 ++ lntest/itest/list_on_test.go | 4 + lntest/itest/lnd_macaroons_test.go | 39 ++- .../lnd_rpc_middleware_interceptor_test.go | 263 ++++++++++-------- lntest/itest/lnd_test_list_on_test.go | 4 - 5 files changed, 195 insertions(+), 131 deletions(-) diff --git a/lntemp/rpc/lnd.go b/lntemp/rpc/lnd.go index 53156dbec..894bfdbd3 100644 --- a/lntemp/rpc/lnd.go +++ b/lntemp/rpc/lnd.go @@ -611,3 +611,19 @@ func (h *HarnessRPC) ForwardingHistory( return resp } + +type MiddlewareClient lnrpc.Lightning_RegisterRPCMiddlewareClient + +// RegisterRPCMiddleware makes a RPC call to the node's RegisterRPCMiddleware +// and asserts. It also returns a cancel context which can cancel the context +// used by the client. +func (h *HarnessRPC) RegisterRPCMiddleware() (MiddlewareClient, + context.CancelFunc) { + + ctxt, cancel := context.WithCancel(h.runCtx) + + stream, err := h.LN.RegisterRPCMiddleware(ctxt) + h.NoError(err, "RegisterRPCMiddleware") + + return stream, cancel +} diff --git a/lntest/itest/list_on_test.go b/lntest/itest/list_on_test.go index ca6ca11e4..7ae784dac 100644 --- a/lntest/itest/list_on_test.go +++ b/lntest/itest/list_on_test.go @@ -341,4 +341,8 @@ var allTestCasesTemp = []*lntemp.TestCase{ Name: "route fee cutoff", TestFunc: testRouteFeeCutoff, }, + { + Name: "rpc middleware interceptor", + TestFunc: testRPCMiddlewareInterceptor, + }, } diff --git a/lntest/itest/lnd_macaroons_test.go b/lntest/itest/lnd_macaroons_test.go index 22ae852ba..c6f3354f5 100644 --- a/lntest/itest/lnd_macaroons_test.go +++ b/lntest/itest/lnd_macaroons_test.go @@ -12,6 +12,7 @@ import ( "github.com/golang/protobuf/proto" "github.com/lightningnetwork/lnd/lnrpc" + "github.com/lightningnetwork/lnd/lntemp/node" "github.com/lightningnetwork/lnd/lntest" "github.com/lightningnetwork/lnd/macaroons" "github.com/stretchr/testify/assert" @@ -58,7 +59,7 @@ func testMacaroonAuthentication(net *lntest.NetworkHarness, ht *harnessTest) { []byte("dummy_root_key"), []byte("0"), "itest", macaroon.LatestVersion, ) - cleanup, client := macaroonClient( + cleanup, client := macaroonClientOld( t, testNode, invalidMac, ) defer cleanup() @@ -75,7 +76,7 @@ func testMacaroonAuthentication(net *lntest.NetworkHarness, ht *harnessTest) { testNode.ReadMacPath(), defaultTimeout, ) require.NoError(t, err) - cleanup, client := macaroonClient( + cleanup, client := macaroonClientOld( t, testNode, readonlyMac, ) defer cleanup() @@ -96,7 +97,7 @@ func testMacaroonAuthentication(net *lntest.NetworkHarness, ht *harnessTest) { readonlyMac, macaroons.TimeoutConstraint(-30), ) require.NoError(t, err) - cleanup, client := macaroonClient( + cleanup, client := macaroonClientOld( t, testNode, timeoutMac, ) defer cleanup() @@ -118,7 +119,7 @@ func testMacaroonAuthentication(net *lntest.NetworkHarness, ht *harnessTest) { ), ) require.NoError(t, err) - cleanup, client := macaroonClient( + cleanup, client := macaroonClientOld( t, testNode, invalidIPAddrMac, ) defer cleanup() @@ -141,7 +142,7 @@ func testMacaroonAuthentication(net *lntest.NetworkHarness, ht *harnessTest) { macaroons.IPLockConstraint("127.0.0.1"), ) require.NoError(t, err) - cleanup, client := macaroonClient(t, testNode, adminMac) + cleanup, client := macaroonClientOld(t, testNode, adminMac) defer cleanup() res, err := client.NewAddress(ctxt, newAddrReq) require.NoError(t, err, "get new address") @@ -174,7 +175,7 @@ func testMacaroonAuthentication(net *lntest.NetworkHarness, ht *harnessTest) { customMac := &macaroon.Macaroon{} err = customMac.UnmarshalBinary(customMacBytes) require.NoError(t, err) - cleanup, client := macaroonClient( + cleanup, client := macaroonClientOld( t, testNode, customMac, ) defer cleanup() @@ -426,7 +427,7 @@ func testBakeMacaroon(net *lntest.NetworkHarness, t *harnessTest) { newMac, err := readMacaroonFromHex(bakeResp.Macaroon) require.NoError(t, err) - cleanup, readOnlyClient := macaroonClient( + cleanup, readOnlyClient := macaroonClientOld( t, testNode, newMac, ) defer cleanup() @@ -505,7 +506,7 @@ func testBakeMacaroon(net *lntest.NetworkHarness, t *harnessTest) { testNode.AdminMacPath(), defaultTimeout, ) require.NoError(tt, err) - cleanup, client := macaroonClient(tt, testNode, adminMac) + cleanup, client := macaroonClientOld(tt, testNode, adminMac) defer cleanup() tc.run(ctxt, tt, client) @@ -530,7 +531,7 @@ func testDeleteMacaroonID(net *lntest.NetworkHarness, t *harnessTest) { testNode.AdminMacPath(), defaultTimeout, ) require.NoError(t.t, err) - cleanup, client := macaroonClient(t.t, testNode, adminMac) + cleanup, client := macaroonClientOld(t.t, testNode, adminMac) defer cleanup() // Record the number of macaroon IDs before creation. @@ -595,7 +596,7 @@ func testDeleteMacaroonID(net *lntest.NetworkHarness, t *harnessTest) { // Check that the deleted macaroon can no longer access macaroon:read. deletedMac, err := readMacaroonFromHex(macList[0]) require.NoError(t.t, err) - cleanup, client = macaroonClient(t.t, testNode, deletedMac) + cleanup, client = macaroonClientOld(t.t, testNode, deletedMac) defer cleanup() // Because the macaroon is deleted, it will be treated as an invalid one. @@ -717,7 +718,8 @@ func readMacaroonFromHex(macHex string) (*macaroon.Macaroon, error) { return mac, nil } -func macaroonClient(t *testing.T, testNode *lntest.HarnessNode, +// TODO(yy): remove. +func macaroonClientOld(t *testing.T, testNode *lntest.HarnessNode, mac *macaroon.Macaroon) (func(), lnrpc.LightningClient) { conn, err := testNode.ConnectRPCWithMacaroon(mac) @@ -729,3 +731,18 @@ func macaroonClient(t *testing.T, testNode *lntest.HarnessNode, } return cleanup, lnrpc.NewLightningClient(conn) } + +func macaroonClient(t *testing.T, testNode *node.HarnessNode, + mac *macaroon.Macaroon) (func(), lnrpc.LightningClient) { + + t.Helper() + + conn, err := testNode.ConnectRPCWithMacaroon(mac) + require.NoError(t, err, "connect to alice") + + cleanup := func() { + err := conn.Close() + require.NoError(t, err, "close") + } + return cleanup, lnrpc.NewLightningClient(conn) +} diff --git a/lntest/itest/lnd_rpc_middleware_interceptor_test.go b/lntest/itest/lnd_rpc_middleware_interceptor_test.go index 6aaa37642..f9c1d7b5c 100644 --- a/lntest/itest/lnd_rpc_middleware_interceptor_test.go +++ b/lntest/itest/lnd_rpc_middleware_interceptor_test.go @@ -8,7 +8,8 @@ import ( "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/lnrpc" - "github.com/lightningnetwork/lnd/lntest" + "github.com/lightningnetwork/lnd/lntemp" + "github.com/lightningnetwork/lnd/lntemp/node" "github.com/lightningnetwork/lnd/macaroons" "github.com/lightningnetwork/lnd/zpay32" "github.com/stretchr/testify/require" @@ -18,133 +19,149 @@ import ( // testRPCMiddlewareInterceptor tests that the RPC middleware interceptor can // be used correctly and in a safe way. -func testRPCMiddlewareInterceptor(net *lntest.NetworkHarness, t *harnessTest) { +func testRPCMiddlewareInterceptor(ht *lntemp.HarnessTest) { // Let's first enable the middleware interceptor. - net.Alice.Cfg.ExtraArgs = append( - net.Alice.Cfg.ExtraArgs, "--rpcmiddleware.enable", - ) - err := net.RestartNode(net.Alice, nil) - require.NoError(t.t, err) + // + // NOTE: we cannot use standby nodes here as the test messes with + // middleware interceptor. Thus we also skip the calling of cleanup of + // each of the following subtests because no standby nodes are used. + alice := ht.NewNode("alice", []string{"--rpcmiddleware.enable"}) + bob := ht.NewNode("bob", nil) // Let's set up a channel between Alice and Bob, just to get some useful // data to inspect when doing RPC calls to Alice later. - net.EnsureConnected(t.t, net.Alice, net.Bob) - net.SendCoins(t.t, btcutil.SatoshiPerBitcoin, net.Alice) - _ = openChannelAndAssert( - t, net, net.Alice, net.Bob, lntest.OpenChannelParams{ - Amt: 1_234_567, - }, - ) + ht.EnsureConnected(alice, bob) + ht.FundCoins(btcutil.SatoshiPerBitcoin, alice) + ht.OpenChannel(alice, bob, lntemp.OpenChannelParams{Amt: 1_234_567}) // Load or bake the macaroons that the simulated users will use to // access the RPC. - readonlyMac, err := net.Alice.ReadMacaroon( - net.Alice.ReadMacPath(), defaultTimeout, + readonlyMac, err := alice.ReadMacaroon( + alice.Cfg.ReadMacPath, defaultTimeout, ) - require.NoError(t.t, err) - adminMac, err := net.Alice.ReadMacaroon( - net.Alice.AdminMacPath(), defaultTimeout, + require.NoError(ht, err) + adminMac, err := alice.ReadMacaroon( + alice.Cfg.AdminMacPath, defaultTimeout, ) - require.NoError(t.t, err) + require.NoError(ht, err) customCaveatReadonlyMac, err := macaroons.SafeCopyMacaroon(readonlyMac) - require.NoError(t.t, err) + require.NoError(ht, err) addConstraint := macaroons.CustomConstraint( "itest-caveat", "itest-value", ) - require.NoError(t.t, addConstraint(customCaveatReadonlyMac)) + require.NoError(ht, addConstraint(customCaveatReadonlyMac)) customCaveatAdminMac, err := macaroons.SafeCopyMacaroon(adminMac) - require.NoError(t.t, err) - require.NoError(t.t, addConstraint(customCaveatAdminMac)) + require.NoError(ht, err) + require.NoError(ht, addConstraint(customCaveatAdminMac)) // Run all sub-tests now. We can't run anything in parallel because that // would cause the main test function to exit and the nodes being // cleaned up. - t.t.Run("registration restrictions", func(tt *testing.T) { - middlewareRegistrationRestrictionTests(tt, net.Alice) + ht.Run("registration restrictions", func(tt *testing.T) { + middlewareRegistrationRestrictionTests(tt, alice) }) - t.t.Run("read-only intercept", func(tt *testing.T) { + + ht.Run("read-only intercept", func(tt *testing.T) { registration := registerMiddleware( - tt, net.Alice, &lnrpc.MiddlewareRegistration{ - MiddlewareName: "itest-interceptor", + tt, alice, &lnrpc.MiddlewareRegistration{ + MiddlewareName: "itest-interceptor-1", ReadOnlyMode: true, }, true, ) defer registration.cancel() middlewareInterceptionTest( - tt, net.Alice, net.Bob, registration, readonlyMac, + tt, alice, bob, registration, readonlyMac, customCaveatReadonlyMac, true, ) }) // We've manually disconnected Bob from Alice in the previous test, make // sure they're connected again. - net.EnsureConnected(t.t, net.Alice, net.Bob) - t.t.Run("encumbered macaroon intercept", func(tt *testing.T) { + // + // NOTE: we may get an error here saying "interceptor RPC client quit" + // as it takes some time for the interceptor to fully quit. Thus we + // restart the node here to make sure the old interceptor is removed + // from registration. + ht.RestartNode(alice) + ht.EnsureConnected(alice, bob) + ht.Run("encumbered macaroon intercept", func(tt *testing.T) { registration := registerMiddleware( - tt, net.Alice, &lnrpc.MiddlewareRegistration{ - MiddlewareName: "itest-interceptor", + tt, alice, &lnrpc.MiddlewareRegistration{ + MiddlewareName: "itest-interceptor-2", CustomMacaroonCaveatName: "itest-caveat", }, true, ) defer registration.cancel() middlewareInterceptionTest( - tt, net.Alice, net.Bob, registration, + tt, alice, bob, registration, customCaveatReadonlyMac, readonlyMac, false, ) }) // Next, run the response manipulation tests. - net.EnsureConnected(t.t, net.Alice, net.Bob) - t.t.Run("read-only not allowed to manipulate", func(tt *testing.T) { + // + // NOTE: we may get an error here saying "interceptor RPC client quit" + // as it takes some time for the interceptor to fully quit. Thus we + // restart the node here to make sure the old interceptor is removed + // from registration. + ht.RestartNode(alice) + ht.EnsureConnected(alice, bob) + ht.Run("read-only not allowed to manipulate", func(tt *testing.T) { registration := registerMiddleware( - tt, net.Alice, &lnrpc.MiddlewareRegistration{ - MiddlewareName: "itest-interceptor", + tt, alice, &lnrpc.MiddlewareRegistration{ + MiddlewareName: "itest-interceptor-3", ReadOnlyMode: true, }, true, ) defer registration.cancel() middlewareRequestManipulationTest( - tt, net.Alice, registration, adminMac, true, + tt, alice, registration, adminMac, true, ) middlewareResponseManipulationTest( - tt, net.Alice, net.Bob, registration, readonlyMac, true, + tt, alice, bob, registration, readonlyMac, true, ) }) - net.EnsureConnected(t.t, net.Alice, net.Bob) - t.t.Run("encumbered macaroon manipulate", func(tt *testing.T) { + + // NOTE: we may get an error here saying "interceptor RPC client quit" + // as it takes some time for the interceptor to fully quit. Thus we + // restart the node here to make sure the old interceptor is removed + // from registration. + ht.RestartNode(alice) + ht.EnsureConnected(alice, bob) + ht.Run("encumbered macaroon manipulate", func(tt *testing.T) { registration := registerMiddleware( - tt, net.Alice, &lnrpc.MiddlewareRegistration{ - MiddlewareName: "itest-interceptor", + tt, alice, &lnrpc.MiddlewareRegistration{ + MiddlewareName: "itest-interceptor-4", CustomMacaroonCaveatName: "itest-caveat", }, true, ) defer registration.cancel() middlewareRequestManipulationTest( - tt, net.Alice, registration, customCaveatAdminMac, - false, + tt, alice, registration, customCaveatAdminMac, false, ) middlewareResponseManipulationTest( - tt, net.Alice, net.Bob, registration, + tt, alice, bob, registration, customCaveatReadonlyMac, false, ) }) // And finally make sure mandatory middleware is always checked for any // RPC request. - t.t.Run("mandatory middleware", func(tt *testing.T) { - middlewareMandatoryTest(tt, net.Alice, net) + ht.Run("mandatory middleware", func(tt *testing.T) { + st := ht.Subtest(tt) + middlewareMandatoryTest(st, alice) }) } // middlewareRegistrationRestrictionTests tests all restrictions that apply to // registering a middleware. func middlewareRegistrationRestrictionTests(t *testing.T, - node *lntest.HarnessNode) { + node *node.HarnessNode) { testCases := []struct { registration *lnrpc.MiddlewareRegistration @@ -189,10 +206,12 @@ func middlewareRegistrationRestrictionTests(t *testing.T, // intercepted. It also makes sure that depending on the mode (read-only or // custom macaroon caveat) a middleware only gets access to the requests it // should be allowed access to. -func middlewareInterceptionTest(t *testing.T, node *lntest.HarnessNode, - peer *lntest.HarnessNode, registration *middlewareHarness, - userMac *macaroon.Macaroon, disallowedMac *macaroon.Macaroon, - readOnly bool) { +func middlewareInterceptionTest(t *testing.T, + node, peer *node.HarnessNode, registration *middlewareHarness, + userMac *macaroon.Macaroon, + disallowedMac *macaroon.Macaroon, readOnly bool) { + + t.Helper() // Everything we test here should be executed in a matter of // milliseconds, so we can use one single timeout context for all calls. @@ -253,10 +272,7 @@ func middlewareInterceptionTest(t *testing.T, node *lntest.HarnessNode, // Disconnect Bob to trigger a peer event without using Alice's RPC // interface itself. - _, err = peer.DisconnectPeer(ctxc, &lnrpc.DisconnectPeerRequest{ - PubKey: node.PubKeyStr, - }) - require.NoError(t, err) + peer.RPC.DisconnectPeer(node.PubKeyStr) peerEvent, err := resp2.Recv() require.NoError(t, err) require.Equal(t, lnrpc.PeerEvent_PEER_OFFLINE, peerEvent.GetType()) @@ -330,10 +346,12 @@ func middlewareInterceptionTest(t *testing.T, node *lntest.HarnessNode, // middlewareResponseManipulationTest tests that unary and streaming responses // can be intercepted and also manipulated, at least if the middleware didn't // register for read-only access. -func middlewareResponseManipulationTest(t *testing.T, node *lntest.HarnessNode, - peer *lntest.HarnessNode, registration *middlewareHarness, +func middlewareResponseManipulationTest(t *testing.T, + node, peer *node.HarnessNode, registration *middlewareHarness, userMac *macaroon.Macaroon, readOnly bool) { + t.Helper() + // Everything we test here should be executed in a matter of // milliseconds, so we can use one single timeout context for all calls. ctxb := context.Background() @@ -421,10 +439,7 @@ func middlewareResponseManipulationTest(t *testing.T, node *lntest.HarnessNode, // Disconnect Bob to trigger a peer event without using Alice's RPC // interface itself. - _, err = peer.DisconnectPeer(ctxc, &lnrpc.DisconnectPeerRequest{ - PubKey: node.PubKeyStr, - }) - require.NoError(t, err) + peer.RPC.DisconnectPeer(node.PubKeyStr) peerEvent, err := resp2.Recv() require.NoError(t, err) @@ -448,10 +463,12 @@ func middlewareResponseManipulationTest(t *testing.T, node *lntest.HarnessNode, // middlewareRequestManipulationTest tests that unary and streaming requests // can be intercepted and also manipulated, at least if the middleware didn't // register for read-only access. -func middlewareRequestManipulationTest(t *testing.T, node *lntest.HarnessNode, +func middlewareRequestManipulationTest(t *testing.T, node *node.HarnessNode, registration *middlewareHarness, userMac *macaroon.Macaroon, readOnly bool) { + t.Helper() + // Everything we test here should be executed in a matter of // milliseconds, so we can use one single timeout context for all calls. ctxb := context.Background() @@ -528,54 +545,44 @@ func middlewareRequestManipulationTest(t *testing.T, node *lntest.HarnessNode, // middlewareMandatoryTest tests that all RPC requests are blocked if there is // a mandatory middleware declared that's currently not registered. -func middlewareMandatoryTest(t *testing.T, node *lntest.HarnessNode, - net *lntest.NetworkHarness) { - +func middlewareMandatoryTest(ht *lntemp.HarnessTest, node *node.HarnessNode) { // Let's declare our itest interceptor as mandatory but don't register // it just yet. That should cause all RPC requests to fail, except for // the registration itself. - node.Cfg.ExtraArgs = append( - node.Cfg.ExtraArgs, + node.Cfg.SkipUnlock = true + ht.RestartNodeWithExtraArgs(node, []string{ + "--noseedbackup", "--rpcmiddleware.enable", "--rpcmiddleware.addmandatory=itest-interceptor", - ) - err := net.RestartNodeNoUnlock(node, nil, false) - require.NoError(t, err) + }) // The "wait for node to start" flag of the above restart does too much // and has a call to GetInfo built in, which will fail in this special // test case. So we need to do the wait and client setup manually here. - conn, err := node.ConnectRPC(true) - require.NoError(t, err) + conn, err := node.ConnectRPC() + require.NoError(ht, err) node.InitRPCClients(conn) - err = node.WaitUntilStateReached(lnrpc.WalletState_RPC_ACTIVE) - require.NoError(t, err) - node.LightningClient = lnrpc.NewLightningClient(conn) + err = node.WaitUntilServerActive() + require.NoError(ht, err) ctxb := context.Background() ctxc, cancel := context.WithTimeout(ctxb, defaultTimeout) defer cancel() // Test a unary request first. - _, err = node.ListChannels(ctxc, &lnrpc.ListChannelsRequest{}) - require.Error(t, err) - require.Contains( - t, err.Error(), "middleware 'itest-interceptor' is "+ - "currently not registered", - ) + _, err = node.RPC.LN.ListChannels(ctxc, &lnrpc.ListChannelsRequest{}) + require.Contains(ht, err.Error(), "middleware 'itest-interceptor' is "+ + "currently not registered") // Then a streaming one. - stream, err := node.SubscribeInvoices(ctxc, &lnrpc.InvoiceSubscription{}) - require.NoError(t, err) + stream := node.RPC.SubscribeInvoices(&lnrpc.InvoiceSubscription{}) _, err = stream.Recv() - require.Error(t, err) - require.Contains( - t, err.Error(), "middleware 'itest-interceptor' is "+ - "currently not registered", - ) + require.Error(ht, err) + require.Contains(ht, err.Error(), "middleware 'itest-interceptor' is "+ + "currently not registered") // Now let's register the middleware and try again. registration := registerMiddleware( - t, node, &lnrpc.MiddlewareRegistration{ + ht.T, node, &lnrpc.MiddlewareRegistration{ MiddlewareName: "itest-interceptor", CustomMacaroonCaveatName: "itest-caveat", }, true, @@ -584,16 +591,13 @@ func middlewareMandatoryTest(t *testing.T, node *lntest.HarnessNode, // Both the unary and streaming requests should now be allowed. time.Sleep(500 * time.Millisecond) - _, err = node.ListChannels(ctxc, &lnrpc.ListChannelsRequest{}) - require.NoError(t, err) - _, err = node.SubscribeInvoices(ctxc, &lnrpc.InvoiceSubscription{}) - require.NoError(t, err) + node.RPC.ListChannels(&lnrpc.ListChannelsRequest{}) + node.RPC.SubscribeInvoices(&lnrpc.InvoiceSubscription{}) // We now shut down the node manually to prevent the test from failing - // because we can't call the stop RPC if we unregister the middleware in - // the defer statement above. - err = net.ShutdownNode(node) - require.NoError(t, err) + // because we can't call the stop RPC if we unregister the middleware + // in the defer statement above. + ht.KillNode(node) } // assertInterceptedType makes sure that the intercept message sent by the RPC @@ -648,35 +652,62 @@ type middlewareHarness struct { // registerMiddleware creates a new middleware harness and sends the initial // register message to the RPC server. -func registerMiddleware(t *testing.T, node *lntest.HarnessNode, +func registerMiddleware(t *testing.T, node *node.HarnessNode, registration *lnrpc.MiddlewareRegistration, waitForRegister bool) *middlewareHarness { - ctxc, cancel := context.WithCancel(context.Background()) + t.Helper() - middlewareStream, err := node.RegisterRPCMiddleware(ctxc) - require.NoError(t, err) + middlewareStream, cancel := node.RPC.RegisterRPCMiddleware() - err = middlewareStream.Send(&lnrpc.RPCMiddlewareResponse{ - MiddlewareMessage: &lnrpc.RPCMiddlewareResponse_Register{ + errChan := make(chan error) + go func() { + msg := &lnrpc.RPCMiddlewareResponse_Register{ Register: registration, - }, - }) - require.NoError(t, err) + } + err := middlewareStream.Send(&lnrpc.RPCMiddlewareResponse{ + MiddlewareMessage: msg, + }) - if waitForRegister { - // Wait for the registration complete message. - regCompleteMsg, err := middlewareStream.Recv() - require.NoError(t, err) - require.True(t, regCompleteMsg.GetRegComplete()) + errChan <- err + }() + + select { + case <-time.After(defaultTimeout): + require.Fail(t, "registerMiddleware send timeout") + case err := <-errChan: + require.NoError(t, err, "registerMiddleware send failed") } - return &middlewareHarness{ + mh := &middlewareHarness{ t: t, cancel: cancel, stream: middlewareStream, responsesChan: make(chan *lnrpc.RPCMessage), } + + if !waitForRegister { + return mh + } + + // Wait for the registration complete message. + msg := make(chan *lnrpc.RPCMiddlewareRequest) + go func() { + regCompleteMsg, err := middlewareStream.Recv() + require.NoError(t, err, "registerMiddleware recv failed") + + msg <- regCompleteMsg + }() + + select { + case <-time.After(defaultTimeout): + require.Fail(t, "registerMiddleware recv timeout") + + case m := <-msg: + require.True(t, m.GetRegComplete()) + } + + return mh } // interceptUnary intercepts a unary call, optionally requesting to replace the diff --git a/lntest/itest/lnd_test_list_on_test.go b/lntest/itest/lnd_test_list_on_test.go index 31f8217a2..32d2d55fe 100644 --- a/lntest/itest/lnd_test_list_on_test.go +++ b/lntest/itest/lnd_test_list_on_test.go @@ -116,10 +116,6 @@ var allTestCases = []*testCase{ name: "wallet import pubkey", test: testWalletImportPubKey, }, - { - name: "rpc middleware interceptor", - test: testRPCMiddlewareInterceptor, - }, { name: "wipe forwarding packages", test: testWipeForwardingPackages, From 9794dbd7b23239c4c54c0118f2908f3587f8ca31 Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Tue, 9 Aug 2022 14:30:44 +0800 Subject: [PATCH 32/44] itest: refactor `testMacaroonAuthentication` --- lntest/itest/list_on_test.go | 4 +++ lntest/itest/lnd_macaroons_test.go | 42 ++++++++++++++------------- lntest/itest/lnd_test_list_on_test.go | 4 --- 3 files changed, 26 insertions(+), 24 deletions(-) diff --git a/lntest/itest/list_on_test.go b/lntest/itest/list_on_test.go index 7ae784dac..e445dbe86 100644 --- a/lntest/itest/list_on_test.go +++ b/lntest/itest/list_on_test.go @@ -345,4 +345,8 @@ var allTestCasesTemp = []*lntemp.TestCase{ Name: "rpc middleware interceptor", TestFunc: testRPCMiddlewareInterceptor, }, + { + Name: "macaroon authentication", + TestFunc: testMacaroonAuthentication, + }, } diff --git a/lntest/itest/lnd_macaroons_test.go b/lntest/itest/lnd_macaroons_test.go index c6f3354f5..dd28765c3 100644 --- a/lntest/itest/lnd_macaroons_test.go +++ b/lntest/itest/lnd_macaroons_test.go @@ -12,6 +12,7 @@ import ( "github.com/golang/protobuf/proto" "github.com/lightningnetwork/lnd/lnrpc" + "github.com/lightningnetwork/lnd/lntemp" "github.com/lightningnetwork/lnd/lntemp/node" "github.com/lightningnetwork/lnd/lntest" "github.com/lightningnetwork/lnd/macaroons" @@ -24,13 +25,14 @@ import ( // enabled on the gRPC interface, no requests with missing or invalid // macaroons are allowed. Further, the specific access rights (read/write, // entity based) and first-party caveats are tested as well. -func testMacaroonAuthentication(net *lntest.NetworkHarness, ht *harnessTest) { +func testMacaroonAuthentication(ht *lntemp.HarnessTest) { var ( infoReq = &lnrpc.GetInfoRequest{} newAddrReq = &lnrpc.NewAddressRequest{ Type: AddrTypeWitnessPubkeyHash, } - testNode = net.Alice + testNode = ht.Alice + testClient = testNode.RPC.LN ) testCases := []struct { @@ -42,7 +44,7 @@ func testMacaroonAuthentication(net *lntest.NetworkHarness, ht *harnessTest) { // enabled. name: "no macaroon", run: func(ctxt context.Context, t *testing.T) { - conn, err := testNode.ConnectRPC(false) + conn, err := testNode.ConnectRPCWithMacaroon(nil) require.NoError(t, err) defer func() { _ = conn.Close() }() client := lnrpc.NewLightningClient(conn) @@ -59,7 +61,7 @@ func testMacaroonAuthentication(net *lntest.NetworkHarness, ht *harnessTest) { []byte("dummy_root_key"), []byte("0"), "itest", macaroon.LatestVersion, ) - cleanup, client := macaroonClientOld( + cleanup, client := macaroonClient( t, testNode, invalidMac, ) defer cleanup() @@ -73,10 +75,10 @@ func testMacaroonAuthentication(net *lntest.NetworkHarness, ht *harnessTest) { name: "read only macaroon", run: func(ctxt context.Context, t *testing.T) { readonlyMac, err := testNode.ReadMacaroon( - testNode.ReadMacPath(), defaultTimeout, + testNode.Cfg.ReadMacPath, defaultTimeout, ) require.NoError(t, err) - cleanup, client := macaroonClientOld( + cleanup, client := macaroonClient( t, testNode, readonlyMac, ) defer cleanup() @@ -90,14 +92,14 @@ func testMacaroonAuthentication(net *lntest.NetworkHarness, ht *harnessTest) { name: "expired macaroon", run: func(ctxt context.Context, t *testing.T) { readonlyMac, err := testNode.ReadMacaroon( - testNode.ReadMacPath(), defaultTimeout, + testNode.Cfg.ReadMacPath, defaultTimeout, ) require.NoError(t, err) timeoutMac, err := macaroons.AddConstraints( readonlyMac, macaroons.TimeoutConstraint(-30), ) require.NoError(t, err) - cleanup, client := macaroonClientOld( + cleanup, client := macaroonClient( t, testNode, timeoutMac, ) defer cleanup() @@ -110,7 +112,7 @@ func testMacaroonAuthentication(net *lntest.NetworkHarness, ht *harnessTest) { name: "invalid IP macaroon", run: func(ctxt context.Context, t *testing.T) { readonlyMac, err := testNode.ReadMacaroon( - testNode.ReadMacPath(), defaultTimeout, + testNode.Cfg.ReadMacPath, defaultTimeout, ) require.NoError(t, err) invalidIPAddrMac, err := macaroons.AddConstraints( @@ -119,7 +121,7 @@ func testMacaroonAuthentication(net *lntest.NetworkHarness, ht *harnessTest) { ), ) require.NoError(t, err) - cleanup, client := macaroonClientOld( + cleanup, client := macaroonClient( t, testNode, invalidIPAddrMac, ) defer cleanup() @@ -134,7 +136,7 @@ func testMacaroonAuthentication(net *lntest.NetworkHarness, ht *harnessTest) { name: "correct macaroon", run: func(ctxt context.Context, t *testing.T) { adminMac, err := testNode.ReadMacaroon( - testNode.AdminMacPath(), defaultTimeout, + testNode.Cfg.AdminMacPath, defaultTimeout, ) require.NoError(t, err) adminMac, err = macaroons.AddConstraints( @@ -142,7 +144,7 @@ func testMacaroonAuthentication(net *lntest.NetworkHarness, ht *harnessTest) { macaroons.IPLockConstraint("127.0.0.1"), ) require.NoError(t, err) - cleanup, client := macaroonClientOld(t, testNode, adminMac) + cleanup, client := macaroonClient(t, testNode, adminMac) defer cleanup() res, err := client.NewAddress(ctxt, newAddrReq) require.NoError(t, err, "get new address") @@ -164,7 +166,7 @@ func testMacaroonAuthentication(net *lntest.NetworkHarness, ht *harnessTest) { "Permissions", }}, } - bakeRes, err := testNode.BakeMacaroon(ctxt, req) + bakeRes, err := testClient.BakeMacaroon(ctxt, req) require.NoError(t, err) // Create a connection that uses the custom macaroon. @@ -175,7 +177,7 @@ func testMacaroonAuthentication(net *lntest.NetworkHarness, ht *harnessTest) { customMac := &macaroon.Macaroon{} err = customMac.UnmarshalBinary(customMacBytes) require.NoError(t, err) - cleanup, client := macaroonClientOld( + cleanup, client := macaroonClient( t, testNode, customMac, ) defer cleanup() @@ -219,7 +221,7 @@ func testMacaroonAuthentication(net *lntest.NetworkHarness, ht *harnessTest) { }}, AllowExternalPermissions: true, } - bakeResp, err := testNode.BakeMacaroon(ctxt, req) + bakeResp, err := testClient.BakeMacaroon(ctxt, req) require.NoError(t, err) macBytes, err := hex.DecodeString(bakeResp.Macaroon) @@ -233,7 +235,7 @@ func testMacaroonAuthentication(net *lntest.NetworkHarness, ht *harnessTest) { // Test that CheckMacaroonPermissions accurately // characterizes macaroon as valid, even if the // permissions are not native to LND. - checkResp, err := testNode.CheckMacaroonPermissions( + checkResp, err := testClient.CheckMacaroonPermissions( ctxt, checkReq, ) require.NoError(t, err) @@ -254,7 +256,7 @@ func testMacaroonAuthentication(net *lntest.NetworkHarness, ht *harnessTest) { checkReq.Macaroon = timeoutMacBytes - _, err = testNode.CheckMacaroonPermissions( + _, err = testClient.CheckMacaroonPermissions( ctxt, checkReq, ) require.Error(t, err) @@ -270,7 +272,7 @@ func testMacaroonAuthentication(net *lntest.NetworkHarness, ht *harnessTest) { checkReq.Permissions = wrongPermissions checkReq.Macaroon = macBytes - _, err = testNode.CheckMacaroonPermissions( + _, err = testClient.CheckMacaroonPermissions( ctxt, checkReq, ) require.Error(t, err) @@ -280,9 +282,9 @@ func testMacaroonAuthentication(net *lntest.NetworkHarness, ht *harnessTest) { for _, tc := range testCases { tc := tc - ht.t.Run(tc.name, func(tt *testing.T) { + ht.Run(tc.name, func(tt *testing.T) { ctxt, cancel := context.WithTimeout( - context.Background(), defaultTimeout, + ht.Context(), defaultTimeout, ) defer cancel() diff --git a/lntest/itest/lnd_test_list_on_test.go b/lntest/itest/lnd_test_list_on_test.go index 32d2d55fe..bf2f903e5 100644 --- a/lntest/itest/lnd_test_list_on_test.go +++ b/lntest/itest/lnd_test_list_on_test.go @@ -52,10 +52,6 @@ var allTestCases = []*testCase{ name: "cpfp", test: testCPFP, }, - { - name: "macaroon authentication", - test: testMacaroonAuthentication, - }, { name: "bake macaroon", test: testBakeMacaroon, From 64bf6f4e26e1a69e261e8412f454f7bc64ebb130 Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Tue, 9 Aug 2022 14:33:15 +0800 Subject: [PATCH 33/44] itest: refactor `testBakeMacaroon` --- lntest/itest/list_on_test.go | 4 ++++ lntest/itest/lnd_macaroons_test.go | 14 +++++++------- lntest/itest/lnd_test_list_on_test.go | 4 ---- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/lntest/itest/list_on_test.go b/lntest/itest/list_on_test.go index e445dbe86..90cc6730c 100644 --- a/lntest/itest/list_on_test.go +++ b/lntest/itest/list_on_test.go @@ -349,4 +349,8 @@ var allTestCasesTemp = []*lntemp.TestCase{ Name: "macaroon authentication", TestFunc: testMacaroonAuthentication, }, + { + Name: "bake macaroon", + TestFunc: testBakeMacaroon, + }, } diff --git a/lntest/itest/lnd_macaroons_test.go b/lntest/itest/lnd_macaroons_test.go index dd28765c3..4739d4626 100644 --- a/lntest/itest/lnd_macaroons_test.go +++ b/lntest/itest/lnd_macaroons_test.go @@ -296,8 +296,8 @@ func testMacaroonAuthentication(ht *lntemp.HarnessTest) { // testBakeMacaroon checks that when creating macaroons, the permissions param // in the request must be set correctly, and the baked macaroon has the intended // permissions. -func testBakeMacaroon(net *lntest.NetworkHarness, t *harnessTest) { - var testNode = net.Alice +func testBakeMacaroon(ht *lntemp.HarnessTest) { + var testNode = ht.Alice testCases := []struct { name string @@ -429,7 +429,7 @@ func testBakeMacaroon(net *lntest.NetworkHarness, t *harnessTest) { newMac, err := readMacaroonFromHex(bakeResp.Macaroon) require.NoError(t, err) - cleanup, readOnlyClient := macaroonClientOld( + cleanup, readOnlyClient := macaroonClient( t, testNode, newMac, ) defer cleanup() @@ -498,17 +498,17 @@ func testBakeMacaroon(net *lntest.NetworkHarness, t *harnessTest) { for _, tc := range testCases { tc := tc - t.t.Run(tc.name, func(tt *testing.T) { + ht.Run(tc.name, func(tt *testing.T) { ctxt, cancel := context.WithTimeout( - context.Background(), defaultTimeout, + ht.Context(), defaultTimeout, ) defer cancel() adminMac, err := testNode.ReadMacaroon( - testNode.AdminMacPath(), defaultTimeout, + testNode.Cfg.AdminMacPath, defaultTimeout, ) require.NoError(tt, err) - cleanup, client := macaroonClientOld(tt, testNode, adminMac) + cleanup, client := macaroonClient(tt, testNode, adminMac) defer cleanup() tc.run(ctxt, tt, client) diff --git a/lntest/itest/lnd_test_list_on_test.go b/lntest/itest/lnd_test_list_on_test.go index bf2f903e5..0e3e78ad0 100644 --- a/lntest/itest/lnd_test_list_on_test.go +++ b/lntest/itest/lnd_test_list_on_test.go @@ -52,10 +52,6 @@ var allTestCases = []*testCase{ name: "cpfp", test: testCPFP, }, - { - name: "bake macaroon", - test: testBakeMacaroon, - }, { name: "delete macaroon id", test: testDeleteMacaroonID, From 4b469eb0c571139a344372503eaefcbfe0a79b52 Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Tue, 9 Aug 2022 14:35:45 +0800 Subject: [PATCH 34/44] itest: refactor `testDeleteMacaroonID` --- lntest/itest/list_on_test.go | 4 +++ lntest/itest/lnd_macaroons_test.go | 46 +++++++++++++-------------- lntest/itest/lnd_test_list_on_test.go | 4 --- 3 files changed, 27 insertions(+), 27 deletions(-) diff --git a/lntest/itest/list_on_test.go b/lntest/itest/list_on_test.go index 90cc6730c..2bbabf2ef 100644 --- a/lntest/itest/list_on_test.go +++ b/lntest/itest/list_on_test.go @@ -353,4 +353,8 @@ var allTestCasesTemp = []*lntemp.TestCase{ Name: "bake macaroon", TestFunc: testBakeMacaroon, }, + { + Name: "delete macaroon id", + TestFunc: testDeleteMacaroonID, + }, } diff --git a/lntest/itest/lnd_macaroons_test.go b/lntest/itest/lnd_macaroons_test.go index 4739d4626..3622c6ba2 100644 --- a/lntest/itest/lnd_macaroons_test.go +++ b/lntest/itest/lnd_macaroons_test.go @@ -520,26 +520,26 @@ func testBakeMacaroon(ht *lntemp.HarnessTest) { // specified ID and invalidates all macaroons derived from the key with that ID. // Also, it checks deleting the reserved marcaroon ID, DefaultRootKeyID or is // forbidden. -func testDeleteMacaroonID(net *lntest.NetworkHarness, t *harnessTest) { +func testDeleteMacaroonID(ht *lntemp.HarnessTest) { var ( - ctxb = context.Background() - testNode = net.Alice + ctxb = ht.Context() + testNode = ht.Alice ) ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) defer cancel() // Use admin macaroon to create a connection. adminMac, err := testNode.ReadMacaroon( - testNode.AdminMacPath(), defaultTimeout, + testNode.Cfg.AdminMacPath, defaultTimeout, ) - require.NoError(t.t, err) - cleanup, client := macaroonClientOld(t.t, testNode, adminMac) + require.NoError(ht, err) + cleanup, client := macaroonClient(ht.T, testNode, adminMac) defer cleanup() // Record the number of macaroon IDs before creation. listReq := &lnrpc.ListMacaroonIDsRequest{} listResp, err := client.ListMacaroonIDs(ctxt, listReq) - require.NoError(t.t, err) + require.NoError(ht, err) numMacIDs := len(listResp.RootKeyIds) // Create macaroons for testing. @@ -554,17 +554,17 @@ func testDeleteMacaroonID(net *lntest.NetworkHarness, t *harnessTest) { }}, } resp, err := client.BakeMacaroon(ctxt, req) - require.NoError(t.t, err) + require.NoError(ht, err) macList = append(macList, resp.Macaroon) } // Check that the creation is successful. listReq = &lnrpc.ListMacaroonIDsRequest{} listResp, err = client.ListMacaroonIDs(ctxt, listReq) - require.NoError(t.t, err) + require.NoError(ht, err) // The number of macaroon IDs should be increased by len(rootKeyIDs). - require.Equal(t.t, numMacIDs+len(rootKeyIDs), len(listResp.RootKeyIds)) + require.Equal(ht, numMacIDs+len(rootKeyIDs), len(listResp.RootKeyIds)) // First test: check deleting the DefaultRootKeyID returns an error. defaultID, _ := strconv.ParseUint( @@ -574,38 +574,38 @@ func testDeleteMacaroonID(net *lntest.NetworkHarness, t *harnessTest) { RootKeyId: defaultID, } _, err = client.DeleteMacaroonID(ctxt, req) - require.Error(t.t, err) - require.Contains( - t.t, err.Error(), macaroons.ErrDeletionForbidden.Error(), - ) + require.Error(ht, err) + require.Contains(ht, err.Error(), + macaroons.ErrDeletionForbidden.Error()) // Second test: check deleting the customized ID returns success. req = &lnrpc.DeleteMacaroonIDRequest{ RootKeyId: rootKeyIDs[0], } resp, err := client.DeleteMacaroonID(ctxt, req) - require.NoError(t.t, err) - require.True(t.t, resp.Deleted) + require.NoError(ht, err) + require.True(ht, resp.Deleted) // Check that the deletion is successful. listReq = &lnrpc.ListMacaroonIDsRequest{} listResp, err = client.ListMacaroonIDs(ctxt, listReq) - require.NoError(t.t, err) + require.NoError(ht, err) // The number of macaroon IDs should be decreased by 1. - require.Equal(t.t, numMacIDs+len(rootKeyIDs)-1, len(listResp.RootKeyIds)) + require.Equal(ht, numMacIDs+len(rootKeyIDs)-1, len(listResp.RootKeyIds)) // Check that the deleted macaroon can no longer access macaroon:read. deletedMac, err := readMacaroonFromHex(macList[0]) - require.NoError(t.t, err) - cleanup, client = macaroonClientOld(t.t, testNode, deletedMac) + require.NoError(ht, err) + cleanup, client = macaroonClient(ht.T, testNode, deletedMac) defer cleanup() - // Because the macaroon is deleted, it will be treated as an invalid one. + // Because the macaroon is deleted, it will be treated as an invalid + // one. listReq = &lnrpc.ListMacaroonIDsRequest{} _, err = client.ListMacaroonIDs(ctxt, listReq) - require.Error(t.t, err) - require.Contains(t.t, err.Error(), "cannot get macaroon") + require.Error(ht, err) + require.Contains(ht, err.Error(), "cannot get macaroon") } // testStatelessInit checks that the stateless initialization of the daemon diff --git a/lntest/itest/lnd_test_list_on_test.go b/lntest/itest/lnd_test_list_on_test.go index 0e3e78ad0..ba81d5864 100644 --- a/lntest/itest/lnd_test_list_on_test.go +++ b/lntest/itest/lnd_test_list_on_test.go @@ -52,10 +52,6 @@ var allTestCases = []*testCase{ name: "cpfp", test: testCPFP, }, - { - name: "delete macaroon id", - test: testDeleteMacaroonID, - }, { name: "psbt channel funding", test: testPsbtChanFunding, From 8a4d2741a38069bf904f13b452c79315a84aa791 Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Tue, 9 Aug 2022 15:18:39 +0800 Subject: [PATCH 35/44] lntemp+itest: refactor `testStatelessInit` --- lntemp/harness.go | 6 ++ lntemp/node/harness_node.go | 13 ++++ lntemp/rpc/wallet_unlocker.go | 14 +++++ lntest/itest/list_on_test.go | 4 ++ lntest/itest/lnd_macaroons_test.go | 90 ++++++++++----------------- lntest/itest/lnd_test_list_on_test.go | 4 -- 6 files changed, 71 insertions(+), 60 deletions(-) diff --git a/lntemp/harness.go b/lntemp/harness.go index 8acbf4a42..75454c105 100644 --- a/lntemp/harness.go +++ b/lntemp/harness.go @@ -502,6 +502,12 @@ func (h *HarnessTest) RestartNode(hn *node.HarnessNode) { } } +// RestartNodeNoUnlock restarts a given node without unlocking its wallet. +func (h *HarnessTest) RestartNodeNoUnlock(hn *node.HarnessNode) { + err := h.manager.restartNode(h.runCtx, hn, nil) + require.NoErrorf(h, err, "failed to restart node %s", hn.Name()) +} + // RestartNodeWithChanBackups restarts a given node with the specified channel // backups. func (h *HarnessTest) RestartNodeWithChanBackups(hn *node.HarnessNode, diff --git a/lntemp/node/harness_node.go b/lntemp/node/harness_node.go index 230be2a68..1dc484bed 100644 --- a/lntemp/node/harness_node.go +++ b/lntemp/node/harness_node.go @@ -488,6 +488,19 @@ func (hn *HarnessNode) InitNode(macBytes []byte) error { return hn.initLightningClient() } +// InitChangePassword initializes a harness node by passing the change password +// request via RPC. After the request is submitted, this method will block until +// a macaroon-authenticated RPC connection can be established to the harness +// node. Once established, the new connection is used to initialize the +// RPC clients and subscribes the HarnessNode to topology changes. +func (hn *HarnessNode) ChangePasswordAndInit( + req *lnrpc.ChangePasswordRequest) ( + *lnrpc.ChangePasswordResponse, error) { + + response := hn.RPC.ChangePassword(req) + return response, hn.InitNode(response.AdminMacaroon) +} + // waitTillServerState makes a subscription to the server's state change and // blocks until the server is in the targeted state. func (hn *HarnessNode) waitTillServerState( diff --git a/lntemp/rpc/wallet_unlocker.go b/lntemp/rpc/wallet_unlocker.go index 726abef85..c4fb60c86 100644 --- a/lntemp/rpc/wallet_unlocker.go +++ b/lntemp/rpc/wallet_unlocker.go @@ -48,3 +48,17 @@ func (h *HarnessRPC) GenSeed(req *lnrpc.GenSeedRequest) *lnrpc.GenSeedResponse { return resp } + +// ChangePassword makes a RPC request to WalletUnlocker and asserts there's no +// error. +func (h *HarnessRPC) ChangePassword( + req *lnrpc.ChangePasswordRequest) *lnrpc.ChangePasswordResponse { + + ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout) + defer cancel() + + resp, err := h.WalletUnlocker.ChangePassword(ctxt, req) + h.NoError(err, "ChangePassword") + + return resp +} diff --git a/lntest/itest/list_on_test.go b/lntest/itest/list_on_test.go index 2bbabf2ef..514edba19 100644 --- a/lntest/itest/list_on_test.go +++ b/lntest/itest/list_on_test.go @@ -357,4 +357,8 @@ var allTestCasesTemp = []*lntemp.TestCase{ Name: "delete macaroon id", TestFunc: testDeleteMacaroonID, }, + { + Name: "stateless init", + TestFunc: testStatelessInit, + }, } diff --git a/lntest/itest/lnd_macaroons_test.go b/lntest/itest/lnd_macaroons_test.go index 3622c6ba2..01d576f8f 100644 --- a/lntest/itest/lnd_macaroons_test.go +++ b/lntest/itest/lnd_macaroons_test.go @@ -1,7 +1,6 @@ package itest import ( - "bytes" "context" "encoding/hex" "os" @@ -14,7 +13,6 @@ import ( "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lntemp" "github.com/lightningnetwork/lnd/lntemp/node" - "github.com/lightningnetwork/lnd/lntest" "github.com/lightningnetwork/lnd/macaroons" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -612,7 +610,7 @@ func testDeleteMacaroonID(ht *lntemp.HarnessTest) { // does not write any macaroon files to the daemon's file system and returns // the admin macaroon in the response. It then checks that the password // change of the wallet can also happen stateless. -func testStatelessInit(net *lntest.NetworkHarness, t *harnessTest) { +func testStatelessInit(ht *lntemp.HarnessTest) { var ( initPw = []byte("stateless") newPw = []byte("stateless-new") @@ -624,85 +622,79 @@ func testStatelessInit(net *lntest.NetworkHarness, t *harnessTest) { // First, create a new node and request it to initialize stateless. // This should return us the binary serialized admin macaroon that we // can then use for further calls. - carol, _, macBytes, err := net.NewNodeWithSeed( - "Carol", nil, initPw, true, - ) - require.NoError(t.t, err) - if len(macBytes) == 0 { - t.Fatalf("invalid macaroon returned in stateless init") - } + carol, _, macBytes := ht.NewNodeWithSeed("Carol", nil, initPw, true) + require.NotEmpty(ht, macBytes, + "invalid macaroon returned in stateless init") // Now make sure no macaroon files have been created by the node Carol. - _, err = os.Stat(carol.AdminMacPath()) - require.Error(t.t, err) - _, err = os.Stat(carol.ReadMacPath()) - require.Error(t.t, err) - _, err = os.Stat(carol.InvoiceMacPath()) - require.Error(t.t, err) + _, err := os.Stat(carol.Cfg.AdminMacPath) + require.Error(ht, err) + _, err = os.Stat(carol.Cfg.ReadMacPath) + require.Error(ht, err) + _, err = os.Stat(carol.Cfg.InvoiceMacPath) + require.Error(ht, err) // Then check that we can unmarshal the binary serialized macaroon. adminMac := &macaroon.Macaroon{} err = adminMac.UnmarshalBinary(macBytes) - require.NoError(t.t, err) + require.NoError(ht, err) // Find out if we can actually use the macaroon that has been returned // to us for a RPC call. conn, err := carol.ConnectRPCWithMacaroon(adminMac) - require.NoError(t.t, err) + require.NoError(ht, err) defer conn.Close() adminMacClient := lnrpc.NewLightningClient(conn) - ctxt, _ := context.WithTimeout(context.Background(), defaultTimeout) + ctxt, cancel := context.WithTimeout(ht.Context(), defaultTimeout) + defer cancel() res, err := adminMacClient.NewAddress(ctxt, newAddrReq) - require.NoError(t.t, err) + require.NoError(ht, err) if !strings.HasPrefix(res.Address, harnessNetParams.Bech32HRPSegwit) { - t.Fatalf("returned address was not a regtest address") + require.Fail(ht, "returned address was not a regtest address") } // As a second part, shut down the node and then try to change the // password when we start it up again. - if err := net.RestartNodeNoUnlock(carol, nil, true); err != nil { - t.Fatalf("Node restart failed: %v", err) - } + ht.RestartNodeNoUnlock(carol) changePwReq := &lnrpc.ChangePasswordRequest{ CurrentPassword: initPw, NewPassword: newPw, StatelessInit: true, } - response, err := carol.InitChangePassword(changePwReq) - require.NoError(t.t, err) + response, err := carol.ChangePasswordAndInit(changePwReq) + require.NoError(ht, err) // Again, make sure no macaroon files have been created by the node // Carol. - _, err = os.Stat(carol.AdminMacPath()) - require.Error(t.t, err) - _, err = os.Stat(carol.ReadMacPath()) - require.Error(t.t, err) - _, err = os.Stat(carol.InvoiceMacPath()) - require.Error(t.t, err) + _, err = os.Stat(carol.Cfg.AdminMacPath) + require.Error(ht, err) + _, err = os.Stat(carol.Cfg.ReadMacPath) + require.Error(ht, err) + _, err = os.Stat(carol.Cfg.InvoiceMacPath) + require.Error(ht, err) // Then check that we can unmarshal the new binary serialized macaroon // and that it really is a new macaroon. - if err = adminMac.UnmarshalBinary(response.AdminMacaroon); err != nil { - t.Fatalf("unable to unmarshal macaroon: %v", err) - } - if bytes.Equal(response.AdminMacaroon, macBytes) { - t.Fatalf("expected new macaroon to be different") - } + err = adminMac.UnmarshalBinary(response.AdminMacaroon) + require.NoError(ht, err, "unable to unmarshal macaroon") + require.NotEqual(ht, response.AdminMacaroon, macBytes, + "expected new macaroon to be different") // Finally, find out if we can actually use the new macaroon that has // been returned to us for a RPC call. conn2, err := carol.ConnectRPCWithMacaroon(adminMac) - require.NoError(t.t, err) + require.NoError(ht, err) defer conn2.Close() adminMacClient = lnrpc.NewLightningClient(conn2) // Changing the password takes a while, so we use the default timeout // of 30 seconds to wait for the connection to be ready. - ctxt, _ = context.WithTimeout(context.Background(), defaultTimeout) + ctxt, cancel = context.WithTimeout(ht.Context(), defaultTimeout) + defer cancel() res, err = adminMacClient.NewAddress(ctxt, newAddrReq) - require.NoError(t.t, err) + require.NoError(ht, err) if !strings.HasPrefix(res.Address, harnessNetParams.Bech32HRPSegwit) { - t.Fatalf("returned address was not a regtest address") + require.Fail(ht, "returned address was not a regtest address") } } @@ -720,20 +712,6 @@ func readMacaroonFromHex(macHex string) (*macaroon.Macaroon, error) { return mac, nil } -// TODO(yy): remove. -func macaroonClientOld(t *testing.T, testNode *lntest.HarnessNode, - mac *macaroon.Macaroon) (func(), lnrpc.LightningClient) { - - conn, err := testNode.ConnectRPCWithMacaroon(mac) - require.NoError(t, err, "connect to alice") - - cleanup := func() { - err := conn.Close() - require.NoError(t, err, "close") - } - return cleanup, lnrpc.NewLightningClient(conn) -} - func macaroonClient(t *testing.T, testNode *node.HarnessNode, mac *macaroon.Macaroon) (func(), lnrpc.LightningClient) { diff --git a/lntest/itest/lnd_test_list_on_test.go b/lntest/itest/lnd_test_list_on_test.go index ba81d5864..c6da53385 100644 --- a/lntest/itest/lnd_test_list_on_test.go +++ b/lntest/itest/lnd_test_list_on_test.go @@ -92,10 +92,6 @@ var allTestCases = []*testCase{ name: "forward interceptor dedup htlcs", test: testForwardInterceptorDedupHtlc, }, - { - name: "stateless init", - test: testStatelessInit, - }, { name: "wallet import account", test: testWalletImportAccount, From 05f198f34ed586e9b9eb1c0242516f106a46ada0 Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Tue, 9 Aug 2022 15:27:54 +0800 Subject: [PATCH 36/44] itest: refactor `testSingleHopInvoice` --- lntest/itest/list_on_test.go | 4 + lntest/itest/lnd_single_hop_invoice_test.go | 222 ++++++-------------- lntest/itest/lnd_test_list_on_test.go | 4 - 3 files changed, 66 insertions(+), 164 deletions(-) diff --git a/lntest/itest/list_on_test.go b/lntest/itest/list_on_test.go index 514edba19..3d5635b00 100644 --- a/lntest/itest/list_on_test.go +++ b/lntest/itest/list_on_test.go @@ -361,4 +361,8 @@ var allTestCasesTemp = []*lntemp.TestCase{ Name: "stateless init", TestFunc: testStatelessInit, }, + { + Name: "single hop invoice", + TestFunc: testSingleHopInvoice, + }, } diff --git a/lntest/itest/lnd_single_hop_invoice_test.go b/lntest/itest/lnd_single_hop_invoice_test.go index 74f377458..ce75c42d0 100644 --- a/lntest/itest/lnd_single_hop_invoice_test.go +++ b/lntest/itest/lnd_single_hop_invoice_test.go @@ -2,35 +2,34 @@ package itest import ( "bytes" - "context" "encoding/hex" - "time" "github.com/btcsuite/btcd/btcutil" - "github.com/davecgh/go-spew/spew" "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/lntemp" "github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/record" "github.com/stretchr/testify/require" ) -func testSingleHopInvoice(net *lntest.NetworkHarness, t *harnessTest) { - ctxb := context.Background() - - // Open a channel with 100k satoshis between Alice and Bob with Alice being - // the sole funder of the channel. +func testSingleHopInvoice(ht *lntemp.HarnessTest) { + // Open a channel with 100k satoshis between Alice and Bob with Alice + // being the sole funder of the channel. chanAmt := btcutil.Amount(100000) - chanPoint := openChannelAndAssert( - t, net, net.Alice, net.Bob, - lntest.OpenChannelParams{ - Amt: chanAmt, - }, + alice, bob := ht.Alice, ht.Bob + cp := ht.OpenChannel( + alice, bob, lntemp.OpenChannelParams{Amt: chanAmt}, ) + // assertAmountPaid is a helper closure that asserts the amount paid by + // Alice and received by Bob are expected. + assertAmountPaid := func(expected int64) { + ht.AssertAmountPaid("alice -> bob", alice, cp, expected, 0) + ht.AssertAmountPaid("bob <- alice", bob, cp, 0, expected) + } + // Now that the channel is open, create an invoice for Bob which // expects a payment of 1000 satoshis from Alice paid via a particular // preimage. @@ -41,61 +40,20 @@ func testSingleHopInvoice(net *lntest.NetworkHarness, t *harnessTest) { RPreimage: preimage, Value: paymentAmt, } - invoiceResp, err := net.Bob.AddInvoice(ctxb, invoice) - if err != nil { - t.Fatalf("unable to add invoice: %v", err) - } - - // Wait for Alice to recognize and advertise the new channel generated - // above. - err = net.Alice.WaitForNetworkChannelOpen(chanPoint) - if err != nil { - t.Fatalf("alice didn't advertise channel before "+ - "timeout: %v", err) - } - err = net.Bob.WaitForNetworkChannelOpen(chanPoint) - if err != nil { - t.Fatalf("bob didn't advertise channel before "+ - "timeout: %v", err) - } + invoiceResp := bob.RPC.AddInvoice(invoice) // With the invoice for Bob added, send a payment towards Alice paying // to the above generated invoice. - resp := sendAndAssertSuccess( - t, net.Alice, &routerrpc.SendPaymentRequest{ - PaymentRequest: invoiceResp.PaymentRequest, - TimeoutSeconds: 60, - FeeLimitMsat: noFeeLimitMsat, - }, - ) - if hex.EncodeToString(preimage) != resp.PaymentPreimage { - t.Fatalf("preimage mismatch: expected %v, got %v", preimage, - resp.PaymentPreimage) - } + ht.CompletePaymentRequests(alice, []string{invoiceResp.PaymentRequest}) // Bob's invoice should now be found and marked as settled. - payHash := &lnrpc.PaymentHash{ - RHash: invoiceResp.RHash, - } - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - dbInvoice, err := net.Bob.LookupInvoice(ctxt, payHash) - if err != nil { - t.Fatalf("unable to lookup invoice: %v", err) - } - if !dbInvoice.Settled { // nolint:staticcheck - t.Fatalf("bob's invoice should be marked as settled: %v", - spew.Sdump(dbInvoice)) - } + dbInvoice := bob.RPC.LookupInvoice(invoiceResp.RHash) + require.Equal(ht, lnrpc.Invoice_SETTLED, dbInvoice.State, + "bob's invoice should be marked as settled") // With the payment completed all balance related stats should be // properly updated. - err = wait.NoError( - assertAmountSent(paymentAmt, net.Alice, net.Bob), - 3*time.Second, - ) - if err != nil { - t.Fatalf(err.Error()) - } + assertAmountPaid(paymentAmt) // Create another invoice for Bob, this time leaving off the preimage // to one will be randomly generated. We'll test the proper @@ -104,92 +62,58 @@ func testSingleHopInvoice(net *lntest.NetworkHarness, t *harnessTest) { Memo: "test3", Value: paymentAmt, } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - invoiceResp, err = net.Bob.AddInvoice(ctxt, invoice) - if err != nil { - t.Fatalf("unable to add invoice: %v", err) - } + invoiceResp = bob.RPC.AddInvoice(invoice) // Next send another payment, but this time using a zpay32 encoded // invoice rather than manually specifying the payment details. - sendAndAssertSuccess( - t, net.Alice, &routerrpc.SendPaymentRequest{ - PaymentRequest: invoiceResp.PaymentRequest, - TimeoutSeconds: 60, - FeeLimitMsat: noFeeLimitMsat, - }, - ) + ht.CompletePaymentRequests(alice, []string{invoiceResp.PaymentRequest}) // The second payment should also have succeeded, with the balances // being update accordingly. - err = wait.NoError( - assertAmountSent(2*paymentAmt, net.Alice, net.Bob), - 3*time.Second, - ) - if err != nil { - t.Fatalf(err.Error()) - } + assertAmountPaid(paymentAmt * 2) // Next send a keysend payment. keySendPreimage := lntypes.Preimage{3, 4, 5, 11} keySendHash := keySendPreimage.Hash() - sendAndAssertSuccess( - t, net.Alice, &routerrpc.SendPaymentRequest{ - Dest: net.Bob.PubKey[:], - Amt: paymentAmt, - FinalCltvDelta: 40, - PaymentHash: keySendHash[:], - DestCustomRecords: map[uint64][]byte{ - record.KeySendType: keySendPreimage[:], - }, - TimeoutSeconds: 60, - FeeLimitMsat: noFeeLimitMsat, + req := &routerrpc.SendPaymentRequest{ + Dest: bob.PubKey[:], + Amt: paymentAmt, + FinalCltvDelta: 40, + PaymentHash: keySendHash[:], + DestCustomRecords: map[uint64][]byte{ + record.KeySendType: keySendPreimage[:], }, - ) + TimeoutSeconds: 60, + FeeLimitMsat: noFeeLimitMsat, + } + ht.SendPaymentAssertSettled(alice, req) // The keysend payment should also have succeeded, with the balances // being update accordingly. - err = wait.NoError( - assertAmountSent(3*paymentAmt, net.Alice, net.Bob), - 3*time.Second, - ) - if err != nil { - t.Fatalf(err.Error()) - } + assertAmountPaid(paymentAmt * 3) // Assert that the invoice has the proper AMP fields set, since the // legacy keysend payment should have been promoted into an AMP payment // internally. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - keysendInvoice, err := net.Bob.LookupInvoice( - ctxt, &lnrpc.PaymentHash{ - RHash: keySendHash[:], - }, - ) - require.NoError(t.t, err) - require.Equal(t.t, 1, len(keysendInvoice.Htlcs)) + keysendInvoice := bob.RPC.LookupInvoice(keySendHash[:]) + require.Len(ht, keysendInvoice.Htlcs, 1) htlc := keysendInvoice.Htlcs[0] - require.Equal(t.t, uint64(0), htlc.MppTotalAmtMsat) - require.Nil(t.t, htlc.Amp) + require.Zero(ht, htlc.MppTotalAmtMsat) + require.Nil(ht, htlc.Amp) // Now create an invoice and specify routing hints. // We will test that the routing hints are encoded properly. hintChannel := lnwire.ShortChannelID{BlockHeight: 10} - bobPubKey := hex.EncodeToString(net.Bob.PubKey[:]) - hints := []*lnrpc.RouteHint{ - { - HopHints: []*lnrpc.HopHint{ - { - NodeId: bobPubKey, - ChanId: hintChannel.ToUint64(), - FeeBaseMsat: 1, - FeeProportionalMillionths: 1000000, - CltvExpiryDelta: 20, - }, - }, - }, + bobPubKey := hex.EncodeToString(bob.PubKey[:]) + hint := &lnrpc.HopHint{ + NodeId: bobPubKey, + ChanId: hintChannel.ToUint64(), + FeeBaseMsat: 1, + FeeProportionalMillionths: 1000000, + CltvExpiryDelta: 20, } + hints := []*lnrpc.RouteHint{{HopHints: []*lnrpc.HopHint{hint}}} invoice = &lnrpc.Invoice{ Memo: "hints", @@ -197,43 +121,21 @@ func testSingleHopInvoice(net *lntest.NetworkHarness, t *harnessTest) { RouteHints: hints, } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - invoiceResp, err = net.Bob.AddInvoice(ctxt, invoice) - if err != nil { - t.Fatalf("unable to add invoice: %v", err) - } - payreq, err := net.Bob.DecodePayReq(ctxt, &lnrpc.PayReqString{PayReq: invoiceResp.PaymentRequest}) - if err != nil { - t.Fatalf("failed to decode payment request %v", err) - } - if len(payreq.RouteHints) != 1 { - t.Fatalf("expected one routing hint") - } + invoiceResp = bob.RPC.AddInvoice(invoice) + payreq := bob.RPC.DecodePayReq(invoiceResp.PaymentRequest) + require.Len(ht, payreq.RouteHints, 1, "expected one routing hint") routingHint := payreq.RouteHints[0] - if len(routingHint.HopHints) != 1 { - t.Fatalf("expected one hop hint") - } - hopHint := routingHint.HopHints[0] - if hopHint.FeeProportionalMillionths != 1000000 { - t.Fatalf("wrong FeeProportionalMillionths %v", - hopHint.FeeProportionalMillionths) - } - if hopHint.NodeId != bobPubKey { - t.Fatalf("wrong NodeId %v", - hopHint.NodeId) - } - if hopHint.ChanId != hintChannel.ToUint64() { - t.Fatalf("wrong ChanId %v", - hopHint.ChanId) - } - if hopHint.FeeBaseMsat != 1 { - t.Fatalf("wrong FeeBaseMsat %v", - hopHint.FeeBaseMsat) - } - if hopHint.CltvExpiryDelta != 20 { - t.Fatalf("wrong CltvExpiryDelta %v", - hopHint.CltvExpiryDelta) - } + require.Len(ht, routingHint.HopHints, 1, "expected one hop hint") - closeChannelAndAssert(t, net, net.Alice, chanPoint, false) + hopHint := routingHint.HopHints[0] + require.EqualValues(ht, 1000000, hopHint.FeeProportionalMillionths, + "wrong FeeProportionalMillionths") + require.Equal(ht, bobPubKey, hopHint.NodeId, "wrong NodeId") + require.Equal(ht, hintChannel.ToUint64(), hopHint.ChanId, + "wrong ChanId") + require.EqualValues(ht, 1, hopHint.FeeBaseMsat, "wrong FeeBaseMsat") + require.EqualValues(ht, 20, hopHint.CltvExpiryDelta, + "wrong CltvExpiryDelta") + + ht.CloseChannel(alice, cp) } diff --git a/lntest/itest/lnd_test_list_on_test.go b/lntest/itest/lnd_test_list_on_test.go index c6da53385..f91382ab8 100644 --- a/lntest/itest/lnd_test_list_on_test.go +++ b/lntest/itest/lnd_test_list_on_test.go @@ -4,10 +4,6 @@ package itest var allTestCases = []*testCase{ - { - name: "single hop invoice", - test: testSingleHopInvoice, - }, { name: "multiple channel creation and update subscription", test: testBasicChannelCreationAndUpdates, From bc0bdfefc2edd5f6bcf6c7c84c06bf48041e07c6 Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Tue, 9 Aug 2022 15:36:07 +0800 Subject: [PATCH 37/44] itest: refactor `testWipeForwardingPackages` --- lntest/itest/list_on_test.go | 4 + lntest/itest/lnd_test_list_on_test.go | 4 - lntest/itest/lnd_wipe_fwdpkgs_test.go | 251 ++++++++------------------ 3 files changed, 78 insertions(+), 181 deletions(-) diff --git a/lntest/itest/list_on_test.go b/lntest/itest/list_on_test.go index 3d5635b00..151affe52 100644 --- a/lntest/itest/list_on_test.go +++ b/lntest/itest/list_on_test.go @@ -365,4 +365,8 @@ var allTestCasesTemp = []*lntemp.TestCase{ Name: "single hop invoice", TestFunc: testSingleHopInvoice, }, + { + Name: "wipe forwarding packages", + TestFunc: testWipeForwardingPackages, + }, } diff --git a/lntest/itest/lnd_test_list_on_test.go b/lntest/itest/lnd_test_list_on_test.go index f91382ab8..5d26440ac 100644 --- a/lntest/itest/lnd_test_list_on_test.go +++ b/lntest/itest/lnd_test_list_on_test.go @@ -96,10 +96,6 @@ var allTestCases = []*testCase{ name: "wallet import pubkey", test: testWalletImportPubKey, }, - { - name: "wipe forwarding packages", - test: testWipeForwardingPackages, - }, { name: "remote signer", test: testRemoteSigner, diff --git a/lntest/itest/lnd_wipe_fwdpkgs_test.go b/lntest/itest/lnd_wipe_fwdpkgs_test.go index 878211ed8..0f74d4fab 100644 --- a/lntest/itest/lnd_wipe_fwdpkgs_test.go +++ b/lntest/itest/lnd_wipe_fwdpkgs_test.go @@ -1,14 +1,11 @@ package itest import ( - "context" - "testing" "time" "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/lntemp" "github.com/stretchr/testify/require" ) @@ -22,91 +19,9 @@ type pendingChan *lnrpc.PendingChannelsResponse_PendingChannel // - Bob force closes the channel Alice->Bob, and checks from both Bob's // PoV(local force close) and Alice's Pov(remote close) that the forwarding // packages are wiped. -// - Bob coop closes the channel Bob->Carol, and checks from both Bob PoVs that -// the forwarding packages are wiped. -func testWipeForwardingPackages(net *lntest.NetworkHarness, - t *harnessTest) { - - // Setup the test and get the channel points. - pointAB, pointBC, carol, cleanUp := setupFwdPkgTest(net, t) - defer cleanUp() - - // Firstly, Bob force closes the channel. - _, _, err := net.CloseChannel(net.Bob, pointAB, true) - require.NoError(t.t, err, "unable to force close channel") - - // Now that the channel has been force closed, it should show up in - // bob's PendingChannels RPC under the waiting close section. - pendingChan := assertWaitingCloseChannel(t.t, net.Bob) - - // Check that Bob has created forwarding packages. We don't care the - // exact number here as long as these packages are deleted when the - // channel is closed. - require.NotZero(t.t, pendingChan.NumForwardingPackages) - - // Mine 1 block to get the closing transaction confirmed. - _, err = net.Miner.Client.Generate(1) - require.NoError(t.t, err, "unable to mine blocks") - - // Now that the closing transaction is confirmed, the above waiting - // close channel should now become pending force closed channel. - pendingChan = assertPendingForceClosedChannel(t.t, net.Bob) - - // Check the forwarding packages are deleted. - require.Zero(t.t, pendingChan.NumForwardingPackages) - - // For Alice, the forwarding packages should have been wiped too. - pendingChanAlice := assertPendingForceClosedChannel(t.t, net.Alice) - require.Zero(t.t, pendingChanAlice.NumForwardingPackages) - - // Secondly, Bob coop closes the channel. - _, _, err = net.CloseChannel(net.Bob, pointBC, false) - require.NoError(t.t, err, "unable to coop close channel") - - // Now that the channel has been coop closed, it should show up in - // bob's PendingChannels RPC under the waiting close section. - pendingChan = assertWaitingCloseChannel(t.t, net.Bob) - - // Check that Bob has created forwarding packages. We don't care the - // exact number here as long as these packages are deleted when the - // channel is closed. - require.NotZero(t.t, pendingChan.NumForwardingPackages) - - // Since it's a coop close, Carol should see the waiting close channel - // too. - pendingChanCarol := assertWaitingCloseChannel(t.t, carol) - require.NotZero(t.t, pendingChanCarol.NumForwardingPackages) - - // Mine 1 block to get the closing transaction confirmed. - _, err = net.Miner.Client.Generate(1) - require.NoError(t.t, err, "unable to mine blocks") - - // Now that the closing transaction is confirmed, the above waiting - // close channel should now become pending closed channel. Note that - // the name PendingForceClosingChannels is a bit confusing, what it - // really contains is channels whose closing tx has been broadcast. - pendingChan = assertPendingForceClosedChannel(t.t, net.Bob) - - // Check the forwarding packages are deleted. - require.Zero(t.t, pendingChan.NumForwardingPackages) - - // Mine a block to confirm sweep transactions such that they - // don't remain in the mempool for any subsequent tests. - _, err = net.Miner.Client.Generate(1) - require.NoError(t.t, err, "unable to mine blocks") -} - -// setupFwdPkgTest prepares the wipe forwarding packages tests. It creates a -// network topology that has a channel direction: Alice -> Bob -> Carol, sends -// several payments from Alice to Carol, and returns the two channel points(one -// for Alice and Bob, the other for Bob and Carol), the node Carol, and a -// cleanup function to be used when the test finishes. -func setupFwdPkgTest(net *lntest.NetworkHarness, - t *harnessTest) (*lnrpc.ChannelPoint, *lnrpc.ChannelPoint, - *lntest.HarnessNode, func()) { - - ctxb := context.Background() - +// - Bob coop closes the channel Bob->Carol, and checks from both Bob PoVs +// that the forwarding packages are wiped. +func testWipeForwardingPackages(ht *lntemp.HarnessTest) { const ( chanAmt = 10e6 paymentAmt = 10e4 @@ -114,114 +29,96 @@ func setupFwdPkgTest(net *lntest.NetworkHarness, numInvoices = 3 ) - // Grab Alice and Bob from harness net. - alice, bob := net.Alice, net.Bob + // Grab Alice and Bob from HarnessTest. + alice, bob := ht.Alice, ht.Bob // Create a new node Carol, which will create invoices that require // Alice to pay. - carol := net.NewNode(t.t, "Carol", nil) + carol := ht.NewNode("Carol", nil) // Connect Bob to Carol. - net.ConnectNodes(t.t, bob, carol) + ht.ConnectNodes(bob, carol) // Open a channel between Alice and Bob. - chanPointAB := openChannelAndAssert( - t, net, alice, bob, lntest.OpenChannelParams{ - Amt: chanAmt, - }, + chanPointAB := ht.OpenChannel( + alice, bob, lntemp.OpenChannelParams{Amt: chanAmt}, ) // Open a channel between Bob and Carol. - chanPointBC := openChannelAndAssert( - t, net, bob, carol, lntest.OpenChannelParams{ - Amt: chanAmt, - }, + chanPointBC := ht.OpenChannel( + bob, carol, lntemp.OpenChannelParams{Amt: chanAmt}, ) - ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) - defer cancel() + // Before we continue, make sure Alice has seen the channel between Bob + // and Carol. + ht.AssertTopologyChannelOpen(alice, chanPointBC) // Alice sends several payments to Carol through Bob, which triggers // Bob to create forwarding packages. for i := 0; i < numInvoices; i++ { // Add an invoice for Carol. invoice := &lnrpc.Invoice{Memo: "testing", Value: paymentAmt} - invoiceResp, err := carol.AddInvoice(ctxt, invoice) - require.NoError(t.t, err, "unable to add invoice") + resp := carol.RPC.AddInvoice(invoice) // Alice sends a payment to Carol through Bob. - sendAndAssertSuccess( - t, net.Alice, &routerrpc.SendPaymentRequest{ - PaymentRequest: invoiceResp.PaymentRequest, - TimeoutSeconds: 60, - FeeLimitSat: noFeeLimitMsat, - }, - ) + ht.CompletePaymentRequests(alice, []string{resp.PaymentRequest}) } - return chanPointAB, chanPointBC, carol, func() { - shutdownAndAssert(net, t, alice) - shutdownAndAssert(net, t, bob) - shutdownAndAssert(net, t, carol) - } -} - -// assertWaitingCloseChannel checks there is a single channel that is waiting -// for close and returns the channel found. -func assertWaitingCloseChannel(t *testing.T, - node *lntest.HarnessNode) pendingChan { - - ctxb := context.Background() - - var channel pendingChan - require.Eventually(t, func() bool { - ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) - defer cancel() - - req := &lnrpc.PendingChannelsRequest{} - resp, err := node.PendingChannels(ctxt, req) - - // We require the RPC call to be succeeded and won't retry upon - // an error. - require.NoError(t, err, "unable to query for pending channels") - - if err := checkNumWaitingCloseChannels(resp, 1); err != nil { - return false - } - - channel = resp.WaitingCloseChannels[0].Channel - return true - }, defaultTimeout, 200*time.Millisecond) - - return channel -} - -// assertForceClosedChannel checks there is a single channel that is pending -// force closed and returns the channel found. -func assertPendingForceClosedChannel(t *testing.T, - node *lntest.HarnessNode) pendingChan { - - ctxb := context.Background() - - var channel pendingChan - require.Eventually(t, func() bool { - ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) - defer cancel() - - req := &lnrpc.PendingChannelsRequest{} - resp, err := node.PendingChannels(ctxt, req) - - // We require the RPC call to be succeeded and won't retry upon - // an error. - require.NoError(t, err, "unable to query for pending channels") - - if err := checkNumForceClosedChannels(resp, 1); err != nil { - return false - } - - channel = resp.PendingForceClosingChannels[0].Channel - return true - }, defaultTimeout, 200*time.Millisecond) - - return channel + // TODO(yy): remove the sleep once the following bug is fixed. + // When the invoice is reported settled, the commitment dance is not + // yet finished, which can cause an error when closing the channel, + // saying there's active HTLCs. We need to investigate this issue and + // reverse the order to, first finish the commitment dance, then report + // the invoice as settled. + time.Sleep(2 * time.Second) + + // Firstly, Bob force closes the channel. + ht.CloseChannelAssertPending(bob, chanPointAB, true) + + // Now that the channel has been force closed, it should show up in + // bob's PendingChannels RPC under the waiting close section. + pendingAB := ht.AssertChannelWaitingClose(bob, chanPointAB).Channel + + // Check that Bob has created forwarding packages. We don't care the + // exact number here as long as these packages are deleted when the + // channel is closed. + require.NotZero(ht, pendingAB.NumForwardingPackages) + + // Secondly, Bob coop closes the channel. + ht.CloseChannelAssertPending(bob, chanPointBC, false) + + // Now that the channel has been coop closed, it should show up in + // bob's PendingChannels RPC under the waiting close section. + pendingBC := ht.AssertChannelWaitingClose(bob, chanPointBC).Channel + + // Check that Bob has created forwarding packages. We don't care the + // exact number here as long as these packages are deleted when the + // channel is closed. + require.NotZero(ht, pendingBC.NumForwardingPackages) + + // Since it's a coop close, Carol should see the waiting close channel + // too. + pendingBC = ht.AssertChannelWaitingClose(carol, chanPointBC).Channel + require.NotZero(ht, pendingBC.NumForwardingPackages) + + // Mine 1 block to get the two closing transactions confirmed. + ht.MineBlocksAndAssertNumTxes(1, 2) + + // Now that the closing transaction is confirmed, the above waiting + // close channel should now become pending force closed channel. + pendingAB = ht.AssertChannelPendingForceClose(bob, chanPointAB).Channel + + // Check the forwarding pacakges are deleted. + require.Zero(ht, pendingAB.NumForwardingPackages) + + // For Alice, the forwarding packages should have been wiped too. + pending := ht.AssertChannelPendingForceClose(alice, chanPointAB) + pendingAB = pending.Channel + require.Zero(ht, pendingAB.NumForwardingPackages) + + // Mine 1 block to get Alice's sweeping tx confirmed. + ht.MineBlocksAndAssertNumTxes(1, 1) + + // Clean up the force closed channel. + ht.CleanupForceClose(bob, chanPointAB) } From 59578d9f1a87db24a30303ec3bc0a3b7e48f95e0 Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Thu, 25 Aug 2022 04:27:00 +0800 Subject: [PATCH 38/44] routing: add more verbose logs --- routing/bandwidth.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/routing/bandwidth.go b/routing/bandwidth.go index 8abf025ab..19c608701 100644 --- a/routing/bandwidth.go +++ b/routing/bandwidth.go @@ -77,6 +77,7 @@ func (b *bandwidthManager) getBandwidth(cid lnwire.ShortChannelID, if err != nil { // If the link isn't online, then we'll report that it has // zero bandwidth. + log.Warnf("ShortChannelID=%v: link not found: %v", cid, err) return 0 } @@ -84,12 +85,15 @@ func (b *bandwidthManager) getBandwidth(cid lnwire.ShortChannelID, // to forward any HTLCs, then we'll treat it as if it isn't online in // the first place. if !link.EligibleToForward() { + log.Warnf("ShortChannelID=%v: not eligible to forward", cid) return 0 } // If our link isn't currently in a state where it can add another // outgoing htlc, treat the link as unusable. if err := link.MayAddOutgoingHtlc(amount); err != nil { + log.Warnf("ShortChannelID=%v: cannot add outgoing htlc: %v", + cid, err) return 0 } From f9ede5af73683e8244e45e6230d40606d69f697f Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Thu, 1 Sep 2022 13:53:06 +0800 Subject: [PATCH 39/44] funding+lnwallet: add more debug logs --- funding/manager.go | 4 ++++ lnwallet/wallet.go | 7 +++++++ 2 files changed, 11 insertions(+) diff --git a/funding/manager.go b/funding/manager.go index c357d6dc7..809a56a07 100644 --- a/funding/manager.go +++ b/funding/manager.go @@ -1505,6 +1505,10 @@ func (f *Manager) handleFundingOpen(peer lnpeer.Peer, return } + log.Debugf("Initialized channel reservation: zeroConf=%v, psbt=%v, "+ + "cannedShim=%v", reservation.IsZeroConf(), + reservation.IsPsbt(), reservation.IsCannedShim()) + if zeroConf { // Store an alias for zero-conf channels. Other option-scid // channels will do this at a later point. diff --git a/lnwallet/wallet.go b/lnwallet/wallet.go index 49c6410be..6b1400fc6 100644 --- a/lnwallet/wallet.go +++ b/lnwallet/wallet.go @@ -798,6 +798,9 @@ func (l *LightningWallet) handleFundingReserveRequest(req *InitFundingReserveMsg return } + walletLog.Debugf("Registered funding intent for "+ + "PendingChanID: %x", req.PendingChanID) + localFundingAmt = fundingIntent.LocalFundingAmt() remoteFundingAmt = fundingIntent.RemoteFundingAmt() } @@ -891,6 +894,10 @@ func (l *LightningWallet) handleFundingReserveRequest(req *InitFundingReserveMsg // completed, or canceled. req.resp <- reservation req.err <- nil + + walletLog.Debugf("Successfully handled funding reservation with "+ + "pendingChanID: %x, reservationID: %v", + reservation.pendingChanID, reservation.reservationID) } // enforceReservedValue enforces that the wallet, upon a new channel being From fe376385d1c864453ad08353b62e5da6f9b60a57 Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Tue, 25 Oct 2022 21:27:40 +0800 Subject: [PATCH 40/44] lntemp+lntest: fix make lint --- lntemp/rpc/router.go | 2 + lntest/harness_node.go | 2 +- lntest/itest/lnd_routing_test.go | 83 ++++++++++++++++++-------------- 3 files changed, 51 insertions(+), 36 deletions(-) diff --git a/lntemp/rpc/router.go b/lntemp/rpc/router.go index 70f09fc10..368ceffae 100644 --- a/lntemp/rpc/router.go +++ b/lntemp/rpc/router.go @@ -14,6 +14,8 @@ import ( // UpdateChanStatus makes a UpdateChanStatus RPC call to node's RouterClient // and asserts. +// +//nolint:lll func (h *HarnessRPC) UpdateChanStatus( req *routerrpc.UpdateChanStatusRequest) *routerrpc.UpdateChanStatusResponse { diff --git a/lntest/harness_node.go b/lntest/harness_node.go index 1eb0b9ac8..67cffe1bb 100644 --- a/lntest/harness_node.go +++ b/lntest/harness_node.go @@ -487,7 +487,7 @@ func executePgQuery(query string) error { postgresDatabaseDsn("postgres"), ) if err != nil { - return fmt.Errorf("unable to connect to database: %v", err) + return fmt.Errorf("unable to connect to database: %w", err) } defer pool.Close() diff --git a/lntest/itest/lnd_routing_test.go b/lntest/itest/lnd_routing_test.go index a27300337..e425dbe44 100644 --- a/lntest/itest/lnd_routing_test.go +++ b/lntest/itest/lnd_routing_test.go @@ -901,73 +901,86 @@ func testQueryRoutes(ht *lntemp.HarnessTest) { 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 + expectedTotalAmtMSat := + (paymentAmt * mSat) + expectedTotalFeesMSat + + if route.TotalFees != route.TotalFeesMsat/mSat { ht.Fatalf("route %v: total fees %v (msat) does not "+ "round down to %v (sat)", - i, route.TotalFeesMsat, route.TotalFees) // nolint:staticcheck + i, route.TotalFeesMsat, route.TotalFees) } if route.TotalFeesMsat != int64(expectedTotalFeesMSat) { - ht.Fatalf("route %v: total fees in msat expected %v got %v", - i, expectedTotalFeesMSat, route.TotalFeesMsat) + ht.Fatalf("route %v: total fees in msat expected %v "+ + "got %v", i, expectedTotalFeesMSat, + route.TotalFeesMsat) } - if route.TotalAmt != route.TotalAmtMsat/mSat { // nolint:staticcheck + if route.TotalAmt != route.TotalAmtMsat/mSat { ht.Fatalf("route %v: total amt %v (msat) does not "+ "round down to %v (sat)", - i, route.TotalAmtMsat, route.TotalAmt) // nolint:staticcheck + i, route.TotalAmtMsat, route.TotalAmt) } if route.TotalAmtMsat != int64(expectedTotalAmtMSat) { - ht.Fatalf("route %v: total amt in msat expected %v got %v", - i, expectedTotalAmtMSat, route.TotalAmtMsat) + ht.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. + // 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 - ht.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.Fee != hop.FeeMsat/mSat { + ht.Fatalf("route %v hop %v: fee %v (msat) "+ + "does not round down to %v (sat)", + i, j, hop.FeeMsat, hop.Fee) } if hop.FeeMsat != int64(feePerHopMSat) { - ht.Fatalf("route %v hop %v: fee in msat expected %v got %v", + ht.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 - ht.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.AmtToForward != hop.AmtToForwardMsat/mSat { + ht.Fatalf("route %v hop %v: amt to forward %v"+ + "(msat) does not round down to %v(sat)", + i, j, hop.AmtToForwardMsat, + hop.AmtToForward) } - if hop.AmtToForwardMsat != int64(expectedAmtToForwardMSat) { - ht.Fatalf("route %v hop %v: amt to forward in msat "+ - "expected %v got %v", - i, j, expectedAmtToForwardMSat, hop.AmtToForwardMsat) + if hop.AmtToForwardMsat != + int64(expectedAmtToForwardMSat) { + + ht.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. + + // 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 - ht.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.Fee != 0 || hop.FeeMsat != 0 { + ht.Fatalf("route %v hop %v: fee expected 0 got %v "+ + "(sat) %v (msat)", i, len(route.Hops)-1, + hop.Fee, hop.FeeMsat) } - if hop.AmtToForward != hop.AmtToForwardMsat/mSat { // nolint:staticcheck - ht.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.AmtToForward != hop.AmtToForwardMsat/mSat { + ht.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) } if hop.AmtToForwardMsat != paymentAmt*mSat { ht.Fatalf("route %v hop %v: amt to forward in msat "+ - "expected %v got %v", - i, len(route.Hops)-1, paymentAmt*mSat, hop.AmtToForwardMsat) + "expected %v got %v", i, len(route.Hops)-1, + paymentAmt*mSat, hop.AmtToForwardMsat) } } From 3353643006e6f206877e1adab74a8f7dd5b4f988 Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Thu, 8 Dec 2022 17:54:39 +0800 Subject: [PATCH 41/44] itest: fix test `testListPayments` --- lntemp/harness.go | 6 +++--- lntemp/rpc/lnd.go | 4 ++++ lntest/itest/lnd_payment_test.go | 22 ++++++++++++++++------ 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/lntemp/harness.go b/lntemp/harness.go index 75454c105..233495a41 100644 --- a/lntemp/harness.go +++ b/lntemp/harness.go @@ -294,6 +294,9 @@ func (h *HarnessTest) resetStandbyNodes(t *testing.T) { // config for the coming test. This will also inherit the // test's running context. h.RestartNodeWithExtraArgs(hn, hn.Cfg.OriginalExtraArgs) + + // Update the node's internal state. + hn.UpdateState() } } @@ -405,9 +408,6 @@ func (h *HarnessTest) cleanupStandbyNode(hn *node.HarnessNode) { // Delete all payments made from this test. hn.RPC.DeleteAllPayments() - // Update the node's internal state. - hn.UpdateState() - // Finally, check the node is in a clean state for the following tests. h.validateNodeState(hn) } diff --git a/lntemp/rpc/lnd.go b/lntemp/rpc/lnd.go index 894bfdbd3..7a95fbe35 100644 --- a/lntemp/rpc/lnd.go +++ b/lntemp/rpc/lnd.go @@ -194,6 +194,10 @@ func (h *HarnessRPC) ListInvoices( ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout) defer cancel() + if req == nil { + req = &lnrpc.ListInvoiceRequest{} + } + resp, err := h.LN.ListInvoices(ctxt, req) h.NoError(err, "ListInvoice") diff --git a/lntest/itest/lnd_payment_test.go b/lntest/itest/lnd_payment_test.go index e965b7c3c..65343825e 100644 --- a/lntest/itest/lnd_payment_test.go +++ b/lntest/itest/lnd_payment_test.go @@ -1,7 +1,6 @@ package itest import ( - "bytes" "context" "crypto/sha256" "encoding/hex" @@ -31,11 +30,18 @@ func testListPayments(ht *lntemp.HarnessTest) { alice, bob, lntemp.OpenChannelParams{Amt: chanAmt}, ) + // Get the number of invoices Bob already has. + // + // TODO(yy): we can remove this check once the `DeleteAllInvoices` rpc + // is added. + invResp := bob.RPC.ListInvoices(nil) + numOldInvoices := len(invResp.Invoices) + // Now that the channel is open, create an invoice for Bob which // expects a payment of 1000 satoshis from Alice paid via a particular // preimage. const paymentAmt = 1000 - preimage := bytes.Repeat([]byte("B"), 32) + preimage := ht.Random32Bytes() invoice := &lnrpc.Invoice{ Memo: "testing", RPreimage: preimage, @@ -43,6 +49,10 @@ func testListPayments(ht *lntemp.HarnessTest) { } invoiceResp := bob.RPC.AddInvoice(invoice) + // Check that Bob has added the invoice. + numInvoices := numOldInvoices + 1 + ht.AssertNumInvoices(bob, 1) + // With the invoice for Bob added, send a payment towards Alice paying // to the above generated invoice. payReqs := []string{invoiceResp.PaymentRequest} @@ -113,8 +123,8 @@ func testListPayments(ht *lntemp.HarnessTest) { invReq := &lnrpc.ListInvoiceRequest{ CreationDateStart: 1227035905, } - invResp := bob.RPC.ListInvoices(invReq) - require.Len(ht, invResp.Invoices, 1) + invResp = bob.RPC.ListInvoices(invReq) + require.Len(ht, invResp.Invoices, numInvoices) // Use an end date long time ago should return us nothing. invReq = &lnrpc.ListInvoiceRequest{ @@ -135,7 +145,7 @@ func testListPayments(ht *lntemp.HarnessTest) { CreationDateEnd: 5392552705, } invResp = bob.RPC.ListInvoices(invReq) - require.Len(ht, invResp.Invoices, 1) + require.Len(ht, invResp.Invoices, numInvoices) // Delete all payments from Alice. DB should have no payments. alice.RPC.DeleteAllPayments() @@ -152,7 +162,7 @@ func testListPayments(ht *lntemp.HarnessTest) { time.Sleep(2 * time.Second) // Close the channel. - defer ht.CloseChannel(alice, chanPoint) + ht.CloseChannel(alice, chanPoint) } // testPaymentFollowingChannelOpen tests that the channel transition from From 23ec7b23e1b56b2f5c105e3e4a37cd197e701349 Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Tue, 13 Dec 2022 11:19:59 +0800 Subject: [PATCH 42/44] lntemp: make sure mempool is synced when funding nodes --- lntemp/harness.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lntemp/harness.go b/lntemp/harness.go index 233495a41..033633f3a 100644 --- a/lntemp/harness.go +++ b/lntemp/harness.go @@ -216,7 +216,8 @@ func (h *HarnessTest) SetupStandbyNodes() { // We generate several blocks in order to give the outputs created // above a good number of confirmations. - h.MineBlocks(numBlocksSendOutput) + const totalTxes = 20 + h.MineBlocksAndAssertNumTxes(numBlocksSendOutput, totalTxes) // Now we want to wait for the nodes to catch up. h.WaitForBlockchainSync(h.Alice) From d4b2798570ecf0ce9980b9f4182d93e8a42f619c Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Tue, 17 Jan 2023 07:34:07 +0800 Subject: [PATCH 43/44] itest: make sure SendToRoute stream is recieved with timeout --- lntest/itest/lnd_channel_policy_test.go | 4 ++-- lntest/itest/lnd_routing_test.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lntest/itest/lnd_channel_policy_test.go b/lntest/itest/lnd_channel_policy_test.go index 236f0e73b..faac28c39 100644 --- a/lntest/itest/lnd_channel_policy_test.go +++ b/lntest/itest/lnd_channel_policy_test.go @@ -199,7 +199,7 @@ func testUpdateChannelPolicy(ht *lntemp.HarnessTest) { // We expect this payment to fail, and that the min_htlc value is // communicated back to us, since the attempted HTLC value was too low. - sendResp, err := alicePayStream.Recv() + sendResp, err := ht.ReceiveSendToRouteUpdate(alicePayStream) require.NoError(ht, err, "unable to receive payment stream") // Expected as part of the error message. @@ -238,7 +238,7 @@ func testUpdateChannelPolicy(ht *lntemp.HarnessTest) { err = alicePayStream.Send(sendReq) require.NoError(ht, err, "unable to send payment") - sendResp, err = alicePayStream.Recv() + sendResp, err = ht.ReceiveSendToRouteUpdate(alicePayStream) require.NoError(ht, err, "unable to receive payment stream") require.Empty(ht, sendResp.PaymentError, "expected payment to succeed") diff --git a/lntest/itest/lnd_routing_test.go b/lntest/itest/lnd_routing_test.go index e425dbe44..b94e3b488 100644 --- a/lntest/itest/lnd_routing_test.go +++ b/lntest/itest/lnd_routing_test.go @@ -173,7 +173,7 @@ func testSingleHopSendToRouteCase(ht *lntemp.HarnessTest, err := alicePayStream.Send(sendReq) require.NoError(ht, err, "unable to send payment") - resp, err := alicePayStream.Recv() + resp, err := ht.ReceiveSendToRouteUpdate(alicePayStream) require.NoError(ht, err, "unable to receive stream") require.Emptyf(ht, resp.PaymentError, "received payment error from %s: %v", From 00a790944787bbd2ade8dc9e15399c1e470bd2ff Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Fri, 12 Aug 2022 18:04:13 +0800 Subject: [PATCH 44/44] docs: update release docs --- docs/release-notes/release-notes-0.16.0.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/release-notes/release-notes-0.16.0.md b/docs/release-notes/release-notes-0.16.0.md index c6f5f8170..fa664cdbd 100644 --- a/docs/release-notes/release-notes-0.16.0.md +++ b/docs/release-notes/release-notes-0.16.0.md @@ -348,7 +348,8 @@ PRs([6776](https://github.com/lightningnetwork/lnd/pull/6776), [6822](https://github.com/lightningnetwork/lnd/pull/6822), [7172](https://github.com/lightningnetwork/lnd/pull/7172), [7242](https://github.com/lightningnetwork/lnd/pull/7242), -[7245](https://github.com/lightningnetwork/lnd/pull/7245)) have been made to +[7245](https://github.com/lightningnetwork/lnd/pull/7245)), +[6823](https://github.com/lightningnetwork/lnd/pull/6823)) have been made to refactor the itest for code health and maintenance. # Contributors (Alphabetical Order)