diff --git a/docs/release-notes/release-notes-0.16.0.md b/docs/release-notes/release-notes-0.16.0.md index d16f7954c..92338cd9f 100644 --- a/docs/release-notes/release-notes-0.16.0.md +++ b/docs/release-notes/release-notes-0.16.0.md @@ -369,7 +369,8 @@ PRs([6776](https://github.com/lightningnetwork/lnd/pull/6776), [7172](https://github.com/lightningnetwork/lnd/pull/7172), [7242](https://github.com/lightningnetwork/lnd/pull/7242), [7245](https://github.com/lightningnetwork/lnd/pull/7245)), -[6823](https://github.com/lightningnetwork/lnd/pull/6823)) have been made to +[6823](https://github.com/lightningnetwork/lnd/pull/6823), +[6824](https://github.com/lightningnetwork/lnd/pull/6824),) have been made to refactor the itest for code health and maintenance. # Contributors (Alphabetical Order) diff --git a/lntemp/harness.go b/lntemp/harness.go index 033633f3a..781a5ccf3 100644 --- a/lntemp/harness.go +++ b/lntemp/harness.go @@ -4,7 +4,6 @@ import ( "context" "encoding/hex" "fmt" - "sync" "testing" "time" @@ -676,6 +675,32 @@ func (h *HarnessTest) NewNodeWithSeedEtcd(name string, etcdCfg *etcd.Config, return h.newNodeWithSeed(name, extraArgs, req, statelessInit) } +// NewNodeRemoteSigner creates a new remote signer node and asserts its +// creation. +func (h *HarnessTest) NewNodeRemoteSigner(name string, extraArgs []string, + password []byte, watchOnly *lnrpc.WatchOnly) *node.HarnessNode { + + hn, err := h.manager.newNode(h.T, name, extraArgs, password, true) + require.NoErrorf(h, err, "unable to create new node for %s", name) + + err = hn.StartWithNoAuth(h.runCtx) + require.NoError(h, err, "failed to start node %s", name) + + // With the seed created, construct the init request to the node, + // including the newly generated seed. + initReq := &lnrpc.InitWalletRequest{ + WalletPassword: password, + WatchOnly: watchOnly, + } + + // Pass the init request via rpc to finish unlocking the node. This + // will also initialize the macaroon-authenticated LightningClient. + _, err = h.manager.initWalletAndNode(hn, initReq) + require.NoErrorf(h, err, "failed to init node %s", name) + + return hn +} + // 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()) @@ -797,6 +822,26 @@ type OpenChannelParams struct { // ScidAlias denotes whether the channel will be an option-scid-alias // channel type negotiation. ScidAlias bool + + // BaseFee is the channel base fee applied during the channel + // announcement phase. + BaseFee uint64 + + // FeeRate is the channel fee rate in ppm applied during the channel + // announcement phase. + FeeRate uint64 + + // UseBaseFee, if set, instructs the downstream logic to apply the + // user-specified channel base fee to the channel update announcement. + // If set to false it avoids applying a base fee of 0 and instead + // activates the default configured base fee. + UseBaseFee bool + + // UseFeeRate, if set, instructs the downstream logic to apply the + // user-specified channel fee rate to the channel update announcement. + // If set to false it avoids applying a fee rate of 0 and instead + // activates the default configured fee rate. + UseFeeRate bool } // prepareOpenChannel waits for both nodes to be synced to chain and returns an @@ -833,6 +878,10 @@ func (h *HarnessTest) prepareOpenChannel(srcNode, destNode *node.HarnessNode, CommitmentType: p.CommitmentType, ZeroConf: p.ZeroConf, ScidAlias: p.ScidAlias, + BaseFee: p.BaseFee, + FeeRate: p.FeeRate, + UseBaseFee: p.UseBaseFee, + UseFeeRate: p.UseFeeRate, } } @@ -886,24 +935,76 @@ func (h *HarnessTest) OpenChannelAssertStream(srcNode, } // OpenChannel attempts to open a channel with the specified parameters -// extended from Alice to Bob. Additionally, the following items are asserted, -// - 6 blocks will be mined so the channel will be announced if it's public. -// - the funding transaction should be found in the first block. +// extended from Alice to Bob. Additionally, for public channels, it will mine +// extra blocks so they are announced to the network. In specific, the +// following items are asserted, +// - for non-zero conf channel, 1 blocks will be mined to confirm the funding +// tx. // - both nodes should see the channel edge update in their network graph. // - both nodes can report the status of the new channel from ListChannels. +// - extra blocks are mined if it's a public channel. func (h *HarnessTest) OpenChannel(alice, bob *node.HarnessNode, p OpenChannelParams) *lnrpc.ChannelPoint { + // First, open the channel without announcing it. + cp := h.OpenChannelNoAnnounce(alice, bob, p) + + // If this is a private channel, there's no need to mine extra blocks + // since it will never be announced to the network. + if p.Private { + return cp + } + + // Mine extra blocks to announce the channel. + if p.ZeroConf { + // For a zero-conf channel, no blocks have been mined so we + // need to mine 6 blocks. + // + // Mine 1 block to confirm the funding transaction. + h.MineBlocksAndAssertNumTxes(numBlocksOpenChannel, 1) + } else { + // For a regular channel, 1 block has already been mined to + // confirm the funding transaction, so we mine 5 blocks. + h.MineBlocks(numBlocksOpenChannel - 1) + } + + return cp +} + +// OpenChannelNoAnnounce attempts to open a channel with the specified +// parameters extended from Alice to Bob without mining the necessary blocks to +// announce the channel. Additionally, the following items are asserted, +// - for non-zero conf channel, 1 blocks will be mined to confirm the funding +// tx. +// - both nodes should see the channel edge update in their network graph. +// - both nodes can report the status of the new channel from ListChannels. +func (h *HarnessTest) OpenChannelNoAnnounce(alice, bob *node.HarnessNode, + p OpenChannelParams) *lnrpc.ChannelPoint { + chanOpenUpdate := h.OpenChannelAssertStream(alice, bob, p) - // Mine 6 blocks, then wait for Alice's node to notify us that the - // channel has been opened. The funding transaction should be found - // within the first newly mined block. We mine 6 blocks so that in the - // case that the channel is public, it is announced to the network. - block := h.MineBlocksAndAssertNumTxes(numBlocksOpenChannel, 1)[0] + // Open a zero conf channel. + if p.ZeroConf { + return h.openChannelZeroConf(alice, bob, chanOpenUpdate) + } + + // Open a non-zero conf channel. + return h.openChannel(alice, bob, chanOpenUpdate) +} + +// openChannel attempts to open a channel with the specified parameters +// extended from Alice to Bob. Additionally, the following items are asserted, +// - 1 block is mined and the funding transaction should be found in it. +// - both nodes should see the channel edge update in their network graph. +// - both nodes can report the status of the new channel from ListChannels. +func (h *HarnessTest) openChannel(alice, bob *node.HarnessNode, + stream rpc.OpenChanClient) *lnrpc.ChannelPoint { + + // Mine 1 block to confirm the funding transaction. + block := h.MineBlocksAndAssertNumTxes(1, 1)[0] // Wait for the channel open event. - fundingChanPoint := h.WaitForChannelOpenEvent(chanOpenUpdate) + fundingChanPoint := h.WaitForChannelOpenEvent(stream) // Check that the funding tx is found in the first block. fundingTxID := h.GetChanPointFundingTxid(fundingChanPoint) @@ -918,9 +1019,27 @@ func (h *HarnessTest) OpenChannel(alice, bob *node.HarnessNode, h.AssertChannelExists(alice, fundingChanPoint) h.AssertChannelExists(bob, fundingChanPoint) - // Finally, check the blocks are synced. - h.WaitForBlockchainSync(alice) - h.WaitForBlockchainSync(bob) + return fundingChanPoint +} + +// openChannelZeroConf attempts to open a channel with the specified parameters +// extended from Alice to Bob. Additionally, the following items are asserted, +// - both nodes should see the channel edge update in their network graph. +// - both nodes can report the status of the new channel from ListChannels. +func (h *HarnessTest) openChannelZeroConf(alice, bob *node.HarnessNode, + stream rpc.OpenChanClient) *lnrpc.ChannelPoint { + + // Wait for the channel open event. + fundingChanPoint := h.WaitForChannelOpenEvent(stream) + + // Check that both alice and bob have seen the channel from their + // network topology. + h.AssertTopologyChannelOpen(alice, fundingChanPoint) + h.AssertTopologyChannelOpen(bob, fundingChanPoint) + + // Finally, check that the channel can be seen in their ListChannels. + h.AssertChannelExists(alice, fundingChanPoint) + h.AssertChannelExists(bob, fundingChanPoint) return fundingChanPoint } @@ -1145,36 +1264,54 @@ func (h *HarnessTest) FundCoinsP2TR(amt btcutil.Amount, h.fundCoins(amt, target, lnrpc.AddressType_TAPROOT_PUBKEY, true) } -// CompletePaymentRequests sends payments from a node to complete all payment -// requests. This function does not return until all payments successfully -// complete without errors. -func (h *HarnessTest) CompletePaymentRequests(hn *node.HarnessNode, - paymentRequests []string) { +// completePaymentRequestsAssertStatus sends payments from a node to complete +// all payment requests. This function does not return until all payments +// have reached the specified status. +func (h *HarnessTest) completePaymentRequestsAssertStatus(hn *node.HarnessNode, + paymentRequests []string, status lnrpc.Payment_PaymentStatus) { - var wg sync.WaitGroup + // Create a buffered chan to signal the results. + results := make(chan rpc.PaymentClient, len(paymentRequests)) // send sends a payment and asserts if it doesn't succeeded. send := func(payReq string) { - defer wg.Done() - req := &routerrpc.SendPaymentRequest{ PaymentRequest: payReq, TimeoutSeconds: defaultPaymentTimeout, FeeLimitMsat: noFeeLimitMsat, } stream := hn.RPC.SendPayment(req) - h.AssertPaymentStatusFromStream(stream, lnrpc.Payment_SUCCEEDED) + + // Signal sent succeeded. + results <- stream } // Launch all payments simultaneously. for _, payReq := range paymentRequests { payReqCopy := payReq - wg.Add(1) go send(payReqCopy) } - // Wait for all payments to report success. - wg.Wait() + // Wait for all payments to report the expected status. + timer := time.After(DefaultTimeout) + select { + case stream := <-results: + h.AssertPaymentStatusFromStream(stream, status) + + case <-timer: + require.Fail(h, "timeout", "waiting payment results timeout") + } +} + +// CompletePaymentRequests sends payments from a node to complete all payment +// requests. This function does not return until all payments successfully +// complete without errors. +func (h *HarnessTest) CompletePaymentRequests(hn *node.HarnessNode, + paymentRequests []string) { + + h.completePaymentRequestsAssertStatus( + hn, paymentRequests, lnrpc.Payment_SUCCEEDED, + ) } // CompletePaymentRequestsNoWait sends payments from a node to complete all @@ -1188,21 +1325,10 @@ func (h *HarnessTest) CompletePaymentRequestsNoWait(hn *node.HarnessNode, // we return. oldResp := h.GetChannelByChanPoint(hn, chanPoint) - // send sends a payment and asserts if it doesn't succeeded. - send := func(payReq string) { - req := &routerrpc.SendPaymentRequest{ - PaymentRequest: payReq, - TimeoutSeconds: defaultPaymentTimeout, - FeeLimitMsat: noFeeLimitMsat, - } - hn.RPC.SendPayment(req) - } - - // Launch all payments simultaneously. - for _, payReq := range paymentRequests { - payReqCopy := payReq - go send(payReqCopy) - } + // Send payments and assert they are in-flight. + h.completePaymentRequestsAssertStatus( + hn, paymentRequests, lnrpc.Payment_IN_FLIGHT, + ) // We are not waiting for feedback in the form of a response, but we // should still wait long enough for the server to receive and handle @@ -1797,3 +1923,92 @@ func (h *HarnessTest) QueryRoutesAndRetry(hn *node.HarnessNode, return routes } + +// ReceiveHtlcInterceptor waits until a message is received on the htlc +// interceptor stream or the timeout is reached. +func (h *HarnessTest) ReceiveHtlcInterceptor( + stream rpc.InterceptorClient) *routerrpc.ForwardHtlcInterceptRequest { + + chanMsg := make(chan *routerrpc.ForwardHtlcInterceptRequest) + 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 intercepting htlc") + + case err := <-errChan: + require.Failf(h, "err from stream", + "received err from stream: %v", err) + + case updateMsg := <-chanMsg: + return updateMsg + } + + return nil +} + +// ReceiveChannelEvent waits until a message is received from the +// ChannelEventsClient stream or the timeout is reached. +func (h *HarnessTest) ReceiveChannelEvent( + stream rpc.ChannelEventsClient) *lnrpc.ChannelEventUpdate { + + chanMsg := make(chan *lnrpc.ChannelEventUpdate) + 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 intercepting htlc") + + case err := <-errChan: + require.Failf(h, "err from stream", + "received err from stream: %v", err) + + case updateMsg := <-chanMsg: + return updateMsg + } + + return nil +} + +// GetOutputIndex returns the output index of the given address in the given +// transaction. +func (h *HarnessTest) GetOutputIndex(txid *chainhash.Hash, addr string) int { + // We'll then extract the raw transaction from the mempool in order to + // determine the index of the p2tr output. + tx := h.Miner.GetRawTransaction(txid) + + p2trOutputIndex := -1 + for i, txOut := range tx.MsgTx().TxOut { + _, addrs, _, err := txscript.ExtractPkScriptAddrs( + txOut.PkScript, h.Miner.ActiveNet, + ) + require.NoError(h, err) + + if addrs[0].String() == addr { + p2trOutputIndex = i + } + } + require.Greater(h, p2trOutputIndex, -1) + + return p2trOutputIndex +} diff --git a/lntemp/harness_assertion.go b/lntemp/harness_assertion.go index e2d7c05e3..9b61c9186 100644 --- a/lntemp/harness_assertion.go +++ b/lntemp/harness_assertion.go @@ -1,12 +1,14 @@ package lntemp import ( + "bytes" "context" "crypto/rand" "encoding/hex" "encoding/json" "fmt" "math" + "sort" "strings" "time" @@ -127,6 +129,9 @@ func (h *HarnessTest) ConnectNodesPerm(a, b *node.HarnessNode) { func (h *HarnessTest) DisconnectNodes(a, b *node.HarnessNode) { bobInfo := b.RPC.GetInfo() a.RPC.DisconnectPeer(bobInfo.IdentityPubkey) + + // Assert disconnected. + h.AssertPeerNotConnected(a, b) } // EnsureConnected will try to connect to two nodes, returning no error if they @@ -729,6 +734,51 @@ func (h *HarnessTest) AssertNumUTXOs(hn *node.HarnessNode, return h.AssertNumUTXOsWithConf(hn, num, math.MaxInt32, 0) } +// getUTXOs gets the number of newly created UTOXs within the current test +// scope. +func (h *HarnessTest) getUTXOs(hn *node.HarnessNode, account string, + max, min int32) []*lnrpc.Utxo { + + var unconfirmed bool + + if max == 0 { + unconfirmed = true + } + + req := &walletrpc.ListUnspentRequest{ + Account: account, + MaxConfs: max, + MinConfs: min, + UnconfirmedOnly: unconfirmed, + } + resp := hn.RPC.ListUnspent(req) + + return resp.Utxos +} + +// GetUTXOs returns all the UTXOs for the given node's account, including +// confirmed and unconfirmed. +func (h *HarnessTest) GetUTXOs(hn *node.HarnessNode, + account string) []*lnrpc.Utxo { + + return h.getUTXOs(hn, account, math.MaxInt32, 0) +} + +// GetUTXOsConfirmed returns the confirmed UTXOs for the given node's account. +func (h *HarnessTest) GetUTXOsConfirmed(hn *node.HarnessNode, + account string) []*lnrpc.Utxo { + + return h.getUTXOs(hn, account, math.MaxInt32, 1) +} + +// GetUTXOsUnconfirmed returns the unconfirmed UTXOs for the given node's +// account. +func (h *HarnessTest) GetUTXOsUnconfirmed(hn *node.HarnessNode, + account string) []*lnrpc.Utxo { + + return h.getUTXOs(hn, account, 0, 0) +} + // WaitForBalanceConfirmed waits until the node sees the expected confirmed // balance in its wallet. func (h *HarnessTest) WaitForBalanceConfirmed(hn *node.HarnessNode, @@ -878,6 +928,16 @@ func (h *HarnessTest) AssertPaymentStatusFromStream(stream rpc.PaymentClient, return h.assertPaymentStatusWithTimeout(stream, status, DefaultTimeout) } +// AssertPaymentSucceedWithTimeout asserts that a payment is succeeded within +// the specified timeout. +func (h *HarnessTest) AssertPaymentSucceedWithTimeout(stream rpc.PaymentClient, + timeout time.Duration) *lnrpc.Payment { + + return h.assertPaymentStatusWithTimeout( + stream, lnrpc.Payment_SUCCEEDED, timeout, + ) +} + // assertPaymentStatusWithTimeout takes a client stream and asserts the payment // is in desired status before the specified timeout. The payment found is // returned once succeeded. @@ -889,7 +949,9 @@ 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, err := h.ReceivePaymentUpdate(stream) + payment, err := h.receivePaymentUpdateWithTimeout( + stream, timeout, + ) if err != nil { return fmt.Errorf("received error from payment "+ "stream: %s", err) @@ -918,8 +980,17 @@ func (h *HarnessTest) assertPaymentStatusWithTimeout(stream rpc.PaymentClient, func (h *HarnessTest) ReceivePaymentUpdate( stream rpc.PaymentClient) (*lnrpc.Payment, error) { + return h.receivePaymentUpdateWithTimeout(stream, DefaultTimeout) +} + +// receivePaymentUpdateWithTimeout waits until a message is received on the +// payment client stream or the timeout is reached. +func (h *HarnessTest) receivePaymentUpdateWithTimeout(stream rpc.PaymentClient, + timeout time.Duration) (*lnrpc.Payment, error) { + chanMsg := make(chan *lnrpc.Payment, 1) errChan := make(chan error, 1) + go func() { // Consume one message. This will block until the message is // received. @@ -933,7 +1004,7 @@ func (h *HarnessTest) ReceivePaymentUpdate( }() select { - case <-time.After(DefaultTimeout): + case <-time.After(timeout): require.Fail(h, "timeout", "timeout waiting for payment update") return nil, nil @@ -2074,3 +2145,145 @@ func (h *HarnessTest) ReceiveSendToRouteUpdate( return updateMsg, nil } } + +// AssertInvoiceEqual asserts that two lnrpc.Invoices are equivalent. A custom +// comparison function is defined for these tests, since proto message returned +// from unary and streaming RPCs (as of protobuf 1.23.0 and grpc 1.29.1) aren't +// consistent with the private fields set on the messages. As a result, we +// avoid using require.Equal and test only the actual data members. +func (h *HarnessTest) AssertInvoiceEqual(a, b *lnrpc.Invoice) { + // Ensure the HTLCs are sorted properly before attempting to compare. + sort.Slice(a.Htlcs, func(i, j int) bool { + return a.Htlcs[i].ChanId < a.Htlcs[j].ChanId + }) + sort.Slice(b.Htlcs, func(i, j int) bool { + return b.Htlcs[i].ChanId < b.Htlcs[j].ChanId + }) + + require.Equal(h, a.Memo, b.Memo) + require.Equal(h, a.RPreimage, b.RPreimage) + require.Equal(h, a.RHash, b.RHash) + require.Equal(h, a.Value, b.Value) + require.Equal(h, a.ValueMsat, b.ValueMsat) + require.Equal(h, a.CreationDate, b.CreationDate) + require.Equal(h, a.SettleDate, b.SettleDate) + require.Equal(h, a.PaymentRequest, b.PaymentRequest) + require.Equal(h, a.DescriptionHash, b.DescriptionHash) + require.Equal(h, a.Expiry, b.Expiry) + require.Equal(h, a.FallbackAddr, b.FallbackAddr) + require.Equal(h, a.CltvExpiry, b.CltvExpiry) + require.Equal(h, a.RouteHints, b.RouteHints) + require.Equal(h, a.Private, b.Private) + require.Equal(h, a.AddIndex, b.AddIndex) + require.Equal(h, a.SettleIndex, b.SettleIndex) + require.Equal(h, a.AmtPaidSat, b.AmtPaidSat) + require.Equal(h, a.AmtPaidMsat, b.AmtPaidMsat) + require.Equal(h, a.State, b.State) + require.Equal(h, a.Features, b.Features) + require.Equal(h, a.IsKeysend, b.IsKeysend) + require.Equal(h, a.PaymentAddr, b.PaymentAddr) + require.Equal(h, a.IsAmp, b.IsAmp) + + require.Equal(h, len(a.Htlcs), len(b.Htlcs)) + for i := range a.Htlcs { + htlcA, htlcB := a.Htlcs[i], b.Htlcs[i] + require.Equal(h, htlcA.ChanId, htlcB.ChanId) + require.Equal(h, htlcA.HtlcIndex, htlcB.HtlcIndex) + require.Equal(h, htlcA.AmtMsat, htlcB.AmtMsat) + require.Equal(h, htlcA.AcceptHeight, htlcB.AcceptHeight) + require.Equal(h, htlcA.AcceptTime, htlcB.AcceptTime) + require.Equal(h, htlcA.ResolveTime, htlcB.ResolveTime) + require.Equal(h, htlcA.ExpiryHeight, htlcB.ExpiryHeight) + require.Equal(h, htlcA.State, htlcB.State) + require.Equal(h, htlcA.CustomRecords, htlcB.CustomRecords) + require.Equal(h, htlcA.MppTotalAmtMsat, htlcB.MppTotalAmtMsat) + require.Equal(h, htlcA.Amp, htlcB.Amp) + } +} + +// AssertUTXOInWallet asserts that a given UTXO can be found in the node's +// wallet. +func (h *HarnessTest) AssertUTXOInWallet(hn *node.HarnessNode, + op *lnrpc.OutPoint, account string) { + + err := wait.NoError(func() error { + utxos := h.GetUTXOs(hn, account) + + err := fmt.Errorf("tx with hash %x not found", op.TxidBytes) + for _, utxo := range utxos { + if !bytes.Equal(utxo.Outpoint.TxidBytes, op.TxidBytes) { + continue + } + + err = fmt.Errorf("tx with output index %v not found", + op.OutputIndex) + if utxo.Outpoint.OutputIndex != op.OutputIndex { + continue + } + + return nil + } + + return err + }, DefaultTimeout) + + require.NoErrorf(h, err, "outpoint %v not found in %s's wallet", + op, hn.Name()) +} + +// AssertWalletAccountBalance asserts that the unconfirmed and confirmed +// balance for the given account is satisfied by the WalletBalance and +// ListUnspent RPCs. The unconfirmed balance is not checked for neutrino nodes. +func (h *HarnessTest) AssertWalletAccountBalance(hn *node.HarnessNode, + account string, confirmedBalance, unconfirmedBalance int64) { + + err := wait.NoError(func() error { + balanceResp := hn.RPC.WalletBalance() + require.Contains(h, balanceResp.AccountBalance, account) + accountBalance := balanceResp.AccountBalance[account] + + // Check confirmed balance. + if accountBalance.ConfirmedBalance != confirmedBalance { + return fmt.Errorf("expected confirmed balance %v, "+ + "got %v", confirmedBalance, + accountBalance.ConfirmedBalance) + } + + utxos := h.GetUTXOsConfirmed(hn, account) + var totalConfirmedVal int64 + for _, utxo := range utxos { + totalConfirmedVal += utxo.AmountSat + } + if totalConfirmedVal != confirmedBalance { + return fmt.Errorf("expected total confirmed utxo "+ + "balance %v, got %v", confirmedBalance, + totalConfirmedVal) + } + + // Skip unconfirmed balance checks for neutrino nodes. + if h.IsNeutrinoBackend() { + return nil + } + + // Check unconfirmed balance. + if accountBalance.UnconfirmedBalance != unconfirmedBalance { + return fmt.Errorf("expected unconfirmed balance %v, "+ + "got %v", unconfirmedBalance, + accountBalance.UnconfirmedBalance) + } + + utxos = h.GetUTXOsUnconfirmed(hn, account) + var totalUnconfirmedVal int64 + for _, utxo := range utxos { + totalUnconfirmedVal += utxo.AmountSat + } + if totalUnconfirmedVal != unconfirmedBalance { + return fmt.Errorf("expected total unconfirmed utxo "+ + "balance %v, got %v", unconfirmedBalance, + totalUnconfirmedVal) + } + + return nil + }, DefaultTimeout) + require.NoError(h, err, "timeout checking wallet account balance") +} diff --git a/lntemp/harness_miner.go b/lntemp/harness_miner.go index 916b57500..9c9b758f2 100644 --- a/lntemp/harness_miner.go +++ b/lntemp/harness_miner.go @@ -11,6 +11,7 @@ import ( "testing" "time" + "github.com/btcsuite/btcd/btcjson" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" @@ -253,6 +254,16 @@ func (h *HarnessMiner) GetRawTransaction(txid *chainhash.Hash) *btcutil.Tx { return tx } +// GetRawTransactionVerbose makes a RPC call to the miner's +// GetRawTransactionVerbose and asserts. +func (h *HarnessMiner) GetRawTransactionVerbose( + txid *chainhash.Hash) *btcjson.TxRawResult { + + tx, err := h.Client.GetRawTransactionVerbose(txid) + require.NoErrorf(h, err, "failed to get raw tx verbose: %v", txid) + return tx +} + // AssertTxInMempool asserts a given transaction can be found in the mempool. func (h *HarnessMiner) AssertTxInMempool(txid *chainhash.Hash) *wire.MsgTx { var msgTx *wire.MsgTx diff --git a/lntemp/node/harness_node.go b/lntemp/node/harness_node.go index 1dc484bed..069d23ec8 100644 --- a/lntemp/node/harness_node.go +++ b/lntemp/node/harness_node.go @@ -135,15 +135,24 @@ func NewHarnessNode(t *testing.T, cfg *BaseNodeConfig) (*HarnessNode, error) { }, nil } -// InitRPCClients initializes a list of RPC clients for the node. -func (hn *HarnessNode) InitRPCClients(c *grpc.ClientConn) { +// Initialize creates a list of new RPC clients using the passed connection, +// initializes the node's internal state and creates a topology watcher. +func (hn *HarnessNode) Initialize(c *grpc.ClientConn) { hn.conn = c // Init all the rpc clients. hn.RPC = rpc.NewHarnessRPC(hn.runCtx, hn.T, c, hn.Name()) - // Init the node's internal state. - hn.State = newState(hn.RPC) + // Init the node's state. + // + // If we already have a state, it means we are restarting the node and + // we will only reset its internal states. Otherwise we'll create a new + // state. + if hn.State != nil { + hn.State.resetEphermalStates(hn.RPC) + } else { + hn.State = newState(hn.RPC) + } // Init the topology watcher. hn.Watcher = newNodeWatcher(hn.RPC, hn.State) @@ -164,11 +173,11 @@ func (hn *HarnessNode) String() string { type nodeCfg struct { LogFilenamePrefix string ExtraArgs []string - HasSeed bool + SkipUnlock bool + Password []byte P2PPort int RPCPort int RESTPort int - ProfilePort int AcceptKeySend bool FeeURL string } @@ -185,6 +194,8 @@ func (hn *HarnessNode) String() string { PubKey: hn.PubKeyStr, State: hn.State, NodeCfg: nodeCfg{ + SkipUnlock: hn.Cfg.SkipUnlock, + Password: hn.Cfg.Password, LogFilenamePrefix: hn.Cfg.LogFilenamePrefix, ExtraArgs: hn.Cfg.ExtraArgs, P2PPort: hn.Cfg.P2PPort, @@ -434,8 +445,9 @@ func (hn *HarnessNode) Start(ctxt context.Context) error { return err } - // Init all the RPC clients. - hn.InitRPCClients(conn) + // Init the node by creating the RPC clients, initializing node's + // internal state and watcher. + hn.Initialize(conn) // Wait till the server is starting. if err := hn.WaitUntilStarted(); err != nil { @@ -477,8 +489,9 @@ func (hn *HarnessNode) InitNode(macBytes []byte) error { } } - // Init all the RPC clients. - hn.InitRPCClients(conn) + // Init the node by creating the RPC clients, initializing node's + // internal state and watcher. + hn.Initialize(conn) // Wait till the server is starting. if err := hn.WaitUntilStarted(); err != nil { diff --git a/lntemp/node/state.go b/lntemp/node/state.go index 5b3450d38..e852bf9ab 100644 --- a/lntemp/node/state.go +++ b/lntemp/node/state.go @@ -241,6 +241,14 @@ func (s *State) updatePaymentStats() { } resp := s.rpc.ListPayments(req) + // Exit early when the there's no payment. + // + // NOTE: we need to exit early here because when there's no invoice the + // `LastOffsetIndex` will be zero. + if len(resp.Payments) == 0 { + return + } + s.Payment.LastIndexOffset = resp.LastIndexOffset for _, payment := range resp.Payments { if payment.Status == lnrpc.Payment_FAILED || @@ -261,6 +269,14 @@ func (s *State) updateInvoiceStats() { } resp := s.rpc.ListInvoices(req) + // Exit early when the there's no invoice. + // + // NOTE: we need to exit early here because when there's no invoice the + // `LastOffsetIndex` will be zero. + if len(resp.Invoices) == 0 { + return + } + s.Invoice.LastIndexOffset = resp.LastIndexOffset for _, invoice := range resp.Invoices { if invoice.State == lnrpc.Invoice_SETTLED || @@ -328,3 +344,17 @@ func (s *State) String() string { return fmt.Sprintf("\n%s", stateBytes) } + +// resetEphermalStates resets the current state with a new HarnessRPC and empty +// private fields which are used to track state only valid for the last test. +func (s *State) resetEphermalStates(rpc *rpc.HarnessRPC) { + s.rpc = rpc + + // Reset ephermal states which are used to record info from finished + // tests. + s.openChans = &SyncMap[wire.OutPoint, []*OpenChannelUpdate]{} + s.closedChans = &SyncMap[wire.OutPoint, *lnrpc.ClosedChannelUpdate]{} + s.numChanUpdates = &SyncMap[wire.OutPoint, int]{} + s.nodeUpdates = &SyncMap[string, []*lnrpc.NodeUpdate]{} + s.policyUpdates = &SyncMap[wire.OutPoint, PolicyUpdate]{} +} diff --git a/lntemp/rpc/chain_notifier.go b/lntemp/rpc/chain_notifier.go index 3e969f197..4f5a815f8 100644 --- a/lntemp/rpc/chain_notifier.go +++ b/lntemp/rpc/chain_notifier.go @@ -1,5 +1,44 @@ package rpc +import ( + "github.com/lightningnetwork/lnd/lnrpc/chainrpc" +) + // ===================== // ChainClient related RPCs. // ===================== + +type ConfNtfnClient chainrpc.ChainNotifier_RegisterConfirmationsNtfnClient + +// RegisterConfirmationsNtfn creates a notification client to watch a given +// transaction being confirmed. +func (h *HarnessRPC) RegisterConfirmationsNtfn( + req *chainrpc.ConfRequest) ConfNtfnClient { + + // RegisterConfirmationsNtfn 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.ChainClient.RegisterConfirmationsNtfn( + h.runCtx, req, + ) + h.NoError(err, "RegisterConfirmationsNtfn") + + return client +} + +type SpendClient chainrpc.ChainNotifier_RegisterSpendNtfnClient + +// RegisterSpendNtfn creates a notification client to watch a given +// transaction being spent. +func (h *HarnessRPC) RegisterSpendNtfn(req *chainrpc.SpendRequest) SpendClient { + // RegisterSpendNtfn 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.ChainClient.RegisterSpendNtfn( + h.runCtx, req, + ) + h.NoError(err, "RegisterSpendNtfn") + + return client +} diff --git a/lntemp/rpc/lnd.go b/lntemp/rpc/lnd.go index 7a95fbe35..41c6e31b0 100644 --- a/lntemp/rpc/lnd.go +++ b/lntemp/rpc/lnd.go @@ -631,3 +631,54 @@ func (h *HarnessRPC) RegisterRPCMiddleware() (MiddlewareClient, return stream, cancel } + +type ChannelEventsClient lnrpc.Lightning_SubscribeChannelEventsClient + +// SubscribeChannelEvents creates a subscription client for channel events and +// asserts its creation. +func (h *HarnessRPC) SubscribeChannelEvents() ChannelEventsClient { + req := &lnrpc.ChannelEventSubscription{} + + // SubscribeChannelEvents 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.LN.SubscribeChannelEvents(h.runCtx, req) + h.NoError(err, "SubscribeChannelEvents") + + return client +} + +type CustomMessageClient lnrpc.Lightning_SubscribeCustomMessagesClient + +// SubscribeCustomMessages creates a subscription client for custom messages. +func (h *HarnessRPC) SubscribeCustomMessages() (CustomMessageClient, + context.CancelFunc) { + + ctxt, cancel := context.WithCancel(h.runCtx) + + req := &lnrpc.SubscribeCustomMessagesRequest{} + + // SubscribeCustomMessages 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. + stream, err := h.LN.SubscribeCustomMessages(ctxt, req) + h.NoError(err, "SubscribeCustomMessages") + + return stream, cancel +} + +// SendCustomMessage makes a RPC call to the node's SendCustomMessage and +// returns the response. +func (h *HarnessRPC) SendCustomMessage( + req *lnrpc.SendCustomMessageRequest) *lnrpc.SendCustomMessageResponse { + + ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout) + defer cancel() + + resp, err := h.LN.SendCustomMessage(ctxt, req) + h.NoError(err, "SendCustomMessage") + + return resp +} diff --git a/lntemp/rpc/router.go b/lntemp/rpc/router.go index 368ceffae..a61f7b8dc 100644 --- a/lntemp/rpc/router.go +++ b/lntemp/rpc/router.go @@ -150,3 +150,43 @@ func (h *HarnessRPC) XImportMissionControlAssertErr( _, err := h.Router.XImportMissionControl(ctxt, req) require.Error(h, err, "expect an error from x import mission control") } + +// BuildRoute makes a RPC call to the node's RouterClient and asserts. +func (h *HarnessRPC) BuildRoute( + req *routerrpc.BuildRouteRequest) *routerrpc.BuildRouteResponse { + + ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout) + defer cancel() + + resp, err := h.Router.BuildRoute(ctxt, req) + h.NoError(err, "BuildRoute") + + return resp +} + +type InterceptorClient routerrpc.Router_HtlcInterceptorClient + +// HtlcInterceptor makes a RPC call to the node's RouterClient and asserts. +func (h *HarnessRPC) HtlcInterceptor() (InterceptorClient, context.CancelFunc) { + // HtlcInterceptor 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 cancel context here instead of a timeout + // context. + ctxt, cancel := context.WithCancel(h.runCtx) + resp, err := h.Router.HtlcInterceptor(ctxt) + h.NoError(err, "HtlcInterceptor") + + return resp, cancel +} + +type TrackPaymentsClient routerrpc.Router_TrackPaymentsClient + +// TrackPayments makes a RPC call to the node's RouterClient and asserts. +func (h *HarnessRPC) TrackPayments( + req *routerrpc.TrackPaymentsRequest) TrackPaymentsClient { + + resp, err := h.Router.TrackPayments(h.runCtx, req) + h.NoError(err, "TrackPayments") + + return resp +} diff --git a/lntemp/rpc/signer.go b/lntemp/rpc/signer.go index c7f54e12c..fea7bf9dd 100644 --- a/lntemp/rpc/signer.go +++ b/lntemp/rpc/signer.go @@ -4,13 +4,39 @@ import ( "context" "github.com/lightningnetwork/lnd/lnrpc/signrpc" + "github.com/stretchr/testify/require" ) // ===================== // Signer related RPCs. // ===================== -// SignOutputRaw makes a RPC call to node's SignOutputRaw and asserts. +// DeriveSharedKey makes a RPC call to the node's SignerClient and asserts. +func (h *HarnessRPC) DeriveSharedKey( + req *signrpc.SharedKeyRequest) *signrpc.SharedKeyResponse { + + ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout) + defer cancel() + + resp, err := h.Signer.DeriveSharedKey(ctxt, req) + h.NoError(err, "DeriveSharedKey") + + return resp +} + +// DeriveSharedKeyErr makes a RPC call to the node's SignerClient and asserts +// there is an error. +func (h *HarnessRPC) DeriveSharedKeyErr(req *signrpc.SharedKeyRequest) error { + ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout) + defer cancel() + + _, err := h.Signer.DeriveSharedKey(ctxt, req) + require.Error(h, err, "expected error from calling DeriveSharedKey") + + return err +} + +// SignOutputRaw makes a RPC call to the node's SignerClient and asserts. func (h *HarnessRPC) SignOutputRaw(req *signrpc.SignReq) *signrpc.SignResp { ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout) defer cancel() @@ -20,3 +46,152 @@ func (h *HarnessRPC) SignOutputRaw(req *signrpc.SignReq) *signrpc.SignResp { return resp } + +// SignOutputRawErr makes a RPC call to the node's SignerClient and asserts an +// error is returned. +func (h *HarnessRPC) SignOutputRawErr(req *signrpc.SignReq) error { + ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout) + defer cancel() + + _, err := h.Signer.SignOutputRaw(ctxt, req) + require.Error(h, err, "expect to fail to sign raw output") + + return err +} + +// MuSig2CreateSession makes a RPC call to the node's SignerClient and asserts. +func (h *HarnessRPC) MuSig2CreateSession( + req *signrpc.MuSig2SessionRequest) *signrpc.MuSig2SessionResponse { + + ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout) + defer cancel() + + resp, err := h.Signer.MuSig2CreateSession(ctxt, req) + h.NoError(err, "MuSig2CreateSession") + + return resp +} + +// MuSig2CombineKeys makes a RPC call to the node's SignerClient and asserts. +// +//nolint:lll +func (h *HarnessRPC) MuSig2CombineKeys( + req *signrpc.MuSig2CombineKeysRequest) *signrpc.MuSig2CombineKeysResponse { + + ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout) + defer cancel() + + resp, err := h.Signer.MuSig2CombineKeys(ctxt, req) + h.NoError(err, "MuSig2CombineKeys") + + return resp +} + +// MuSig2RegisterNonces makes a RPC call to the node's SignerClient and asserts. +// +//nolint:lll +func (h *HarnessRPC) MuSig2RegisterNonces( + req *signrpc.MuSig2RegisterNoncesRequest) *signrpc.MuSig2RegisterNoncesResponse { + + ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout) + defer cancel() + + resp, err := h.Signer.MuSig2RegisterNonces(ctxt, req) + h.NoError(err, "MuSig2RegisterNonces") + + return resp +} + +// MuSig2Sign makes a RPC call to the node's SignerClient and asserts. +func (h *HarnessRPC) MuSig2Sign( + req *signrpc.MuSig2SignRequest) *signrpc.MuSig2SignResponse { + + ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout) + defer cancel() + + resp, err := h.Signer.MuSig2Sign(ctxt, req) + h.NoError(err, "MuSig2Sign") + + return resp +} + +// MuSig2SignErr makes a RPC call to the node's SignerClient and asserts an +// error is returned. +func (h *HarnessRPC) MuSig2SignErr(req *signrpc.MuSig2SignRequest) error { + ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout) + defer cancel() + + _, err := h.Signer.MuSig2Sign(ctxt, req) + require.Error(h, err, "expect an error") + + return err +} + +// MuSig2CombineSig makes a RPC call to the node's SignerClient and asserts. +func (h *HarnessRPC) MuSig2CombineSig( + r *signrpc.MuSig2CombineSigRequest) *signrpc.MuSig2CombineSigResponse { + + ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout) + defer cancel() + + resp, err := h.Signer.MuSig2CombineSig(ctxt, r) + h.NoError(err, "MuSig2CombineSig") + + return resp +} + +// MuSig2Cleanup makes a RPC call to the node's SignerClient and asserts. +func (h *HarnessRPC) MuSig2Cleanup( + req *signrpc.MuSig2CleanupRequest) *signrpc.MuSig2CleanupResponse { + + ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout) + defer cancel() + + resp, err := h.Signer.MuSig2Cleanup(ctxt, req) + h.NoError(err, "MuSig2Cleanup") + + return resp +} + +// SignMessageSigner makes a RPC call to the node's SignerClient and asserts. +// +// NOTE: there's already `SignMessage` in `h.LN`. +func (h *HarnessRPC) SignMessageSigner( + req *signrpc.SignMessageReq) *signrpc.SignMessageResp { + + ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout) + defer cancel() + + resp, err := h.Signer.SignMessage(ctxt, req) + h.NoError(err, "SignMessage") + + return resp +} + +// VerifyMessageSigner makes a RPC call to the node's SignerClient and asserts. +// +// NOTE: there's already `VerifyMessageSigner` in `h.LN`. +func (h *HarnessRPC) VerifyMessageSigner( + req *signrpc.VerifyMessageReq) *signrpc.VerifyMessageResp { + + ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout) + defer cancel() + + resp, err := h.Signer.VerifyMessage(ctxt, req) + h.NoError(err, "VerifyMessage") + + return resp +} + +// ComputeInputScript makes a RPC call to the node's SignerClient and asserts. +func (h *HarnessRPC) ComputeInputScript( + req *signrpc.SignReq) *signrpc.InputScriptResp { + + ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout) + defer cancel() + + resp, err := h.Signer.ComputeInputScript(ctxt, req) + h.NoError(err, "ComputeInputScript") + + return resp +} diff --git a/lntemp/rpc/wallet_kit.go b/lntemp/rpc/wallet_kit.go index fe1ccdec1..402f30681 100644 --- a/lntemp/rpc/wallet_kit.go +++ b/lntemp/rpc/wallet_kit.go @@ -163,3 +163,85 @@ func (h *HarnessRPC) PublishTransaction( return resp } + +// BumpFee makes a RPC call to the node's WalletKitClient and asserts. +func (h *HarnessRPC) BumpFee( + req *walletrpc.BumpFeeRequest) *walletrpc.BumpFeeResponse { + + ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout) + defer cancel() + + resp, err := h.WalletKit.BumpFee(ctxt, req) + h.NoError(err, "BumpFee") + + return resp +} + +// ListAccounts makes a RPC call to the node's WalletKitClient and asserts. +func (h *HarnessRPC) ListAccounts( + req *walletrpc.ListAccountsRequest) *walletrpc.ListAccountsResponse { + + ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout) + defer cancel() + + resp, err := h.WalletKit.ListAccounts(ctxt, req) + h.NoError(err, "ListAccounts") + + return resp +} + +// ImportAccount makes a RPC call to the node's WalletKitClient and asserts. +func (h *HarnessRPC) ImportAccount( + req *walletrpc.ImportAccountRequest) *walletrpc.ImportAccountResponse { + + ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout) + defer cancel() + + resp, err := h.WalletKit.ImportAccount(ctxt, req) + h.NoError(err, "ImportAccount") + + return resp +} + +// ImportPublicKey makes a RPC call to the node's WalletKitClient and asserts. +// +//nolint:lll +func (h *HarnessRPC) ImportPublicKey( + req *walletrpc.ImportPublicKeyRequest) *walletrpc.ImportPublicKeyResponse { + + ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout) + defer cancel() + + resp, err := h.WalletKit.ImportPublicKey(ctxt, req) + h.NoError(err, "ImportPublicKey") + + return resp +} + +// SignPsbt makes a RPC call to the node's WalletKitClient and asserts. +func (h *HarnessRPC) SignPsbt( + req *walletrpc.SignPsbtRequest) *walletrpc.SignPsbtResponse { + + ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout) + defer cancel() + + resp, err := h.WalletKit.SignPsbt(ctxt, req) + h.NoError(err, "SignPsbt") + + return resp +} + +// ImportTapscript makes a RPC call to the node's WalletKitClient and asserts. +// +//nolint:lll +func (h *HarnessRPC) ImportTapscript( + req *walletrpc.ImportTapscriptRequest) *walletrpc.ImportTapscriptResponse { + + ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout) + defer cancel() + + resp, err := h.WalletKit.ImportTapscript(ctxt, req) + h.NoError(err, "ImportTapscript") + + return resp +} diff --git a/lntest/itest/assertions.go b/lntest/itest/assertions.go index feae9e160..a917c2c1a 100644 --- a/lntest/itest/assertions.go +++ b/lntest/itest/assertions.go @@ -949,60 +949,6 @@ func assertNumPendingChannels(t *harnessTest, node *lntest.HarnessNode, require.NoErrorf(t.t, err, "got err: %v", predErr) } -// verifyCloseUpdate is used to verify that a closed channel update is of the -// expected type. -func verifyCloseUpdate(chanUpdate *lnrpc.ChannelEventUpdate, - closeType lnrpc.ChannelCloseSummary_ClosureType, - closeInitiator lnrpc.Initiator) error { - - // We should receive one inactive and one closed notification - // for each channel. - switch update := chanUpdate.Channel.(type) { - case *lnrpc.ChannelEventUpdate_InactiveChannel: - if chanUpdate.Type != lnrpc.ChannelEventUpdate_INACTIVE_CHANNEL { - return fmt.Errorf("update type mismatch: expected %v, got %v", - lnrpc.ChannelEventUpdate_INACTIVE_CHANNEL, - chanUpdate.Type) - } - - case *lnrpc.ChannelEventUpdate_ClosedChannel: - if chanUpdate.Type != - lnrpc.ChannelEventUpdate_CLOSED_CHANNEL { - - return fmt.Errorf("update type mismatch: expected %v, got %v", - lnrpc.ChannelEventUpdate_CLOSED_CHANNEL, - chanUpdate.Type) - } - - if update.ClosedChannel.CloseType != closeType { - return fmt.Errorf("channel closure type "+ - "mismatch: expected %v, got %v", - closeType, - update.ClosedChannel.CloseType) - } - - if update.ClosedChannel.CloseInitiator != closeInitiator { - return fmt.Errorf("expected close intiator: %v, got: %v", - closeInitiator, - update.ClosedChannel.CloseInitiator) - } - - case *lnrpc.ChannelEventUpdate_FullyResolvedChannel: - if chanUpdate.Type != lnrpc.ChannelEventUpdate_FULLY_RESOLVED_CHANNEL { - return fmt.Errorf("update type mismatch: expected %v, got %v", - lnrpc.ChannelEventUpdate_FULLY_RESOLVED_CHANNEL, - chanUpdate.Type) - } - - default: - return fmt.Errorf("channel update channel of wrong type, "+ - "expected closed channel, got %T", - update) - } - - return nil -} - // assertNodeNumChannels polls the provided node's list channels rpc until it // reaches the desired number of total channels. func assertNodeNumChannels(t *harnessTest, node *lntest.HarnessNode, diff --git a/lntest/itest/list_on_test.go b/lntest/itest/list_on_test.go index 151affe52..3bd12f9d3 100644 --- a/lntest/itest/list_on_test.go +++ b/lntest/itest/list_on_test.go @@ -275,6 +275,10 @@ var allTestCasesTemp = []*lntemp.TestCase{ Name: "open channel reorg test", TestFunc: testOpenChannelAfterReorg, }, + { + Name: "psbt channel funding", + TestFunc: testPsbtChanFunding, + }, { Name: "psbt channel funding external", TestFunc: testPsbtChanFundingExternal, @@ -283,6 +287,10 @@ var allTestCasesTemp = []*lntemp.TestCase{ Name: "psbt channel funding single step", TestFunc: testPsbtChanFundingSingleStep, }, + { + Name: "sign psbt", + TestFunc: testSignPsbt, + }, { Name: "resolution handoff", TestFunc: testResHandoff, @@ -369,4 +377,128 @@ var allTestCasesTemp = []*lntemp.TestCase{ Name: "wipe forwarding packages", TestFunc: testWipeForwardingPackages, }, + { + Name: "switch circuit persistence", + TestFunc: testSwitchCircuitPersistence, + }, + { + Name: "switch offline delivery", + TestFunc: testSwitchOfflineDelivery, + }, + { + Name: "switch offline delivery persistence", + TestFunc: testSwitchOfflineDeliveryPersistence, + }, + { + Name: "switch offline delivery outgoing offline", + TestFunc: testSwitchOfflineDeliveryOutgoingOffline, + }, + { + Name: "sendtoroute multi path payment", + TestFunc: testSendToRouteMultiPath, + }, + { + Name: "send multi path payment", + TestFunc: testSendMultiPathPayment, + }, + { + Name: "sendpayment amp invoice", + TestFunc: testSendPaymentAMPInvoice, + }, + { + Name: "sendpayment amp invoice repeat", + TestFunc: testSendPaymentAMPInvoiceRepeat, + }, + { + Name: "send payment amp", + TestFunc: testSendPaymentAMP, + }, + { + Name: "sendtoroute amp", + TestFunc: testSendToRouteAMP, + }, + { + Name: "forward interceptor dedup htlcs", + TestFunc: testForwardInterceptorDedupHtlc, + }, + { + Name: "forward interceptor", + TestFunc: testForwardInterceptorBasic, + }, + { + Name: "zero conf channel open", + TestFunc: testZeroConfChannelOpen, + }, + { + Name: "option scid alias", + TestFunc: testOptionScidAlias, + }, + { + Name: "scid alias channel update", + TestFunc: testUpdateChannelPolicyScidAlias, + }, + { + Name: "scid alias upgrade", + TestFunc: testOptionScidUpgrade, + }, + { + Name: "nonstd sweep", + TestFunc: testNonstdSweep, + }, + { + Name: "multiple channel creation and update subscription", + TestFunc: testBasicChannelCreationAndUpdates, + }, + { + Name: "derive shared key", + TestFunc: testDeriveSharedKey, + }, + { + Name: "sign output raw", + TestFunc: testSignOutputRaw, + }, + { + Name: "sign verify message", + TestFunc: testSignVerifyMessage, + }, + { + Name: "cpfp", + TestFunc: testCPFP, + }, + { + Name: "taproot", + TestFunc: testTaproot, + }, + { + Name: "wallet import account", + TestFunc: testWalletImportAccount, + }, + { + Name: "wallet import pubkey", + TestFunc: testWalletImportPubKey, + }, + { + Name: "async payments benchmark", + TestFunc: testAsyncPayments, + }, + { + Name: "remote signer", + TestFunc: testRemoteSigner, + }, + { + Name: "taproot coop close", + TestFunc: testTaprootCoopClose, + }, + { + Name: "trackpayments", + TestFunc: testTrackPayments, + }, + { + Name: "open channel fee policy", + TestFunc: testOpenChannelUpdateFeePolicy, + }, + { + Name: "custom message", + TestFunc: testCustomMessage, + }, } diff --git a/lntest/itest/lnd_amp_test.go b/lntest/itest/lnd_amp_test.go index 2ed33951a..46dddbd08 100644 --- a/lntest/itest/lnd_amp_test.go +++ b/lntest/itest/lnd_amp_test.go @@ -1,10 +1,7 @@ package itest import ( - "context" - "crypto/rand" "encoding/hex" - "sort" "testing" "time" @@ -14,40 +11,41 @@ import ( "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" ) // testSendPaymentAMPInvoice tests that we can send an AMP payment to a // specified AMP invoice using SendPaymentV2. -func testSendPaymentAMPInvoice(net *lntest.NetworkHarness, t *harnessTest) { - t.t.Run("native payaddr", func(t *testing.T) { - tt := newHarnessTest(t, net) - testSendPaymentAMPInvoiceCase(net, tt, false) +func testSendPaymentAMPInvoice(ht *lntemp.HarnessTest) { + succeed := ht.Run("native payaddr", func(t *testing.T) { + tt := ht.Subtest(t) + testSendPaymentAMPInvoiceCase(tt, false) }) - t.t.Run("external payaddr", func(t *testing.T) { - tt := newHarnessTest(t, net) - testSendPaymentAMPInvoiceCase(net, tt, true) + + // Abort the test if failed. + if !succeed { + return + } + + ht.Run("external payaddr", func(t *testing.T) { + tt := ht.Subtest(t) + testSendPaymentAMPInvoiceCase(tt, true) }) } -func testSendPaymentAMPInvoiceCase(net *lntest.NetworkHarness, t *harnessTest, +func testSendPaymentAMPInvoiceCase(ht *lntemp.HarnessTest, useExternalPayAddr bool) { - ctxb := context.Background() - - ctx := newMppTestContext(t, net) - defer ctx.shutdownNodes() + mts := newMppTestScenario(ht) // Subscribe to bob's invoices. Do this early in the test to make sure // that the subscription has actually been completed when we add an // invoice. Otherwise the notification will be missed. req := &lnrpc.InvoiceSubscription{} - ctxc, cancelSubscription := context.WithCancel(ctxb) - bobInvoiceSubscription, err := ctx.bob.SubscribeInvoices(ctxc, req) - require.NoError(t.t, err) - defer cancelSubscription() + bobInvoiceSubscription := mts.bob.RPC.SubscribeInvoices(req) const paymentAmt = btcutil.Amount(300000) @@ -61,49 +59,45 @@ func testSendPaymentAMPInvoiceCase(net *lntest.NetworkHarness, t *harnessTest, // \ / // \__ Dave ____/ // - ctx.openChannel(ctx.carol, ctx.bob, 135000) - ctx.openChannel(ctx.alice, ctx.carol, 235000) - ctx.openChannel(ctx.dave, ctx.bob, 135000) - ctx.openChannel(ctx.alice, ctx.dave, 135000) - ctx.openChannel(ctx.eve, ctx.bob, 135000) - ctx.openChannel(ctx.carol, ctx.eve, 135000) + mppReq := &mppOpenChannelRequest{ + amtAliceCarol: 235000, + amtAliceDave: 135000, + amtCarolBob: 135000, + amtCarolEve: 135000, + amtDaveBob: 135000, + amtEveBob: 135000, + } + mts.openChannels(mppReq) + chanPointAliceDave := mts.channelPoints[1] + chanPointDaveBob := mts.channelPoints[4] - defer ctx.closeChannels() - - ctx.waitForChannels() - - addInvoiceResp, err := ctx.bob.AddInvoice(context.Background(), &lnrpc.Invoice{ + invoice := &lnrpc.Invoice{ Value: int64(paymentAmt), IsAmp: true, - }) - require.NoError(t.t, err) + } + addInvoiceResp := mts.bob.RPC.AddInvoice(invoice) // Ensure we get a notification of the invoice being added by Bob. - rpcInvoice, err := bobInvoiceSubscription.Recv() - require.NoError(t.t, err) + rpcInvoice := ht.ReceiveInvoiceUpdate(bobInvoiceSubscription) - require.False(t.t, rpcInvoice.Settled) // nolint:staticcheck - require.Equal(t.t, lnrpc.Invoice_OPEN, rpcInvoice.State) - require.Equal(t.t, int64(0), rpcInvoice.AmtPaidSat) - require.Equal(t.t, int64(0), rpcInvoice.AmtPaidMsat) - - require.Equal(t.t, 0, len(rpcInvoice.Htlcs)) + require.False(ht, rpcInvoice.Settled) + require.Equal(ht, lnrpc.Invoice_OPEN, rpcInvoice.State) + require.Equal(ht, int64(0), rpcInvoice.AmtPaidSat) + require.Equal(ht, int64(0), rpcInvoice.AmtPaidMsat) + require.Equal(ht, 0, len(rpcInvoice.Htlcs)) // Increase Dave's fee to make the test deterministic. Otherwise it // would be unpredictable whether pathfinding would go through Charlie // or Dave for the first shard. - _, err = ctx.dave.UpdateChannelPolicy( - context.Background(), - &lnrpc.PolicyUpdateRequest{ - Scope: &lnrpc.PolicyUpdateRequest_Global{Global: true}, - BaseFeeMsat: 500000, - FeeRate: 0.001, - TimeLockDelta: 40, - }, + expectedPolicy := mts.updateDaveGlobalPolicy() + + // Make sure Alice has heard it for both Dave's channels. + ht.AssertChannelPolicyUpdate( + mts.alice, mts.dave, expectedPolicy, chanPointAliceDave, false, + ) + ht.AssertChannelPolicyUpdate( + mts.alice, mts.dave, expectedPolicy, chanPointDaveBob, false, ) - if err != nil { - t.Fatalf("dave policy update: %v", err) - } // Generate an external payment address when attempting to pseudo-reuse // an AMP invoice. When using an external payment address, we'll also @@ -116,19 +110,16 @@ func testSendPaymentAMPInvoiceCase(net *lntest.NetworkHarness, t *harnessTest, ) if useExternalPayAddr { expNumInvoices = 2 - externalPayAddr = make([]byte, 32) - _, err = rand.Read(externalPayAddr) - require.NoError(t.t, err) + externalPayAddr = ht.Random32Bytes() } - payment := sendAndAssertSuccess( - t, ctx.alice, &routerrpc.SendPaymentRequest{ - PaymentRequest: addInvoiceResp.PaymentRequest, - PaymentAddr: externalPayAddr, - TimeoutSeconds: 60, - FeeLimitMsat: noFeeLimitMsat, - }, - ) + sendReq := &routerrpc.SendPaymentRequest{ + PaymentRequest: addInvoiceResp.PaymentRequest, + PaymentAddr: externalPayAddr, + TimeoutSeconds: 60, + FeeLimitMsat: noFeeLimitMsat, + } + payment := ht.SendPaymentAssertSettled(mts.alice, sendReq) // Check that Alice split the payment in at least three shards. Because // the hand-off of the htlc to the link is asynchronous (via a mailbox), @@ -145,232 +136,201 @@ func testSendPaymentAMPInvoiceCase(net *lntest.NetworkHarness, t *harnessTest, } const minExpectedShards = 3 - if succeeded < minExpectedShards { - t.Fatalf("expected at least %v shards, but got %v", - minExpectedShards, succeeded) - } + require.GreaterOrEqual(ht, succeeded, minExpectedShards, + "expected num of shards not reached") // When an external payment address is supplied, we'll get an extra // notification for the JIT inserted invoice, since it differs from the // original. if useExternalPayAddr { - _, err = bobInvoiceSubscription.Recv() - require.NoError(t.t, err) + ht.ReceiveInvoiceUpdate(bobInvoiceSubscription) } // There should now be a settle event for the invoice. - rpcInvoice, err = bobInvoiceSubscription.Recv() - require.NoError(t.t, err) + rpcInvoice = ht.ReceiveInvoiceUpdate(bobInvoiceSubscription) // Also fetch Bob's invoice from ListInvoices and assert it is equal to // the one received via the subscription. - invoiceResp, err := ctx.bob.ListInvoices( - ctxb, &lnrpc.ListInvoiceRequest{}, - ) - require.NoError(t.t, err) - require.Equal(t.t, expNumInvoices, len(invoiceResp.Invoices)) - assertInvoiceEqual(t.t, rpcInvoice, invoiceResp.Invoices[expNumInvoices-1]) + invoices := ht.AssertNumInvoices(mts.bob, expNumInvoices) + ht.AssertInvoiceEqual(rpcInvoice, invoices[expNumInvoices-1]) // Assert that the invoice is settled for the total payment amount and // has the correct payment address. - require.True(t.t, rpcInvoice.Settled) // nolint:staticcheck - require.Equal(t.t, lnrpc.Invoice_SETTLED, rpcInvoice.State) - require.Equal(t.t, int64(paymentAmt), rpcInvoice.AmtPaidSat) - require.Equal(t.t, int64(paymentAmt*1000), rpcInvoice.AmtPaidMsat) + require.True(ht, rpcInvoice.Settled) + require.Equal(ht, lnrpc.Invoice_SETTLED, rpcInvoice.State) + require.Equal(ht, int64(paymentAmt), rpcInvoice.AmtPaidSat) + require.Equal(ht, int64(paymentAmt*1000), rpcInvoice.AmtPaidMsat) // Finally, assert that the same set id is recorded for each htlc, and // that the preimage hash pair is valid. var setID []byte - require.Equal(t.t, succeeded, len(rpcInvoice.Htlcs)) + require.Equal(ht, succeeded, len(rpcInvoice.Htlcs)) for _, htlc := range rpcInvoice.Htlcs { - require.NotNil(t.t, htlc.Amp) + require.NotNil(ht, htlc.Amp) if setID == nil { setID = make([]byte, 32) copy(setID, htlc.Amp.SetId) } - require.Equal(t.t, setID, htlc.Amp.SetId) + require.Equal(ht, setID, htlc.Amp.SetId) // Parse the child hash and child preimage, and assert they are // well-formed. childHash, err := lntypes.MakeHash(htlc.Amp.Hash) - require.NoError(t.t, err) + require.NoError(ht, err) childPreimage, err := lntypes.MakePreimage(htlc.Amp.Preimage) - require.NoError(t.t, err) + require.NoError(ht, err) // Assert that the preimage actually matches the hashes. validPreimage := childPreimage.Matches(childHash) - require.True(t.t, validPreimage) + require.True(ht, validPreimage) } // The set ID we extract above should be shown in the final settled // state. ampState := rpcInvoice.AmpInvoiceState[hex.EncodeToString(setID)] - require.Equal(t.t, lnrpc.InvoiceHTLCState_SETTLED, ampState.State) + require.Equal(ht, lnrpc.InvoiceHTLCState_SETTLED, ampState.State) + + // Finally, close all channels. + mts.closeChannels() } // testSendPaymentAMPInvoiceRepeat tests that it's possible to pay an AMP // invoice multiple times by having the client generate a new setID each time. -func testSendPaymentAMPInvoiceRepeat(net *lntest.NetworkHarness, - t *harnessTest) { - +func testSendPaymentAMPInvoiceRepeat(ht *lntemp.HarnessTest) { // In this basic test, we'll only need two nodes as we want to // primarily test the recurring payment feature. So we'll re-use the - carol := net.NewNode(t.t, "Carol", nil) - defer shutdownAndAssert(net, t, carol) + carol := ht.NewNode("Carol", nil) // Send Carol enough coins to be able to open a channel to Dave. - net.SendCoins(t.t, btcutil.SatoshiPerBitcoin, carol) + ht.FundCoins(btcutil.SatoshiPerBitcoin, carol) - dave := net.NewNode(t.t, "Dave", nil) - defer shutdownAndAssert(net, t, dave) - - // Before we start the test, we'll ensure both sides are connected to - // the funding flow can properly be executed. - net.EnsureConnected(t.t, carol, dave) + dave := ht.NewNode("Dave", nil) // Set up an invoice subscription so we can be notified when Dave // receives his repeated payments. req := &lnrpc.InvoiceSubscription{} - ctxb := context.Background() - ctxc, cancelSubscription := context.WithCancel(ctxb) - invSubscription, err := dave.SubscribeInvoices(ctxc, req) - require.NoError(t.t, err) - defer cancelSubscription() + invSubscription := dave.RPC.SubscribeInvoices(req) + + // Before we start the test, we'll ensure both sides are connected to + // the funding flow can properly be executed. + ht.EnsureConnected(carol, dave) // Establish a channel between Carol and Dave. chanAmt := btcutil.Amount(100_000) - chanPoint := openChannelAndAssert( - t, net, carol, dave, - lntest.OpenChannelParams{ - Amt: chanAmt, - }, + ht.OpenChannel( + carol, dave, lntemp.OpenChannelParams{Amt: chanAmt}, ) - err = carol.WaitForNetworkChannelOpen(chanPoint) - require.NoError(t.t, err, "carol didn't report channel") - err = dave.WaitForNetworkChannelOpen(chanPoint) - require.NoError(t.t, err, "dave didn't report channel") // Create an AMP invoice of a trivial amount, that we'll pay repeatedly // in this integration test. paymentAmt := 10000 - addInvoiceResp, err := dave.AddInvoice(ctxb, &lnrpc.Invoice{ + invoice := &lnrpc.Invoice{ Value: int64(paymentAmt), IsAmp: true, - }) - require.NoError(t.t, err) + } + addInvoiceResp := dave.RPC.AddInvoice(invoice) // We should get an initial notification that the HTLC has been added. - rpcInvoice, err := invSubscription.Recv() - require.NoError(t.t, err) - require.False(t.t, rpcInvoice.Settled) // nolint:staticcheck - require.Equal(t.t, lnrpc.Invoice_OPEN, rpcInvoice.State) - require.Equal(t.t, int64(0), rpcInvoice.AmtPaidSat) - require.Equal(t.t, int64(0), rpcInvoice.AmtPaidMsat) - - require.Equal(t.t, 0, len(rpcInvoice.Htlcs)) + rpcInvoice := ht.ReceiveInvoiceUpdate(invSubscription) + require.False(ht, rpcInvoice.Settled) + require.Equal(ht, lnrpc.Invoice_OPEN, rpcInvoice.State) + require.Equal(ht, int64(0), rpcInvoice.AmtPaidSat) + require.Equal(ht, int64(0), rpcInvoice.AmtPaidMsat) + require.Equal(ht, 0, len(rpcInvoice.Htlcs)) // Now we'll use Carol to pay the invoice that Dave created. - _ = sendAndAssertSuccess( - t, carol, &routerrpc.SendPaymentRequest{ - PaymentRequest: addInvoiceResp.PaymentRequest, - TimeoutSeconds: 60, - FeeLimitMsat: noFeeLimitMsat, - }, + ht.CompletePaymentRequests( + carol, []string{addInvoiceResp.PaymentRequest}, ) // Dave should get a notification that the invoice has been settled. - invoiceNtfn, err := invSubscription.Recv() - require.NoError(t.t, err) + invoiceNtfn := ht.ReceiveInvoiceUpdate(invSubscription) // The notification should signal that the invoice is now settled, and // should also include the set ID, and show the proper amount paid. - require.True(t.t, invoiceNtfn.Settled) // nolint:staticcheck - require.Equal(t.t, lnrpc.Invoice_SETTLED, invoiceNtfn.State) - require.Equal(t.t, paymentAmt, int(invoiceNtfn.AmtPaidSat)) - require.Equal(t.t, 1, len(invoiceNtfn.AmpInvoiceState)) + require.True(ht, invoiceNtfn.Settled) + require.Equal(ht, lnrpc.Invoice_SETTLED, invoiceNtfn.State) + require.Equal(ht, paymentAmt, int(invoiceNtfn.AmtPaidSat)) + require.Equal(ht, 1, len(invoiceNtfn.AmpInvoiceState)) var firstSetID []byte for setIDStr, ampState := range invoiceNtfn.AmpInvoiceState { firstSetID, _ = hex.DecodeString(setIDStr) - require.Equal(t.t, lnrpc.InvoiceHTLCState_SETTLED, ampState.State) + require.Equal(ht, lnrpc.InvoiceHTLCState_SETTLED, + ampState.State) } // Pay the invoice again, we should get another notification that Dave // has received another payment. - _ = sendAndAssertSuccess( - t, carol, &routerrpc.SendPaymentRequest{ - PaymentRequest: addInvoiceResp.PaymentRequest, - TimeoutSeconds: 60, - FeeLimitMsat: noFeeLimitMsat, - }, + ht.CompletePaymentRequests( + carol, []string{addInvoiceResp.PaymentRequest}, ) // Dave should get another notification. - invoiceNtfn, err = invSubscription.Recv() - require.NoError(t.t, err) + invoiceNtfn = ht.ReceiveInvoiceUpdate(invSubscription) // The invoice should still be shown as settled, and also include the // information about this newly generated setID, showing 2x the amount // paid. - require.True(t.t, invoiceNtfn.Settled) // nolint:staticcheck - require.Equal(t.t, paymentAmt*2, int(invoiceNtfn.AmtPaidSat)) + require.True(ht, invoiceNtfn.Settled) + require.Equal(ht, paymentAmt*2, int(invoiceNtfn.AmtPaidSat)) var secondSetID []byte for setIDStr, ampState := range invoiceNtfn.AmpInvoiceState { secondSetID, _ = hex.DecodeString(setIDStr) - require.Equal(t.t, lnrpc.InvoiceHTLCState_SETTLED, ampState.State) + require.Equal(ht, lnrpc.InvoiceHTLCState_SETTLED, + ampState.State) } // The returned invoice should only include a single HTLC since we // return the "projected" sub-invoice for a given setID. - require.Equal(t.t, 1, len(invoiceNtfn.Htlcs)) + require.Equal(ht, 1, len(invoiceNtfn.Htlcs)) // However the AMP state index should show that there've been two // repeated payments to this invoice so far. - require.Equal(t.t, 2, len(invoiceNtfn.AmpInvoiceState)) + require.Equal(ht, 2, len(invoiceNtfn.AmpInvoiceState)) // Now we'll look up the invoice using the new LookupInvoice2 RPC call // by the set ID of each of the invoices. - subInvoice1, err := dave.LookupInvoiceV2(ctxb, &invoicesrpc.LookupInvoiceMsg{ + msg := &invoicesrpc.LookupInvoiceMsg{ InvoiceRef: &invoicesrpc.LookupInvoiceMsg_SetId{ SetId: firstSetID, }, LookupModifier: invoicesrpc.LookupModifier_HTLC_SET_ONLY, - }) - require.Nil(t.t, err) - subInvoice2, err := dave.LookupInvoiceV2(ctxb, &invoicesrpc.LookupInvoiceMsg{ + } + subInvoice1 := dave.RPC.LookupInvoiceV2(msg) + msg = &invoicesrpc.LookupInvoiceMsg{ InvoiceRef: &invoicesrpc.LookupInvoiceMsg_SetId{ SetId: secondSetID, }, LookupModifier: invoicesrpc.LookupModifier_HTLC_SET_ONLY, - }) - require.Nil(t.t, err) + } + subInvoice2 := dave.RPC.LookupInvoiceV2(msg) // Each invoice should only show a single HTLC present, as we passed // the HTLC set only modifier. - require.Equal(t.t, 1, len(subInvoice1.Htlcs)) - require.Equal(t.t, 1, len(subInvoice2.Htlcs)) + require.Equal(ht, 1, len(subInvoice1.Htlcs)) + require.Equal(ht, 1, len(subInvoice2.Htlcs)) // If we look up the same invoice, by its payment address, but now with // the HTLC blank modifier, then none of them should be returned. - rootInvoice, err := dave.LookupInvoiceV2(ctxb, &invoicesrpc.LookupInvoiceMsg{ + msg = &invoicesrpc.LookupInvoiceMsg{ InvoiceRef: &invoicesrpc.LookupInvoiceMsg_PaymentAddr{ PaymentAddr: addInvoiceResp.PaymentAddr, }, LookupModifier: invoicesrpc.LookupModifier_HTLC_SET_BLANK, - }) - require.Nil(t.t, err) - require.Equal(t.t, 0, len(rootInvoice.Htlcs)) + } + rootInvoice := dave.RPC.LookupInvoiceV2(msg) + require.Equal(ht, 0, len(rootInvoice.Htlcs)) // If we look up the same invoice, by its payment address, but without // that modified, then we should get all the relevant HTLCs. - rootInvoice, err = dave.LookupInvoiceV2(ctxb, - &invoicesrpc.LookupInvoiceMsg{ - InvoiceRef: &invoicesrpc.LookupInvoiceMsg_PaymentAddr{ - PaymentAddr: addInvoiceResp.PaymentAddr, - }, - }) - require.Nil(t.t, err) - require.Equal(t.t, 2, len(rootInvoice.Htlcs)) + msg = &invoicesrpc.LookupInvoiceMsg{ + InvoiceRef: &invoicesrpc.LookupInvoiceMsg_PaymentAddr{ + PaymentAddr: addInvoiceResp.PaymentAddr, + }, + } + rootInvoice = dave.RPC.LookupInvoiceV2(msg) + require.Equal(ht, 2, len(rootInvoice.Htlcs)) // Finally, we'll test that if we subscribe for notifications of // settled invoices, we get a backlog, which includes the invoice we @@ -379,29 +339,22 @@ func testSendPaymentAMPInvoiceRepeat(net *lntest.NetworkHarness, req = &lnrpc.InvoiceSubscription{ SettleIndex: 1, } - ctxc, cancelSubscription2 := context.WithCancel(ctxb) - invSub2, err := dave.SubscribeInvoices(ctxc, req) - require.NoError(t.t, err) - defer cancelSubscription2() + invSub2 := dave.RPC.SubscribeInvoices(req) // The first invoice we get back should match the state of the invoice // after our second payment: amt updated, but only a single HTLC shown // through. - backlogInv, _ := invSub2.Recv() - require.Equal(t.t, 1, len(backlogInv.Htlcs)) - require.Equal(t.t, 2, len(backlogInv.AmpInvoiceState)) - require.True(t.t, backlogInv.Settled) // nolint:staticcheck - require.Equal(t.t, paymentAmt*2, int(backlogInv.AmtPaidSat)) + backlogInv := ht.ReceiveInvoiceUpdate(invSub2) + require.Equal(ht, 1, len(backlogInv.Htlcs)) + require.Equal(ht, 2, len(backlogInv.AmpInvoiceState)) + require.True(ht, backlogInv.Settled) + require.Equal(ht, paymentAmt*2, int(backlogInv.AmtPaidSat)) } // testSendPaymentAMP tests that we can send an AMP payment to a specified // destination using SendPaymentV2. -func testSendPaymentAMP(net *lntest.NetworkHarness, t *harnessTest) { - ctxb := context.Background() - - ctx := newMppTestContext(t, net) - defer ctx.shutdownNodes() - +func testSendPaymentAMP(ht *lntemp.HarnessTest) { + mts := newMppTestScenario(ht) const paymentAmt = btcutil.Amount(300000) // Set up a network with three different paths Alice <-> Bob. Channel @@ -414,44 +367,37 @@ func testSendPaymentAMP(net *lntest.NetworkHarness, t *harnessTest) { // \ / // \__ Dave ____/ // - ctx.openChannel(ctx.carol, ctx.bob, 135000) - ctx.openChannel(ctx.alice, ctx.carol, 235000) - ctx.openChannel(ctx.dave, ctx.bob, 135000) - ctx.openChannel(ctx.alice, ctx.dave, 135000) - ctx.openChannel(ctx.eve, ctx.bob, 135000) - ctx.openChannel(ctx.carol, ctx.eve, 135000) - - defer ctx.closeChannels() - - ctx.waitForChannels() + mppReq := &mppOpenChannelRequest{ + amtAliceCarol: 235000, + amtAliceDave: 135000, + amtCarolBob: 135000, + amtCarolEve: 135000, + amtDaveBob: 135000, + amtEveBob: 135000, + } + mts.openChannels(mppReq) + chanPointAliceDave := mts.channelPoints[1] // Increase Dave's fee to make the test deterministic. Otherwise it // would be unpredictable whether pathfinding would go through Charlie // or Dave for the first shard. - _, err := ctx.dave.UpdateChannelPolicy( - context.Background(), - &lnrpc.PolicyUpdateRequest{ - Scope: &lnrpc.PolicyUpdateRequest_Global{Global: true}, - BaseFeeMsat: 500000, - FeeRate: 0.001, - TimeLockDelta: 40, - }, - ) - if err != nil { - t.Fatalf("dave policy update: %v", err) - } + expectedPolicy := mts.updateDaveGlobalPolicy() - payment := sendAndAssertSuccess( - t, ctx.alice, &routerrpc.SendPaymentRequest{ - Dest: ctx.bob.PubKey[:], - Amt: int64(paymentAmt), - FinalCltvDelta: chainreg.DefaultBitcoinTimeLockDelta, - TimeoutSeconds: 60, - FeeLimitMsat: noFeeLimitMsat, - Amp: true, - }, + // Make sure Alice has heard it. + ht.AssertChannelPolicyUpdate( + mts.alice, mts.dave, expectedPolicy, chanPointAliceDave, false, ) + sendReq := &routerrpc.SendPaymentRequest{ + Dest: mts.bob.PubKey[:], + Amt: int64(paymentAmt), + FinalCltvDelta: chainreg.DefaultBitcoinTimeLockDelta, + TimeoutSeconds: 60, + FeeLimitMsat: noFeeLimitMsat, + Amp: true, + } + payment := ht.SendPaymentAssertSettled(mts.alice, sendReq) + // Check that Alice split the payment in at least three shards. Because // the hand-off of the htlc to the link is asynchronous (via a mailbox), // there is some non-determinism in the process. Depending on whether @@ -467,64 +413,55 @@ func testSendPaymentAMP(net *lntest.NetworkHarness, t *harnessTest) { } const minExpectedShards = 3 - if succeeded < minExpectedShards { - t.Fatalf("expected at least %v shards, but got %v", - minExpectedShards, succeeded) - } + require.GreaterOrEqual(ht, succeeded, minExpectedShards, + "expected num of shards not reached") - // Fetch Bob's invoices. - invoiceResp, err := ctx.bob.ListInvoices( - ctxb, &lnrpc.ListInvoiceRequest{}, - ) - require.NoError(t.t, err) - - // There should only be one invoice. - require.Equal(t.t, 1, len(invoiceResp.Invoices)) - rpcInvoice := invoiceResp.Invoices[0] + // Fetch Bob's invoices. There should only be one invoice. + invoices := ht.AssertNumInvoices(mts.bob, 1) + rpcInvoice := invoices[0] // Assert that the invoice is settled for the total payment amount and // has the correct payment address. - require.True(t.t, rpcInvoice.Settled) // nolint:staticcheck - require.Equal(t.t, lnrpc.Invoice_SETTLED, rpcInvoice.State) - require.Equal(t.t, int64(paymentAmt), rpcInvoice.AmtPaidSat) - require.Equal(t.t, int64(paymentAmt*1000), rpcInvoice.AmtPaidMsat) + require.True(ht, rpcInvoice.Settled) + require.Equal(ht, lnrpc.Invoice_SETTLED, rpcInvoice.State) + require.Equal(ht, int64(paymentAmt), rpcInvoice.AmtPaidSat) + require.Equal(ht, int64(paymentAmt*1000), rpcInvoice.AmtPaidMsat) // Finally, assert that the same set id is recorded for each htlc, and // that the preimage hash pair is valid. var setID []byte - require.Equal(t.t, succeeded, len(rpcInvoice.Htlcs)) + require.Equal(ht, succeeded, len(rpcInvoice.Htlcs)) for _, htlc := range rpcInvoice.Htlcs { - require.NotNil(t.t, htlc.Amp) + require.NotNil(ht, htlc.Amp) if setID == nil { setID = make([]byte, 32) copy(setID, htlc.Amp.SetId) } - require.Equal(t.t, setID, htlc.Amp.SetId) + require.Equal(ht, setID, htlc.Amp.SetId) // Parse the child hash and child preimage, and assert they are // well-formed. childHash, err := lntypes.MakeHash(htlc.Amp.Hash) - require.NoError(t.t, err) + require.NoError(ht, err) childPreimage, err := lntypes.MakePreimage(htlc.Amp.Preimage) - require.NoError(t.t, err) + require.NoError(ht, err) // Assert that the preimage actually matches the hashes. validPreimage := childPreimage.Matches(childHash) - require.True(t.t, validPreimage) + require.True(ht, validPreimage) } // The set ID we extract above should be shown in the final settled // state. ampState := rpcInvoice.AmpInvoiceState[hex.EncodeToString(setID)] - require.Equal(t.t, lnrpc.InvoiceHTLCState_SETTLED, ampState.State) + require.Equal(ht, lnrpc.InvoiceHTLCState_SETTLED, ampState.State) + + // Finally, close all channels. + mts.closeChannels() } -func testSendToRouteAMP(net *lntest.NetworkHarness, t *harnessTest) { - ctxb := context.Background() - - ctx := newMppTestContext(t, net) - defer ctx.shutdownNodes() - +func testSendToRouteAMP(ht *lntemp.HarnessTest) { + mts := newMppTestScenario(ht) const ( paymentAmt = btcutil.Amount(300000) numShards = 3 @@ -532,63 +469,50 @@ func testSendToRouteAMP(net *lntest.NetworkHarness, t *harnessTest) { chanAmt = shardAmt * 3 / 2 ) + // Subscribe to bob's invoices. + req := &lnrpc.InvoiceSubscription{} + bobInvoiceSubscription := mts.bob.RPC.SubscribeInvoices(req) + // Set up a network with three different paths Alice <-> Bob. // _ Eve _ // / \ // Alice -- Carol ---- Bob // \ / // \__ Dave ____/ - // - ctx.openChannel(ctx.carol, ctx.bob, chanAmt) - ctx.openChannel(ctx.dave, ctx.bob, chanAmt) - ctx.openChannel(ctx.alice, ctx.dave, chanAmt) - ctx.openChannel(ctx.eve, ctx.bob, chanAmt) - ctx.openChannel(ctx.carol, ctx.eve, chanAmt) - - // Since the channel Alice-> Carol will have to carry two - // shards, we make it larger. - ctx.openChannel(ctx.alice, ctx.carol, chanAmt+shardAmt) - - defer ctx.closeChannels() - - ctx.waitForChannels() - - // Subscribe to bob's invoices. - req := &lnrpc.InvoiceSubscription{} - ctxc, cancelSubscription := context.WithCancel(ctxb) - bobInvoiceSubscription, err := ctx.bob.SubscribeInvoices(ctxc, req) - require.NoError(t.t, err) - defer cancelSubscription() + /// + mppReq := &mppOpenChannelRequest{ + // Since the channel Alice-> Carol will have to carry two + // shards, we make it larger. + amtAliceCarol: chanAmt + shardAmt, + amtAliceDave: chanAmt, + amtCarolBob: chanAmt, + amtCarolEve: chanAmt, + amtDaveBob: chanAmt, + amtEveBob: chanAmt, + } + mts.openChannels(mppReq) // We'll send shards along three routes from Alice. - sendRoutes := [numShards][]*lntest.HarnessNode{ - {ctx.carol, ctx.bob}, - {ctx.dave, ctx.bob}, - {ctx.carol, ctx.eve, ctx.bob}, + sendRoutes := [numShards][]*node.HarnessNode{ + {mts.carol, mts.bob}, + {mts.dave, mts.bob}, + {mts.carol, mts.eve, mts.bob}, } - payAddr := make([]byte, 32) - _, err = rand.Read(payAddr) - require.NoError(t.t, err) - - setID := make([]byte, 32) - _, err = rand.Read(setID) - require.NoError(t.t, err) + payAddr := ht.Random32Bytes() + setID := ht.Random32Bytes() var sharer amp.Sharer - sharer, err = amp.NewSeedSharer() - require.NoError(t.t, err) + sharer, err := amp.NewSeedSharer() + require.NoError(ht, err) childPreimages := make(map[lntypes.Preimage]uint32) responses := make(chan *lnrpc.HTLCAttempt, len(sendRoutes)) // Define a closure for sending each of the three shards. - sendShard := func(i int, hops []*lntest.HarnessNode) { + sendShard := func(i int, hops []*node.HarnessNode) { // Build a route for the specified hops. - r, err := ctx.buildRoute(ctxb, shardAmt, ctx.alice, hops) - if err != nil { - t.Fatalf("unable to build route: %v", err) - } + r := mts.buildRoute(shardAmt, mts.alice, hops) // Set the MPP records to indicate this is a payment shard. hop := r.Hops[len(r.Hops)-1] @@ -602,7 +526,7 @@ func testSendToRouteAMP(net *lntest.NetworkHarness, t *harnessTest) { if i < len(sendRoutes)-1 { var left amp.Sharer left, sharer, err = sharer.Split() - require.NoError(t.t, err) + require.NoError(ht, err) child = left.Child(uint32(i)) } else { @@ -622,15 +546,10 @@ func testSendToRouteAMP(net *lntest.NetworkHarness, t *harnessTest) { Route: r, } - // We'll send all shards in their own goroutine, since SendToRoute will - // block as long as the payment is in flight. + // We'll send all shards in their own goroutine, since + // SendToRoute will block as long as the payment is in flight. go func() { - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - resp, err := ctx.alice.RouterClient.SendToRouteV2(ctxt, sendReq) - if err != nil { - t.Fatalf("unable to send payment: %v", err) - } - + resp := mts.alice.RPC.SendToRouteV2(sendReq) responses <- resp }() } @@ -640,21 +559,21 @@ func testSendToRouteAMP(net *lntest.NetworkHarness, t *harnessTest) { // Ensure we get a notification of the invoice being added by Bob. rpcInvoice, err := bobInvoiceSubscription.Recv() - require.NoError(t.t, err) + require.NoError(ht, err) - require.False(t.t, rpcInvoice.Settled) // nolint:staticcheck - require.Equal(t.t, lnrpc.Invoice_OPEN, rpcInvoice.State) - require.Equal(t.t, int64(0), rpcInvoice.AmtPaidSat) - require.Equal(t.t, int64(0), rpcInvoice.AmtPaidMsat) - require.Equal(t.t, payAddr, rpcInvoice.PaymentAddr) + require.False(ht, rpcInvoice.Settled) + require.Equal(ht, lnrpc.Invoice_OPEN, rpcInvoice.State) + require.Equal(ht, int64(0), rpcInvoice.AmtPaidSat) + require.Equal(ht, int64(0), rpcInvoice.AmtPaidMsat) + require.Equal(ht, payAddr, rpcInvoice.PaymentAddr) - require.Equal(t.t, 0, len(rpcInvoice.Htlcs)) + require.Equal(ht, 0, len(rpcInvoice.Htlcs)) sendShard(1, sendRoutes[1]) sendShard(2, sendRoutes[2]) // Assert that all of the child preimages are unique. - require.Equal(t.t, len(sendRoutes), len(childPreimages)) + require.Equal(ht, len(sendRoutes), len(childPreimages)) // Make a copy of the childPreimages map for validating the resulting // invoice. @@ -665,24 +584,23 @@ func testSendToRouteAMP(net *lntest.NetworkHarness, t *harnessTest) { // Wait for all responses to be back, and check that they all // succeeded. + timer := time.After(defaultTimeout) for range sendRoutes { var resp *lnrpc.HTLCAttempt select { case resp = <-responses: - case <-time.After(defaultTimeout): - t.Fatalf("response not received") + case <-timer: + require.Fail(ht, "response not received") } - if resp.Failure != nil { - t.Fatalf("received payment failure : %v", resp.Failure) - } + require.Nil(ht, resp.Failure, "received payment failure") preimage, err := lntypes.MakePreimage(resp.Preimage) - require.NoError(t.t, err) + require.NoError(ht, err) // Assert that the response includes one of our child preimages. _, ok := childPreimages[preimage] - require.True(t.t, ok) + require.True(ht, ok) // Remove this preimage from out set so that we ensure all // responses have a unique child preimage. @@ -692,109 +610,51 @@ func testSendToRouteAMP(net *lntest.NetworkHarness, t *harnessTest) { // There should now be a settle event for the invoice. rpcInvoice, err = bobInvoiceSubscription.Recv() - require.NoError(t.t, err) + require.NoError(ht, err) // Also fetch Bob's invoice from ListInvoices and assert it is equal to // the one received via the subscription. - invoiceResp, err := ctx.bob.ListInvoices( - ctxb, &lnrpc.ListInvoiceRequest{}, - ) - require.NoError(t.t, err) - require.Equal(t.t, 1, len(invoiceResp.Invoices)) - assertInvoiceEqual(t.t, rpcInvoice, invoiceResp.Invoices[0]) + invoices := ht.AssertNumInvoices(mts.bob, 1) + ht.AssertInvoiceEqual(rpcInvoice, invoices[0]) // Assert that the invoice is settled for the total payment amount and // has the correct payment address. - require.True(t.t, rpcInvoice.Settled) // nolint:staticcheck - require.Equal(t.t, lnrpc.Invoice_SETTLED, rpcInvoice.State) - require.Equal(t.t, int64(paymentAmt), rpcInvoice.AmtPaidSat) - require.Equal(t.t, int64(paymentAmt*1000), rpcInvoice.AmtPaidMsat) - require.Equal(t.t, payAddr, rpcInvoice.PaymentAddr) + require.True(ht, rpcInvoice.Settled) + require.Equal(ht, lnrpc.Invoice_SETTLED, rpcInvoice.State) + require.Equal(ht, int64(paymentAmt), rpcInvoice.AmtPaidSat) + require.Equal(ht, int64(paymentAmt*1000), rpcInvoice.AmtPaidMsat) + require.Equal(ht, payAddr, rpcInvoice.PaymentAddr) // Finally, assert that the proper set id is recorded for each htlc, and // that the preimage hash pair is valid. - require.Equal(t.t, numShards, len(rpcInvoice.Htlcs)) + require.Equal(ht, numShards, len(rpcInvoice.Htlcs)) for _, htlc := range rpcInvoice.Htlcs { - require.NotNil(t.t, htlc.Amp) - require.Equal(t.t, setID, htlc.Amp.SetId) + require.NotNil(ht, htlc.Amp) + require.Equal(ht, setID, htlc.Amp.SetId) // Parse the child hash and child preimage, and assert they are // well-formed. childHash, err := lntypes.MakeHash(htlc.Amp.Hash) - require.NoError(t.t, err) + require.NoError(ht, err) childPreimage, err := lntypes.MakePreimage(htlc.Amp.Preimage) - require.NoError(t.t, err) + require.NoError(ht, err) // Assert that the preimage actually matches the hashes. validPreimage := childPreimage.Matches(childHash) - require.True(t.t, validPreimage) + require.True(ht, validPreimage) // Assert that the HTLC includes one of our child preimages. childIndex, ok := childPreimages[childPreimage] - require.True(t.t, ok) + require.True(ht, ok) // Assert that the correct child index is reflected. - require.Equal(t.t, childIndex, htlc.Amp.ChildIndex) + require.Equal(ht, childIndex, htlc.Amp.ChildIndex) // Remove this preimage from our set so that we ensure all HTLCs // have a unique child preimage. delete(childPreimages, childPreimage) } -} - -// assertInvoiceEqual asserts that two lnrpc.Invoices are equivalent. A custom -// comparison function is defined for these tests, since proto message returned -// from unary and streaming RPCs (as of protobuf 1.23.0 and grpc 1.29.1) aren't -// consistent with the private fields set on the messages. As a result, we avoid -// using require.Equal and test only the actual data members. -func assertInvoiceEqual(t *testing.T, a, b *lnrpc.Invoice) { - t.Helper() - - // Ensure the HTLCs are sorted properly before attempting to compare. - sort.Slice(a.Htlcs, func(i, j int) bool { - return a.Htlcs[i].ChanId < a.Htlcs[j].ChanId - }) - sort.Slice(b.Htlcs, func(i, j int) bool { - return b.Htlcs[i].ChanId < b.Htlcs[j].ChanId - }) - - require.Equal(t, a.Memo, b.Memo) - require.Equal(t, a.RPreimage, b.RPreimage) - require.Equal(t, a.RHash, b.RHash) - require.Equal(t, a.Value, b.Value) - require.Equal(t, a.ValueMsat, b.ValueMsat) - require.Equal(t, a.CreationDate, b.CreationDate) - require.Equal(t, a.SettleDate, b.SettleDate) - require.Equal(t, a.PaymentRequest, b.PaymentRequest) - require.Equal(t, a.DescriptionHash, b.DescriptionHash) - require.Equal(t, a.Expiry, b.Expiry) - require.Equal(t, a.FallbackAddr, b.FallbackAddr) - require.Equal(t, a.CltvExpiry, b.CltvExpiry) - require.Equal(t, a.RouteHints, b.RouteHints) - require.Equal(t, a.Private, b.Private) - require.Equal(t, a.AddIndex, b.AddIndex) - require.Equal(t, a.SettleIndex, b.SettleIndex) - require.Equal(t, a.AmtPaidSat, b.AmtPaidSat) - require.Equal(t, a.AmtPaidMsat, b.AmtPaidMsat) - require.Equal(t, a.State, b.State) - require.Equal(t, a.Features, b.Features) - require.Equal(t, a.IsKeysend, b.IsKeysend) - require.Equal(t, a.PaymentAddr, b.PaymentAddr) - require.Equal(t, a.IsAmp, b.IsAmp) - - require.Equal(t, len(a.Htlcs), len(b.Htlcs)) - for i := range a.Htlcs { - htlcA, htlcB := a.Htlcs[i], b.Htlcs[i] - require.Equal(t, htlcA.ChanId, htlcB.ChanId) - require.Equal(t, htlcA.HtlcIndex, htlcB.HtlcIndex) - require.Equal(t, htlcA.AmtMsat, htlcB.AmtMsat) - require.Equal(t, htlcA.AcceptHeight, htlcB.AcceptHeight) - require.Equal(t, htlcA.AcceptTime, htlcB.AcceptTime) - require.Equal(t, htlcA.ResolveTime, htlcB.ResolveTime) - require.Equal(t, htlcA.ExpiryHeight, htlcB.ExpiryHeight) - require.Equal(t, htlcA.State, htlcB.State) - require.Equal(t, htlcA.CustomRecords, htlcB.CustomRecords) - require.Equal(t, htlcA.MppTotalAmtMsat, htlcB.MppTotalAmtMsat) - require.Equal(t, htlcA.Amp, htlcB.Amp) - } + + // Finally, close all channels. + mts.closeChannels() } diff --git a/lntest/itest/lnd_custom_message.go b/lntest/itest/lnd_custom_message.go index 1d9c36d45..5faf425a1 100644 --- a/lntest/itest/lnd_custom_message.go +++ b/lntest/itest/lnd_custom_message.go @@ -1,13 +1,11 @@ package itest import ( - "context" "fmt" - "sync" "time" "github.com/lightningnetwork/lnd/lnrpc" - "github.com/lightningnetwork/lnd/lntest" + "github.com/lightningnetwork/lnd/lntemp" "github.com/lightningnetwork/lnd/lnwire" "github.com/stretchr/testify/require" ) @@ -15,16 +13,8 @@ import ( // testCustomMessage tests sending and receiving of overridden custom message // types (within the message type range usually reserved for protocol messages) // via the send and subscribe custom message APIs. -func testCustomMessage(net *lntest.NetworkHarness, t *harnessTest) { - ctx, cancel := context.WithCancel(context.Background()) - var wg sync.WaitGroup - - // At the end of our test, cancel our context and wait for all - // goroutines to exit. - defer func() { - cancel() - wg.Wait() - }() +func testCustomMessage(ht *lntemp.HarnessTest) { + alice, bob := ht.Alice, ht.Bob var ( overrideType1 uint32 = 554 @@ -34,28 +24,19 @@ func testCustomMessage(net *lntest.NetworkHarness, t *harnessTest) { // Update Alice to accept custom protocol messages with type 1 but do // not allow Bob to handle them yet. - net.Alice.Cfg.ExtraArgs = append( - net.Alice.Cfg.ExtraArgs, + extraArgs := []string{ fmt.Sprintf(msgOverrideArg, overrideType1), - ) - require.NoError(t.t, net.RestartNode(net.Alice, nil, nil)) - - // Wait for Alice's server to be active after the restart before we - // try to subscribe to our message stream. - require.NoError(t.t, net.Alice.WaitUntilServerActive()) + } + ht.RestartNodeWithExtraArgs(alice, extraArgs) // Subscribe Alice to custom messages before we send any, so that we // don't miss any. - msgClient, err := net.Alice.LightningClient.SubscribeCustomMessages( - ctx, &lnrpc.SubscribeCustomMessagesRequest{}, - ) - require.NoError(t.t, err, "alice could not subscribe") + msgClient, cancel := alice.RPC.SubscribeCustomMessages() + defer cancel() // Create a channel to receive custom messages on. messages := make(chan *lnrpc.CustomMessage) - wg.Add(1) go func() { - defer wg.Done() for { // If we fail to receive, just exit. The test should // fail elsewhere if it doesn't get a message that it @@ -69,120 +50,108 @@ func testCustomMessage(net *lntest.NetworkHarness, t *harnessTest) { // test is shutting down. select { case messages <- msg: - case <-ctx.Done(): + case <-ht.Context().Done(): return } } }() // Connect alice and bob so that they can exchange messages. - net.EnsureConnected(t.t, net.Alice, net.Bob) + ht.EnsureConnected(alice, bob) // Create a custom message that is within our allowed range. msgType := uint32(lnwire.CustomTypeStart + 1) msgData := []byte{1, 2, 3} // Send it from Bob to Alice. - ctxt, _ := context.WithTimeout(ctx, defaultTimeout) - _, err = net.Bob.LightningClient.SendCustomMessage( - ctxt, &lnrpc.SendCustomMessageRequest{ - Peer: net.Alice.PubKey[:], - Type: msgType, - Data: msgData, - }, - ) - require.NoError(t.t, err, "bob could not send") + bobMsg := &lnrpc.SendCustomMessageRequest{ + Peer: alice.PubKey[:], + Type: msgType, + Data: msgData, + } + bob.RPC.SendCustomMessage(bobMsg) - // Wait for Alice to receive the message. It should come through because - // it is within our allowed range. + // Wait for Alice to receive the message. It should come through + // because it is within our allowed range. select { case msg := <-messages: - // Check our type and data and (sanity) check the peer we got it - // from. - require.Equal(t.t, msgType, msg.Type, "first msg type wrong") - require.Equal(t.t, msgData, msg.Data, "first msg data wrong") - require.Equal(t.t, net.Bob.PubKey[:], msg.Peer, "first msg "+ + // Check our type and data and (sanity) check the peer we got + // it from. + require.Equal(ht, msgType, msg.Type, "first msg type wrong") + require.Equal(ht, msgData, msg.Data, "first msg data wrong") + require.Equal(ht, bob.PubKey[:], msg.Peer, "first msg "+ "peer wrong") case <-time.After(defaultTimeout): - t.t.Fatalf("alice did not receive first custom message: %v", + ht.Fatalf("alice did not receive first custom message: %v", msgType) } // Try to send a message from Bob to Alice which has a message type // outside of the custom type range and assert that it fails. - ctxt, _ = context.WithTimeout(ctx, defaultTimeout) - _, err = net.Bob.LightningClient.SendCustomMessage( - ctxt, &lnrpc.SendCustomMessageRequest{ - Peer: net.Alice.PubKey[:], - Type: overrideType1, - Data: msgData, - }, - ) - require.Error(t.t, err, "bob should not be able to send type 1") + bobMsg = &lnrpc.SendCustomMessageRequest{ + Peer: alice.PubKey[:], + Type: overrideType1, + Data: msgData, + } + _, err := bob.RPC.LN.SendCustomMessage(ht.Context(), bobMsg) + require.Error(ht, err, "bob should not be able to send type 1") // Now, restart Bob with the ability to send two different custom // protocol messages. - net.Bob.Cfg.ExtraArgs = append( - net.Bob.Cfg.ExtraArgs, + extraArgs = []string{ fmt.Sprintf(msgOverrideArg, overrideType1), fmt.Sprintf(msgOverrideArg, overrideType2), - ) - require.NoError(t.t, net.RestartNode(net.Bob, nil, nil)) + } + ht.RestartNodeWithExtraArgs(bob, extraArgs) // Make sure Bob and Alice are connected after his restart. - net.EnsureConnected(t.t, net.Alice, net.Bob) + ht.EnsureConnected(alice, bob) // Send a message from Bob to Alice with a type that Bob is allowed to // send, but Alice will not handle as a custom message. - ctxt, _ = context.WithTimeout(ctx, defaultTimeout) - _, err = net.Bob.LightningClient.SendCustomMessage( - ctxt, &lnrpc.SendCustomMessageRequest{ - Peer: net.Alice.PubKey[:], - Type: overrideType2, - Data: msgData, - }, - ) - require.NoError(t.t, err, "bob should be able to send type 2") + bobMsg = &lnrpc.SendCustomMessageRequest{ + Peer: alice.PubKey[:], + Type: overrideType2, + Data: msgData, + } + bob.RPC.SendCustomMessage(bobMsg) // Do a quick check that Alice did not receive this message in her - // stream. Note that this is an instant check, so could miss the message - // being received. We'll also check below that she didn't get it, this - // is just a sanity check. + // stream. Note that this is an instant check, so could miss the + // message being received. We'll also check below that she didn't get + // it, this is just a sanity check. select { case msg := <-messages: - t.t.Fatalf("unexpected message: %v", msg) + ht.Fatalf("unexpected message: %v", msg) default: } // Finally, send a custom message with a type that Bob is allowed to // send and Alice is configured to receive. - ctxt, _ = context.WithTimeout(ctx, defaultTimeout) - _, err = net.Bob.LightningClient.SendCustomMessage( - ctxt, &lnrpc.SendCustomMessageRequest{ - Peer: net.Alice.PubKey[:], - Type: overrideType1, - Data: msgData, - }, - ) - require.NoError(t.t, err, "bob should be able to send type 1") + bobMsg = &lnrpc.SendCustomMessageRequest{ + Peer: alice.PubKey[:], + Type: overrideType1, + Data: msgData, + } + bob.RPC.SendCustomMessage(bobMsg) // Wait to receive a message from Bob. This check serves to ensure that // our message type 1 was delivered, and assert that the preceding one - // was not (we could have missed it in our check above). When we receive - // the second message, we know that the first one did not go through, - // because we expect our messages to deliver in order. + // was not (we could have missed it in our check above). When we + // receive the second message, we know that the first one did not go + // through, because we expect our messages to deliver in order. select { case msg := <-messages: - // Check our type and data and (sanity) check the peer we got it - // from. - require.Equal(t.t, overrideType1, msg.Type, "second message "+ + // Check our type and data and (sanity) check the peer we got + // it from. + require.Equal(ht, overrideType1, msg.Type, "second message "+ "type") - require.Equal(t.t, msgData, msg.Data, "second message data") - require.Equal(t.t, net.Bob.PubKey[:], msg.Peer, "second "+ + require.Equal(ht, msgData, msg.Data, "second message data") + require.Equal(ht, bob.PubKey[:], msg.Peer, "second "+ "message peer") case <-time.After(defaultTimeout): - t.t.Fatalf("alice did not receive second custom message") + ht.Fatalf("alice did not receive second custom message") } } diff --git a/lntest/itest/lnd_forward_interceptor_test.go b/lntest/itest/lnd_forward_interceptor_test.go index 56eccb275..4f667552b 100644 --- a/lntest/itest/lnd_forward_interceptor_test.go +++ b/lntest/itest/lnd_forward_interceptor_test.go @@ -1,19 +1,18 @@ package itest import ( - "context" - "encoding/hex" "fmt" - "sync" + "strings" "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/lightningnetwork/lnd/lntest/wait" + "github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/routing/route" "github.com/stretchr/testify/require" "google.golang.org/grpc/codes" @@ -35,163 +34,145 @@ type interceptorTestCase struct { // testForwardInterceptorDedupHtlc tests that upon reconnection, duplicate // HTLCs aren't re-notified using the HTLC interceptor API. -func testForwardInterceptorDedupHtlc(net *lntest.NetworkHarness, t *harnessTest) { +func testForwardInterceptorDedupHtlc(ht *lntemp.HarnessTest) { // Initialize the test context with 3 connected nodes. - alice := net.NewNode(t.t, "alice", nil) - defer shutdownAndAssert(net, t, alice) + ts := newInterceptorTestScenario(ht) - bob := net.NewNode(t.t, "bob", nil) - defer shutdownAndAssert(net, t, bob) - - carol := net.NewNode(t.t, "carol", nil) - defer shutdownAndAssert(net, t, carol) - - tc := newInterceptorTestContext(t, net, alice, bob, carol) - - const ( - chanAmt = btcutil.Amount(300000) - ) + alice, bob, carol := ts.alice, ts.bob, ts.carol // Open and wait for channels. - tc.openChannel(tc.alice, tc.bob, chanAmt) - tc.openChannel(tc.bob, tc.carol, chanAmt) - defer tc.closeChannels() - tc.waitForChannels() + const chanAmt = btcutil.Amount(300000) + p := lntemp.OpenChannelParams{Amt: chanAmt} + reqs := []*lntemp.OpenChannelRequest{ + {Local: alice, Remote: bob, Param: p}, + {Local: bob, Remote: carol, Param: p}, + } + resp := ht.OpenMultiChannelsAsync(reqs) + cpAB, cpBC := resp[0], resp[1] - ctxb := context.Background() - ctxt, cancelInterceptor := context.WithCancel(ctxb) - interceptor, err := tc.bob.RouterClient.HtlcInterceptor(ctxt) - require.NoError(tc.t.t, err, "failed to create HtlcInterceptor") + // Make sure Alice is aware of channel Bob=>Carol. + ht.AssertTopologyChannelOpen(alice, cpBC) - addResponse, err := tc.carol.AddInvoice(ctxb, &lnrpc.Invoice{ - ValueMsat: 1000, - }) - require.NoError(tc.t.t, err, "unable to add invoice") + // Connect the interceptor. + interceptor, cancelInterceptor := bob.RPC.HtlcInterceptor() - invoice, err := tc.carol.LookupInvoice(ctxb, &lnrpc.PaymentHash{ - RHashStr: hex.EncodeToString(addResponse.RHash), - }) - require.NoError(tc.t.t, err, "unable to find invoice") + // Prepare the test cases. + req := &lnrpc.Invoice{ValueMsat: 1000} + addResponse := carol.RPC.AddInvoice(req) + invoice := carol.RPC.LookupInvoice(addResponse.RHash) + tc := &interceptorTestCase{ + amountMsat: 1000, + invoice: invoice, + payAddr: invoice.PaymentAddr, + } + + // We initiate a payment from Alice. + done := make(chan struct{}) + go func() { + // Signal that all the payments have been sent. + defer close(done) + + ts.sendPaymentAndAssertAction(tc) + }() // We start the htlc interceptor with a simple implementation that // saves all intercepted packets. These packets are held to simulate a // pending payment. - interceptedPacketstMap := &sync.Map{} - var wg sync.WaitGroup - wg.Add(1) - go func() { - defer wg.Done() - for { - packet, err := interceptor.Recv() - if err != nil { - // If it is just the error result of the - // context cancellation the we exit silently. - status, ok := status.FromError(err) - if ok && status.Code() == codes.Canceled { - return - } - - // Otherwise it an unexpected error, we fail - // the test. - require.NoError( - tc.t.t, err, - "unexpected error in interceptor.Recv()", - ) - return - } - interceptedPacketstMap.Store( - packet.IncomingCircuitKey.HtlcId, packet, - ) - } - }() - - // We initiate a payment from Alice. - wg.Add(1) - go func() { - defer wg.Done() - _, _ = tc.sendAliceToCarolPayment( - ctxb, 1000, - invoice.RHash, invoice.PaymentAddr, - ) - }() + packet := ht.ReceiveHtlcInterceptor(interceptor) // Here we should wait for the channel to contain a pending htlc, and // also be shown as being active. - err = wait.Predicate(func() bool { - channels, err := tc.bob.ListChannels(ctxt, &lnrpc.ListChannelsRequest{ - ActiveOnly: true, - Peer: tc.alice.PubKey[:], - }) - if err != nil { - return false + err := wait.NoError(func() error { + channel := ht.QueryChannelByChanPoint(bob, cpAB) + + if len(channel.PendingHtlcs) == 0 { + return fmt.Errorf("expect alice <> bob channel to " + + "have pending htlcs") } - if len(channels.Channels) == 0 { - return false + if channel.Active { + return nil } - aliceChan := channels.Channels[0] - if len(aliceChan.PendingHtlcs) == 0 { - return false - } - return aliceChan.Active + return fmt.Errorf("channel not active") }, defaultTimeout) require.NoError( - tc.t.t, err, "alice <> bob channel pending htlc never arrived", + ht, err, "alice <> bob channel pending htlc never arrived", ) // At this point we want to make bob's link send all pending htlcs to // the switch again. We force this behavior by disconnecting and // connecting to the peer. - if err := tc.net.DisconnectNodes(tc.bob, tc.alice); err != nil { - tc.t.Fatalf("failed to disconnect alice and bob") - } - tc.net.EnsureConnected(tc.t.t, tc.bob, tc.alice) + ht.DisconnectNodes(bob, alice) + ht.EnsureConnected(bob, alice) // Here we wait for the channel to be active again. - err = wait.Predicate(func() bool { - req := &lnrpc.ListChannelsRequest{ - ActiveOnly: true, - Peer: tc.alice.PubKey[:], - } - - channels, err := tc.bob.ListChannels(ctxt, req) - return err == nil && len(channels.Channels) > 0 - }, defaultTimeout) - require.NoError( - tc.t.t, err, "alice <> bob channel didn't re-activate", - ) + ht.AssertChannelExists(bob, cpAB) // Now that the channel is active we make sure the test passes as // expected. - payments, err := tc.alice.ListPayments(ctxb, &lnrpc.ListPaymentsRequest{ - IncludeIncomplete: true, - }) - require.NoError(tc.t.t, err, "failed to fetch payment") // We expect one in flight payment since we held the htlcs. - require.Equal(tc.t.t, len(payments.Payments), 1) - require.Equal(tc.t.t, payments.Payments[0].Status, lnrpc.Payment_IN_FLIGHT) - - // We now fail all htlcs to cancel the payment. - packetsCount := 0 - interceptedPacketstMap.Range(func(_, packet interface{}) bool { - p := packet.(*routerrpc.ForwardHtlcInterceptRequest) - _ = interceptor.Send(&routerrpc.ForwardHtlcInterceptResponse{ - IncomingCircuitKey: p.IncomingCircuitKey, - Action: routerrpc.ResolveHoldForwardAction_FAIL, - }) - packetsCount++ - return true - }) + var preimage lntypes.Preimage + copy(preimage[:], invoice.RPreimage) + ht.AssertPaymentStatus(alice, preimage, lnrpc.Payment_IN_FLIGHT) // At this point if we have more than one held htlcs then we should - // fail. This means we hold the same htlc twice which is a risk we - // want to eliminate. If we don't have the same htlc twice in theory we - // can cancel one and settle the other by mistake. - require.Equal(tc.t.t, packetsCount, 1) + // fail. This means we hold the same htlc twice which is a risk we want + // to eliminate. If we don't have the same htlc twice in theory we can + // cancel one and settle the other by mistake. + errDone := make(chan struct{}) + go func() { + defer close(errDone) + _, err := interceptor.Recv() + require.Error(ht, err, "expected an error from interceptor") + + status, ok := status.FromError(err) + switch { + // If it is just the error result of the context cancellation + // the we exit silently. + case ok && status.Code() == codes.Canceled: + fallthrough + + // When the test ends, during the node's shutdown it will close + // the connection. + case strings.Contains(err.Error(), "closed network connection"): + fallthrough + + case strings.Contains(err.Error(), "EOF"): + return + } + + // Otherwise we receive an unexpected error. + require.Failf(ht, "interceptor", "unexpected err: %v", err) + }() + + // We now fail all htlcs to cancel the payment. + err = interceptor.Send(&routerrpc.ForwardHtlcInterceptResponse{ + IncomingCircuitKey: packet.IncomingCircuitKey, + Action: routerrpc.ResolveHoldForwardAction_FAIL, + }) + require.NoError(ht, err, "failed to send request") + + // Cancel the context, which will disconnect the above interceptor. cancelInterceptor() - wg.Wait() + + // Make sure all goroutines are finished. + select { + case <-done: + case <-time.After(defaultTimeout): + require.Fail(ht, "timeout waiting for sending payment") + } + + select { + case <-errDone: + case <-time.After(defaultTimeout): + require.Fail(ht, "timeout waiting for interceptor error") + } + + // Finally, close channels. + ht.CloseChannel(alice, cpAB) + ht.CloseChannel(bob, cpBC) } // testForwardInterceptorBasic tests the forward interceptor RPC layer. @@ -204,265 +185,191 @@ func testForwardInterceptorDedupHtlc(net *lntest.NetworkHarness, t *harnessTest) // 3. Intercepted held htlcs result in no payment (invoice is not settled). // 4. When Interceptor disconnects it resumes all held htlcs, which result in // valid payment (invoice is settled). -func testForwardInterceptorBasic(net *lntest.NetworkHarness, t *harnessTest) { - // Initialize the test context with 3 connected nodes. - alice := net.NewNode(t.t, "alice", nil) - defer shutdownAndAssert(net, t, alice) +func testForwardInterceptorBasic(ht *lntemp.HarnessTest) { + ts := newInterceptorTestScenario(ht) - bob := net.NewNode(t.t, "bob", nil) - defer shutdownAndAssert(net, t, bob) - - carol := net.NewNode(t.t, "carol", nil) - defer shutdownAndAssert(net, t, carol) - - testContext := newInterceptorTestContext(t, net, alice, bob, carol) - - const ( - chanAmt = btcutil.Amount(300000) - ) + alice, bob, carol := ts.alice, ts.bob, ts.carol // Open and wait for channels. - testContext.openChannel(testContext.alice, testContext.bob, chanAmt) - testContext.openChannel(testContext.bob, testContext.carol, chanAmt) - defer testContext.closeChannels() - testContext.waitForChannels() + const chanAmt = btcutil.Amount(300000) + p := lntemp.OpenChannelParams{Amt: chanAmt} + reqs := []*lntemp.OpenChannelRequest{ + {Local: alice, Remote: bob, Param: p}, + {Local: bob, Remote: carol, Param: p}, + } + resp := ht.OpenMultiChannelsAsync(reqs) + cpAB, cpBC := resp[0], resp[1] + + // Make sure Alice is aware of channel Bob=>Carol. + ht.AssertTopologyChannelOpen(alice, cpBC) // Connect the interceptor. - ctxb := context.Background() - ctxt, cancelInterceptor := context.WithTimeout(ctxb, defaultTimeout) - interceptor, err := testContext.bob.RouterClient.HtlcInterceptor(ctxt) - require.NoError(t.t, err, "failed to create HtlcInterceptor") + interceptor, cancelInterceptor := bob.RPC.HtlcInterceptor() // Prepare the test cases. - testCases := testContext.prepareTestCases() + testCases := ts.prepareTestCases() - // A channel for the interceptor go routine to send the requested packets. - interceptedChan := make(chan *routerrpc.ForwardHtlcInterceptRequest, - len(testCases)) - - // Run the interceptor loop in its own go routine. - var wg sync.WaitGroup - wg.Add(1) + // For each test case make sure we initiate a payment from Alice to + // Carol routed through Bob. For each payment we also test its final + // status according to the interceptorAction specified in the test + // case. + done := make(chan struct{}) go func() { - defer wg.Done() - for { - request, err := interceptor.Recv() - if err != nil { - // If it is just the error result of the context cancellation - // the we exit silently. - status, ok := status.FromError(err) - if ok && status.Code() == codes.Canceled { - return - } - // Otherwise it an unexpected error, we fail the test. - require.NoError(t.t, err, "unexpected error in interceptor.Recv()") - return - } - interceptedChan <- request - } - }() + // Signal that all the payments have been sent. + defer close(done) - // For each test case make sure we initiate a payment from Alice to Carol - // routed through Bob. For each payment we also test its final status - // according to the interceptorAction specified in the test case. - wg.Add(1) - go func() { - defer wg.Done() for _, tc := range testCases { - attempt, err := testContext.sendAliceToCarolPayment( - context.Background(), tc.invoice.ValueMsat, - tc.invoice.RHash, tc.payAddr, - ) - - if t.t.Failed() { - return - } - if err != nil { - require.NoError(t.t, err, "failed to send payment") - } - - switch tc.interceptorAction { - // For 'fail' interceptor action we make sure the payment failed. - case routerrpc.ResolveHoldForwardAction_FAIL: - require.Equal(t.t, lnrpc.HTLCAttempt_FAILED, - attempt.Status, "expected payment to fail") - - // Assert that we get a temporary channel - // failure which has a channel update. - require.NotNil(t.t, attempt.Failure) - require.NotNil(t.t, attempt.Failure.ChannelUpdate) - - require.Equal(t.t, - lnrpc.Failure_TEMPORARY_CHANNEL_FAILURE, - attempt.Failure.Code) - - // For settle and resume we make sure the payment is successful. - case routerrpc.ResolveHoldForwardAction_SETTLE: - fallthrough - - case routerrpc.ResolveHoldForwardAction_RESUME: - require.Equal(t.t, lnrpc.HTLCAttempt_SUCCEEDED, - attempt.Status, "expected payment to succeed") - } + attempt := ts.sendPaymentAndAssertAction(tc) + ts.assertAction(tc, attempt) } }() - // We make sure here the interceptor has processed all packets before we - // check the payment statuses. - for i := 0; i < len(testCases); i++ { - select { - case request := <-interceptedChan: - // Assert sanity of informational packet data. - require.NotZero(t.t, request.OutgoingRequestedChanId) - require.NotZero(t.t, request.IncomingExpiry) - require.NotZero(t.t, request.IncomingAmountMsat) + // We make sure here the interceptor has processed all packets before + // we check the payment statuses. + for _, tc := range testCases { + request := ht.ReceiveHtlcInterceptor(interceptor) - require.Less( - t.t, - request.OutgoingExpiry, request.IncomingExpiry, - ) - require.Less( - t.t, - request.OutgoingAmountMsat, - request.IncomingAmountMsat, - ) + // Assert sanity of informational packet data. + require.NotZero(ht, request.OutgoingRequestedChanId) + require.NotZero(ht, request.IncomingExpiry) + require.NotZero(ht, request.IncomingAmountMsat) - value, ok := request.CustomRecords[customTestKey] - require.True(t.t, ok, "expected custom record") - require.Equal(t.t, customTestValue, value) + require.Less(ht, request.OutgoingExpiry, + request.IncomingExpiry) + require.Less(ht, request.OutgoingAmountMsat, + request.IncomingAmountMsat) - testCase := testCases[i] + value, ok := request.CustomRecords[customTestKey] + require.True(ht, ok, "expected custom record") + require.Equal(ht, customTestValue, value) - // For held packets we ignore, keeping them in hold status. - if testCase.shouldHold { - continue - } - - // For all other packets we resolve according to the test case. - _ = interceptor.Send(&routerrpc.ForwardHtlcInterceptResponse{ - IncomingCircuitKey: request.IncomingCircuitKey, - Action: testCase.interceptorAction, - Preimage: testCase.invoice.RPreimage, - }) - case <-time.After(defaultTimeout): - t.Fatalf("response from interceptor was not received %v", i) + // For held packets we ignore, keeping them in hold status. + if tc.shouldHold { + continue } + + // For all other packets we resolve according to the test case. + err := interceptor.Send(&routerrpc.ForwardHtlcInterceptResponse{ + IncomingCircuitKey: request.IncomingCircuitKey, + Action: tc.interceptorAction, + Preimage: tc.invoice.RPreimage, + }) + require.NoError(ht, err, "failed to send request") } - // At this point we are left with the held packets, we want to make sure - // each one of them has a corresponding 'in-flight' payment at + // At this point we are left with the held packets, we want to make + // sure each one of them has a corresponding 'in-flight' payment at // Alice's node. - payments, err := testContext.alice.ListPayments(context.Background(), - &lnrpc.ListPaymentsRequest{IncludeIncomplete: true}) - require.NoError(t.t, err, "failed to fetch payment") - for _, testCase := range testCases { - if testCase.shouldHold { - hashStr := hex.EncodeToString(testCase.invoice.RHash) - var foundPayment *lnrpc.Payment - expectedAmt := testCase.invoice.ValueMsat - for _, p := range payments.Payments { - if p.PaymentHash == hashStr { - foundPayment = p - break - } - } - require.NotNil(t.t, foundPayment, fmt.Sprintf("expected "+ - "to find pending payment for held htlc %v", - hashStr)) - require.Equal(t.t, lnrpc.Payment_IN_FLIGHT, - foundPayment.Status, "expected payment to be "+ - "in flight") - require.Equal(t.t, expectedAmt, foundPayment.ValueMsat, - "incorrect in flight amount") + if !testCase.shouldHold { + continue } + + var preimage lntypes.Preimage + copy(preimage[:], testCase.invoice.RPreimage) + + payment := ht.AssertPaymentStatus( + alice, preimage, lnrpc.Payment_IN_FLIGHT, + ) + expectedAmt := testCase.invoice.ValueMsat + require.Equal(ht, expectedAmt, payment.ValueMsat, + "incorrect in flight amount") } - // Disconnect interceptor should cause resume held packets. - // After that we wait for all go routines to finish, including the one - // that tests the payment final status for the held payment. + // Cancel the context, which will disconnect the above interceptor. cancelInterceptor() - wg.Wait() + + // Disconnect interceptor should cause resume held packets. After that + // we wait for all go routines to finish, including the one that tests + // the payment final status for the held payment. + select { + case <-done: + case <-time.After(defaultTimeout): + require.Fail(ht, "timeout waiting for sending payment") + } // Verify that we don't get notified about already completed HTLCs // We do that by restarting alice, the sender the HTLCs. Under // https://github.com/lightningnetwork/lnd/issues/5115 - // this should cause all HTLCs settled or failed by the interceptor to renotify. - restartAlice, err := net.SuspendNode(alice) - require.NoError(t.t, err, "failed to suspend alice") + // this should cause all HTLCs settled or failed by the interceptor to + // renotify. + restartAlice := ht.SuspendNode(alice) + require.NoError(ht, restartAlice(), "failed to restart alice") - ctxt, cancelInterceptor = context.WithTimeout(ctxb, defaultTimeout) - defer cancelInterceptor() - interceptor, err = testContext.bob.RouterClient.HtlcInterceptor(ctxt) - require.NoError(t.t, err, "failed to create HtlcInterceptor") + // Make sure the channel is active from Bob's PoV. + ht.AssertChannelExists(bob, cpAB) - err = restartAlice() - require.NoError(t.t, err, "failed to restart alice") + // Create a new interceptor as the old one has quit. + interceptor, cancelInterceptor = bob.RPC.HtlcInterceptor() + done = make(chan struct{}) go func() { - request, err := interceptor.Recv() - if err != nil { - // If it is just the error result of the context cancellation - // the we exit silently. - status, ok := status.FromError(err) - if ok && status.Code() == codes.Canceled { - return - } - // Otherwise it an unexpected error, we fail the test. - require.NoError( - t.t, err, "unexpected error in interceptor.Recv()", - ) + defer close(done) + + _, err := interceptor.Recv() + require.Error(ht, err, "expected an error from interceptor") + + status, ok := status.FromError(err) + switch { + // If it is just the error result of the context cancellation + // the we exit silently. + case ok && status.Code() == codes.Canceled: + fallthrough + + // When the test ends, during the node's shutdown it will close + // the connection. + case strings.Contains(err.Error(), "closed network connection"): + fallthrough + + case strings.Contains(err.Error(), "EOF"): return } - require.Nil(t.t, request, "no more intercepts should arrive") + // Otherwise we receive an unexpected error. + require.Failf(ht, "iinterceptor", "unexpected err: %v", err) }() - err = wait.Predicate(func() bool { - channels, err := bob.ListChannels(ctxt, &lnrpc.ListChannelsRequest{ - ActiveOnly: true, Peer: alice.PubKey[:], - }) - return err == nil && len(channels.Channels) > 0 - }, defaultTimeout) - require.NoError(t.t, err, "alice <> bob channel didn't re-activate") -} - -// interceptorTestContext is a helper struct to hold the test context and -// provide the needed functionality. -type interceptorTestContext struct { - t *harnessTest - net *lntest.NetworkHarness - - // Keep a list of all our active channels. - networkChans []*lnrpc.ChannelPoint - closeChannelFuncs []func() - - alice, bob, carol *lntest.HarnessNode - nodes []*lntest.HarnessNode -} - -func newInterceptorTestContext(t *harnessTest, - net *lntest.NetworkHarness, - alice, bob, carol *lntest.HarnessNode) *interceptorTestContext { - - // Connect nodes - nodes := []*lntest.HarnessNode{alice, bob, carol} - for i := 0; i < len(nodes); i++ { - for j := i + 1; j < len(nodes); j++ { - net.EnsureConnected(t.t, nodes[i], nodes[j]) - } + // Cancel the context, which will disconnect the above interceptor. + cancelInterceptor() + select { + case <-done: + case <-time.After(defaultTimeout): + require.Fail(ht, "timeout waiting for interceptor error") } - ctx := interceptorTestContext{ - t: t, - net: net, + // Finally, close channels. + ht.CloseChannel(alice, cpAB) + ht.CloseChannel(bob, cpBC) +} + +// interceptorTestScenario is a helper struct to hold the test context and +// provide the needed functionality. +type interceptorTestScenario struct { + ht *lntemp.HarnessTest + alice, bob, carol *node.HarnessNode +} + +// newInterceptorTestScenario initializes a new test scenario with three nodes +// and connects them to have the following topology, +// +// Alice --> Bob --> Carol +// +// Among them, Alice and Bob are standby nodes and Carol is a new node. +func newInterceptorTestScenario( + ht *lntemp.HarnessTest) *interceptorTestScenario { + + alice, bob := ht.Alice, ht.Bob + carol := ht.NewNode("carol", nil) + + ht.EnsureConnected(alice, bob) + ht.EnsureConnected(bob, carol) + + return &interceptorTestScenario{ + ht: ht, alice: alice, bob: bob, carol: carol, - nodes: nodes, } - - return &ctx } // prepareTestCases prepares 4 tests: @@ -470,36 +377,41 @@ func newInterceptorTestContext(t *harnessTest, // 2. resumed htlc. // 3. settling htlc externally. // 4. held htlc that is resumed later. -func (c *interceptorTestContext) prepareTestCases() []*interceptorTestCase { +func (c *interceptorTestScenario) prepareTestCases() []*interceptorTestCase { + var ( + actionFail = routerrpc.ResolveHoldForwardAction_FAIL + actionResume = routerrpc.ResolveHoldForwardAction_RESUME + actionSettle = routerrpc.ResolveHoldForwardAction_SETTLE + ) + cases := []*interceptorTestCase{ - {amountMsat: 1000, shouldHold: false, - interceptorAction: routerrpc.ResolveHoldForwardAction_FAIL}, - {amountMsat: 1000, shouldHold: false, - interceptorAction: routerrpc.ResolveHoldForwardAction_RESUME}, - {amountMsat: 1000, shouldHold: false, - interceptorAction: routerrpc.ResolveHoldForwardAction_SETTLE}, - {amountMsat: 1000, shouldHold: true, - interceptorAction: routerrpc.ResolveHoldForwardAction_RESUME}, + { + amountMsat: 1000, shouldHold: false, + interceptorAction: actionFail, + }, + { + amountMsat: 1000, shouldHold: false, + interceptorAction: actionResume, + }, + { + amountMsat: 1000, shouldHold: false, + interceptorAction: actionSettle, + }, + { + amountMsat: 1000, shouldHold: true, + interceptorAction: actionResume, + }, } for _, t := range cases { - addResponse, err := c.carol.AddInvoice(context.Background(), &lnrpc.Invoice{ - ValueMsat: t.amountMsat, - }) - require.NoError(c.t.t, err, "unable to add invoice") - - invoice, err := c.carol.LookupInvoice(context.Background(), &lnrpc.PaymentHash{ - RHashStr: hex.EncodeToString(addResponse.RHash), - }) - require.NoError(c.t.t, err, "unable to find invoice") + inv := &lnrpc.Invoice{ValueMsat: t.amountMsat} + addResponse := c.carol.RPC.AddInvoice(inv) + invoice := c.carol.RPC.LookupInvoice(addResponse.RHash) // We'll need to also decode the returned invoice so we can // grab the payment address which is now required for ALL // payments. - payReq, err := c.carol.DecodePayReq(context.Background(), &lnrpc.PayReqString{ - PayReq: invoice.PaymentRequest, - }) - require.NoError(c.t.t, err, "unable to decode invoice") + payReq := c.carol.RPC.DecodePayReq(invoice.PaymentRequest) t.invoice = invoice t.payAddr = payReq.PaymentAddr @@ -507,69 +419,15 @@ func (c *interceptorTestContext) prepareTestCases() []*interceptorTestCase { return cases } -func (c *interceptorTestContext) openChannel(from, to *lntest.HarnessNode, - chanSize btcutil.Amount) { - - c.net.SendCoins(c.t.t, btcutil.SatoshiPerBitcoin, from) - - chanPoint := openChannelAndAssert( - c.t, c.net, from, to, - lntest.OpenChannelParams{ - Amt: chanSize, - }, - ) - - c.closeChannelFuncs = append(c.closeChannelFuncs, func() { - closeChannelAndAssert(c.t, c.net, from, chanPoint, false) - }) - - c.networkChans = append(c.networkChans, chanPoint) -} - -func (c *interceptorTestContext) closeChannels() { - for _, f := range c.closeChannelFuncs { - f() - } -} - -func (c *interceptorTestContext) waitForChannels() { - // Wait for all nodes to have seen all channels. - for _, chanPoint := range c.networkChans { - for _, node := range c.nodes { - txid, err := lnrpc.GetChanPointFundingTxid(chanPoint) - require.NoError(c.t.t, err, "unable to get txid") - - point := wire.OutPoint{ - Hash: *txid, - Index: chanPoint.OutputIndex, - } - - err = node.WaitForNetworkChannelOpen(chanPoint) - require.NoError(c.t.t, err, fmt.Sprintf("(%d): timeout "+ - "waiting for channel(%s) open", node.NodeID, - point)) - } - } -} - -// sendAliceToCarolPayment sends a payment from alice to carol and make an -// attempt to pay. The lnrpc.HTLCAttempt is returned. -func (c *interceptorTestContext) sendAliceToCarolPayment(ctx context.Context, - amtMsat int64, - paymentHash, paymentAddr []byte) (*lnrpc.HTLCAttempt, error) { +// sendPaymentAndAssertAction sends a payment from alice to carol and asserts +// that the specified interceptor action is taken. +func (c *interceptorTestScenario) sendPaymentAndAssertAction( + tc *interceptorTestCase) *lnrpc.HTLCAttempt { // Build a route from alice to carol. - route, err := c.buildRoute( - ctx, amtMsat, []*lntest.HarnessNode{c.bob, c.carol}, - paymentAddr, + route := c.buildRoute( + tc.amountMsat, []*node.HarnessNode{c.bob, c.carol}, tc.payAddr, ) - if err != nil { - return nil, err - } - sendReq := &routerrpc.SendToRouteRequest{ - PaymentHash: paymentHash, - Route: route, - } // Send a custom record to the forwarding node. route.Hops[0].CustomRecords = map[uint64][]byte{ @@ -577,21 +435,51 @@ func (c *interceptorTestContext) sendAliceToCarolPayment(ctx context.Context, } // Send the payment. - return c.alice.RouterClient.SendToRouteV2(ctx, sendReq) + sendReq := &routerrpc.SendToRouteRequest{ + PaymentHash: tc.invoice.RHash, + Route: route, + } + + return c.alice.RPC.SendToRouteV2(sendReq) +} + +func (c *interceptorTestScenario) assertAction(tc *interceptorTestCase, + attempt *lnrpc.HTLCAttempt) { + + // Now check the expected action has been taken. + switch tc.interceptorAction { + // For 'fail' interceptor action we make sure the payment failed. + case routerrpc.ResolveHoldForwardAction_FAIL: + require.Equal(c.ht, lnrpc.HTLCAttempt_FAILED, attempt.Status, + "expected payment to fail") + + // Assert that we get a temporary channel failure which has a + // channel update. + require.NotNil(c.ht, attempt.Failure) + require.NotNil(c.ht, attempt.Failure.ChannelUpdate) + + require.Equal(c.ht, lnrpc.Failure_TEMPORARY_CHANNEL_FAILURE, + attempt.Failure.Code) + + // For settle and resume we make sure the payment is successful. + case routerrpc.ResolveHoldForwardAction_SETTLE: + fallthrough + + case routerrpc.ResolveHoldForwardAction_RESUME: + require.Equal(c.ht, lnrpc.HTLCAttempt_SUCCEEDED, + attempt.Status, "expected payment to succeed") + } } // buildRoute is a helper function to build a route with given hops. -func (c *interceptorTestContext) buildRoute(ctx context.Context, amtMsat int64, - hops []*lntest.HarnessNode, payAddr []byte) (*lnrpc.Route, error) { +func (c *interceptorTestScenario) buildRoute(amtMsat int64, + hops []*node.HarnessNode, payAddr []byte) *lnrpc.Route { rpcHops := make([][]byte, 0, len(hops)) for _, hop := range hops { k := hop.PubKeyStr pubkey, err := route.NewVertexFromStr(k) - if err != nil { - return nil, fmt.Errorf("error parsing %v: %v", - k, err) - } + require.NoErrorf(c.ht, err, "error parsing %v: %v", k, err) rpcHops = append(rpcHops, pubkey[:]) } @@ -602,10 +490,7 @@ func (c *interceptorTestContext) buildRoute(ctx context.Context, amtMsat int64, PaymentAddr: payAddr, } - routeResp, err := c.alice.RouterClient.BuildRoute(ctx, req) - if err != nil { - return nil, err - } + routeResp := c.alice.RPC.BuildRoute(req) - return routeResp.Route, nil + return routeResp.Route } diff --git a/lntest/itest/lnd_mpp_test.go b/lntest/itest/lnd_mpp_test.go index 7288f42a9..c9d8fd4c2 100644 --- a/lntest/itest/lnd_mpp_test.go +++ b/lntest/itest/lnd_mpp_test.go @@ -1,27 +1,23 @@ package itest import ( - "bytes" - "context" - "fmt" "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/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/routing/route" + "github.com/stretchr/testify/require" ) // testSendToRouteMultiPath tests that we are able to successfully route a // payment using multiple shards across different paths, by using SendToRoute. -func testSendToRouteMultiPath(net *lntest.NetworkHarness, t *harnessTest) { - ctxb := context.Background() - - ctx := newMppTestContext(t, net) - defer ctx.shutdownNodes() +func testSendToRouteMultiPath(ht *lntemp.HarnessTest) { + mts := newMppTestScenario(ht) // To ensure the payment goes through separate paths, we'll set a // channel size that can only carry one shard at a time. We'll divide @@ -39,55 +35,41 @@ func testSendToRouteMultiPath(net *lntest.NetworkHarness, t *harnessTest) { // \ / // \__ Dave ____/ // - ctx.openChannel(ctx.carol, ctx.bob, chanAmt) - ctx.openChannel(ctx.dave, ctx.bob, chanAmt) - ctx.openChannel(ctx.alice, ctx.dave, chanAmt) - ctx.openChannel(ctx.eve, ctx.bob, chanAmt) - ctx.openChannel(ctx.carol, ctx.eve, chanAmt) - - // Since the channel Alice-> Carol will have to carry two - // shards, we make it larger. - ctx.openChannel(ctx.alice, ctx.carol, chanAmt+shardAmt) - - defer ctx.closeChannels() - - ctx.waitForChannels() + req := &mppOpenChannelRequest{ + // Since the channel Alice-> Carol will have to carry two + // shards, we make it larger. + amtAliceCarol: chanAmt + shardAmt, + amtAliceDave: chanAmt, + amtCarolBob: chanAmt, + amtCarolEve: chanAmt, + amtDaveBob: chanAmt, + amtEveBob: chanAmt, + } + mts.openChannels(req) // Make Bob create an invoice for Alice to pay. - payReqs, rHashes, invoices, err := createPayReqs( - ctx.bob, paymentAmt, 1, - ) - if err != nil { - t.Fatalf("unable to create pay reqs: %v", err) - } + payReqs, rHashes, invoices := ht.CreatePayReqs(mts.bob, paymentAmt, 1) rHash := rHashes[0] payReq := payReqs[0] - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - decodeResp, err := ctx.bob.DecodePayReq( - ctxt, &lnrpc.PayReqString{PayReq: payReq}, - ) - if err != nil { - t.Fatalf("decode pay req: %v", err) - } - + decodeResp := mts.bob.RPC.DecodePayReq(payReq) payAddr := decodeResp.PaymentAddr + // Subscribe the invoice. + stream := mts.bob.RPC.SubscribeSingleInvoice(rHash) + // We'll send shards along three routes from Alice. - sendRoutes := [][]*lntest.HarnessNode{ - {ctx.carol, ctx.bob}, - {ctx.dave, ctx.bob}, - {ctx.carol, ctx.eve, ctx.bob}, + sendRoutes := [][]*node.HarnessNode{ + {mts.carol, mts.bob}, + {mts.dave, mts.bob}, + {mts.carol, mts.eve, mts.bob}, } responses := make(chan *lnrpc.HTLCAttempt, len(sendRoutes)) for _, hops := range sendRoutes { // Build a route for the specified hops. - r, err := ctx.buildRoute(ctxb, shardAmt, ctx.alice, hops) - if err != nil { - t.Fatalf("unable to build route: %v", err) - } + r := mts.buildRoute(shardAmt, mts.alice, hops) // Set the MPP records to indicate this is a payment shard. hop := r.Hops[len(r.Hops)-1] @@ -103,62 +85,44 @@ func testSendToRouteMultiPath(net *lntest.NetworkHarness, t *harnessTest) { Route: r, } - // We'll send all shards in their own goroutine, since SendToRoute will - // block as long as the payment is in flight. + // We'll send all shards in their own goroutine, since + // SendToRoute will block as long as the payment is in flight. go func() { - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - resp, err := ctx.alice.RouterClient.SendToRouteV2(ctxt, sendReq) - if err != nil { - t.Fatalf("unable to send payment: %v", err) - } - + resp := mts.alice.RPC.SendToRouteV2(sendReq) responses <- resp }() } // Wait for all responses to be back, and check that they all // succeeded. + timer := time.After(defaultTimeout) for range sendRoutes { var resp *lnrpc.HTLCAttempt select { case resp = <-responses: - case <-time.After(defaultTimeout): - t.Fatalf("response not received") + case <-timer: + require.Fail(ht, "response not received") } - if resp.Failure != nil { - t.Fatalf("received payment failure : %v", resp.Failure) - } + require.Nil(ht, resp.Failure, "received payment failure") // All shards should come back with the preimage. - if !bytes.Equal(resp.Preimage, invoices[0].RPreimage) { - t.Fatalf("preimage doesn't match") - } + require.Equal(ht, resp.Preimage, invoices[0].RPreimage, + "preimage doesn't match") } // assertNumHtlcs is a helper that checks the node's latest payment, // and asserts it was split into num shards. - assertNumHtlcs := func(node *lntest.HarnessNode, num int) { - req := &lnrpc.ListPaymentsRequest{ - IncludeIncomplete: true, - } - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - paymentsResp, err := node.ListPayments(ctxt, req) - if err != nil { - t.Fatalf("error when obtaining payments: %v", - err) - } + assertNumHtlcs := func(hn *node.HarnessNode, num int) { + var preimage lntypes.Preimage + copy(preimage[:], invoices[0].RPreimage) - payments := paymentsResp.Payments - if len(payments) == 0 { - t.Fatalf("no payments found") - } + payment := ht.AssertPaymentStatus( + hn, preimage, lnrpc.Payment_SUCCEEDED, + ) - payment := payments[len(payments)-1] htlcs := payment.Htlcs - if len(htlcs) == 0 { - t.Fatalf("no htlcs") - } + require.NotEmpty(ht, htlcs, "no htlcs") succeeded := 0 for _, htlc := range htlcs { @@ -166,194 +130,217 @@ func testSendToRouteMultiPath(net *lntest.NetworkHarness, t *harnessTest) { succeeded++ } } - - if succeeded != num { - t.Fatalf("expected %v succussful HTLCs, got %v", num, - succeeded) - } + require.Equal(ht, num, succeeded, "HTLCs not matched") } // assertSettledInvoice checks that the invoice for the given payment // hash is settled, and has been paid using num HTLCs. - assertSettledInvoice := func(node *lntest.HarnessNode, rhash []byte, - num int) { + assertSettledInvoice := func(rhash []byte, num int) { + var payHash lntypes.Hash + copy(payHash[:], rhash) + inv := ht.AssertInvoiceState(stream, lnrpc.Invoice_SETTLED) - found := false - offset := uint64(0) - for !found { - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - invoicesResp, err := node.ListInvoices( - ctxt, &lnrpc.ListInvoiceRequest{ - IndexOffset: offset, - }, - ) - if err != nil { - t.Fatalf("error when obtaining payments: %v", - err) - } + // Assert that the amount paid to the invoice is correct. + require.EqualValues(ht, paymentAmt, inv.AmtPaidSat, + "incorrect payment amt") - if len(invoicesResp.Invoices) == 0 { - break - } - - for _, inv := range invoicesResp.Invoices { - if !bytes.Equal(inv.RHash, rhash) { - continue - } - - // Assert that the amount paid to the invoice is - // correct. - if inv.AmtPaidSat != int64(paymentAmt) { - t.Fatalf("incorrect payment amt for "+ - "invoicewant: %d, got %d", - paymentAmt, inv.AmtPaidSat) - } - - if inv.State != lnrpc.Invoice_SETTLED { - t.Fatalf("Invoice not settled: %v", - inv.State) - } - - if len(inv.Htlcs) != num { - t.Fatalf("expected invoice to be "+ - "settled with %v HTLCs, had %v", - num, len(inv.Htlcs)) - } - - found = true - break - } - - offset = invoicesResp.LastIndexOffset - } - - if !found { - t.Fatalf("invoice not found") - } + require.Len(ht, inv.Htlcs, num, "wrong num of HTLCs") } // Finally check that the payment shows up with three settled HTLCs in // Alice's list of payments... - assertNumHtlcs(ctx.alice, 3) + assertNumHtlcs(mts.alice, 3) // ...and in Bob's list of paid invoices. - assertSettledInvoice(ctx.bob, rHash, 3) + assertSettledInvoice(rHash, 3) + + // Finally, close all channels. + mts.closeChannels() } -type mppTestContext struct { - t *harnessTest - net *lntest.NetworkHarness +// mppTestScenario defines a test scenario used for testing MPP-related tests. +// It has two standby nodes, alice and bob, and three new nodes, carol, dave, +// and eve. +type mppTestScenario struct { + ht *lntemp.HarnessTest + + alice, bob, carol, dave, eve *node.HarnessNode + nodes []*node.HarnessNode // Keep a list of all our active channels. - networkChans []*lnrpc.ChannelPoint - closeChannelFuncs []func() - - alice, bob, carol, dave, eve *lntest.HarnessNode - nodes []*lntest.HarnessNode + channelPoints []*lnrpc.ChannelPoint } -func newMppTestContext(t *harnessTest, - net *lntest.NetworkHarness) *mppTestContext { - - alice := net.NewNode(t.t, "alice", nil) - bob := net.NewNode(t.t, "bob", []string{"--accept-amp"}) +// newMppTestScenario initializes a new mpp test scenario with five funded +// nodes and connects them to have the following topology, +// +// _ Eve _ +// / \ +// Alice -- Carol ---- Bob +// \ / +// \__ Dave ____/ +func newMppTestScenario(ht *lntemp.HarnessTest) *mppTestScenario { + alice, bob := ht.Alice, ht.Bob + ht.RestartNodeWithExtraArgs(bob, []string{ + "--maxpendingchannels=2", + "--accept-amp", + }) // Create a five-node context consisting of Alice, Bob and three new // nodes. - carol := net.NewNode(t.t, "carol", nil) - dave := net.NewNode(t.t, "dave", nil) - eve := net.NewNode(t.t, "eve", nil) + carol := ht.NewNode("carol", []string{ + "--maxpendingchannels=2", + "--accept-amp", + }) + dave := ht.NewNode("dave", nil) + eve := ht.NewNode("eve", nil) // Connect nodes to ensure propagation of channels. - nodes := []*lntest.HarnessNode{alice, bob, carol, dave, eve} - for i := 0; i < len(nodes); i++ { - for j := i + 1; j < len(nodes); j++ { - net.EnsureConnected(t.t, nodes[i], nodes[j]) - } + ht.EnsureConnected(alice, carol) + ht.EnsureConnected(alice, dave) + ht.EnsureConnected(carol, bob) + ht.EnsureConnected(carol, eve) + ht.EnsureConnected(dave, bob) + ht.EnsureConnected(eve, bob) + + // Send coins to the nodes and mine 1 blocks to confirm them. + for i := 0; i < 2; i++ { + ht.FundCoinsUnconfirmed(btcutil.SatoshiPerBitcoin, carol) + ht.FundCoinsUnconfirmed(btcutil.SatoshiPerBitcoin, dave) + ht.FundCoinsUnconfirmed(btcutil.SatoshiPerBitcoin, eve) + ht.MineBlocks(1) } - ctx := mppTestContext{ - t: t, - net: net, + mts := &mppTestScenario{ + ht: ht, alice: alice, bob: bob, carol: carol, dave: dave, eve: eve, - nodes: nodes, + nodes: []*node.HarnessNode{alice, bob, carol, dave, eve}, } - return &ctx + return mts } -// openChannel is a helper to open a channel from->to. -func (c *mppTestContext) openChannel(from, to *lntest.HarnessNode, - chanSize btcutil.Amount) { +// mppOpenChannelRequest defines the amounts used for each channel opening. +type mppOpenChannelRequest struct { + // Channel Alice=>Carol. + amtAliceCarol btcutil.Amount - c.net.SendCoins(c.t.t, btcutil.SatoshiPerBitcoin, from) + // Channel Alice=>Dave. + amtAliceDave btcutil.Amount - chanPoint := openChannelAndAssert( - c.t, c.net, from, to, - lntest.OpenChannelParams{Amt: chanSize}, - ) + // Channel Carol=>Bob. + amtCarolBob btcutil.Amount - c.closeChannelFuncs = append(c.closeChannelFuncs, func() { - closeChannelAndAssert(c.t, c.net, from, chanPoint, false) - }) + // Channel Carol=>Eve. + amtCarolEve btcutil.Amount - c.networkChans = append(c.networkChans, chanPoint) + // Channel Dave=>Bob. + amtDaveBob btcutil.Amount + + // Channel Eve=>Bob. + amtEveBob btcutil.Amount } -func (c *mppTestContext) closeChannels() { - for _, f := range c.closeChannelFuncs { - f() +// openChannels is a helper to open channels that sets up a network topology +// with three different paths Alice <-> Bob as following, +// +// _ Eve _ +// / \ +// Alice -- Carol ---- Bob +// \ / +// \__ Dave ____/ +// +// NOTE: all the channels are open together to save blocks mined. +func (m *mppTestScenario) openChannels(r *mppOpenChannelRequest) { + reqs := []*lntemp.OpenChannelRequest{ + { + Local: m.alice, + Remote: m.carol, + Param: lntemp.OpenChannelParams{Amt: r.amtAliceCarol}, + }, + { + Local: m.alice, + Remote: m.dave, + Param: lntemp.OpenChannelParams{Amt: r.amtAliceDave}, + }, + { + Local: m.carol, + Remote: m.bob, + Param: lntemp.OpenChannelParams{Amt: r.amtCarolBob}, + }, + { + Local: m.carol, + Remote: m.eve, + Param: lntemp.OpenChannelParams{Amt: r.amtCarolEve}, + }, + { + Local: m.dave, + Remote: m.bob, + Param: lntemp.OpenChannelParams{Amt: r.amtDaveBob}, + }, + { + Local: m.eve, + Remote: m.bob, + Param: lntemp.OpenChannelParams{Amt: r.amtEveBob}, + }, } -} -func (c *mppTestContext) shutdownNodes() { - shutdownAndAssert(c.net, c.t, c.alice) - shutdownAndAssert(c.net, c.t, c.bob) - shutdownAndAssert(c.net, c.t, c.carol) - shutdownAndAssert(c.net, c.t, c.dave) - shutdownAndAssert(c.net, c.t, c.eve) -} + m.channelPoints = m.ht.OpenMultiChannelsAsync(reqs) -func (c *mppTestContext) waitForChannels() { - // Wait for all nodes to have seen all channels. - for _, chanPoint := range c.networkChans { - for _, node := range c.nodes { - txid, err := lnrpc.GetChanPointFundingTxid(chanPoint) - if err != nil { - c.t.Fatalf("unable to get txid: %v", err) - } - point := wire.OutPoint{ - Hash: *txid, - Index: chanPoint.OutputIndex, - } - - err = node.WaitForNetworkChannelOpen(chanPoint) - if err != nil { - c.t.Fatalf("(%v:%d): timeout waiting for "+ - "channel(%s) open: %v", - node.Cfg.Name, node.NodeID, point, err) - } + // Make sure every node has heard every channel. + for _, hn := range m.nodes { + for _, cp := range m.channelPoints { + m.ht.AssertTopologyChannelOpen(hn, cp) } } } +// closeChannels closes all the open channels from `openChannels`. +func (m *mppTestScenario) closeChannels() { + if m.ht.Failed() { + m.ht.Log("Skipped closing channels for failed test") + return + } + + // TODO(yy): remove the sleep once the following bug is fixed. When the + // payment is reported as settled by Alice, 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) + + // Close all channels without mining the closing transactions. + m.ht.CloseChannelAssertPending(m.alice, m.channelPoints[0], false) + m.ht.CloseChannelAssertPending(m.alice, m.channelPoints[1], false) + m.ht.CloseChannelAssertPending(m.carol, m.channelPoints[2], false) + m.ht.CloseChannelAssertPending(m.carol, m.channelPoints[3], false) + m.ht.CloseChannelAssertPending(m.dave, m.channelPoints[4], false) + m.ht.CloseChannelAssertPending(m.eve, m.channelPoints[5], false) + + // Now mine a block to include all the closing transactions. + m.ht.MineBlocks(1) + + // Assert that the channels are closed. + for _, hn := range m.nodes { + m.ht.AssertNumWaitingClose(hn, 0) + } +} + // Helper function for Alice to build a route from pubkeys. -func (c *mppTestContext) buildRoute(ctxb context.Context, amt btcutil.Amount, - sender *lntest.HarnessNode, hops []*lntest.HarnessNode) (*lnrpc.Route, - error) { +func (m *mppTestScenario) buildRoute(amt btcutil.Amount, + sender *node.HarnessNode, hops []*node.HarnessNode) *lnrpc.Route { rpcHops := make([][]byte, 0, len(hops)) for _, hop := range hops { k := hop.PubKeyStr pubkey, err := route.NewVertexFromStr(k) - if err != nil { - return nil, fmt.Errorf("error parsing %v: %v", - k, err) - } + require.NoErrorf(m.ht, err, "error parsing %v: %v", k, err) rpcHops = append(rpcHops, pubkey[:]) } @@ -362,12 +349,37 @@ func (c *mppTestContext) buildRoute(ctxb context.Context, amt btcutil.Amount, FinalCltvDelta: chainreg.DefaultBitcoinTimeLockDelta, HopPubkeys: rpcHops, } + routeResp := sender.RPC.BuildRoute(req) - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - routeResp, err := sender.RouterClient.BuildRoute(ctxt, req) - if err != nil { - return nil, err + return routeResp.Route +} + +// updatePolicy updates a Dave's global channel policy and returns the expected +// policy for further check. It changes Dave's `FeeBaseMsat` from 1000 msat to +// 500,000 msat, and `FeeProportionalMillonths` from 1 msat to 1000 msat. +func (m *mppTestScenario) updateDaveGlobalPolicy() *lnrpc.RoutingPolicy { + const ( + baseFeeMsat = 500_000 + feeRate = 0.001 + maxHtlcMsat = 133_650_000 + ) + + expectedPolicy := &lnrpc.RoutingPolicy{ + FeeBaseMsat: baseFeeMsat, + FeeRateMilliMsat: feeRate * testFeeBase, + TimeLockDelta: 40, + MinHtlc: 1000, // default value + MaxHtlcMsat: maxHtlcMsat, } - return routeResp.Route, nil + updateFeeReq := &lnrpc.PolicyUpdateRequest{ + BaseFeeMsat: baseFeeMsat, + FeeRate: feeRate, + TimeLockDelta: 40, + Scope: &lnrpc.PolicyUpdateRequest_Global{Global: true}, + MaxHtlcMsat: maxHtlcMsat, + } + m.dave.RPC.UpdateChannelPolicy(updateFeeReq) + + return expectedPolicy } diff --git a/lntest/itest/lnd_nonstd_sweep_test.go b/lntest/itest/lnd_nonstd_sweep_test.go index 36dc7be8c..cc2ac7027 100644 --- a/lntest/itest/lnd_nonstd_sweep_test.go +++ b/lntest/itest/lnd_nonstd_sweep_test.go @@ -1,40 +1,39 @@ package itest import ( - "context" "testing" "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/lnrpc" - "github.com/lightningnetwork/lnd/lntest" + "github.com/lightningnetwork/lnd/lntemp" "github.com/stretchr/testify/require" ) -func testNonstdSweep(net *lntest.NetworkHarness, t *harnessTest) { +func testNonstdSweep(ht *lntemp.HarnessTest) { p2shAddr, err := btcutil.NewAddressScriptHash( make([]byte, 1), harnessNetParams, ) - require.NoError(t.t, err) + require.NoError(ht, err) p2pkhAddr, err := btcutil.NewAddressPubKeyHash( make([]byte, 20), harnessNetParams, ) - require.NoError(t.t, err) + require.NoError(ht, err) p2wshAddr, err := btcutil.NewAddressWitnessScriptHash( make([]byte, 32), harnessNetParams, ) - require.NoError(t.t, err) + require.NoError(ht, err) p2wkhAddr, err := btcutil.NewAddressWitnessPubKeyHash( make([]byte, 20), harnessNetParams, ) - require.NoError(t.t, err) + require.NoError(ht, err) p2trAddr, err := btcutil.NewAddressTaproot( make([]byte, 32), harnessNetParams, ) - require.NoError(t.t, err) + require.NoError(ht, err) tests := []struct { name string @@ -64,10 +63,10 @@ func testNonstdSweep(net *lntest.NetworkHarness, t *harnessTest) { for _, test := range tests { test := test - success := t.t.Run(test.name, func(t *testing.T) { - h := newHarnessTest(t, net) + success := ht.Run(test.name, func(t *testing.T) { + st := ht.Subtest(t) - testNonStdSweepInner(net, h, test.address) + testNonStdSweepInner(st, test.address) }) if !success { break @@ -75,18 +74,14 @@ func testNonstdSweep(net *lntest.NetworkHarness, t *harnessTest) { } } -func testNonStdSweepInner(net *lntest.NetworkHarness, t *harnessTest, - address string) { - - ctxb := context.Background() - - carol := net.NewNode(t.t, "carol", nil) +func testNonStdSweepInner(ht *lntemp.HarnessTest, address string) { + carol := ht.NewNode("carol", nil) // Give Carol a UTXO so SendCoins will behave as expected. - net.SendCoins(t.t, btcutil.SatoshiPerBitcoin, carol) + ht.FundCoins(btcutil.SatoshiPerBitcoin, carol) // Set the fee estimate to 1sat/vbyte. - net.SetFeeEstimate(250) + ht.SetFeeEstimate(250) // Make Carol call SendCoins with the SendAll flag and the created // address. @@ -96,20 +91,13 @@ func testNonStdSweepInner(net *lntest.NetworkHarness, t *harnessTest, SendAll: true, } - ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) - defer cancel() - // If a non-standard transaction was created, then this SendCoins call // will fail. - _, err := carol.SendCoins(ctxt, sendReq) - require.NoError(t.t, err) + carol.RPC.SendCoins(sendReq) // Fetch the txid so we can grab the raw transaction. - txid, err := waitForTxInMempool(net.Miner.Client, minerMempoolTimeout) - require.NoError(t.t, err) - - tx, err := net.Miner.Client.GetRawTransaction(txid) - require.NoError(t.t, err) + txid := ht.Miner.AssertNumTxsInMempool(1)[0] + tx := ht.Miner.GetRawTransaction(txid) msgTx := tx.MsgTx() @@ -123,9 +111,7 @@ func testNonStdSweepInner(net *lntest.NetworkHarness, t *harnessTest, for _, inp := range msgTx.TxIn { // Fetch the previous outpoint's value. prevOut := inp.PreviousOutPoint - - ptx, err := net.Miner.Client.GetRawTransaction(&prevOut.Hash) - require.NoError(t.t, err) + ptx := ht.Miner.GetRawTransaction(&prevOut.Hash) pout := ptx.MsgTx().TxOut[prevOut.Index] inputVal += int(pout.Value) @@ -139,9 +125,11 @@ func testNonStdSweepInner(net *lntest.NetworkHarness, t *harnessTest, // Fetch the vsize of the transaction so we can determine if the // transaction pays >= 1 sat/vbyte. - rawTx, err := net.Miner.Client.GetRawTransactionVerbose(txid) - require.NoError(t.t, err) + rawTx := ht.Miner.GetRawTransactionVerbose(txid) // Require fee >= vbytes. - require.True(t.t, fee >= int(rawTx.Vsize)) + require.True(ht, fee >= int(rawTx.Vsize)) + + // Mine a block to keep the mempool clean. + ht.MineBlocksAndAssertNumTxes(1, 1) } diff --git a/lntest/itest/lnd_onchain_test.go b/lntest/itest/lnd_onchain_test.go index a2d024aa6..2b6b3200e 100644 --- a/lntest/itest/lnd_onchain_test.go +++ b/lntest/itest/lnd_onchain_test.go @@ -2,7 +2,6 @@ package itest import ( "bytes" - "context" "fmt" "github.com/btcsuite/btcd/btcutil" @@ -14,7 +13,6 @@ import ( "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" "github.com/lightningnetwork/lnd/sweep" @@ -80,35 +78,28 @@ func testChainKitGetBlockHash(ht *lntemp.HarnessTest) { // rate by broadcasting a Child-Pays-For-Parent (CPFP) transaction. // // TODO(wilmer): Add RBF case once btcd supports it. -func testCPFP(net *lntest.NetworkHarness, t *harnessTest) { - runCPFP(net, t, net.Alice, net.Bob) +func testCPFP(ht *lntemp.HarnessTest) { + runCPFP(ht, ht.Alice, ht.Bob) } // runCPFP ensures that the daemon can bump an unconfirmed transaction's fee // rate by broadcasting a Child-Pays-For-Parent (CPFP) transaction. -func runCPFP(net *lntest.NetworkHarness, t *harnessTest, - alice, bob *lntest.HarnessNode) { - +func runCPFP(ht *lntemp.HarnessTest, alice, bob *node.HarnessNode) { // Skip this test for neutrino, as it's not aware of mempool // transactions. - if net.BackendCfg.Name() == lntest.NeutrinoBackendName { - t.Skipf("skipping CPFP test for neutrino backend") + if ht.IsNeutrinoBackend() { + ht.Skipf("skipping CPFP test for neutrino backend") } - // We'll start the test by sending Alice some coins, which she'll use to - // send to Bob. - ctxb := context.Background() - net.SendCoins(t.t, btcutil.SatoshiPerBitcoin, alice) + // We'll start the test by sending Alice some coins, which she'll use + // to send to Bob. + ht.FundCoins(btcutil.SatoshiPerBitcoin, alice) // Create an address for Bob to send the coins to. - addrReq := &lnrpc.NewAddressRequest{ + req := &lnrpc.NewAddressRequest{ Type: lnrpc.AddressType_WITNESS_PUBKEY_HASH, } - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - resp, err := bob.NewAddress(ctxt, addrReq) - if err != nil { - t.Fatalf("unable to get new address for bob: %v", err) - } + resp := bob.RPC.NewAddress(req) // Send the coins from Alice to Bob. We should expect a transaction to // be broadcast and seen in the mempool. @@ -116,46 +107,33 @@ func runCPFP(net *lntest.NetworkHarness, t *harnessTest, Addr: resp.Address, Amount: btcutil.SatoshiPerBitcoin, } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - if _, err = alice.SendCoins(ctxt, sendReq); err != nil { - t.Fatalf("unable to send coins to bob: %v", err) - } - - txid, err := waitForTxInMempool(net.Miner.Client, minerMempoolTimeout) - if err != nil { - t.Fatalf("expected one mempool transaction: %v", err) - } + alice.RPC.SendCoins(sendReq) + txid := ht.Miner.AssertNumTxsInMempool(1)[0] // We'll then extract the raw transaction from the mempool in order to // determine the index of Bob's output. - tx, err := net.Miner.Client.GetRawTransaction(txid) - if err != nil { - t.Fatalf("unable to extract raw transaction from mempool: %v", - err) - } + tx := ht.Miner.GetRawTransaction(txid) bobOutputIdx := -1 for i, txOut := range tx.MsgTx().TxOut { _, addrs, _, err := txscript.ExtractPkScriptAddrs( - txOut.PkScript, net.Miner.ActiveNet, + txOut.PkScript, ht.Miner.ActiveNet, ) - if err != nil { - t.Fatalf("unable to extract address from pkScript=%x: "+ - "%v", txOut.PkScript, err) - } + require.NoErrorf(ht, err, "unable to extract address "+ + "from pkScript=%x: %v", txOut.PkScript, err) + if addrs[0].String() == resp.Address { bobOutputIdx = i } } - if bobOutputIdx == -1 { - t.Fatalf("bob's output was not found within the transaction") - } + require.NotEqual(ht, -1, bobOutputIdx, "bob's output was not found "+ + "within the transaction") // Wait until bob has seen the tx and considers it as owned. op := &lnrpc.OutPoint{ TxidBytes: txid[:], OutputIndex: uint32(bobOutputIdx), } - assertWalletUnspent(t, bob, op, "") + ht.AssertUTXOInWallet(bob, op, "") // We'll attempt to bump the fee of this transaction by performing a // CPFP from Alice's point of view. @@ -165,68 +143,38 @@ func runCPFP(net *lntest.NetworkHarness, t *harnessTest, sweep.DefaultMaxFeeRate.FeePerKVByte() / 2000, ), } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - _, err = bob.WalletKitClient.BumpFee(ctxt, bumpFeeReq) - if err != nil { - t.Fatalf("unable to bump fee: %v", err) - } + bob.RPC.BumpFee(bumpFeeReq) // We should now expect to see two transactions within the mempool, a // parent and its child. - _, err = waitForNTxsInMempool(net.Miner.Client, 2, minerMempoolTimeout) - if err != nil { - t.Fatalf("expected two mempool transactions: %v", err) - } + ht.Miner.AssertNumTxsInMempool(2) // We should also expect to see the output being swept by the // UtxoSweeper. We'll ensure it's using the fee rate specified. - pendingSweepsReq := &walletrpc.PendingSweepsRequest{} - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - pendingSweepsResp, err := bob.WalletKitClient.PendingSweeps( - ctxt, pendingSweepsReq, - ) - if err != nil { - t.Fatalf("unable to retrieve pending sweeps: %v", err) - } - if len(pendingSweepsResp.PendingSweeps) != 1 { - t.Fatalf("expected to find %v pending sweep(s), found %v", 1, - len(pendingSweepsResp.PendingSweeps)) - } + pendingSweepsResp := bob.RPC.PendingSweeps() + require.Len(ht, pendingSweepsResp.PendingSweeps, 1, + "expected to find 1 pending sweep") pendingSweep := pendingSweepsResp.PendingSweeps[0] - if !bytes.Equal(pendingSweep.Outpoint.TxidBytes, op.TxidBytes) { - t.Fatalf("expected output txid %x, got %x", op.TxidBytes, - pendingSweep.Outpoint.TxidBytes) - } - if pendingSweep.Outpoint.OutputIndex != op.OutputIndex { - t.Fatalf("expected output index %v, got %v", op.OutputIndex, - pendingSweep.Outpoint.OutputIndex) - } - if pendingSweep.SatPerVbyte != bumpFeeReq.SatPerVbyte { - t.Fatalf("expected sweep sat per vbyte %v, got %v", - bumpFeeReq.SatPerVbyte, pendingSweep.SatPerVbyte) - } + require.Equal(ht, pendingSweep.Outpoint.TxidBytes, op.TxidBytes, + "output txid not matched") + require.Equal(ht, pendingSweep.Outpoint.OutputIndex, op.OutputIndex, + "output index not matched") + require.Equal(ht, pendingSweep.SatPerVbyte, bumpFeeReq.SatPerVbyte, + "sweep sat per vbyte not matched") // Mine a block to clean up the unconfirmed transactions. - mineBlocks(t, net, 1, 2) + ht.MineBlocksAndAssertNumTxes(1, 2) // The input used to CPFP should no longer be pending. - err = wait.NoError(func() error { - req := &walletrpc.PendingSweepsRequest{} - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - resp, err := bob.WalletKitClient.PendingSweeps(ctxt, req) - if err != nil { - return fmt.Errorf("unable to retrieve bob's pending "+ - "sweeps: %v", err) - } + err := wait.NoError(func() error { + resp := bob.RPC.PendingSweeps() if len(resp.PendingSweeps) != 0 { return fmt.Errorf("expected 0 pending sweeps, found %d", len(resp.PendingSweeps)) } return nil }, defaultTimeout) - if err != nil { - t.Fatalf(err.Error()) - } + require.NoError(ht, err, "timeout checking bob's pending sweeps") } // testAnchorReservedValue tests that we won't allow sending transactions when diff --git a/lntest/itest/lnd_open_channel_test.go b/lntest/itest/lnd_open_channel_test.go index 51e9a02d3..b3c4a4aca 100644 --- a/lntest/itest/lnd_open_channel_test.go +++ b/lntest/itest/lnd_open_channel_test.go @@ -1,9 +1,8 @@ package itest import ( - "context" "fmt" - "time" + "testing" "github.com/btcsuite/btcd/btcjson" "github.com/btcsuite/btcd/chaincfg/chainhash" @@ -12,7 +11,8 @@ import ( "github.com/lightningnetwork/lnd/funding" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lntemp" - "github.com/lightningnetwork/lnd/lntest" + "github.com/lightningnetwork/lnd/lntemp/node" + "github.com/lightningnetwork/lnd/lntemp/rpc" "github.com/lightningnetwork/lnd/lntest/wait" "github.com/stretchr/testify/require" ) @@ -172,9 +172,7 @@ func testOpenChannelAfterReorg(ht *lntemp.HarnessTest) { // ChannelUpdate --> defaultBaseFee, provided FeeRate // 4.) baseFee and feeRate provided to OpenChannelRequest // ChannelUpdate --> provided baseFee, provided feeRate. -func testOpenChannelUpdateFeePolicy(net *lntest.NetworkHarness, - t *harnessTest) { - +func testOpenChannelUpdateFeePolicy(ht *lntemp.HarnessTest) { const ( defaultBaseFee = 1000 defaultFeeRate = 1 @@ -189,7 +187,7 @@ func testOpenChannelUpdateFeePolicy(net *lntest.NetworkHarness, chanAmt := funding.MaxBtcFundingAmount pushAmt := chanAmt / 2 - feeScenarios := []lntest.OpenChannelParams{ + feeScenarios := []lntemp.OpenChannelParams{ { Amt: chanAmt, PushAmt: pushAmt, @@ -259,89 +257,79 @@ func testOpenChannelUpdateFeePolicy(net *lntest.NetworkHarness, MaxHtlcMsat: defaultMaxHtlc, } - for i, feeScenario := range feeScenarios { + alice, bob := ht.Alice, ht.Bob + + runTestCase := func(ht *lntemp.HarnessTest, + fs lntemp.OpenChannelParams, + alicePolicy, bobPolicy *lnrpc.RoutingPolicy) { + // Create a channel Alice->Bob. - chanPoint := openChannelAndAssert( - t, net, net.Alice, net.Bob, - feeScenario, - ) + chanPoint := ht.OpenChannel(alice, bob, fs) + defer ht.CloseChannel(alice, chanPoint) - defer closeChannelAndAssert(t, net, net.Alice, chanPoint, false) + // We add all the nodes' update channels to a slice, such that + // we can make sure they all receive the expected updates. + nodes := []*node.HarnessNode{alice, bob} - // We add all the nodes' update channels to a slice, such that we can - // make sure they all receive the expected updates. - nodes := []*lntest.HarnessNode{net.Alice, net.Bob} - - // Alice and Bob should see each other's ChannelUpdates, advertising - // the preferred routing policies. - assertPolicyUpdate( - t, nodes, net.Alice.PubKeyStr, - &expectedPolicies[i], chanPoint, - ) - assertPolicyUpdate( - t, nodes, net.Bob.PubKeyStr, - &bobExpectedPolicy, chanPoint, + // Alice and Bob should see each other's ChannelUpdates, + // advertising the preferred routing policies. + assertNodesPolicyUpdate( + ht, nodes, alice, alicePolicy, chanPoint, ) + assertNodesPolicyUpdate(ht, nodes, bob, bobPolicy, chanPoint) // They should now know about the default policies. for _, node := range nodes { - assertChannelPolicy( - t, node, net.Alice.PubKeyStr, - &expectedPolicies[i], chanPoint, + ht.AssertChannelPolicy( + node, alice.PubKeyStr, alicePolicy, chanPoint, ) - assertChannelPolicy( - t, node, net.Bob.PubKeyStr, - &bobExpectedPolicy, chanPoint, + ht.AssertChannelPolicy( + node, bob.PubKeyStr, bobPolicy, chanPoint, ) } + } - require.NoError( - t.t, net.Alice.WaitForNetworkChannelOpen(chanPoint), - "alice reports channel opening", - ) + for i, feeScenario := range feeScenarios { + ht.Run(fmt.Sprintf("%d", i), func(t *testing.T) { + st := ht.Subtest(t) + ht.EnsureConnected(alice, bob) - require.NoError( - t.t, net.Bob.WaitForNetworkChannelOpen(chanPoint), - "bob reports channel opening", - ) + runTestCase( + st, feeScenario, + &expectedPolicies[i], &bobExpectedPolicy, + ) + }) } } // testBasicChannelCreationAndUpdates tests multiple channel opening and -// closing, and ensures that if a node is subscribed to channel updates -// they will be received correctly for both cooperative and force closed -// channels. -func testBasicChannelCreationAndUpdates(net *lntest.NetworkHarness, - t *harnessTest) { - - runBasicChannelCreationAndUpdates(net, t, net.Alice, net.Bob) +// closing, and ensures that if a node is subscribed to channel updates they +// will be received correctly for both cooperative and force closed channels. +func testBasicChannelCreationAndUpdates(ht *lntemp.HarnessTest) { + runBasicChannelCreationAndUpdates(ht, ht.Alice, ht.Bob) } // runBasicChannelCreationAndUpdates tests multiple channel opening and closing, // and ensures that if a node is subscribed to channel updates they will be // received correctly for both cooperative and force closed channels. -func runBasicChannelCreationAndUpdates(net *lntest.NetworkHarness, - t *harnessTest, alice, bob *lntest.HarnessNode) { +func runBasicChannelCreationAndUpdates(ht *lntemp.HarnessTest, + alice, bob *node.HarnessNode) { - ctxb := context.Background() const ( numChannels = 2 amount = funding.MaxBtcFundingAmount ) // Subscribe Bob and Alice to channel event notifications. - bobChanSub := subscribeChannelNotifications(ctxb, t, bob) - defer close(bobChanSub.quit) - - aliceChanSub := subscribeChannelNotifications(ctxb, t, alice) - defer close(aliceChanSub.quit) + bobChanSub := bob.RPC.SubscribeChannelEvents() + aliceChanSub := alice.RPC.SubscribeChannelEvents() // Open the channels between Alice and Bob, asserting that the channels // have been properly opened on-chain. chanPoints := make([]*lnrpc.ChannelPoint, numChannels) for i := 0; i < numChannels; i++ { - chanPoints[i] = openChannelAndAssert( - t, net, alice, bob, lntest.OpenChannelParams{ + chanPoints[i] = ht.OpenChannel( + alice, bob, lntemp.OpenChannelParams{ Amt: amount, }, ) @@ -350,112 +338,93 @@ func runBasicChannelCreationAndUpdates(net *lntest.NetworkHarness, // Since each of the channels just became open, Bob and Alice should // each receive an open and an active notification for each channel. const numExpectedOpenUpdates = 3 * numChannels - verifyOpenUpdatesReceived := func(sub channelSubscription) error { - numChannelUpds := 0 - for numChannelUpds < numExpectedOpenUpdates { - select { - case update := <-sub.updateChan: - switch update.Type { - case lnrpc.ChannelEventUpdate_PENDING_OPEN_CHANNEL: - if numChannelUpds%3 != 0 { - return fmt.Errorf("expected " + - "open or active" + - "channel ntfn, got pending open " + - "channel ntfn instead") - } - case lnrpc.ChannelEventUpdate_OPEN_CHANNEL: - if numChannelUpds%3 != 1 { - return fmt.Errorf("expected " + - "pending open or active" + - "channel ntfn, got open" + - "channel ntfn instead") - } - case lnrpc.ChannelEventUpdate_ACTIVE_CHANNEL: - if numChannelUpds%3 != 2 { - return fmt.Errorf("expected " + - "pending open or open" + - "channel ntfn, got active " + - "channel ntfn instead") - } - default: - return fmt.Errorf("update type mismatch: "+ - "expected open or active channel "+ - "notification, got: %v", - update.Type) - } - numChannelUpds++ + verifyOpenUpdatesReceived := func(sub rpc.ChannelEventsClient) error { + for i := 0; i < numExpectedOpenUpdates; i++ { + update := ht.ReceiveChannelEvent(sub) - case <-time.After(time.Second * 10): - return fmt.Errorf("timeout waiting for channel "+ - "notifications, only received %d/%d "+ - "chanupds", numChannelUpds, - numExpectedOpenUpdates) + switch update.Type { + case lnrpc.ChannelEventUpdate_PENDING_OPEN_CHANNEL: + if i%3 == 0 { + continue + } + + return fmt.Errorf("expected open or active" + + "channel ntfn, got pending open " + + "channel ntfn instead") + + case lnrpc.ChannelEventUpdate_OPEN_CHANNEL: + if i%3 == 1 { + continue + } + + return fmt.Errorf("expected pending open or " + + "active channel ntfn, got open" + + "channel ntfn instead") + + case lnrpc.ChannelEventUpdate_ACTIVE_CHANNEL: + if i%3 == 2 { + continue + } + + return fmt.Errorf("expected pending open or " + + "open channel ntfn, got active " + + "channel ntfn instead") + + default: + return fmt.Errorf("update type mismatch: "+ + "expected open or active channel "+ + "notification, got: %v", update.Type) } } return nil } - require.NoError( - t.t, verifyOpenUpdatesReceived(bobChanSub), "bob open channels", - ) - require.NoError( - t.t, verifyOpenUpdatesReceived(aliceChanSub), "alice open "+ - "channels", - ) + require.NoError(ht, verifyOpenUpdatesReceived(bobChanSub), + "bob open channels") + require.NoError(ht, verifyOpenUpdatesReceived(aliceChanSub), + "alice open channels") - // Close the channels between Alice and Bob, asserting that the channels - // have been properly closed on-chain. + // Close the channels between Alice and Bob, asserting that the + // channels have been properly closed on-chain. for i, chanPoint := range chanPoints { // Force close the first of the two channels. force := i%2 == 0 - closeChannelAndAssert(t, net, alice, chanPoint, force) if force { - cleanupForceClose(t, net, alice, chanPoint) + ht.ForceCloseChannel(alice, chanPoint) + } else { + ht.CloseChannel(alice, chanPoint) } } // verifyCloseUpdatesReceived is used to verify that Alice and Bob // receive the correct channel updates in order. const numExpectedCloseUpdates = 3 * numChannels - verifyCloseUpdatesReceived := func(sub channelSubscription, + verifyCloseUpdatesReceived := func(sub rpc.ChannelEventsClient, forceType lnrpc.ChannelCloseSummary_ClosureType, closeInitiator lnrpc.Initiator) error { // Ensure one inactive and one closed notification is received // for each closed channel. - numChannelUpds := 0 - for numChannelUpds < numExpectedCloseUpdates { - expectedCloseType := lnrpc.ChannelCloseSummary_COOPERATIVE_CLOSE + for i := 0; i < numExpectedCloseUpdates; i++ { + expectedCloseType := lnrpc. + ChannelCloseSummary_COOPERATIVE_CLOSE // Every other channel should be force closed. If this // channel was force closed, set the expected close type // to the type passed in. - force := (numChannelUpds/3)%2 == 0 + force := (i/3)%2 == 0 if force { expectedCloseType = forceType } - select { - case chanUpdate := <-sub.updateChan: - err := verifyCloseUpdate( - chanUpdate, expectedCloseType, - closeInitiator, - ) - if err != nil { - return err - } - - numChannelUpds++ - - case err := <-sub.errChan: + chanUpdate := ht.ReceiveChannelEvent(sub) + err := verifyCloseUpdate( + chanUpdate, expectedCloseType, + closeInitiator, + ) + if err != nil { return err - - case <-time.After(time.Second * 10): - return fmt.Errorf("timeout waiting "+ - "for channel notifications, only "+ - "received %d/%d chanupds", - numChannelUpds, numChannelUpds) } } @@ -467,7 +436,7 @@ func runBasicChannelCreationAndUpdates(net *lntest.NetworkHarness, // All channels (cooperatively and force closed) should have a remote // close initiator because Alice closed the channels. require.NoError( - t.t, verifyCloseUpdatesReceived( + ht, verifyCloseUpdatesReceived( bobChanSub, lnrpc.ChannelCloseSummary_REMOTE_FORCE_CLOSE, lnrpc.Initiator_INITIATOR_REMOTE, @@ -479,7 +448,7 @@ func runBasicChannelCreationAndUpdates(net *lntest.NetworkHarness, // All channels (cooperatively and force closed) should have a local // close initiator because Alice closed the channels. require.NoError( - t.t, verifyCloseUpdatesReceived( + ht, verifyCloseUpdatesReceived( aliceChanSub, lnrpc.ChannelCloseSummary_LOCAL_FORCE_CLOSE, lnrpc.Initiator_INITIATOR_LOCAL, @@ -516,3 +485,64 @@ func assertMinerBlockHeightDelta(ht *lntemp.HarnessTest, }, defaultTimeout) require.NoError(ht, err, "failed to assert block height delta") } + +// verifyCloseUpdate is used to verify that a closed channel update is of the +// expected type. +func verifyCloseUpdate(chanUpdate *lnrpc.ChannelEventUpdate, + closeType lnrpc.ChannelCloseSummary_ClosureType, + closeInitiator lnrpc.Initiator) error { + + // We should receive one inactive and one closed notification + // for each channel. + switch update := chanUpdate.Channel.(type) { + case *lnrpc.ChannelEventUpdate_InactiveChannel: + if chanUpdate.Type != + lnrpc.ChannelEventUpdate_INACTIVE_CHANNEL { + + return fmt.Errorf("update type mismatch: "+ + "expected %v, got %v", + lnrpc.ChannelEventUpdate_INACTIVE_CHANNEL, + chanUpdate.Type) + } + + case *lnrpc.ChannelEventUpdate_ClosedChannel: + if chanUpdate.Type != + lnrpc.ChannelEventUpdate_CLOSED_CHANNEL { + + return fmt.Errorf("update type mismatch: "+ + "expected %v, got %v", + lnrpc.ChannelEventUpdate_CLOSED_CHANNEL, + chanUpdate.Type) + } + + if update.ClosedChannel.CloseType != closeType { + return fmt.Errorf("channel closure type "+ + "mismatch: expected %v, got %v", + closeType, + update.ClosedChannel.CloseType) + } + + if update.ClosedChannel.CloseInitiator != closeInitiator { + return fmt.Errorf("expected close intiator: %v, "+ + "got: %v", closeInitiator, + update.ClosedChannel.CloseInitiator) + } + + case *lnrpc.ChannelEventUpdate_FullyResolvedChannel: + if chanUpdate.Type != + lnrpc.ChannelEventUpdate_FULLY_RESOLVED_CHANNEL { + + return fmt.Errorf("update type mismatch: "+ + "expected %v, got %v", + lnrpc.ChannelEventUpdate_FULLY_RESOLVED_CHANNEL, + chanUpdate.Type) + } + + default: + return fmt.Errorf("channel update channel of wrong type, "+ + "expected closed channel, got %T", + update) + } + + return nil +} diff --git a/lntest/itest/lnd_payment_test.go b/lntest/itest/lnd_payment_test.go index 65343825e..590870285 100644 --- a/lntest/itest/lnd_payment_test.go +++ b/lntest/itest/lnd_payment_test.go @@ -12,6 +12,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/stretchr/testify/require" @@ -225,35 +226,31 @@ func testPaymentFollowingChannelOpen(ht *lntemp.HarnessTest) { } // testAsyncPayments tests the performance of the async payments. -func testAsyncPayments(net *lntest.NetworkHarness, t *harnessTest) { - runAsyncPayments(net, t, net.Alice, net.Bob) +func testAsyncPayments(ht *lntemp.HarnessTest) { + // We use new nodes here as the benchmark test creates lots of data + // which can be costly to be carried on. + alice := ht.NewNode("Alice", []string{"--pending-commit-interval=3m"}) + bob := ht.NewNode("Bob", []string{"--pending-commit-interval=3m"}) + + ht.EnsureConnected(alice, bob) + ht.FundCoins(btcutil.SatoshiPerBitcoin, alice) + + runAsyncPayments(ht, alice, bob) } // runAsyncPayments tests the performance of the async payments. -func runAsyncPayments(net *lntest.NetworkHarness, t *harnessTest, alice, - bob *lntest.HarnessNode) { - - ctxb := context.Background() - - const ( - paymentAmt = 100 - ) +func runAsyncPayments(ht *lntemp.HarnessTest, alice, bob *node.HarnessNode) { + const paymentAmt = 100 // First establish a channel with a capacity equals to the overall // amount of payments, between Alice and Bob, at the end of the test // Alice should send all money from her side to Bob. channelCapacity := btcutil.Amount(paymentAmt * 2000) - chanPoint := openChannelAndAssert( - t, net, alice, bob, - lntest.OpenChannelParams{ - Amt: channelCapacity, - }, + chanPoint := ht.OpenChannel( + alice, bob, lntemp.OpenChannelParams{Amt: channelCapacity}, ) - info, err := getChanInfo(alice) - if err != nil { - t.Fatalf("unable to get alice channel info: %v", err) - } + info := ht.QueryChannelByChanPoint(alice, chanPoint) // We'll create a number of invoices equal the max number of HTLCs that // can be carried in one direction. The number on the commitment will @@ -267,128 +264,95 @@ func runAsyncPayments(net *lntest.NetworkHarness, t *harnessTest, alice, // With the channel open, we'll create invoices for Bob that Alice // will pay to in order to advance the state of the channel. - bobPayReqs, _, _, err := createPayReqs( - bob, paymentAmt, numInvoices, - ) - if err != nil { - t.Fatalf("unable to create pay reqs: %v", err) - } + bobPayReqs, _, _ := ht.CreatePayReqs(bob, paymentAmt, numInvoices) - // Wait for Alice to receive the channel edge from the funding manager. - err = alice.WaitForNetworkChannelOpen(chanPoint) - if err != nil { - t.Fatalf("alice didn't see the alice->bob channel before "+ - "timeout: %v", err) - } - - // Simultaneously send payments from Alice to Bob using of Bob's payment - // hashes generated above. + // Simultaneously send payments from Alice to Bob using of Bob's + // payment hashes generated above. now := time.Now() - errChan := make(chan error) - statusChan := make(chan *lnrpc.Payment) + + settled := make(chan struct{}) + defer close(settled) + + timeout := lntest.AsyncBenchmarkTimeout * 2 for i := 0; i < numInvoices; i++ { payReq := bobPayReqs[i] go func() { - ctxt, _ := context.WithTimeout(ctxb, lntest.AsyncBenchmarkTimeout) - stream, err := alice.RouterClient.SendPaymentV2( - ctxt, - &routerrpc.SendPaymentRequest{ - PaymentRequest: payReq, - TimeoutSeconds: 60, - FeeLimitMsat: noFeeLimitMsat, - }, - ) - if err != nil { - errChan <- err - } - result, err := getPaymentResult(stream) - if err != nil { - errChan <- err + req := &routerrpc.SendPaymentRequest{ + PaymentRequest: payReq, + TimeoutSeconds: int32(timeout.Seconds()), + FeeLimitMsat: noFeeLimitMsat, } + // AssertPaymentStatusWithTimeout will assert that the + // payment is settled. + stream := alice.RPC.SendPayment(req) + ht.AssertPaymentSucceedWithTimeout(stream, timeout) - statusChan <- result + settled <- struct{}{} }() } // Wait until all the payments have settled. + timer := time.After(timeout) for i := 0; i < numInvoices; i++ { select { - case result := <-statusChan: - if result.Status == lnrpc.Payment_SUCCEEDED { - continue - } - - case err := <-errChan: - t.Fatalf("payment error: %v", err) + case <-settled: + case <-timer: + require.Fail(ht, "timeout", "wait payment failed") } } // All payments have been sent, mark the finish time. timeTaken := time.Since(now) - // Next query for Bob's and Alice's channel states, in order to confirm - // that all payment have been successful transmitted. + // assertChannelState asserts the channel state by checking the values + // in fields, LocalBalance, RemoteBalance and num of PendingHtlcs. + assertChannelState := func(hn *node.HarnessNode, cp *lnrpc.ChannelPoint, + localBalance, remoteBalance int64, numPendingHtlcs int) { - // Wait for the revocation to be received so alice no longer has pending - // htlcs listed and has correct balances. This is needed due to the fact - // that we now pipeline the settles. - err = wait.Predicate(func() bool { - aliceChan, err := getChanInfo(alice) - if err != nil { - return false - } - if len(aliceChan.PendingHtlcs) != 0 { - return false - } - if aliceChan.RemoteBalance != bobAmt { - return false - } - if aliceChan.LocalBalance != aliceAmt { - return false - } + // Get the funding point. + err := wait.NoError(func() error { + // Find the target channel first. + target := ht.GetChannelByChanPoint(hn, cp) - return true - }, defaultTimeout) - if err != nil { - t.Fatalf("failed to assert alice's pending htlcs and/or remote/local balance") + if len(target.PendingHtlcs) != numPendingHtlcs { + return fmt.Errorf("pending htlcs is "+ + "incorrect, got %v, expected %v", + len(target.PendingHtlcs), 0) + } + + if target.LocalBalance != localBalance { + return fmt.Errorf("local balance is "+ + "incorrect, got %v, expected %v", + target.LocalBalance, localBalance) + } + + if target.RemoteBalance != remoteBalance { + return fmt.Errorf("remote balance is "+ + "incorrect, got %v, expected %v", + target.RemoteBalance, remoteBalance) + } + + return nil + }, lntemp.DefaultTimeout) + require.NoError(ht, err, "timeout while chekcing for balance") } + // Wait for the revocation to be received so alice no longer has + // pending htlcs listed and has correct balances. This is needed due to + // the fact that we now pipeline the settles. + assertChannelState(alice, chanPoint, aliceAmt, bobAmt, 0) + // Wait for Bob to receive revocation from Alice. - err = wait.NoError(func() error { - bobChan, err := getChanInfo(bob) - if err != nil { - t.Fatalf("unable to get bob's channel info: %v", err) - } + assertChannelState(bob, chanPoint, bobAmt, aliceAmt, 0) - if len(bobChan.PendingHtlcs) != 0 { - return fmt.Errorf("bob's pending htlcs is incorrect, "+ - "got %v, expected %v", - len(bobChan.PendingHtlcs), 0) - } - - if bobChan.LocalBalance != bobAmt { - return fmt.Errorf("bob's local balance is incorrect, "+ - "got %v, expected %v", bobChan.LocalBalance, - bobAmt) - } - - if bobChan.RemoteBalance != aliceAmt { - return fmt.Errorf("bob's remote balance is incorrect, "+ - "got %v, expected %v", bobChan.RemoteBalance, - aliceAmt) - } - - return nil - }, defaultTimeout) - require.NoError(t.t, err) - - t.Log("\tBenchmark info: Elapsed time: ", timeTaken) - t.Log("\tBenchmark info: TPS: ", float64(numInvoices)/timeTaken.Seconds()) + ht.Log("\tBenchmark info: Elapsed time: ", timeTaken) + ht.Log("\tBenchmark info: TPS: ", + float64(numInvoices)/timeTaken.Seconds()) // 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, alice, chanPoint, false) + ht.CloseChannel(alice, chanPoint) } // testBidirectionalAsyncPayments tests that nodes are able to send the diff --git a/lntest/itest/lnd_psbt_test.go b/lntest/itest/lnd_psbt_test.go index 656a7361c..febe9914d 100644 --- a/lntest/itest/lnd_psbt_test.go +++ b/lntest/itest/lnd_psbt_test.go @@ -3,7 +3,6 @@ package itest import ( "bytes" "context" - "crypto/rand" "fmt" "time" @@ -22,6 +21,7 @@ import ( "github.com/lightningnetwork/lnd/lnrpc/signrpc" "github.com/lightningnetwork/lnd/lnrpc/walletrpc" "github.com/lightningnetwork/lnd/lntemp" + "github.com/lightningnetwork/lnd/lntemp/node" "github.com/lightningnetwork/lnd/lntest" "github.com/stretchr/testify/require" ) @@ -29,80 +29,64 @@ import ( // testPsbtChanFunding makes sure a channel can be opened between carol and dave // by using a Partially Signed Bitcoin Transaction that funds the channel // multisig funding output. -func testPsbtChanFunding(net *lntest.NetworkHarness, t *harnessTest) { +func testPsbtChanFunding(ht *lntemp.HarnessTest) { // First, we'll create two new nodes that we'll use to open channels // between for this test. Dave gets some coins that will be used to // fund the PSBT, just to make sure that Carol has an empty wallet. - 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) - - runPsbtChanFunding(net, t, carol, dave) + runPsbtChanFunding(ht, carol, dave) } // runPsbtChanFunding makes sure a channel can be opened between carol and dave // by using a Partially Signed Bitcoin Transaction that funds the channel // multisig funding output. -func runPsbtChanFunding(net *lntest.NetworkHarness, t *harnessTest, carol, - dave *lntest.HarnessNode) { - - ctxb := context.Background() - - // 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() - +func runPsbtChanFunding(ht *lntemp.HarnessTest, carol, dave *node.HarnessNode) { const chanSize = funding.MaxBtcFundingAmount - net.SendCoins(t.t, btcutil.SatoshiPerBitcoin, dave) + ht.FundCoins(btcutil.SatoshiPerBitcoin, dave) // 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: false, BasePsbt: tempPsbt, }, @@ -110,7 +94,6 @@ func runPsbtChanFunding(net *lntest.NetworkHarness, t *harnessTest, carol, }, }, ) - require.NoError(t.t, err) // We'll now ask Dave's wallet to fund the PSBT for us. This will return // a packet with inputs and outputs set but without any witness data. @@ -123,55 +106,49 @@ func runPsbtChanFunding(net *lntest.NetworkHarness, t *harnessTest, carol, SatPerVbyte: 2, }, } - fundResp, err := dave.WalletKitClient.FundPsbt(ctxt, fundReq) - require.NoError(t.t, err) + fundResp := dave.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. - _, 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) - _, 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, }, }, }) - require.NoError(t.t, err) // Now we'll ask Dave's wallet to sign the PSBT so we can finish the // funding flow. finalizeReq := &walletrpc.FinalizePsbtRequest{ FundedPsbt: fundResp.FundedPsbt, } - finalizeRes, err := dave.WalletKitClient.FinalizePsbt(ctxt, finalizeReq) - require.NoError(t.t, err) + finalizeRes := dave.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, @@ -180,30 +157,25 @@ func runPsbtChanFunding(net *lntest.NetworkHarness, t *harnessTest, carol, } // 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) // Let's progress the second channel now. This time we'll use the raw // wire format transaction directly. - require.NoError(t.t, err) - _, err = carol.FundingStateStep(ctxb, &lnrpc.FundingTransitionMsg{ + carol.RPC.FundingStateStep(&lnrpc.FundingTransitionMsg{ Trigger: &lnrpc.FundingTransitionMsg_PsbtFinalize{ PsbtFinalize: &lnrpc.FundingPsbtFinalize{ - PendingChanId: pendingChanID2[:], + PendingChanId: pendingChanID2, FinalRawTx: finalizeRes.RawFinalTx, }, }, }) - require.NoError(t.t, err) // Consume the "channel pending" update for the second channel. This // waits until the funding transaction was fully compiled and in this // case published. - 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, @@ -214,23 +186,19 @@ func runPsbtChanFunding(net *lntest.NetworkHarness, t *harnessTest, carol, // Great, now we can mine a block to get the transaction confirmed, then // wait for the new channel to be propagated through the network. 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() - 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. @@ -239,18 +207,24 @@ func runPsbtChanFunding(net *lntest.NetworkHarness, t *harnessTest, carol, 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) + ht.CloseChannel(carol, chanPoint2) } // testPsbtChanFundingExternal makes sure a channel can be opened between carol @@ -602,49 +576,38 @@ func testPsbtChanFundingSingleStep(ht *lntemp.HarnessTest) { } // testSignPsbt tests that the SignPsbt RPC works correctly. -func testSignPsbt(net *lntest.NetworkHarness, t *harnessTest) { - runSignPsbtSegWitV0P2WKH(t, net, net.Alice) - runSignPsbtSegWitV0NP2WKH(t, net, net.Alice) - runSignPsbtSegWitV1KeySpendBip86(t, net, net.Alice) - runSignPsbtSegWitV1KeySpendRootHash(t, net, net.Alice) - runSignPsbtSegWitV1ScriptSpend(t, net, net.Alice) +func testSignPsbt(ht *lntemp.HarnessTest) { + runSignPsbtSegWitV0P2WKH(ht, ht.Alice) + runSignPsbtSegWitV0NP2WKH(ht, ht.Alice) + runSignPsbtSegWitV1KeySpendBip86(ht, ht.Alice) + runSignPsbtSegWitV1KeySpendRootHash(ht, ht.Alice) + runSignPsbtSegWitV1ScriptSpend(ht, ht.Alice) - // The above tests all make sure we can sign for keys that aren't in the - // wallet. But we also want to make sure we can fund and then sign PSBTs - // from our wallet. - runFundAndSignPsbt(t, net, net.Alice) + // The above tests all make sure we can sign for keys that aren't in + // the wallet. But we also want to make sure we can fund and then sign + // PSBTs from our wallet. + runFundAndSignPsbt(ht, ht.Alice) } // runSignPsbtSegWitV0P2WKH tests that the SignPsbt RPC works correctly for a // SegWit v0 p2wkh input. -func runSignPsbtSegWitV0P2WKH(t *harnessTest, net *lntest.NetworkHarness, - alice *lntest.HarnessNode) { - - // 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. - ctxb := context.Background() - ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) - defer cancel() - +func runSignPsbtSegWitV0P2WKH(ht *lntemp.HarnessTest, alice *node.HarnessNode) { // We test that we can sign a PSBT that spends funds from an input that // the wallet doesn't know about. To set up that test case, we first // derive an address manually that the wallet won't be watching on // chain. We can do that by exporting the account xpub of lnd's main // account. - accounts, err := alice.WalletKitClient.ListAccounts( - ctxt, &walletrpc.ListAccountsRequest{}, - ) - require.NoError(t.t, err) - require.NotEmpty(t.t, accounts.Accounts) + accounts := alice.RPC.ListAccounts(&walletrpc.ListAccountsRequest{}) + require.NotEmpty(ht, accounts.Accounts) // We also need to parse the accounts, so we have easy access to the // parsed derivation paths. parsedAccounts, err := walletrpc.AccountsToWatchOnly(accounts.Accounts) - require.NoError(t.t, err) + require.NoError(ht, err) account := parsedAccounts[0] xpub, err := hdkeychain.NewKeyFromString(account.Xpub) - require.NoError(t.t, err) + require.NoError(ht, err) const ( changeIndex = 1 @@ -660,27 +623,27 @@ func runSignPsbtSegWitV0P2WKH(t *harnessTest, net *lntest.NetworkHarness, // Let's simulate a change address. change, err := xpub.DeriveNonStandard(changeIndex) // nolint:staticcheck - require.NoError(t.t, err) + require.NoError(ht, err) // At an index that we are certainly not watching in the wallet. addrKey, err := change.DeriveNonStandard(addrIndex) // nolint:staticcheck - require.NoError(t.t, err) + require.NoError(ht, err) addrPubKey, err := addrKey.ECPubKey() - require.NoError(t.t, err) + require.NoError(ht, err) pubKeyHash := btcutil.Hash160(addrPubKey.SerializeCompressed()) witnessAddr, err := btcutil.NewAddressWitnessPubKeyHash( pubKeyHash, harnessNetParams, ) - require.NoError(t.t, err) + require.NoError(ht, err) pkScript, err := txscript.PayToAddrScript(witnessAddr) - require.NoError(t.t, err) + require.NoError(ht, err) // Send some funds to the output and then try to get a signature through // the SignPsbt RPC to spend that output again. assertPsbtSpend( - ctxt, t, net, alice, pkScript, + ht, alice, pkScript, func(packet *psbt.Packet) { in := &packet.Inputs[0] in.Bip32Derivation = []*psbt.Bip32Derivation{{ @@ -690,16 +653,16 @@ func runSignPsbtSegWitV0P2WKH(t *harnessTest, net *lntest.NetworkHarness, in.SighashType = txscript.SigHashAll }, func(packet *psbt.Packet) { - require.Len(t.t, packet.Inputs, 1) - require.Len(t.t, packet.Inputs[0].PartialSigs, 1) + require.Len(ht, packet.Inputs, 1) + require.Len(ht, packet.Inputs[0].PartialSigs, 1) partialSig := packet.Inputs[0].PartialSigs[0] require.Equal( - t.t, partialSig.PubKey, + ht, partialSig.PubKey, addrPubKey.SerializeCompressed(), ) require.Greater( - t.t, len(partialSig.Signature), ecdsa.MinSigLen, + ht, len(partialSig.Signature), ecdsa.MinSigLen, ) }, ) @@ -707,34 +670,25 @@ func runSignPsbtSegWitV0P2WKH(t *harnessTest, net *lntest.NetworkHarness, // runSignPsbtSegWitV0NP2WKH tests that the SignPsbt RPC works correctly for a // SegWit v0 np2wkh input. -func runSignPsbtSegWitV0NP2WKH(t *harnessTest, net *lntest.NetworkHarness, - alice *lntest.HarnessNode) { - - // 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. - ctxb := context.Background() - ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) - defer cancel() +func runSignPsbtSegWitV0NP2WKH(ht *lntemp.HarnessTest, + alice *node.HarnessNode) { // We test that we can sign a PSBT that spends funds from an input that // the wallet doesn't know about. To set up that test case, we first // derive an address manually that the wallet won't be watching on // chain. We can do that by exporting the account xpub of lnd's main // account. - accounts, err := alice.WalletKitClient.ListAccounts( - ctxt, &walletrpc.ListAccountsRequest{}, - ) - require.NoError(t.t, err) - require.NotEmpty(t.t, accounts.Accounts) + accounts := alice.RPC.ListAccounts(&walletrpc.ListAccountsRequest{}) + require.NotEmpty(ht, accounts.Accounts) // We also need to parse the accounts, so we have easy access to the // parsed derivation paths. parsedAccounts, err := walletrpc.AccountsToWatchOnly(accounts.Accounts) - require.NoError(t.t, err) + require.NoError(ht, err) account := parsedAccounts[0] xpub, err := hdkeychain.NewKeyFromString(account.Xpub) - require.NoError(t.t, err) + require.NoError(ht, err) const ( changeIndex = 1 @@ -750,34 +704,34 @@ func runSignPsbtSegWitV0NP2WKH(t *harnessTest, net *lntest.NetworkHarness, // Let's simulate a change address. change, err := xpub.DeriveNonStandard(changeIndex) // nolint:staticcheck - require.NoError(t.t, err) + require.NoError(ht, err) // At an index that we are certainly not watching in the wallet. addrKey, err := change.DeriveNonStandard(addrIndex) // nolint:staticcheck - require.NoError(t.t, err) + require.NoError(ht, err) addrPubKey, err := addrKey.ECPubKey() - require.NoError(t.t, err) + require.NoError(ht, err) pubKeyHash := btcutil.Hash160(addrPubKey.SerializeCompressed()) witnessAddr, err := btcutil.NewAddressWitnessPubKeyHash( pubKeyHash, harnessNetParams, ) - require.NoError(t.t, err) + require.NoError(ht, err) witnessProgram, err := txscript.PayToAddrScript(witnessAddr) - require.NoError(t.t, err) + require.NoError(ht, err) np2wkhAddr, err := btcutil.NewAddressScriptHash( witnessProgram, harnessNetParams, ) - require.NoError(t.t, err) + require.NoError(ht, err) pkScript, err := txscript.PayToAddrScript(np2wkhAddr) - require.NoError(t.t, err) + require.NoError(ht, err) // Send some funds to the output and then try to get a signature through // the SignPsbt RPC to spend that output again. assertPsbtSpend( - ctxt, t, net, alice, pkScript, + ht, alice, pkScript, func(packet *psbt.Packet) { in := &packet.Inputs[0] in.RedeemScript = witnessProgram @@ -788,16 +742,16 @@ func runSignPsbtSegWitV0NP2WKH(t *harnessTest, net *lntest.NetworkHarness, in.SighashType = txscript.SigHashAll }, func(packet *psbt.Packet) { - require.Len(t.t, packet.Inputs, 1) - require.Len(t.t, packet.Inputs[0].PartialSigs, 1) + require.Len(ht, packet.Inputs, 1) + require.Len(ht, packet.Inputs[0].PartialSigs, 1) partialSig := packet.Inputs[0].PartialSigs[0] require.Equal( - t.t, partialSig.PubKey, + ht, partialSig.PubKey, addrPubKey.SerializeCompressed(), ) require.Greater( - t.t, len(partialSig.Signature), ecdsa.MinSigLen, + ht, len(partialSig.Signature), ecdsa.MinSigLen, ) }, ) @@ -805,19 +759,11 @@ func runSignPsbtSegWitV0NP2WKH(t *harnessTest, net *lntest.NetworkHarness, // runSignPsbtSegWitV1KeySpendBip86 tests that the SignPsbt RPC works correctly // for a SegWit v1 p2tr key spend BIP-0086 input. -func runSignPsbtSegWitV1KeySpendBip86(t *harnessTest, net *lntest.NetworkHarness, - alice *lntest.HarnessNode) { - - // 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. - ctxb := context.Background() - ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) - defer cancel() +func runSignPsbtSegWitV1KeySpendBip86(ht *lntemp.HarnessTest, + alice *node.HarnessNode) { // Derive a key we can use for signing. - keyDesc, internalKey, fullDerivationPath := deriveInternalKey( - ctxt, t, alice, - ) + keyDesc, internalKey, fullDerivationPath := deriveInternalKey(ht, alice) // Our taproot key is a BIP0086 key spend only construction that just // commits to the internal key and no root hash. @@ -825,14 +771,14 @@ func runSignPsbtSegWitV1KeySpendBip86(t *harnessTest, net *lntest.NetworkHarness tapScriptAddr, err := btcutil.NewAddressTaproot( schnorr.SerializePubKey(taprootKey), harnessNetParams, ) - require.NoError(t.t, err) + require.NoError(ht, err) p2trPkScript, err := txscript.PayToAddrScript(tapScriptAddr) - require.NoError(t.t, err) + require.NoError(ht, err) // Send some funds to the output and then try to get a signature through // the SignPsbt RPC to spend that output again. assertPsbtSpend( - ctxt, t, net, alice, p2trPkScript, + ht, alice, p2trPkScript, func(packet *psbt.Packet) { in := &packet.Inputs[0] in.Bip32Derivation = []*psbt.Bip32Derivation{{ @@ -846,9 +792,9 @@ func runSignPsbtSegWitV1KeySpendBip86(t *harnessTest, net *lntest.NetworkHarness in.SighashType = txscript.SigHashDefault }, func(packet *psbt.Packet) { - require.Len(t.t, packet.Inputs, 1) + require.Len(ht, packet.Inputs, 1) require.Len( - t.t, packet.Inputs[0].TaprootKeySpendSig, 64, + ht, packet.Inputs[0].TaprootKeySpendSig, 64, ) }, ) @@ -857,37 +803,29 @@ func runSignPsbtSegWitV1KeySpendBip86(t *harnessTest, net *lntest.NetworkHarness // runSignPsbtSegWitV1KeySpendRootHash tests that the SignPsbt RPC works // correctly for a SegWit v1 p2tr key spend that also commits to a script tree // root hash. -func runSignPsbtSegWitV1KeySpendRootHash(t *harnessTest, - net *lntest.NetworkHarness, alice *lntest.HarnessNode) { - - // 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. - ctxb := context.Background() - ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) - defer cancel() +func runSignPsbtSegWitV1KeySpendRootHash(ht *lntemp.HarnessTest, + alice *node.HarnessNode) { // Derive a key we can use for signing. - keyDesc, internalKey, fullDerivationPath := deriveInternalKey( - ctxt, t, alice, - ) + keyDesc, internalKey, fullDerivationPath := deriveInternalKey(ht, alice) // Let's create a taproot script output now. This is a hash lock with a // simple preimage of "foobar". - leaf1 := testScriptHashLock(t.t, []byte("foobar")) + leaf1 := testScriptHashLock(ht.T, []byte("foobar")) rootHash := leaf1.TapHash() taprootKey := txscript.ComputeTaprootOutputKey(internalKey, rootHash[:]) tapScriptAddr, err := btcutil.NewAddressTaproot( schnorr.SerializePubKey(taprootKey), harnessNetParams, ) - require.NoError(t.t, err) + require.NoError(ht, err) p2trPkScript, err := txscript.PayToAddrScript(tapScriptAddr) - require.NoError(t.t, err) + require.NoError(ht, err) // Send some funds to the output and then try to get a signature through // the SignPsbt RPC to spend that output again. assertPsbtSpend( - ctxt, t, net, alice, p2trPkScript, + ht, alice, p2trPkScript, func(packet *psbt.Packet) { in := &packet.Inputs[0] in.Bip32Derivation = []*psbt.Bip32Derivation{{ @@ -902,9 +840,9 @@ func runSignPsbtSegWitV1KeySpendRootHash(t *harnessTest, in.SighashType = txscript.SigHashDefault }, func(packet *psbt.Packet) { - require.Len(t.t, packet.Inputs, 1) + require.Len(ht, packet.Inputs, 1) require.Len( - t.t, packet.Inputs[0].TaprootKeySpendSig, 64, + ht, packet.Inputs[0].TaprootKeySpendSig, 64, ) }, ) @@ -912,43 +850,35 @@ func runSignPsbtSegWitV1KeySpendRootHash(t *harnessTest, // runSignPsbtSegWitV1ScriptSpend tests that the SignPsbt RPC works correctly // for a SegWit v1 p2tr script spend. -func runSignPsbtSegWitV1ScriptSpend(t *harnessTest, - net *lntest.NetworkHarness, alice *lntest.HarnessNode) { - - // 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. - ctxb := context.Background() - ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) - defer cancel() +func runSignPsbtSegWitV1ScriptSpend(ht *lntemp.HarnessTest, + alice *node.HarnessNode) { // Derive a key we can use for signing. - keyDesc, internalKey, fullDerivationPath := deriveInternalKey( - ctxt, t, alice, - ) + keyDesc, internalKey, fullDerivationPath := deriveInternalKey(ht, alice) // Let's create a taproot script output now. This is a hash lock with a // simple preimage of "foobar". - leaf1 := testScriptSchnorrSig(t.t, internalKey) + leaf1 := testScriptSchnorrSig(ht.T, internalKey) rootHash := leaf1.TapHash() taprootKey := txscript.ComputeTaprootOutputKey(internalKey, rootHash[:]) tapScriptAddr, err := btcutil.NewAddressTaproot( schnorr.SerializePubKey(taprootKey), harnessNetParams, ) - require.NoError(t.t, err) + require.NoError(ht, err) p2trPkScript, err := txscript.PayToAddrScript(tapScriptAddr) - require.NoError(t.t, err) + require.NoError(ht, err) // We need to assemble the control block to be able to spend through the // script path. tapscript := input.TapscriptPartialReveal(internalKey, leaf1, nil) controlBlockBytes, err := tapscript.ControlBlock.ToBytes() - require.NoError(t.t, err) + require.NoError(ht, err) // Send some funds to the output and then try to get a signature through // the SignPsbt RPC to spend that output again. assertPsbtSpend( - ctxt, t, net, alice, p2trPkScript, + ht, alice, p2trPkScript, func(packet *psbt.Packet) { in := &packet.Inputs[0] in.Bip32Derivation = []*psbt.Bip32Derivation{{ @@ -968,32 +898,27 @@ func runSignPsbtSegWitV1ScriptSpend(t *harnessTest, }} }, func(packet *psbt.Packet) { - require.Len(t.t, packet.Inputs, 1) + require.Len(ht, packet.Inputs, 1) require.Len( - t.t, packet.Inputs[0].TaprootScriptSpendSig, 1, + ht, packet.Inputs[0].TaprootScriptSpendSig, 1, ) scriptSpendSig := packet.Inputs[0].TaprootScriptSpendSig[0] - require.Len(t.t, scriptSpendSig.Signature, 64) + require.Len(ht, scriptSpendSig.Signature, 64) }, ) } // runFundAndSignPsbt makes sure we can sign PSBTs that were funded by our // internal wallet. -func runFundAndSignPsbt(t *harnessTest, net *lntest.NetworkHarness, - alice *lntest.HarnessNode) { - - ctxb := context.Background() - ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) - defer cancel() +func runFundAndSignPsbt(ht *lntemp.HarnessTest, alice *node.HarnessNode) { + alice.AddToLogf("================ runFundAndSignPsbt ===============") // We'll be using a "main" address where we send the funds to and from // several times. - mainAddrResp, err := alice.NewAddress(ctxt, &lnrpc.NewAddressRequest{ + mainAddrResp := alice.RPC.NewAddress(&lnrpc.NewAddressRequest{ Type: lnrpc.AddressType_WITNESS_PUBKEY_HASH, }) - require.NoError(t.t, err) fundOutputs := map[string]uint64{ mainAddrResp.Address: 999000, @@ -1005,56 +930,53 @@ func runFundAndSignPsbt(t *harnessTest, net *lntest.NetworkHarness, } for _, addrType := range spendAddrTypes { - ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) + ht.Logf("testing with address type %s", addrType) - // First, spend all the coins in the wallet to an address of the - // given type so that UTXO will be picked when funding a PSBT. - sendAllCoinsToAddrType(ctxt, t, net, alice, addrType) + // First, spend all the coins in the wallet to an address of + // the given type so that UTXO will be picked when funding a + // PSBT. + sendAllCoinsToAddrType(ht, alice, addrType) - // Let's fund a PSBT now where we want to send a few sats to our - // main address. - assertPsbtFundSignSpend(ctxt, t, net, alice, fundOutputs, false) + // Let's fund a PSBT now where we want to send a few sats to + // our main address. + assertPsbtFundSignSpend(ht, alice, fundOutputs, false) // Send all coins back to a single address once again. - sendAllCoinsToAddrType(ctxt, t, net, alice, addrType) + sendAllCoinsToAddrType(ht, alice, addrType) // And now make sure the alternate way of signing a PSBT, which - // is calling FinalizePsbt directly, also works for this address - // type. - assertPsbtFundSignSpend(ctxt, t, net, alice, fundOutputs, true) - - cancel() + // is calling FinalizePsbt directly, also works for this + // address type. + assertPsbtFundSignSpend(ht, alice, fundOutputs, true) } } // assertPsbtSpend creates an output with the given pkScript on chain and then // attempts to create a sweep transaction that is signed using the SignPsbt RPC // that spends that output again. -func assertPsbtSpend(ctx context.Context, t *harnessTest, - net *lntest.NetworkHarness, alice *lntest.HarnessNode, pkScript []byte, - decorateUnsigned func(*psbt.Packet), verifySigned func(*psbt.Packet)) { +func assertPsbtSpend(ht *lntemp.HarnessTest, alice *node.HarnessNode, + pkScript []byte, decorateUnsigned func(*psbt.Packet), + verifySigned func(*psbt.Packet)) { // Let's send some coins to that address now. utxo := &wire.TxOut{ Value: 600_000, PkScript: pkScript, } - resp, err := alice.WalletKitClient.SendOutputs( - ctx, &walletrpc.SendOutputsRequest{ - Outputs: []*signrpc.TxOut{{ - Value: utxo.Value, - PkScript: utxo.PkScript, - }}, - MinConfs: 0, - SpendUnconfirmed: true, - SatPerKw: 2500, - }, - ) - require.NoError(t.t, err) + req := &walletrpc.SendOutputsRequest{ + Outputs: []*signrpc.TxOut{{ + Value: utxo.Value, + PkScript: utxo.PkScript, + }}, + MinConfs: 0, + SpendUnconfirmed: true, + SatPerKw: 2500, + } + resp := alice.RPC.SendOutputs(req) prevTx := wire.NewMsgTx(2) - err = prevTx.Deserialize(bytes.NewReader(resp.RawTx)) - require.NoError(t.t, err) + err := prevTx.Deserialize(bytes.NewReader(resp.RawTx)) + require.NoError(ht, err) prevOut := -1 for idx, txOut := range prevTx.TxOut { @@ -1062,7 +984,7 @@ func assertPsbtSpend(ctx context.Context, t *harnessTest, prevOut = idx } } - require.Greater(t.t, prevOut, -1) + require.Greater(ht, prevOut, -1) // Okay, we have everything we need to create a PSBT now. pendingTx := &wire.MsgTx{ @@ -1080,7 +1002,7 @@ func assertPsbtSpend(ctx context.Context, t *harnessTest, }}, } packet, err := psbt.NewFromUnsignedTx(pendingTx) - require.NoError(t.t, err) + require.NoError(ht, err) // Now let's add the meta information that we need for signing. packet.Inputs[0].WitnessUtxo = utxo @@ -1090,20 +1012,16 @@ func assertPsbtSpend(ctx context.Context, t *harnessTest, // That's it, we should be able to sign the PSBT now. var buf bytes.Buffer err = packet.Serialize(&buf) - require.NoError(t.t, err) + require.NoError(ht, err) - signResp, err := alice.WalletKitClient.SignPsbt( - ctx, &walletrpc.SignPsbtRequest{ - FundedPsbt: buf.Bytes(), - }, - ) - require.NoError(t.t, err) + signReq := &walletrpc.SignPsbtRequest{FundedPsbt: buf.Bytes()} + signResp := alice.RPC.SignPsbt(signReq) // Let's make sure we have a partial signature. signedPacket, err := psbt.NewFromRawBytes( bytes.NewReader(signResp.SignedPsbt), false, ) - require.NoError(t.t, err) + require.NoError(ht, err) // Allow the caller to also verify (and potentially move) some of the // returned fields. @@ -1111,87 +1029,72 @@ func assertPsbtSpend(ctx context.Context, t *harnessTest, // We should be able to finalize the PSBT and extract the final TX now. err = psbt.MaybeFinalizeAll(signedPacket) - require.NoError(t.t, err) + require.NoError(ht, err) finalTx, err := psbt.Extract(signedPacket) - require.NoError(t.t, err) + require.NoError(ht, err) // Make sure we can also sign a second time. This makes sure any key // tweaking that happened for the signing didn't affect any keys in the // cache. - signResp2, err := alice.WalletKitClient.SignPsbt( - ctx, &walletrpc.SignPsbtRequest{ - FundedPsbt: buf.Bytes(), - }, - ) - require.NoError(t.t, err) + r := &walletrpc.SignPsbtRequest{FundedPsbt: buf.Bytes()} + signResp2 := alice.RPC.SignPsbt(r) signedPacket2, err := psbt.NewFromRawBytes( bytes.NewReader(signResp2.SignedPsbt), false, ) - require.NoError(t.t, err) + require.NoError(ht, err) verifySigned(signedPacket2) buf.Reset() err = finalTx.Serialize(&buf) - require.NoError(t.t, err) + require.NoError(ht, err) // Publish the second transaction and then mine both of them. - _, err = alice.WalletKitClient.PublishTransaction( - ctx, &walletrpc.Transaction{ - TxHex: buf.Bytes(), - }, - ) - require.NoError(t.t, err) + txReq := &walletrpc.Transaction{TxHex: buf.Bytes()} + alice.RPC.PublishTransaction(txReq) // Mine one block which should contain two transactions. - block := mineBlocks(t, net, 1, 2)[0] + block := ht.MineBlocksAndAssertNumTxes(1, 2)[0] firstTxHash := prevTx.TxHash() secondTxHash := finalTx.TxHash() - assertTxInBlock(t, block, &firstTxHash) - assertTxInBlock(t, block, &secondTxHash) + ht.Miner.AssertTxInBlock(block, &firstTxHash) + ht.Miner.AssertTxInBlock(block, &secondTxHash) } // assertPsbtFundSignSpend funds a PSBT from the internal wallet and then // attempts to sign it by using the SignPsbt or FinalizePsbt method. -func assertPsbtFundSignSpend(ctx context.Context, t *harnessTest, - net *lntest.NetworkHarness, alice *lntest.HarnessNode, +func assertPsbtFundSignSpend(ht *lntemp.HarnessTest, alice *node.HarnessNode, fundOutputs map[string]uint64, useFinalize bool) { - fundResp, err := alice.WalletKitClient.FundPsbt( - ctx, &walletrpc.FundPsbtRequest{ - Template: &walletrpc.FundPsbtRequest_Raw{ - Raw: &walletrpc.TxTemplate{ - Outputs: fundOutputs, - }, + fundResp := alice.RPC.FundPsbt(&walletrpc.FundPsbtRequest{ + Template: &walletrpc.FundPsbtRequest_Raw{ + Raw: &walletrpc.TxTemplate{ + Outputs: fundOutputs, }, - Fees: &walletrpc.FundPsbtRequest_SatPerVbyte{ - SatPerVbyte: 2, - }, - MinConfs: 1, }, + Fees: &walletrpc.FundPsbtRequest_SatPerVbyte{ + SatPerVbyte: 2, + }, + MinConfs: 1, + }, ) - require.NoError(t.t, err) - require.GreaterOrEqual( - t.t, fundResp.ChangeOutputIndex, int32(-1), - ) + require.GreaterOrEqual(ht, fundResp.ChangeOutputIndex, int32(-1)) var signedPsbt []byte if useFinalize { - finalizeResp, err := alice.WalletKitClient.FinalizePsbt( - ctx, &walletrpc.FinalizePsbtRequest{ + finalizeResp := alice.RPC.FinalizePsbt( + &walletrpc.FinalizePsbtRequest{ FundedPsbt: fundResp.FundedPsbt, }, ) - require.NoError(t.t, err) signedPsbt = finalizeResp.SignedPsbt } else { - signResp, err := alice.WalletKitClient.SignPsbt( - ctx, &walletrpc.SignPsbtRequest{ + signResp := alice.RPC.SignPsbt( + &walletrpc.SignPsbtRequest{ FundedPsbt: fundResp.FundedPsbt, }, ) - require.NoError(t.t, err) signedPsbt = signResp.SignedPsbt } @@ -1200,46 +1103,41 @@ func assertPsbtFundSignSpend(ctx context.Context, t *harnessTest, signedPacket, err := psbt.NewFromRawBytes( bytes.NewReader(signedPsbt), false, ) - require.NoError(t.t, err) + require.NoError(ht, err) // We should be able to finalize the PSBT and extract the final // TX now. err = psbt.MaybeFinalizeAll(signedPacket) - require.NoError(t.t, err) + require.NoError(ht, err) finalTx, err := psbt.Extract(signedPacket) - require.NoError(t.t, err) + require.NoError(ht, err) var buf bytes.Buffer err = finalTx.Serialize(&buf) - require.NoError(t.t, err) + require.NoError(ht, err) // Publish the second transaction and then mine both of them. - _, err = alice.WalletKitClient.PublishTransaction( - ctx, &walletrpc.Transaction{ - TxHex: buf.Bytes(), - }, - ) - require.NoError(t.t, err) + alice.RPC.PublishTransaction(&walletrpc.Transaction{ + TxHex: buf.Bytes(), + }) - // Mine one block which should contain two transactions. - block := mineBlocks(t, net, 1, 1)[0] + // Mine one block which should contain one transaction. + block := ht.MineBlocksAndAssertNumTxes(1, 1)[0] finalTxHash := finalTx.TxHash() - assertTxInBlock(t, block, &finalTxHash) + ht.Miner.AssertTxInBlock(block, &finalTxHash) } // deriveInternalKey derives a signing key and returns its descriptor, full // derivation path and parsed public key. -func deriveInternalKey(ctx context.Context, t *harnessTest, - alice *lntest.HarnessNode) (*signrpc.KeyDescriptor, *btcec.PublicKey, +func deriveInternalKey(ht *lntemp.HarnessTest, + alice *node.HarnessNode) (*signrpc.KeyDescriptor, *btcec.PublicKey, []uint32) { // For the next step, we need a public key. Let's use a special family // for this. - keyDesc, err := alice.WalletKitClient.DeriveNextKey( - ctx, &walletrpc.KeyReq{KeyFamily: testTaprootKeyFamily}, - ) - require.NoError(t.t, err) + req := &walletrpc.KeyReq{KeyFamily: testTaprootKeyFamily} + keyDesc := alice.RPC.DeriveNextKey(req) // The DeriveNextKey returns a key from the internal 1017 scope. fullDerivationPath := []uint32{ @@ -1251,7 +1149,7 @@ func deriveInternalKey(ctx context.Context, t *harnessTest, } parsedPubKey, err := btcec.ParsePubKey(keyDesc.RawKeyBytes) - require.NoError(t.t, err) + require.NoError(ht, err) return keyDesc, parsedPubKey, fullDerivationPath } @@ -1345,22 +1243,19 @@ func receiveChanUpdate(ctx context.Context, // sendAllCoinsToAddrType sweeps all coins from the wallet and sends them to a // new address of the given type. -func sendAllCoinsToAddrType(ctx context.Context, t *harnessTest, - net *lntest.NetworkHarness, node *lntest.HarnessNode, - addrType lnrpc.AddressType) { +func sendAllCoinsToAddrType(ht *lntemp.HarnessTest, + hn *node.HarnessNode, addrType lnrpc.AddressType) { - resp, err := node.NewAddress(ctx, &lnrpc.NewAddressRequest{ + resp := hn.RPC.NewAddress(&lnrpc.NewAddressRequest{ Type: addrType, }) - require.NoError(t.t, err) - _, err = node.SendCoins(ctx, &lnrpc.SendCoinsRequest{ - Addr: resp.Address, - SendAll: true, + hn.RPC.SendCoins(&lnrpc.SendCoinsRequest{ + Addr: resp.Address, + SendAll: true, + SpendUnconfirmed: true, }) - require.NoError(t.t, err) - _ = mineBlocks(t, net, 1, 1)[0] - err = node.WaitForBlockchainSync() - require.NoError(t.t, err) + ht.MineBlocksAndAssertNumTxes(1, 1) + ht.WaitForBlockchainSync(hn) } diff --git a/lntest/itest/lnd_remote_signer_test.go b/lntest/itest/lnd_remote_signer_test.go index 45dcbc3e1..032301340 100644 --- a/lntest/itest/lnd_remote_signer_test.go +++ b/lntest/itest/lnd_remote_signer_test.go @@ -1,7 +1,6 @@ package itest import ( - "context" "fmt" "testing" @@ -11,7 +10,8 @@ import ( "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/walletrpc" - "github.com/lightningnetwork/lnd/lntest" + "github.com/lightningnetwork/lnd/lntemp" + "github.com/lightningnetwork/lnd/lntemp/node" "github.com/stretchr/testify/require" ) @@ -54,105 +54,94 @@ var ( // testRemoteSigner tests that a watch-only wallet can use a remote signing // wallet to perform any signing or ECDH operations. -func testRemoteSigner(net *lntest.NetworkHarness, t *harnessTest) { - ctxb := context.Background() - - subTests := []struct { +func testRemoteSigner(ht *lntemp.HarnessTest) { + type testCase struct { name string randomSeed bool sendCoins bool - fn func(tt *harnessTest, wo, carol *lntest.HarnessNode) - }{{ + fn func(tt *lntemp.HarnessTest, + wo, carol *node.HarnessNode) + } + + subTests := []testCase{{ name: "random seed", randomSeed: true, - fn: func(tt *harnessTest, wo, carol *lntest.HarnessNode) { + fn: func(tt *lntemp.HarnessTest, wo, carol *node.HarnessNode) { // Nothing more to test here. }, }, { name: "account import", - fn: func(tt *harnessTest, wo, carol *lntest.HarnessNode) { + fn: func(tt *lntemp.HarnessTest, wo, carol *node.HarnessNode) { runWalletImportAccountScenario( - net, tt, - walletrpc.AddressType_WITNESS_PUBKEY_HASH, + tt, walletrpc.AddressType_WITNESS_PUBKEY_HASH, carol, wo, ) }, }, { name: "basic channel open close", sendCoins: true, - fn: func(tt *harnessTest, wo, carol *lntest.HarnessNode) { - runBasicChannelCreationAndUpdates( - net, tt, wo, carol, - ) + fn: func(tt *lntemp.HarnessTest, wo, carol *node.HarnessNode) { + runBasicChannelCreationAndUpdates(tt, wo, carol) }, }, { name: "async payments", sendCoins: true, - fn: func(tt *harnessTest, wo, carol *lntest.HarnessNode) { - runAsyncPayments(net, tt, wo, carol) + fn: func(tt *lntemp.HarnessTest, wo, carol *node.HarnessNode) { + runAsyncPayments(tt, wo, carol) }, }, { name: "shared key", - fn: func(tt *harnessTest, wo, carol *lntest.HarnessNode) { + fn: func(tt *lntemp.HarnessTest, wo, carol *node.HarnessNode) { runDeriveSharedKey(tt, wo) }, }, { name: "cpfp", sendCoins: true, - fn: func(tt *harnessTest, wo, carol *lntest.HarnessNode) { - runCPFP(net, tt, wo, carol) + fn: func(tt *lntemp.HarnessTest, wo, carol *node.HarnessNode) { + runCPFP(tt, wo, carol) }, }, { name: "psbt", randomSeed: true, - fn: func(tt *harnessTest, wo, carol *lntest.HarnessNode) { - runPsbtChanFunding(net, tt, carol, wo) - runSignPsbtSegWitV0P2WKH(tt, net, wo) - runSignPsbtSegWitV1KeySpendBip86(tt, net, wo) - runSignPsbtSegWitV1KeySpendRootHash(tt, net, wo) - runSignPsbtSegWitV1ScriptSpend(tt, net, wo) + fn: func(tt *lntemp.HarnessTest, wo, carol *node.HarnessNode) { + runPsbtChanFunding(tt, carol, wo) + runSignPsbtSegWitV0P2WKH(tt, wo) + runSignPsbtSegWitV1KeySpendBip86(tt, wo) + runSignPsbtSegWitV1KeySpendRootHash(tt, wo) + runSignPsbtSegWitV1ScriptSpend(tt, wo) }, }, { name: "sign output raw", sendCoins: true, - fn: func(tt *harnessTest, wo, carol *lntest.HarnessNode) { - runSignOutputRaw(tt, net, wo) + fn: func(tt *lntemp.HarnessTest, wo, carol *node.HarnessNode) { + runSignOutputRaw(tt, wo) }, }, { name: "sign verify msg", sendCoins: true, - fn: func(tt *harnessTest, wo, carol *lntest.HarnessNode) { - runSignVerifyMessage(tt, net, wo) + fn: func(tt *lntemp.HarnessTest, wo, carol *node.HarnessNode) { + runSignVerifyMessage(tt, wo) }, }, { name: "taproot", sendCoins: true, randomSeed: true, - fn: func(tt *harnessTest, wo, carol *lntest.HarnessNode) { - longTimeout := 3 * defaultTimeout - ctxt, cancel := context.WithTimeout(ctxb, longTimeout) - testTaprootSendCoinsKeySpendBip86(ctxt, tt, wo, net) - testTaprootComputeInputScriptKeySpendBip86( - ctxt, tt, wo, net, - ) - testTaprootSignOutputRawScriptSpend(ctxt, tt, wo, net) - testTaprootSignOutputRawKeySpendBip86(ctxt, tt, wo, net) - testTaprootSignOutputRawKeySpendRootHash( - ctxt, tt, wo, net, - ) - cancel() - - ctxt, cancel = context.WithTimeout(ctxb, longTimeout) - testTaprootMuSig2KeySpendRootHash(ctxt, tt, wo, net) - testTaprootMuSig2ScriptSpend(ctxt, tt, wo, net) - testTaprootMuSig2KeySpendBip86(ctxt, tt, wo, net) - testTaprootMuSig2CombinedLeafKeySpend(ctxt, tt, wo, net) - cancel() + fn: func(tt *lntemp.HarnessTest, wo, carol *node.HarnessNode) { + testTaprootSendCoinsKeySpendBip86(tt, wo) + testTaprootComputeInputScriptKeySpendBip86(tt, wo) + testTaprootSignOutputRawScriptSpend(tt, wo) + testTaprootSignOutputRawKeySpendBip86(tt, wo) + testTaprootSignOutputRawKeySpendRootHash(tt, wo) + testTaprootMuSig2KeySpendRootHash(tt, wo) + testTaprootMuSig2ScriptSpend(tt, wo) + testTaprootMuSig2KeySpendBip86(tt, wo) + testTaprootMuSig2CombinedLeafKeySpend(tt, wo) }, }} - for _, st := range subTests { - subTest := st + prepareTest := func(st *lntemp.HarnessTest, + subTest testCase) (*node.HarnessNode, + *node.HarnessNode, *node.HarnessNode) { // Signer is our signing node and has the wallet with the full // master private key. We test that we can create the watch-only @@ -162,34 +151,32 @@ func testRemoteSigner(net *lntest.NetworkHarness, t *harnessTest) { password := []byte("itestpassword") var ( signerNodePubKey = nodePubKey - watchOnlyAccounts = deriveCustomScopeAccounts(t.t) - signer *lntest.HarnessNode + watchOnlyAccounts = deriveCustomScopeAccounts(ht.T) + signer *node.HarnessNode err error ) if !subTest.randomSeed { - signer, err = net.RestoreNodeWithSeed( + signer = st.RestoreNodeWithSeed( "Signer", nil, password, nil, rootKey, 0, nil, ) - require.NoError(t.t, err) } else { - signer = net.NewNode(t.t, "Signer", nil) + signer = st.NewNode("Signer", nil) signerNodePubKey = signer.PubKeyStr - rpcAccts, err := signer.WalletKitClient.ListAccounts( - ctxb, &walletrpc.ListAccountsRequest{}, + rpcAccts := signer.RPC.ListAccounts( + &walletrpc.ListAccountsRequest{}, ) - require.NoError(t.t, err) watchOnlyAccounts, err = walletrpc.AccountsToWatchOnly( rpcAccts.Accounts, ) - require.NoError(t.t, err) + require.NoError(st, err) } // WatchOnly is the node that has a watch-only wallet and uses // the Signer node for any operation that requires access to // private keys. - watchOnly, err := net.NewNodeRemoteSigner( + watchOnly := st.NewNodeRemoteSigner( "WatchOnly", []string{ "--remotesigner.enable", fmt.Sprintf( @@ -210,33 +197,35 @@ func testRemoteSigner(net *lntest.NetworkHarness, t *harnessTest) { Accounts: watchOnlyAccounts, }, ) - require.NoError(t.t, err) - resp, err := watchOnly.GetInfo(ctxb, &lnrpc.GetInfoRequest{}) - require.NoError(t.t, err) - - require.Equal(t.t, signerNodePubKey, resp.IdentityPubkey) + resp := watchOnly.RPC.GetInfo() + require.Equal(st, signerNodePubKey, resp.IdentityPubkey) if subTest.sendCoins { - net.SendCoins(t.t, btcutil.SatoshiPerBitcoin, watchOnly) - assertAccountBalance( - t.t, watchOnly, "default", + st.FundCoins(btcutil.SatoshiPerBitcoin, watchOnly) + ht.AssertWalletAccountBalance( + watchOnly, "default", btcutil.SatoshiPerBitcoin, 0, ) } - carol := net.NewNode(t.t, "carol", nil) - net.EnsureConnected(t.t, watchOnly, carol) + carol := st.NewNode("carol", nil) + st.EnsureConnected(watchOnly, carol) - success := t.t.Run(subTest.name, func(tt *testing.T) { - ht := newHarnessTest(tt, net) - subTest.fn(ht, watchOnly, carol) + return signer, watchOnly, carol + } + + for _, testCase := range subTests { + subTest := testCase + + success := ht.Run(subTest.name, func(tt *testing.T) { + // Skip the cleanup here as no standby node is used. + st := ht.Subtest(tt) + + _, watchOnly, carol := prepareTest(st, subTest) + subTest.fn(st, watchOnly, carol) }) - shutdownAndAssert(net, t, carol) - shutdownAndAssert(net, t, watchOnly) - shutdownAndAssert(net, t, signer) - if !success { return } diff --git a/lntest/itest/lnd_rpc_middleware_interceptor_test.go b/lntest/itest/lnd_rpc_middleware_interceptor_test.go index f9c1d7b5c..f9a793db4 100644 --- a/lntest/itest/lnd_rpc_middleware_interceptor_test.go +++ b/lntest/itest/lnd_rpc_middleware_interceptor_test.go @@ -560,7 +560,7 @@ func middlewareMandatoryTest(ht *lntemp.HarnessTest, node *node.HarnessNode) { // test case. So we need to do the wait and client setup manually here. conn, err := node.ConnectRPC() require.NoError(ht, err) - node.InitRPCClients(conn) + node.Initialize(conn) err = node.WaitUntilServerActive() require.NoError(ht, err) diff --git a/lntest/itest/lnd_send_multi_path_payment_test.go b/lntest/itest/lnd_send_multi_path_payment_test.go index fe9e00335..0e5cfc24a 100644 --- a/lntest/itest/lnd_send_multi_path_payment_test.go +++ b/lntest/itest/lnd_send_multi_path_payment_test.go @@ -1,22 +1,19 @@ package itest import ( - "context" "encoding/hex" "github.com/btcsuite/btcd/btcutil" "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" ) // testSendMultiPathPayment tests that we are able to successfully route a // payment using multiple shards across different paths. -func testSendMultiPathPayment(net *lntest.NetworkHarness, t *harnessTest) { - ctxb := context.Background() - - ctx := newMppTestContext(t, net) - defer ctx.shutdownNodes() +func testSendMultiPathPayment(ht *lntemp.HarnessTest) { + mts := newMppTestScenario(ht) const paymentAmt = btcutil.Amount(300000) @@ -30,57 +27,45 @@ func testSendMultiPathPayment(net *lntest.NetworkHarness, t *harnessTest) { // \ / // \__ Dave ____/ // - ctx.openChannel(ctx.carol, ctx.bob, 135000) - ctx.openChannel(ctx.alice, ctx.carol, 235000) - ctx.openChannel(ctx.dave, ctx.bob, 135000) - ctx.openChannel(ctx.alice, ctx.dave, 135000) - ctx.openChannel(ctx.eve, ctx.bob, 135000) - ctx.openChannel(ctx.carol, ctx.eve, 135000) - - defer ctx.closeChannels() - - ctx.waitForChannels() + req := &mppOpenChannelRequest{ + amtAliceCarol: 235000, + amtAliceDave: 135000, + amtCarolBob: 135000, + amtCarolEve: 135000, + amtDaveBob: 135000, + amtEveBob: 135000, + } + mts.openChannels(req) + chanPointAliceDave := mts.channelPoints[1] // Increase Dave's fee to make the test deterministic. Otherwise it // would be unpredictable whether pathfinding would go through Charlie // or Dave for the first shard. - _, err := ctx.dave.UpdateChannelPolicy( - context.Background(), - &lnrpc.PolicyUpdateRequest{ - Scope: &lnrpc.PolicyUpdateRequest_Global{Global: true}, - BaseFeeMsat: 500000, - FeeRate: 0.001, - TimeLockDelta: 40, - }, + expectedPolicy := mts.updateDaveGlobalPolicy() + + // Make sure Alice has heard it. + ht.AssertChannelPolicyUpdate( + mts.alice, mts.dave, expectedPolicy, chanPointAliceDave, false, ) - if err != nil { - t.Fatalf("dave policy update: %v", err) - } + // Our first test will be Alice paying Bob using a SendPayment call. // Let Bob create an invoice for Alice to pay. - payReqs, rHashes, invoices, err := createPayReqs( - ctx.bob, paymentAmt, 1, - ) - if err != nil { - t.Fatalf("unable to create pay reqs: %v", err) - } + payReqs, rHashes, invoices := ht.CreatePayReqs(mts.bob, paymentAmt, 1) rHash := rHashes[0] payReq := payReqs[0] - payment := sendAndAssertSuccess( - t, ctx.alice, &routerrpc.SendPaymentRequest{ - PaymentRequest: payReq, - MaxParts: 10, - TimeoutSeconds: 60, - FeeLimitMsat: noFeeLimitMsat, - }, - ) + sendReq := &routerrpc.SendPaymentRequest{ + PaymentRequest: payReq, + MaxParts: 10, + TimeoutSeconds: 60, + FeeLimitMsat: noFeeLimitMsat, + } + payment := ht.SendPaymentAssertSettled(mts.alice, sendReq) // Make sure we got the preimage. - if payment.PaymentPreimage != hex.EncodeToString(invoices[0].RPreimage) { - t.Fatalf("preimage doesn't match") - } + require.Equal(ht, hex.EncodeToString(invoices[0].RPreimage), + payment.PaymentPreimage, "preimage doesn't match") // Check that Alice split the payment in at least three shards. Because // the hand-off of the htlc to the link is asynchronous (via a mailbox), @@ -97,32 +82,17 @@ func testSendMultiPathPayment(net *lntest.NetworkHarness, t *harnessTest) { } const minExpectedShards = 3 - if succeeded < minExpectedShards { - t.Fatalf("expected at least %v shards, but got %v", - minExpectedShards, succeeded) - } + require.GreaterOrEqual(ht, succeeded, minExpectedShards, + "expected shards not reached") - // Make sure Bob show the invoice as settled for the full - // amount. - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - inv, err := ctx.bob.LookupInvoice( - ctxt, &lnrpc.PaymentHash{ - RHash: rHash, - }, - ) - if err != nil { - t.Fatalf("error when obtaining invoice: %v", err) - } + // Make sure Bob show the invoice as settled for the full amount. + inv := mts.bob.RPC.LookupInvoice(rHash) - if inv.AmtPaidSat != int64(paymentAmt) { - t.Fatalf("incorrect payment amt for invoice"+ - "want: %d, got %d", - paymentAmt, inv.AmtPaidSat) - } + require.EqualValues(ht, paymentAmt, inv.AmtPaidSat, + "incorrect payment amt") - if inv.State != lnrpc.Invoice_SETTLED { - t.Fatalf("Invoice not settled: %v", inv.State) - } + require.Equal(ht, lnrpc.Invoice_SETTLED, inv.State, + "Invoice not settled") settled := 0 for _, htlc := range inv.Htlcs { @@ -130,8 +100,9 @@ func testSendMultiPathPayment(net *lntest.NetworkHarness, t *harnessTest) { settled++ } } - if settled != succeeded { - t.Fatalf("expected invoice to be settled "+ - "with %v HTLCs, had %v", succeeded, settled) - } + require.Equal(ht, succeeded, settled, + "num of HTLCs wrong") + + // Finally, close all channels. + mts.closeChannels() } diff --git a/lntest/itest/lnd_signer_test.go b/lntest/itest/lnd_signer_test.go index 6cee27b82..15b7903f3 100644 --- a/lntest/itest/lnd_signer_test.go +++ b/lntest/itest/lnd_signer_test.go @@ -2,9 +2,7 @@ package itest import ( "bytes" - "context" "crypto/sha256" - "fmt" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/schnorr" @@ -15,7 +13,8 @@ import ( "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/signrpc" "github.com/lightningnetwork/lnd/lnrpc/walletrpc" - "github.com/lightningnetwork/lnd/lntest" + "github.com/lightningnetwork/lnd/lntemp" + "github.com/lightningnetwork/lnd/lntemp/node" "github.com/stretchr/testify/require" ) @@ -23,21 +22,19 @@ import ( // DeriveSharedKey. It creates an ephemeral private key, performing an ECDH with // the node's pubkey and a customized public key to check the validity of the // result. -func testDeriveSharedKey(net *lntest.NetworkHarness, t *harnessTest) { - runDeriveSharedKey(t, net.Alice) +func testDeriveSharedKey(ht *lntemp.HarnessTest) { + runDeriveSharedKey(ht, ht.Alice) } // runDeriveSharedKey checks the ECDH performed by the endpoint // DeriveSharedKey. It creates an ephemeral private key, performing an ECDH with // the node's pubkey and a customized public key to check the validity of the // result. -func runDeriveSharedKey(t *harnessTest, alice *lntest.HarnessNode) { - ctxb := context.Background() - +func runDeriveSharedKey(ht *lntemp.HarnessTest, alice *node.HarnessNode) { // Create an ephemeral key, extracts its public key, and make a // PrivKeyECDH using the ephemeral key. ephemeralPriv, err := btcec.NewPrivateKey() - require.NoError(t.t, err, "failed to create ephemeral key") + require.NoError(ht, err, "failed to create ephemeral key") ephemeralPubBytes := ephemeralPriv.PubKey().SerializeCompressed() privKeyECDH := &keychain.PrivKeyECDH{PrivKey: ephemeralPriv} @@ -47,30 +44,27 @@ func runDeriveSharedKey(t *harnessTest, alice *lntest.HarnessNode) { assertECDHMatch := func(pub *btcec.PublicKey, req *signrpc.SharedKeyRequest) { - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - resp, err := alice.SignerClient.DeriveSharedKey(ctxt, req) - require.NoError(t.t, err, "calling DeriveSharedKey failed") + resp := alice.RPC.DeriveSharedKey(req) sharedKey, _ := privKeyECDH.ECDH(pub) - require.Equal( - t.t, sharedKey[:], resp.SharedKey, - "failed to derive the expected key", - ) + require.Equal(ht, sharedKey[:], resp.SharedKey, + "failed to derive the expected key") } nodePub, err := btcec.ParsePubKey(alice.PubKey[:]) - require.NoError(t.t, err, "failed to parse node pubkey") + require.NoError(ht, err, "failed to parse node pubkey") customizedKeyFamily := int32(keychain.KeyFamilyMultiSig) customizedIndex := int32(1) - customizedPub, err := deriveCustomizedKey( - ctxb, alice, &signrpc.KeyLocator{ - KeyFamily: customizedKeyFamily, - KeyIndex: customizedIndex, - }, - ) - require.NoError(t.t, err, "failed to create customized pubkey") + // Derive a customized key. + deriveReq := &signrpc.KeyLocator{ + KeyFamily: customizedKeyFamily, + KeyIndex: customizedIndex, + } + resp := alice.RPC.DeriveKey(deriveReq) + customizedPub, err := btcec.ParsePubKey(resp.RawKeyBytes) + require.NoError(ht, err, "failed to parse node pubkey") // Test DeriveSharedKey with no optional arguments. It will result in // performing an ECDH between the ephemeral key and the node's pubkey. @@ -154,12 +148,8 @@ func runDeriveSharedKey(t *harnessTest, alice *lntest.HarnessNode) { // assertErrorMatch checks when calling DeriveSharedKey with invalid // params, the expected error is returned. assertErrorMatch := func(match string, req *signrpc.SharedKeyRequest) { - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - _, err := alice.SignerClient.DeriveSharedKey(ctxt, req) - require.Error(t.t, err, "expected to have an error") - require.Contains( - t.t, err.Error(), match, "error failed to match", - ) + err := alice.RPC.DeriveSharedKeyErr(req) + require.Contains(ht, err.Error(), match, "error not match") } // Test that EphemeralPubkey must be supplied. @@ -204,43 +194,35 @@ func runDeriveSharedKey(t *harnessTest, alice *lntest.HarnessNode) { // testSignOutputRaw makes sure that the SignOutputRaw RPC can be used with all // custom ways of specifying the signing key in the key descriptor/locator. -func testSignOutputRaw(net *lntest.NetworkHarness, t *harnessTest) { - runSignOutputRaw(t, net, net.Alice) +func testSignOutputRaw(ht *lntemp.HarnessTest) { + runSignOutputRaw(ht, ht.Alice) } // runSignOutputRaw makes sure that the SignOutputRaw RPC can be used with all // custom ways of specifying the signing key in the key descriptor/locator. -func runSignOutputRaw(t *harnessTest, net *lntest.NetworkHarness, - alice *lntest.HarnessNode) { - - ctxb := context.Background() - ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) - defer cancel() - +func runSignOutputRaw(ht *lntemp.HarnessTest, alice *node.HarnessNode) { // For the next step, we need a public key. Let's use a special family // for this. We want this to be an index of zero. const testCustomKeyFamily = 44 - keyDesc, err := alice.WalletKitClient.DeriveNextKey( - ctxt, &walletrpc.KeyReq{ - KeyFamily: testCustomKeyFamily, - }, - ) - require.NoError(t.t, err) - require.Equal(t.t, int32(0), keyDesc.KeyLoc.KeyIndex) + req := &walletrpc.KeyReq{ + KeyFamily: testCustomKeyFamily, + } + keyDesc := alice.RPC.DeriveNextKey(req) + require.Equal(ht, int32(0), keyDesc.KeyLoc.KeyIndex) targetPubKey, err := btcec.ParsePubKey(keyDesc.RawKeyBytes) - require.NoError(t.t, err) + require.NoError(ht, err) // First, try with a key descriptor that only sets the public key. assertSignOutputRaw( - t, net, alice, targetPubKey, &signrpc.KeyDescriptor{ + ht, alice, targetPubKey, &signrpc.KeyDescriptor{ RawKeyBytes: keyDesc.RawKeyBytes, }, ) // Now try again, this time only with the (0 index!) key locator. assertSignOutputRaw( - t, net, alice, targetPubKey, &signrpc.KeyDescriptor{ + ht, alice, targetPubKey, &signrpc.KeyDescriptor{ KeyLoc: &signrpc.KeyLocator{ KeyFamily: keyDesc.KeyLoc.KeyFamily, KeyIndex: keyDesc.KeyLoc.KeyIndex, @@ -250,29 +232,25 @@ func runSignOutputRaw(t *harnessTest, net *lntest.NetworkHarness, // And now test everything again with a new key where we know the index // is not 0. - ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout) - defer cancel() - keyDesc, err = alice.WalletKitClient.DeriveNextKey( - ctxt, &walletrpc.KeyReq{ - KeyFamily: testCustomKeyFamily, - }, - ) - require.NoError(t.t, err) - require.Equal(t.t, int32(1), keyDesc.KeyLoc.KeyIndex) + req = &walletrpc.KeyReq{ + KeyFamily: testCustomKeyFamily, + } + keyDesc = alice.RPC.DeriveNextKey(req) + require.Equal(ht, int32(1), keyDesc.KeyLoc.KeyIndex) targetPubKey, err = btcec.ParsePubKey(keyDesc.RawKeyBytes) - require.NoError(t.t, err) + require.NoError(ht, err) // First, try with a key descriptor that only sets the public key. assertSignOutputRaw( - t, net, alice, targetPubKey, &signrpc.KeyDescriptor{ + ht, alice, targetPubKey, &signrpc.KeyDescriptor{ RawKeyBytes: keyDesc.RawKeyBytes, }, ) // Now try again, this time only with the key locator. assertSignOutputRaw( - t, net, alice, targetPubKey, &signrpc.KeyDescriptor{ + ht, alice, targetPubKey, &signrpc.KeyDescriptor{ KeyLoc: &signrpc.KeyLocator{ KeyFamily: keyDesc.KeyLoc.KeyFamily, KeyIndex: keyDesc.KeyLoc.KeyIndex, @@ -284,53 +262,44 @@ func runSignOutputRaw(t *harnessTest, net *lntest.NetworkHarness, // assertSignOutputRaw sends coins to a p2wkh address derived from the given // target public key and then tries to spend that output again by invoking the // SignOutputRaw RPC with the key descriptor provided. -func assertSignOutputRaw(t *harnessTest, net *lntest.NetworkHarness, - alice *lntest.HarnessNode, targetPubKey *btcec.PublicKey, +func assertSignOutputRaw(ht *lntemp.HarnessTest, + alice *node.HarnessNode, targetPubKey *btcec.PublicKey, keyDesc *signrpc.KeyDescriptor) { - ctxb := context.Background() - ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) - defer cancel() - pubKeyHash := btcutil.Hash160(targetPubKey.SerializeCompressed()) targetAddr, err := btcutil.NewAddressWitnessPubKeyHash( pubKeyHash, harnessNetParams, ) - require.NoError(t.t, err) + require.NoError(ht, err) targetScript, err := txscript.PayToAddrScript(targetAddr) - require.NoError(t.t, err) + require.NoError(ht, err) // Send some coins to the generated p2wpkh address. - _, err = alice.SendCoins(ctxt, &lnrpc.SendCoinsRequest{ + req := &lnrpc.SendCoinsRequest{ Addr: targetAddr.String(), Amount: 800_000, - }) - require.NoError(t.t, err) + } + alice.RPC.SendCoins(req) // Wait until the TX is found in the mempool. - txid, err := waitForTxInMempool(net.Miner.Client, minerMempoolTimeout) - require.NoError(t.t, err) + txid := ht.Miner.AssertNumTxsInMempool(1)[0] - targetOutputIndex := getOutputIndex( - t, net.Miner, txid, targetAddr.String(), - ) + targetOutputIndex := ht.GetOutputIndex(txid, targetAddr.String()) // Clear the mempool. - mineBlocks(t, net, 1, 1) + ht.MineBlocksAndAssertNumTxes(1, 1) // Try to spend the output now to a new p2wkh address. - p2wkhResp, err := alice.NewAddress(ctxt, &lnrpc.NewAddressRequest{ - Type: lnrpc.AddressType_WITNESS_PUBKEY_HASH, - }) - require.NoError(t.t, err) + addrReq := &lnrpc.NewAddressRequest{Type: AddrTypeWitnessPubkeyHash} + p2wkhResp := alice.RPC.NewAddress(addrReq) p2wkhAdrr, err := btcutil.DecodeAddress( p2wkhResp.Address, harnessNetParams, ) - require.NoError(t.t, err) + require.NoError(ht, err) p2wkhPkScript, err := txscript.PayToAddrScript(p2wkhAdrr) - require.NoError(t.t, err) + require.NoError(ht, err) tx := wire.NewMsgTx(2) tx.TxIn = []*wire.TxIn{{ @@ -346,24 +315,22 @@ func assertSignOutputRaw(t *harnessTest, net *lntest.NetworkHarness, }} var buf bytes.Buffer - require.NoError(t.t, tx.Serialize(&buf)) + require.NoError(ht, tx.Serialize(&buf)) - signResp, err := alice.SignerClient.SignOutputRaw( - ctxt, &signrpc.SignReq{ - RawTxBytes: buf.Bytes(), - SignDescs: []*signrpc.SignDescriptor{{ - Output: &signrpc.TxOut{ - PkScript: targetScript, - Value: 800_000, - }, - InputIndex: 0, - KeyDesc: keyDesc, - Sighash: uint32(txscript.SigHashAll), - WitnessScript: targetScript, - }}, - }, - ) - require.NoError(t.t, err) + signReq := &signrpc.SignReq{ + RawTxBytes: buf.Bytes(), + SignDescs: []*signrpc.SignDescriptor{{ + Output: &signrpc.TxOut{ + PkScript: targetScript, + Value: 800_000, + }, + InputIndex: 0, + KeyDesc: keyDesc, + Sighash: uint32(txscript.SigHashAll), + WitnessScript: targetScript, + }}, + } + signResp := alice.RPC.SignOutputRaw(signReq) tx.TxIn[0].Witness = wire.TxWitness{ append(signResp.RawSigs[0], byte(txscript.SigHashAll)), @@ -371,68 +338,38 @@ func assertSignOutputRaw(t *harnessTest, net *lntest.NetworkHarness, } buf.Reset() - require.NoError(t.t, tx.Serialize(&buf)) + require.NoError(ht, tx.Serialize(&buf)) - _, err = alice.WalletKitClient.PublishTransaction( - ctxt, &walletrpc.Transaction{ - TxHex: buf.Bytes(), - }, - ) - require.NoError(t.t, err) + alice.RPC.PublishTransaction(&walletrpc.Transaction{ + TxHex: buf.Bytes(), + }) // Wait until the spending tx is found. - txid, err = waitForTxInMempool(net.Miner.Client, minerMempoolTimeout) - require.NoError(t.t, err) - p2wkhOutputIndex := getOutputIndex( - t, net.Miner, txid, p2wkhAdrr.String(), - ) + txid = ht.Miner.AssertNumTxsInMempool(1)[0] + p2wkhOutputIndex := ht.GetOutputIndex(txid, p2wkhAdrr.String()) + op := &lnrpc.OutPoint{ TxidBytes: txid[:], OutputIndex: uint32(p2wkhOutputIndex), } - assertWalletUnspent(t, alice, op, "") + ht.AssertUTXOInWallet(alice, op, "") - // Mine another block to clean up the mempool and to make sure the spend - // tx is actually included in a block. - mineBlocks(t, net, 1, 1) -} - -// deriveCustomizedKey uses the family and index to derive a public key from -// the node's walletkit client. -func deriveCustomizedKey(ctx context.Context, node *lntest.HarnessNode, - keyLoc *signrpc.KeyLocator) (*btcec.PublicKey, error) { - - ctxt, cancel := context.WithTimeout(ctx, defaultTimeout) - defer cancel() - - resp, err := node.WalletKitClient.DeriveKey(ctxt, keyLoc) - if err != nil { - return nil, fmt.Errorf("failed to derive key: %v", err) - } - pub, err := btcec.ParsePubKey(resp.RawKeyBytes) - if err != nil { - return nil, fmt.Errorf("failed to parse node pubkey: %v", err) - } - return pub, nil + // Mine another block to clean up the mempool and to make sure the + // spend tx is actually included in a block. + ht.MineBlocksAndAssertNumTxes(1, 1) } // testSignVerifyMessage makes sure that the SignMessage RPC can be used with // all custom flags by verifying with VerifyMessage. Tests both ECDSA and // Schnorr signatures. -func testSignVerifyMessage(net *lntest.NetworkHarness, t *harnessTest) { - runSignVerifyMessage(t, net, net.Alice) +func testSignVerifyMessage(ht *lntemp.HarnessTest) { + runSignVerifyMessage(ht, ht.Alice) } -// runSignVerifyMessage makes sure that the SignMessage RPC can be used with all -// custom flags by verifying with VerifyMessage. Tests both ECDSA and Schnorr -// signatures. -func runSignVerifyMessage(t *harnessTest, net *lntest.NetworkHarness, - alice *lntest.HarnessNode) { - - ctxb := context.Background() - ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) - defer cancel() - +// runSignVerifyMessage makes sure that the SignMessage RPC can be used with +// all custom flags by verifying with VerifyMessage. Tests both ECDSA and +// Schnorr signatures. +func runSignVerifyMessage(ht *lntemp.HarnessTest, alice *node.HarnessNode) { aliceMsg := []byte("alice msg") keyLoc := &signrpc.KeyLocator{ KeyFamily: int32(keychain.KeyFamilyNodeKey), @@ -446,11 +383,17 @@ func runSignVerifyMessage(t *harnessTest, net *lntest.NetworkHarness, SchnorrSig: false, } - signMsgResp, err := alice.SignerClient.SignMessage(ctxt, signMsgReq) - require.NoError(t.t, err, "failed to sign message") + signMsgResp := alice.RPC.SignMessageSigner(signMsgReq) - customPubKey, err := deriveCustomizedKey(ctxt, alice, keyLoc) - require.NoError(t.t, err, "failed to create customized pubkey") + deriveCustomizedKey := func() *btcec.PublicKey { + resp := alice.RPC.DeriveKey(keyLoc) + pub, err := btcec.ParsePubKey(resp.RawKeyBytes) + require.NoError(ht, err, "failed to parse node pubkey") + + return pub + } + + customPubKey := deriveCustomizedKey() verifyReq := &signrpc.VerifyMessageReq{ Msg: aliceMsg, @@ -458,10 +401,8 @@ func runSignVerifyMessage(t *harnessTest, net *lntest.NetworkHarness, Pubkey: customPubKey.SerializeCompressed(), IsSchnorrSig: false, } - verifyResp, err := alice.SignerClient.VerifyMessage(ctxt, verifyReq) - require.NoError(t.t, err) - - require.True(t.t, verifyResp.Valid, "failed to verify message") + verifyResp := alice.RPC.VerifyMessageSigner(verifyReq) + require.True(ht, verifyResp.Valid, "failed to verify message") // Use a different key locator. keyLoc = &signrpc.KeyLocator{ @@ -475,12 +416,8 @@ func runSignVerifyMessage(t *harnessTest, net *lntest.NetworkHarness, KeyLoc: keyLoc, SchnorrSig: true, } - - signMsgResp, err = alice.SignerClient.SignMessage(ctxt, signMsgReq) - require.NoError(t.t, err) - - customPubKey, err = deriveCustomizedKey(ctxt, alice, keyLoc) - require.NoError(t.t, err, "failed to create customized pubkey") + signMsgResp = alice.RPC.SignMessageSigner(signMsgReq) + customPubKey = deriveCustomizedKey() // Verify the Schnorr signature. verifyReq = &signrpc.VerifyMessageReq{ @@ -489,10 +426,8 @@ func runSignVerifyMessage(t *harnessTest, net *lntest.NetworkHarness, Pubkey: schnorr.SerializePubKey(customPubKey), IsSchnorrSig: true, } - verifyResp, err = alice.SignerClient.VerifyMessage(ctxt, verifyReq) - require.NoError(t.t, err) - - require.True(t.t, verifyResp.Valid, "failed to verify message") + verifyResp = alice.RPC.VerifyMessageSigner(verifyReq) + require.True(ht, verifyResp.Valid, "failed to verify message") // Also test that we can tweak a private key and verify the message // against the tweaked public key. @@ -502,8 +437,7 @@ func runSignVerifyMessage(t *harnessTest, net *lntest.NetworkHarness, ) signMsgReq.SchnorrSigTapTweak = tweakBytes[:] - signMsgResp, err = alice.SignerClient.SignMessage(ctxt, signMsgReq) - require.NoError(t.t, err) + signMsgResp = alice.RPC.SignMessageSigner(signMsgReq) verifyReq = &signrpc.VerifyMessageReq{ Msg: aliceMsg, @@ -511,8 +445,6 @@ func runSignVerifyMessage(t *harnessTest, net *lntest.NetworkHarness, Pubkey: schnorr.SerializePubKey(tweakedPubKey), IsSchnorrSig: true, } - verifyResp, err = alice.SignerClient.VerifyMessage(ctxt, verifyReq) - require.NoError(t.t, err) - - require.True(t.t, verifyResp.Valid, "failed to verify message") + verifyResp = alice.RPC.VerifyMessageSigner(verifyReq) + require.True(ht, verifyResp.Valid, "failed to verify message") } diff --git a/lntest/itest/lnd_switch_test.go b/lntest/itest/lnd_switch_test.go index a3c8ee637..fd90be4a7 100644 --- a/lntest/itest/lnd_switch_test.go +++ b/lntest/itest/lnd_switch_test.go @@ -1,17 +1,19 @@ package itest import ( - "context" - "time" - "github.com/btcsuite/btcd/btcutil" - "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/lnrpc" - "github.com/lightningnetwork/lnd/lntest" - "github.com/lightningnetwork/lnd/lntest/wait" + "github.com/lightningnetwork/lnd/lntemp" + "github.com/lightningnetwork/lnd/lntemp/node" "github.com/stretchr/testify/require" ) +const ( + numPayments = 5 + paymentAmt = 1000 + baseFee = 1 +) + // testSwitchCircuitPersistence creates a multihop network to ensure the sender // and intermediaries are persisting their open payment circuits. After // forwarding a packet via an outgoing link, all are restarted, and expected to @@ -19,228 +21,51 @@ import ( // // The general flow of this test: // 1. Carol --> Dave --> Alice --> Bob forward payment -// 2. X X X Bob restart sender and intermediaries +// 2. -------X X X Bob restart sender and intermediaries // 3. Carol <-- Dave <-- Alice <-- Bob expect settle to propagate -func testSwitchCircuitPersistence(net *lntest.NetworkHarness, t *harnessTest) { - ctxb := context.Background() - - const chanAmt = btcutil.Amount(1000000) - const pushAmt = btcutil.Amount(900000) - 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, - PushAmt: pushAmt, - }, - ) - 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 ndoe, 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 := net.NewNode(t.t, "Dave", nil) - defer shutdownAndAssert(net, t, 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, - PushAmt: pushAmt, - }, - ) - 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 is started in htlchodl mode so that we can disconnect the - // intermediary hops before starting the settle. - carol := net.NewNode(t.t, "Carol", []string{"--hodl.exit-settle"}) - 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, - PushAmt: pushAmt, - }, - ) - 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 Carol, which expect a payment from Bob for 1k - // satoshis with a different preimage each time. - const numPayments = 5 - const paymentAmt = 1000 - payReqs, _, _, err := createPayReqs( - carol, 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) - - // Using Carol as the source, pay to the 5 invoices from Bob created - // above. - err = completePaymentRequests( - net.Bob, net.Bob.RouterClient, payReqs, false, - ) - if err != nil { - t.Fatalf("unable to send payments: %v", err) - } - - // Wait until all nodes in the network have 5 outstanding htlcs. - var predErr error - err = wait.Predicate(func() bool { - predErr = assertNumActiveHtlcs(nodes, numPayments) - return predErr == nil - }, defaultTimeout) - if err != nil { - t.Fatalf("htlc mismatch: %v", predErr) - } +// +//nolint:dupword +func testSwitchCircuitPersistence(ht *lntemp.HarnessTest) { + // Setup our test scenario. We should now have four nodes running with + // three channels. + s := setupScenarioFourNodes(ht) + defer s.cleanUp() // Restart the intermediaries and the sender. - if err := net.RestartNode(dave, nil); err != nil { - t.Fatalf("Node restart failed: %v", err) - } - - if err := net.RestartNode(net.Alice, nil); err != nil { - t.Fatalf("Node restart failed: %v", err) - } - - if err := net.RestartNode(net.Bob, nil); err != nil { - t.Fatalf("Node restart failed: %v", err) - } + ht.RestartNode(s.dave) + ht.RestartNode(s.alice) + ht.RestartNode(s.bob) // Ensure all of the intermediate links are reconnected. - net.EnsureConnected(t.t, net.Alice, dave) - net.EnsureConnected(t.t, net.Bob, net.Alice) + ht.EnsureConnected(s.alice, s.dave) + ht.EnsureConnected(s.bob, s.alice) // Ensure all nodes in the network still have 5 outstanding htlcs. - err = wait.Predicate(func() bool { - predErr = assertNumActiveHtlcs(nodes, numPayments) - return predErr == nil - }, defaultTimeout) - if err != nil { - t.Fatalf("htlc mismatch: %v", predErr) - } + s.assertHTLCs(ht, numPayments) // Now restart carol without hodl mode, to settle back the outstanding // payments. - carol.SetExtraArgs(nil) - if err := net.RestartNode(carol, nil); err != nil { - t.Fatalf("Node restart failed: %v", err) - } + s.carol.SetExtraArgs(nil) + ht.RestartNode(s.carol) - net.EnsureConnected(t.t, dave, carol) + ht.EnsureConnected(s.dave, s.carol) // After the payments settle, there should be no active htlcs on any of // the nodes in the network. - err = wait.Predicate(func() bool { - predErr = assertNumActiveHtlcs(nodes, 0) - return predErr == nil - }, defaultTimeout) - if err != nil { - t.Fatalf("htlc mismatch: %v", predErr) - } + s.assertHTLCs(ht, 0) // When asserting the amount of satoshis moved, we'll factor in the // default base fee, as we didn't modify the fee structure when // creating the seed nodes in the network. - const baseFee = 1 // At this point all the channels within our proto network should be - // shifted by 5k satoshis in the direction of Carol, the sink within the - // payment flow generated above. The order of asserts corresponds to - // increasing of time is needed to embed the HTLC in commitment + // 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 Bob->Alice->David->Carol, order is Carol, // David, Alice, Bob. var amountPaid = int64(5000) - assertAmountPaid(t, "Dave(local) => Carol(remote)", carol, - carolFundPoint, int64(0), amountPaid) - assertAmountPaid(t, "Dave(local) => Carol(remote)", dave, - carolFundPoint, amountPaid, int64(0)) - assertAmountPaid(t, "Alice(local) => Dave(remote)", dave, - daveFundPoint, int64(0), amountPaid+(baseFee*numPayments)) - assertAmountPaid(t, "Alice(local) => Dave(remote)", net.Alice, - daveFundPoint, amountPaid+(baseFee*numPayments), int64(0)) - assertAmountPaid(t, "Bob(local) => Alice(remote)", net.Alice, - aliceFundPoint, int64(0), amountPaid+((baseFee*numPayments)*2)) - assertAmountPaid(t, "Bob(local) => Alice(remote)", net.Bob, - aliceFundPoint, amountPaid+(baseFee*numPayments)*2, int64(0)) + s.assertAmoutPaid(ht, amountPaid, numPayments) // Lastly, we will send one more payment to ensure all channels are // still functioning properly. @@ -248,40 +73,15 @@ func testSwitchCircuitPersistence(net *lntest.NetworkHarness, t *harnessTest) { Memo: "testing", Value: paymentAmt, } - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - resp, err := carol.AddInvoice(ctxt, finalInvoice) - if err != nil { - t.Fatalf("unable to add invoice: %v", err) - } - - payReqs = []string{resp.PaymentRequest} + resp := s.carol.RPC.AddInvoice(finalInvoice) + payReqs := []string{resp.PaymentRequest} // Using Carol as the source, pay to the 5 invoices from Bob created // above. - err = completePaymentRequests( - net.Bob, net.Bob.RouterClient, payReqs, true, - ) - if err != nil { - t.Fatalf("unable to send payments: %v", err) - } + ht.CompletePaymentRequests(s.bob, payReqs) amountPaid = int64(6000) - assertAmountPaid(t, "Dave(local) => Carol(remote)", carol, - carolFundPoint, int64(0), amountPaid) - assertAmountPaid(t, "Dave(local) => Carol(remote)", dave, - carolFundPoint, amountPaid, int64(0)) - assertAmountPaid(t, "Alice(local) => Dave(remote)", dave, - daveFundPoint, int64(0), amountPaid+(baseFee*(numPayments+1))) - assertAmountPaid(t, "Alice(local) => Dave(remote)", net.Alice, - daveFundPoint, amountPaid+(baseFee*(numPayments+1)), int64(0)) - assertAmountPaid(t, "Bob(local) => Alice(remote)", net.Alice, - aliceFundPoint, int64(0), amountPaid+((baseFee*(numPayments+1))*2)) - assertAmountPaid(t, "Bob(local) => Alice(remote)", net.Bob, - aliceFundPoint, amountPaid+(baseFee*(numPayments+1))*2, int64(0)) - - closeChannelAndAssert(t, net, net.Alice, chanPointAlice, false) - closeChannelAndAssert(t, net, dave, chanPointDave, false) - closeChannelAndAssert(t, net, carol, chanPointCarol, false) + s.assertAmoutPaid(ht, amountPaid, numPayments+1) } // testSwitchOfflineDelivery constructs a set of multihop payments, and tests @@ -295,244 +95,41 @@ func testSwitchCircuitPersistence(net *lntest.NetworkHarness, t *harnessTest) { // 2. Carol --- Dave X Alice --- Bob disconnect intermediaries // 3. Carol --- Dave X Alice <-- Bob settle last hop // 4. Carol <-- Dave <-- Alice --- Bob reconnect, expect settle to propagate -func testSwitchOfflineDelivery(net *lntest.NetworkHarness, t *harnessTest) { - ctxb := context.Background() - - const chanAmt = btcutil.Amount(1000000) - const pushAmt = btcutil.Amount(900000) - 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, - PushAmt: pushAmt, - }, - ) - 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 ndoe, 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 := net.NewNode(t.t, "Dave", nil) - defer shutdownAndAssert(net, t, 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, - PushAmt: pushAmt, - }, - ) - 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 is started in htlchodl mode so that we can disconnect the - // intermediary hops before starting the settle. - carol := net.NewNode(t.t, "Carol", []string{"--hodl.exit-settle"}) - 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, - PushAmt: pushAmt, - }, - ) - 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 Carol, which expect a payment from Bob for 1k - // satoshis with a different preimage each time. - const numPayments = 5 - const paymentAmt = 1000 - payReqs, _, _, err := createPayReqs( - carol, 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) - } - - // Make sure all nodes are fully synced before we continue. - for _, node := range nodes { - err := node.WaitForBlockchainSync() - if err != nil { - t.Fatalf("unable to wait for sync: %v", err) - } - } - - // Using Carol as the source, pay to the 5 invoices from Bob created - // above. - err = completePaymentRequests( - net.Bob, net.Bob.RouterClient, payReqs, false, - ) - if err != nil { - t.Fatalf("unable to send payments: %v", err) - } - - // Wait for all of the payments to reach Carol. - var predErr error - err = wait.Predicate(func() bool { - predErr = assertNumActiveHtlcs(nodes, numPayments) - return predErr == nil - }, defaultTimeout) - if err != nil { - t.Fatalf("htlc mismatch: %v", predErr) - } - - peerReq := &lnrpc.PeerEventSubscription{} - peerClient, err := dave.SubscribePeerEvents(ctxb, peerReq) - require.NoError(t.t, err) +func testSwitchOfflineDelivery(ht *lntemp.HarnessTest) { + // Setup our test scenario. We should now have four nodes running with + // three channels. + s := setupScenarioFourNodes(ht) + defer s.cleanUp() // First, disconnect Dave and Alice so that their link is broken. - if err := net.DisconnectNodes(dave, net.Alice); err != nil { - t.Fatalf("unable to disconnect alice from dave: %v", err) - } - - // Wait to receive the PEER_OFFLINE event before reconnecting them. - peerEvent, err := peerClient.Recv() - require.NoError(t.t, err) - require.Equal(t.t, lnrpc.PeerEvent_PEER_OFFLINE, peerEvent.GetType()) + ht.DisconnectNodes(s.dave, s.alice) // Then, reconnect them to ensure Dave doesn't just fail back the htlc. - // We use EnsureConnected here in case they have already re-connected. - net.EnsureConnected(t.t, dave, net.Alice) + ht.ConnectNodes(s.dave, s.alice) // Wait to ensure that the payment remain are not failed back after // reconnecting. All node should report the number payments initiated // for the duration of the interval. - err = wait.Invariant(func() bool { - predErr = assertNumActiveHtlcs(nodes, numPayments) - return predErr == nil - }, defaultTimeout) - if err != nil { - t.Fatalf("htlc change: %v", predErr) - } + s.assertHTLCs(ht, numPayments) // Now, disconnect Dave from Alice again before settling back the // payment. - if err := net.DisconnectNodes(dave, net.Alice); err != nil { - t.Fatalf("unable to disconnect alice from dave: %v", err) - } - - // Wait to receive the PEER_ONLINE and then the PEER_OFFLINE event - // before advancing. - peerEvent2, err := peerClient.Recv() - require.NoError(t.t, err) - require.Equal(t.t, lnrpc.PeerEvent_PEER_ONLINE, peerEvent2.GetType()) - - peerEvent3, err := peerClient.Recv() - require.NoError(t.t, err) - require.Equal(t.t, lnrpc.PeerEvent_PEER_OFFLINE, peerEvent3.GetType()) + ht.DisconnectNodes(s.dave, s.alice) // Now restart carol without hodl mode, to settle back the outstanding // payments. - carol.SetExtraArgs(nil) - if err := net.RestartNode(carol, nil); err != nil { - t.Fatalf("Node restart failed: %v", err) - } + s.carol.SetExtraArgs(nil) + ht.RestartNode(s.carol) // Wait for Carol to report no outstanding htlcs. - carolNode := []*lntest.HarnessNode{carol} - err = wait.Predicate(func() bool { - predErr = assertNumActiveHtlcs(carolNode, 0) - return predErr == nil - }, defaultTimeout) - if err != nil { - t.Fatalf("htlc mismatch: %v", predErr) - } - - // Make sure all nodes are fully synced again. - for _, node := range nodes { - err := node.WaitForBlockchainSync() - if err != nil { - t.Fatalf("unable to wait for sync: %v", err) - } - } + ht.AssertNumActiveHtlcs(s.carol, 0) // Now that the settles have reached Dave, reconnect him with Alice, // allowing the settles to return to the sender. - net.EnsureConnected(t.t, dave, net.Alice) + ht.EnsureConnected(s.dave, s.alice) // Wait until all outstanding htlcs in the network have been settled. - err = wait.Predicate(func() bool { - return assertNumActiveHtlcs(nodes, 0) == nil - }, defaultTimeout) - if err != nil { - t.Fatalf("htlc mismatch: %v", predErr) - } + s.assertHTLCs(ht, 0) // When asserting the amount of satoshis moved, we'll factor in the // default base fee, as we didn't modify the fee structure when @@ -546,18 +143,7 @@ func testSwitchOfflineDelivery(net *lntest.NetworkHarness, t *harnessTest) { // transaction, in channel Bob->Alice->David->Carol, order is Carol, // David, Alice, Bob. var amountPaid = int64(5000) - assertAmountPaid(t, "Dave(local) => Carol(remote)", carol, - carolFundPoint, int64(0), amountPaid) - assertAmountPaid(t, "Dave(local) => Carol(remote)", dave, - carolFundPoint, amountPaid, int64(0)) - assertAmountPaid(t, "Alice(local) => Dave(remote)", dave, - daveFundPoint, int64(0), amountPaid+(baseFee*numPayments)) - assertAmountPaid(t, "Alice(local) => Dave(remote)", net.Alice, - daveFundPoint, amountPaid+(baseFee*numPayments), int64(0)) - assertAmountPaid(t, "Bob(local) => Alice(remote)", net.Alice, - aliceFundPoint, int64(0), amountPaid+((baseFee*numPayments)*2)) - assertAmountPaid(t, "Bob(local) => Alice(remote)", net.Bob, - aliceFundPoint, amountPaid+(baseFee*numPayments)*2, int64(0)) + s.assertAmoutPaid(ht, amountPaid, numPayments) // Lastly, we will send one more payment to ensure all channels are // still functioning properly. @@ -565,40 +151,15 @@ func testSwitchOfflineDelivery(net *lntest.NetworkHarness, t *harnessTest) { Memo: "testing", Value: paymentAmt, } - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - resp, err := carol.AddInvoice(ctxt, finalInvoice) - if err != nil { - t.Fatalf("unable to add invoice: %v", err) - } - - payReqs = []string{resp.PaymentRequest} + resp := s.carol.RPC.AddInvoice(finalInvoice) + payReqs := []string{resp.PaymentRequest} // Using Carol as the source, pay to the 5 invoices from Bob created // above. - err = completePaymentRequests( - net.Bob, net.Bob.RouterClient, payReqs, true, - ) - if err != nil { - t.Fatalf("unable to send payments: %v", err) - } + ht.CompletePaymentRequests(s.bob, payReqs) amountPaid = int64(6000) - assertAmountPaid(t, "Dave(local) => Carol(remote)", carol, - carolFundPoint, int64(0), amountPaid) - assertAmountPaid(t, "Dave(local) => Carol(remote)", dave, - carolFundPoint, amountPaid, int64(0)) - assertAmountPaid(t, "Alice(local) => Dave(remote)", dave, - daveFundPoint, int64(0), amountPaid+(baseFee*(numPayments+1))) - assertAmountPaid(t, "Alice(local) => Dave(remote)", net.Alice, - daveFundPoint, amountPaid+(baseFee*(numPayments+1)), int64(0)) - assertAmountPaid(t, "Bob(local) => Alice(remote)", net.Alice, - aliceFundPoint, int64(0), amountPaid+((baseFee*(numPayments+1))*2)) - assertAmountPaid(t, "Bob(local) => Alice(remote)", net.Bob, - aliceFundPoint, amountPaid+(baseFee*(numPayments+1))*2, int64(0)) - - closeChannelAndAssert(t, net, net.Alice, chanPointAlice, false) - closeChannelAndAssert(t, net, dave, chanPointDave, false) - closeChannelAndAssert(t, net, carol, chanPointCarol, false) + s.assertAmoutPaid(ht, amountPaid, numPayments+1) } // testSwitchOfflineDeliveryPersistence constructs a set of multihop payments, @@ -613,214 +174,48 @@ func testSwitchOfflineDelivery(net *lntest.NetworkHarness, t *harnessTest) { // 3. Carol --- Dave X Alice <-- Bob settle last hop // 4. Carol --- Dave X X Bob restart Alice // 5. Carol <-- Dave <-- Alice --- Bob expect settle to propagate -func testSwitchOfflineDeliveryPersistence(net *lntest.NetworkHarness, t *harnessTest) { - ctxb := context.Background() - - const chanAmt = btcutil.Amount(1000000) - const pushAmt = btcutil.Amount(900000) - 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, - PushAmt: pushAmt, - }, - ) - 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 ndoe, 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 := net.NewNode(t.t, "Dave", nil) - defer shutdownAndAssert(net, t, 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, - PushAmt: pushAmt, - }, - ) - - 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 is started in htlchodl mode so that we can disconnect the - // intermediary hops before starting the settle. - carol := net.NewNode(t.t, "Carol", []string{"--hodl.exit-settle"}) - 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, - PushAmt: pushAmt, - }, - ) - 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 Carol, which expect a payment from Bob for 1k - // satoshis with a different preimage each time. - const numPayments = 5 - const paymentAmt = 1000 - payReqs, _, _, err := createPayReqs( - carol, 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) - } - - // Using Carol as the source, pay to the 5 invoices from Bob created - // above. - err = completePaymentRequests( - net.Bob, net.Bob.RouterClient, payReqs, false, - ) - if err != nil { - t.Fatalf("unable to send payments: %v", err) - } - - var predErr error - err = wait.Predicate(func() bool { - predErr = assertNumActiveHtlcs(nodes, numPayments) - return predErr == nil - }, defaultTimeout) - if err != nil { - t.Fatalf("htlc mismatch: %v", predErr) - } +func testSwitchOfflineDeliveryPersistence(ht *lntemp.HarnessTest) { + // Setup our test scenario. We should now have four nodes running with + // three channels. + s := setupScenarioFourNodes(ht) + defer s.cleanUp() // Disconnect the two intermediaries, Alice and Dave, by shutting down // Alice. - if err := net.StopNode(net.Alice); err != nil { - t.Fatalf("unable to shutdown alice: %v", err) - } + restartAlice := ht.SuspendNode(s.alice) // Now restart carol without hodl mode, to settle back the outstanding // payments. - carol.SetExtraArgs(nil) - if err := net.RestartNode(carol, nil); err != nil { - t.Fatalf("Node restart failed: %v", err) - } + s.carol.SetExtraArgs(nil) + ht.RestartNode(s.carol) // Make Carol and Dave are reconnected before waiting for the htlcs to // clear. - net.EnsureConnected(t.t, dave, carol) + ht.EnsureConnected(s.dave, s.carol) - // Wait for Carol to report no outstanding htlcs, and also for Dav to + // Wait for Carol to report no outstanding htlcs, and also for Dave to // receive all the settles from Carol. - carolNode := []*lntest.HarnessNode{carol} - err = wait.Predicate(func() bool { - predErr = assertNumActiveHtlcs(carolNode, 0) - if predErr != nil { - return false - } - - predErr = assertNumActiveHtlcsChanPoint(dave, carolFundPoint, 0) - return predErr == nil - }, defaultTimeout) - if err != nil { - t.Fatalf("htlc mismatch: %v", predErr) - } + ht.AssertNumActiveHtlcs(s.carol, 0) + // As an intermediate node, Dave should now have zero outgoing HTLCs + // and 5 incoming HTLCs from Alice. + ht.AssertNumActiveHtlcs(s.dave, numPayments) // Finally, restart dave who received the settles, but was unable to // deliver them to Alice since they were disconnected. - if err := net.RestartNode(dave, nil); err != nil { - t.Fatalf("unable to restart dave: %v", err) - } - if err = net.RestartNode(net.Alice, nil); err != nil { - t.Fatalf("unable to restart alice: %v", err) - } + ht.RestartNode(s.dave) + require.NoError(ht, restartAlice(), "restart alice failed") // Force Dave and Alice to reconnect before waiting for the htlcs to // clear. - net.EnsureConnected(t.t, dave, net.Alice) + ht.EnsureConnected(s.dave, s.alice) // After reconnection succeeds, the settles should be propagated all // the way back to the sender. All nodes should report no active htlcs. - err = wait.Predicate(func() bool { - return assertNumActiveHtlcs(nodes, 0) == nil - }, defaultTimeout) - if err != nil { - t.Fatalf("htlc mismatch: %v", predErr) - } + s.assertHTLCs(ht, 0) // When asserting the amount of satoshis moved, we'll factor in the // default base fee, as we didn't modify the fee structure when // creating the seed nodes in the network. - const baseFee = 1 // At this point all the channels within our proto network should be // shifted by 5k satoshis in the direction of Carol, the sink within the @@ -829,18 +224,7 @@ func testSwitchOfflineDeliveryPersistence(net *lntest.NetworkHarness, t *harness // transaction, in channel Bob->Alice->David->Carol, order is Carol, // David, Alice, Bob. var amountPaid = int64(5000) - assertAmountPaid(t, "Dave(local) => Carol(remote)", carol, - carolFundPoint, int64(0), amountPaid) - assertAmountPaid(t, "Dave(local) => Carol(remote)", dave, - carolFundPoint, amountPaid, int64(0)) - assertAmountPaid(t, "Alice(local) => Dave(remote)", dave, - daveFundPoint, int64(0), amountPaid+(baseFee*numPayments)) - assertAmountPaid(t, "Alice(local) => Dave(remote)", net.Alice, - daveFundPoint, amountPaid+(baseFee*numPayments), int64(0)) - assertAmountPaid(t, "Bob(local) => Alice(remote)", net.Alice, - aliceFundPoint, int64(0), amountPaid+((baseFee*numPayments)*2)) - assertAmountPaid(t, "Bob(local) => Alice(remote)", net.Bob, - aliceFundPoint, amountPaid+(baseFee*numPayments)*2, int64(0)) + s.assertAmoutPaid(ht, amountPaid, numPayments) // Lastly, we will send one more payment to ensure all channels are // still functioning properly. @@ -848,51 +232,27 @@ func testSwitchOfflineDeliveryPersistence(net *lntest.NetworkHarness, t *harness Memo: "testing", Value: paymentAmt, } - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - resp, err := carol.AddInvoice(ctxt, finalInvoice) - if err != nil { - t.Fatalf("unable to add invoice: %v", err) - } - - payReqs = []string{resp.PaymentRequest} + resp := s.carol.RPC.AddInvoice(finalInvoice) + payReqs := []string{resp.PaymentRequest} // Before completing the final payment request, ensure that the // connection between Dave and Carol has been healed. - net.EnsureConnected(t.t, dave, carol) + ht.EnsureConnected(s.dave, s.carol) // Using Carol as the source, pay to the 5 invoices from Bob created // above. - err = completePaymentRequests( - net.Bob, net.Bob.RouterClient, payReqs, true, - ) - if err != nil { - t.Fatalf("unable to send payments: %v", err) - } + ht.CompletePaymentRequests(s.bob, payReqs) amountPaid = int64(6000) - assertAmountPaid(t, "Dave(local) => Carol(remote)", carol, - carolFundPoint, int64(0), amountPaid) - assertAmountPaid(t, "Dave(local) => Carol(remote)", dave, - carolFundPoint, amountPaid, int64(0)) - assertAmountPaid(t, "Alice(local) => Dave(remote)", dave, - daveFundPoint, int64(0), amountPaid+(baseFee*(numPayments+1))) - assertAmountPaid(t, "Alice(local) => Dave(remote)", net.Alice, - daveFundPoint, amountPaid+(baseFee*(numPayments+1)), int64(0)) - assertAmountPaid(t, "Bob(local) => Alice(remote)", net.Alice, - aliceFundPoint, int64(0), amountPaid+((baseFee*(numPayments+1))*2)) - assertAmountPaid(t, "Bob(local) => Alice(remote)", net.Bob, - aliceFundPoint, amountPaid+(baseFee*(numPayments+1))*2, int64(0)) - - closeChannelAndAssert(t, net, net.Alice, chanPointAlice, false) - closeChannelAndAssert(t, net, dave, chanPointDave, false) - closeChannelAndAssert(t, net, carol, chanPointCarol, false) + s.assertAmoutPaid(ht, amountPaid, numPayments+1) } -// testSwitchOfflineDeliveryOutgoingOffline constructs a set of multihop payments, -// and tests that the returning payments are not lost if a peer on the backwards -// path is offline when the settle/fails are received AND the peer buffering the -// responses is completely restarts. We expect the payments to be reloaded from -// disk, and transmitted as soon as the intermediaries are reconnected. +// testSwitchOfflineDeliveryOutgoingOffline constructs a set of multihop +// payments, and tests that the returning payments are not lost if a peer on +// the backwards path is offline when the settle/fails are received AND the +// peer buffering the responses is completely restarts. We expect the payments +// to be reloaded from disk, and transmitted as soon as the intermediaries are +// reconnected. // // The general flow of this test: // 1. Carol --> Dave --> Alice --> Bob forward payment @@ -900,237 +260,247 @@ func testSwitchOfflineDeliveryPersistence(net *lntest.NetworkHarness, t *harness // 3. Carol --- Dave X Alice <-- Bob settle last hop // 4. Carol --- Dave X X shutdown Bob, restart Alice // 5. Carol <-- Dave <-- Alice X expect settle to propagate -func testSwitchOfflineDeliveryOutgoingOffline( - net *lntest.NetworkHarness, t *harnessTest) { - - const chanAmt = btcutil.Amount(1000000) - const pushAmt = btcutil.Amount(900000) - 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, - PushAmt: pushAmt, - }, - ) - 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 ndoe, 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 := net.NewNode(t.t, "Dave", nil) - defer shutdownAndAssert(net, t, 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, - PushAmt: pushAmt, - }, - ) - 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 is started in htlchodl mode so that we can disconnect the - // intermediary hops before starting the settle. - carol := net.NewNode(t.t, "Carol", []string{"--hodl.exit-settle"}) - net.ConnectNodes(t.t, carol, dave) - net.SendCoins(t.t, btcutil.SatoshiPerBitcoin, carol) - - chanPointCarol := openChannelAndAssert( - t, net, carol, dave, - lntest.OpenChannelParams{ - Amt: chanAmt, - PushAmt: pushAmt, - }, - ) - 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 Carol, which expect a payment from Bob for 1k - // satoshis with a different preimage each time. - const numPayments = 5 - const paymentAmt = 1000 - payReqs, _, _, err := createPayReqs( - carol, 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) - } - - // Using Carol as the source, pay to the 5 invoices from Bob created - // above. - err = completePaymentRequests( - net.Bob, net.Bob.RouterClient, payReqs, false, - ) - if err != nil { - t.Fatalf("unable to send payments: %v", err) - } - - // Wait for all payments to reach Carol. - var predErr error - err = wait.Predicate(func() bool { - return assertNumActiveHtlcs(nodes, numPayments) == nil - }, defaultTimeout) - if err != nil { - t.Fatalf("htlc mismatch: %v", predErr) - } +func testSwitchOfflineDeliveryOutgoingOffline(ht *lntemp.HarnessTest) { + // Setup our test scenario. We should now have four nodes running with + // three channels. Note that we won't call the cleanUp function here as + // we will manually stop the node Carol and her channel. + s := setupScenarioFourNodes(ht) // Disconnect the two intermediaries, Alice and Dave, so that when carol // restarts, the response will be held by Dave. - if err := net.StopNode(net.Alice); err != nil { - t.Fatalf("unable to shutdown alice: %v", err) - } + restartAlice := ht.SuspendNode(s.alice) // Now restart carol without hodl mode, to settle back the outstanding // payments. - carol.SetExtraArgs(nil) - if err := net.RestartNode(carol, nil); err != nil { - t.Fatalf("Node restart failed: %v", err) - } + s.carol.SetExtraArgs(nil) + ht.RestartNode(s.carol) // Wait for Carol to report no outstanding htlcs. - carolNode := []*lntest.HarnessNode{carol} - err = wait.Predicate(func() bool { - predErr = assertNumActiveHtlcs(carolNode, 0) - if predErr != nil { - return false - } - - predErr = assertNumActiveHtlcsChanPoint(dave, carolFundPoint, 0) - return predErr == nil - }, defaultTimeout) - if err != nil { - t.Fatalf("htlc mismatch: %v", predErr) - } + ht.AssertNumActiveHtlcs(s.carol, 0) + // As an intermediate node, Dave should now have zero outgoing HTLCs + // and 5 incoming HTLCs from Alice. + ht.AssertNumActiveHtlcs(s.dave, numPayments) // Now check that the total amount was transferred from Dave to Carol. // The amount transferred should be exactly equal to the invoice total // payment amount, 5k satsohis. const amountPaid = int64(5000) - assertAmountPaid(t, "Dave(local) => Carol(remote)", carol, - carolFundPoint, int64(0), amountPaid) - assertAmountPaid(t, "Dave(local) => Carol(remote)", dave, - carolFundPoint, amountPaid, int64(0)) + ht.AssertAmountPaid( + "Dave(local) => Carol(remote)", s.carol, + s.chanPointCarolDave, int64(0), amountPaid, + ) + ht.AssertAmountPaid( + "Dave(local) => Carol(remote)", s.dave, + s.chanPointCarolDave, amountPaid, int64(0), + ) // Shutdown carol and leave her offline for the rest of the test. This // is critical, as we wish to see if Dave can propragate settles even if // the outgoing link is never revived. - shutdownAndAssert(net, t, carol) + ht.Shutdown(s.carol) // Now restart Dave, ensuring he is both persisting the settles, and is // able to reforward them to Alice after recovering from a restart. - if err := net.RestartNode(dave, nil); err != nil { - t.Fatalf("unable to restart dave: %v", err) - } - if err = net.RestartNode(net.Alice, nil); err != nil { - t.Fatalf("unable to restart alice: %v", err) - } + ht.RestartNode(s.dave) + require.NoErrorf(ht, restartAlice(), "restart alice failed") // Ensure that Dave is reconnected to Alice before waiting for the // htlcs to clear. - net.EnsureConnected(t.t, dave, net.Alice) + ht.EnsureConnected(s.dave, s.alice) // Since Carol has been shutdown permanently, we will wait until all // other nodes in the network report no active htlcs. - nodesMinusCarol := []*lntest.HarnessNode{net.Bob, net.Alice, dave} - err = wait.Predicate(func() bool { - predErr = assertNumActiveHtlcs(nodesMinusCarol, 0) - return predErr == nil - }, defaultTimeout) - if err != nil { - t.Fatalf("htlc mismatch: %v", predErr) - } + ht.AssertNumActiveHtlcs(s.alice, 0) + ht.AssertNumActiveHtlcs(s.bob, 0) + ht.AssertNumActiveHtlcs(s.dave, 0) // When asserting the amount of satoshis moved, we'll factor in the // default base fee, as we didn't modify the fee structure when // creating the seed nodes in the network. - const baseFee = 1 // At this point, all channels (minus Carol, who is shutdown) should // show a shift of 5k satoshis towards Carol. The order of asserts // corresponds to increasing of time is needed to embed the HTLC in // commitment transaction, in channel Bob->Alice->David, order is // David, Alice, Bob. - assertAmountPaid(t, "Alice(local) => Dave(remote)", dave, - daveFundPoint, int64(0), amountPaid+(baseFee*numPayments)) - assertAmountPaid(t, "Alice(local) => Dave(remote)", net.Alice, - daveFundPoint, amountPaid+(baseFee*numPayments), int64(0)) - assertAmountPaid(t, "Bob(local) => Alice(remote)", net.Alice, - aliceFundPoint, int64(0), amountPaid+((baseFee*numPayments)*2)) - assertAmountPaid(t, "Bob(local) => Alice(remote)", net.Bob, - aliceFundPoint, amountPaid+(baseFee*numPayments)*2, int64(0)) + ht.AssertAmountPaid( + "Alice(local) => Dave(remote)", s.dave, s.chanPointDaveAlice, + int64(0), amountPaid+(baseFee*numPayments), + ) + ht.AssertAmountPaid( + "Alice(local) => Dave(remote)", s.alice, s.chanPointDaveAlice, + amountPaid+(baseFee*numPayments), int64(0), + ) + ht.AssertAmountPaid( + "Bob(local) => Alice(remote)", s.alice, s.chanPointAliceBob, + int64(0), amountPaid+((baseFee*numPayments)*2), + ) + ht.AssertAmountPaid( + "Bob(local) => Alice(remote)", s.bob, s.chanPointAliceBob, + amountPaid+(baseFee*numPayments)*2, int64(0), + ) - closeChannelAndAssert(t, net, net.Alice, chanPointAlice, false) - closeChannelAndAssert(t, net, dave, chanPointDave, false) + ht.CloseChannel(s.alice, s.chanPointAliceBob) + ht.CloseChannel(s.dave, s.chanPointDaveAlice) +} + +// scenarioFourNodes specifies a scenario which we have a topology that has +// four nodes and three channels. +type scenarioFourNodes struct { + alice *node.HarnessNode + bob *node.HarnessNode + carol *node.HarnessNode + dave *node.HarnessNode + + chanPointAliceBob *lnrpc.ChannelPoint + chanPointCarolDave *lnrpc.ChannelPoint + chanPointDaveAlice *lnrpc.ChannelPoint + + cleanUp func() +} + +// setupScenarioFourNodes creates a topology for switch tests. It will create +// two new nodes: Carol and Dave, such that there will be a 4 nodes, 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 +// +// Once the network is created, Carol will generate 5 invoices and Bob will pay +// them using the above path. +// +// NOTE: caller needs to call cleanUp to clean the nodes and channels created +// from this setup. +func setupScenarioFourNodes(ht *lntemp.HarnessTest) *scenarioFourNodes { + const ( + chanAmt = btcutil.Amount(1000000) + pushAmt = btcutil.Amount(900000) + ) + + params := lntemp.OpenChannelParams{ + Amt: chanAmt, + PushAmt: pushAmt, + } + + // Grab the standby nodes. + alice, bob := ht.Alice, ht.Bob + + // 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 := ht.NewNode("Dave", nil) + ht.ConnectNodes(dave, alice) + ht.FundCoins(btcutil.SatoshiPerBitcoin, dave) + + // Next, we'll create Carol and establish a channel to from her to + // Dave. Carol is started in htlchodl mode so that we can disconnect + // the intermediary hops before starting the settle. + carol := ht.NewNode("Carol", []string{"--hodl.exit-settle"}) + ht.ConnectNodes(carol, dave) + ht.FundCoins(btcutil.SatoshiPerBitcoin, carol) + + // Open channels in batch to save blocks mined. + reqs := []*lntemp.OpenChannelRequest{ + {Local: alice, Remote: bob, Param: params}, + {Local: dave, Remote: alice, Param: params}, + {Local: carol, Remote: dave, Param: params}, + } + resp := ht.OpenMultiChannelsAsync(reqs) + + // Wait for all nodes to have seen all channels. + nodes := []*node.HarnessNode{alice, bob, carol, dave} + for _, chanPoint := range resp { + for _, node := range nodes { + ht.AssertTopologyChannelOpen(node, chanPoint) + } + } + + chanPointAliceBob := resp[0] + chanPointDaveAlice := resp[1] + chanPointCarolDave := resp[2] + + // Create 5 invoices for Carol, which expect a payment from Bob for 1k + // satoshis with a different preimage each time. + payReqs, _, _ := ht.CreatePayReqs(carol, paymentAmt, numPayments) + + // Using Carol as the source, pay to the 5 invoices from Bob created + // above. + ht.CompletePaymentRequestsNoWait(bob, payReqs, chanPointAliceBob) + + // Create a cleanUp to wipe the states. + cleanUp := func() { + if ht.Failed() { + ht.Skip("Skipped cleanup for failed test") + return + } + + ht.CloseChannel(alice, chanPointAliceBob) + ht.CloseChannel(dave, chanPointDaveAlice) + ht.CloseChannel(carol, chanPointCarolDave) + } + + s := &scenarioFourNodes{ + alice, bob, carol, dave, chanPointAliceBob, + chanPointCarolDave, chanPointDaveAlice, cleanUp, + } + + // Wait until all nodes in the network have 5 outstanding htlcs. + s.assertHTLCs(ht, numPayments) + + return s +} + +// assertHTLCs is a helper function which asserts the desired num of +// HTLCs has been seen in the nodes. +func (s *scenarioFourNodes) assertHTLCs(ht *lntemp.HarnessTest, num int) { + // Alice should have both the same number of outgoing and + // incoming HTLCs. + ht.AssertNumActiveHtlcs(s.alice, num*2) + // Bob should have num of incoming HTLCs. + ht.AssertNumActiveHtlcs(s.bob, num) + // Dave should have both the same number of outgoing and + // incoming HTLCs. + ht.AssertNumActiveHtlcs(s.dave, num*2) + // Carol should have the num of outgoing HTLCs. + ht.AssertNumActiveHtlcs(s.carol, num) +} + +// assertAmoutPaid is a helper method which takes a given paid amount +// and number of payments and asserts the desired payments are made in +// the four nodes. +func (s *scenarioFourNodes) assertAmoutPaid(ht *lntemp.HarnessTest, + amt int64, num int64) { + + ht.AssertAmountPaid( + "Dave(local) => Carol(remote)", s.carol, + s.chanPointCarolDave, int64(0), amt, + ) + ht.AssertAmountPaid( + "Dave(local) => Carol(remote)", s.dave, + s.chanPointCarolDave, amt, int64(0), + ) + ht.AssertAmountPaid( + "Alice(local) => Dave(remote)", s.dave, + s.chanPointDaveAlice, + int64(0), amt+(baseFee*num), + ) + ht.AssertAmountPaid( + "Alice(local) => Dave(remote)", s.alice, + s.chanPointDaveAlice, + amt+(baseFee*num), int64(0), + ) + ht.AssertAmountPaid( + "Bob(local) => Alice(remote)", s.alice, + s.chanPointAliceBob, + int64(0), amt+((baseFee*num)*2), + ) + ht.AssertAmountPaid( + "Bob(local) => Alice(remote)", s.bob, + s.chanPointAliceBob, + amt+(baseFee*num)*2, int64(0), + ) } diff --git a/lntest/itest/lnd_taproot_test.go b/lntest/itest/lnd_taproot_test.go index ad16cce7c..192ded4cd 100644 --- a/lntest/itest/lnd_taproot_test.go +++ b/lntest/itest/lnd_taproot_test.go @@ -2,7 +2,6 @@ package itest import ( "bytes" - "context" "crypto/sha256" "encoding/hex" "testing" @@ -21,7 +20,8 @@ import ( "github.com/lightningnetwork/lnd/lnrpc/chainrpc" "github.com/lightningnetwork/lnd/lnrpc/signrpc" "github.com/lightningnetwork/lnd/lnrpc/walletrpc" - "github.com/lightningnetwork/lnd/lntest" + "github.com/lightningnetwork/lnd/lntemp" + "github.com/lightningnetwork/lnd/lntemp/node" "github.com/lightningnetwork/lnd/lnwallet/chainfee" "github.com/stretchr/testify/require" ) @@ -44,127 +44,111 @@ var ( // testTaproot ensures that the daemon can send to and spend from taproot (p2tr) // outputs. -func testTaproot(net *lntest.NetworkHarness, t *harnessTest) { - ctxb := context.Background() +func testTaproot(ht *lntemp.HarnessTest) { + testTaprootSendCoinsKeySpendBip86(ht, ht.Alice) + testTaprootComputeInputScriptKeySpendBip86(ht, ht.Alice) + testTaprootSignOutputRawScriptSpend(ht, ht.Alice) + testTaprootSignOutputRawKeySpendBip86(ht, ht.Alice) + testTaprootSignOutputRawKeySpendRootHash(ht, ht.Alice) - longTimeout := 2 * defaultTimeout - ctxt, cancel := context.WithTimeout(ctxb, longTimeout) - testTaprootSendCoinsKeySpendBip86(ctxt, t, net.Alice, net) - testTaprootComputeInputScriptKeySpendBip86(ctxt, t, net.Alice, net) - testTaprootSignOutputRawScriptSpend(ctxt, t, net.Alice, net) - testTaprootSignOutputRawKeySpendBip86(ctxt, t, net.Alice, net) - testTaprootSignOutputRawKeySpendRootHash(ctxt, t, net.Alice, net) - cancel() + testTaprootMuSig2KeySpendBip86(ht, ht.Alice) + testTaprootMuSig2KeySpendRootHash(ht, ht.Alice) + testTaprootMuSig2ScriptSpend(ht, ht.Alice) + testTaprootMuSig2CombinedLeafKeySpend(ht, ht.Alice) - ctxt, cancel = context.WithTimeout(ctxb, longTimeout) - testTaprootMuSig2KeySpendBip86(ctxt, t, net.Alice, net) - testTaprootMuSig2KeySpendRootHash(ctxt, t, net.Alice, net) - testTaprootMuSig2ScriptSpend(ctxt, t, net.Alice, net) - testTaprootMuSig2CombinedLeafKeySpend(ctxt, t, net.Alice, net) - cancel() - - ctxt, cancel = context.WithTimeout(ctxb, longTimeout) - testTaprootImportTapscriptFullTree(ctxt, t, net.Alice, net) - testTaprootImportTapscriptPartialReveal(ctxt, t, net.Alice, net) - testTaprootImportTapscriptRootHashOnly(ctxt, t, net.Alice, net) - testTaprootImportTapscriptFullKey(ctxt, t, net.Alice, net) - cancel() + testTaprootImportTapscriptFullTree(ht, ht.Alice) + testTaprootImportTapscriptPartialReveal(ht, ht.Alice) + testTaprootImportTapscriptRootHashOnly(ht, ht.Alice) + testTaprootImportTapscriptFullKey(ht, ht.Alice) } // testTaprootSendCoinsKeySpendBip86 tests sending to and spending from // p2tr key spend only (BIP-0086) addresses through the SendCoins RPC which // internally uses the ComputeInputScript method for signing. -func testTaprootSendCoinsKeySpendBip86(ctxt context.Context, - t *harnessTest, alice *lntest.HarnessNode, net *lntest.NetworkHarness) { +func testTaprootSendCoinsKeySpendBip86(ht *lntemp.HarnessTest, + alice *node.HarnessNode) { // We'll start the test by sending Alice some coins, which she'll use to // send to herself on a p2tr output. - net.SendCoins(t.t, btcutil.SatoshiPerBitcoin, alice) + ht.FundCoins(btcutil.SatoshiPerBitcoin, alice) // Let's create a p2tr address now. - p2trResp, err := alice.NewAddress(ctxt, &lnrpc.NewAddressRequest{ - Type: lnrpc.AddressType_TAPROOT_PUBKEY, + p2trResp := alice.RPC.NewAddress(&lnrpc.NewAddressRequest{ + Type: AddrTypeTaprootPubkey, }) - require.NoError(t.t, err) // Assert this is a segwit v1 address that starts with bcrt1p. require.Contains( - t.t, p2trResp.Address, net.Miner.ActiveNet.Bech32HRPSegwit+"1p", + ht, p2trResp.Address, ht.Miner.ActiveNet.Bech32HRPSegwit+"1p", ) // Send the coins from Alice's wallet to her own, but to the new p2tr // address. - _, err = alice.SendCoins(ctxt, &lnrpc.SendCoinsRequest{ + alice.RPC.SendCoins(&lnrpc.SendCoinsRequest{ Addr: p2trResp.Address, Amount: 0.5 * btcutil.SatoshiPerBitcoin, }) - require.NoError(t.t, err) - txid, err := waitForTxInMempool(net.Miner.Client, defaultTimeout) - require.NoError(t.t, err) + txid := ht.Miner.AssertNumTxsInMempool(1)[0] // Wait until bob has seen the tx and considers it as owned. - p2trOutputIndex := getOutputIndex(t, net.Miner, txid, p2trResp.Address) + p2trOutputIndex := ht.GetOutputIndex(txid, p2trResp.Address) op := &lnrpc.OutPoint{ TxidBytes: txid[:], OutputIndex: uint32(p2trOutputIndex), } - assertWalletUnspent(t, alice, op, "") + ht.AssertUTXOInWallet(alice, op, "") // Mine a block to clean up the mempool. - mineBlocks(t, net, 1, 1) + ht.MineBlocksAndAssertNumTxes(1, 1) // Let's sweep the whole wallet to a new p2tr address, making sure we // can sign transactions with v0 and v1 inputs. - p2trResp, err = alice.NewAddress(ctxt, &lnrpc.NewAddressRequest{ + p2trResp = alice.RPC.NewAddress(&lnrpc.NewAddressRequest{ Type: lnrpc.AddressType_TAPROOT_PUBKEY, }) - require.NoError(t.t, err) - _, err = alice.SendCoins(ctxt, &lnrpc.SendCoinsRequest{ + alice.RPC.SendCoins(&lnrpc.SendCoinsRequest{ Addr: p2trResp.Address, SendAll: true, }) - require.NoError(t.t, err) // Make sure the coins sent to the address are confirmed correctly, // including the confirmation notification. - confirmAddress(ctxt, t, net, alice, p2trResp.Address) + confirmAddress(ht, alice, p2trResp.Address) } -// testTaprootComputeInputScriptKeySpendBip86 tests sending to and spending -// from p2tr key spend only (BIP-0086) addresses through the ComputeInputScript -// RPC. -func testTaprootComputeInputScriptKeySpendBip86(ctxt context.Context, - t *harnessTest, alice *lntest.HarnessNode, net *lntest.NetworkHarness) { +// testTaprootComputeInputScriptKeySpendBip86 tests sending to and spending from +// p2tr key spend only (BIP-0086) addresses through the SendCoins RPC which +// internally uses the ComputeInputScript method for signing. +func testTaprootComputeInputScriptKeySpendBip86(ht *lntemp.HarnessTest, + alice *node.HarnessNode) { - // We'll start the test by sending Alice some coins, which she'll use to - // send to herself on a p2tr output. - net.SendCoins(t.t, btcutil.SatoshiPerBitcoin, alice) + // We'll start the test by sending Alice some coins, which she'll use + // to send to herself on a p2tr output. + ht.FundCoins(btcutil.SatoshiPerBitcoin, alice) // Let's create a p2tr address now. p2trAddr, p2trPkScript := newAddrWithScript( - ctxt, t.t, alice, lnrpc.AddressType_TAPROOT_PUBKEY, + ht, alice, lnrpc.AddressType_TAPROOT_PUBKEY, ) // Send the coins from Alice's wallet to her own, but to the new p2tr // address. - _, err := alice.SendCoins(ctxt, &lnrpc.SendCoinsRequest{ + req := &lnrpc.SendCoinsRequest{ Addr: p2trAddr.String(), Amount: testAmount, - }) - require.NoError(t.t, err) - - txid, err := waitForTxInMempool(net.Miner.Client, defaultTimeout) - require.NoError(t.t, err) + } + alice.RPC.SendCoins(req) // Wait until bob has seen the tx and considers it as owned. - p2trOutputIndex := getOutputIndex(t, net.Miner, txid, p2trAddr.String()) + txid := ht.Miner.AssertNumTxsInMempool(1)[0] + p2trOutputIndex := ht.GetOutputIndex(txid, p2trAddr.String()) op := &lnrpc.OutPoint{ TxidBytes: txid[:], OutputIndex: uint32(p2trOutputIndex), } - assertWalletUnspent(t, alice, op, "") + ht.AssertUTXOInWallet(alice, op, "") p2trOutpoint := wire.OutPoint{ Hash: *txid, @@ -172,11 +156,11 @@ func testTaprootComputeInputScriptKeySpendBip86(ctxt context.Context, } // Mine a block to clean up the mempool. - mineBlocks(t, net, 1, 1) + ht.MineBlocksAndAssertNumTxes(1, 1) // We'll send the coins back to a p2wkh address. p2wkhAddr, p2wkhPkScript := newAddrWithScript( - ctxt, t.t, alice, lnrpc.AddressType_WITNESS_PUBKEY_HASH, + ht, alice, lnrpc.AddressType_WITNESS_PUBKEY_HASH, ) // Create fee estimation for a p2tr input and p2wkh output. @@ -198,31 +182,29 @@ func testTaprootComputeInputScriptKeySpendBip86(ctxt context.Context, }} var buf bytes.Buffer - require.NoError(t.t, tx.Serialize(&buf)) + require.NoError(ht, tx.Serialize(&buf)) utxoInfo := []*signrpc.TxOut{{ PkScript: p2trPkScript, Value: testAmount, }} - signResp, err := alice.SignerClient.ComputeInputScript( - ctxt, &signrpc.SignReq{ - RawTxBytes: buf.Bytes(), - SignDescs: []*signrpc.SignDescriptor{{ - Output: utxoInfo[0], - InputIndex: 0, - Sighash: uint32(txscript.SigHashDefault), - }}, - PrevOutputs: utxoInfo, - }, - ) - require.NoError(t.t, err) + signReq := &signrpc.SignReq{ + RawTxBytes: buf.Bytes(), + SignDescs: []*signrpc.SignDescriptor{{ + Output: utxoInfo[0], + InputIndex: 0, + Sighash: uint32(txscript.SigHashDefault), + }}, + PrevOutputs: utxoInfo, + } + signResp := alice.RPC.ComputeInputScript(signReq) tx.TxIn[0].Witness = signResp.InputScripts[0].Witness // Serialize, weigh and publish the TX now, then make sure the // coins are sent and confirmed to the final sweep destination address. publishTxAndConfirmSweep( - ctxt, t, net, alice, tx, estimatedWeight, + ht, alice, tx, estimatedWeight, &chainrpc.SpendRequest{ Outpoint: &chainrpc.Outpoint{ Hash: p2trOutpoint.Hash[:], @@ -236,41 +218,39 @@ func testTaprootComputeInputScriptKeySpendBip86(ctxt context.Context, // testTaprootSignOutputRawScriptSpend tests sending to and spending from p2tr // script addresses using the script path with the SignOutputRaw RPC. -func testTaprootSignOutputRawScriptSpend(ctxt context.Context, t *harnessTest, - alice *lntest.HarnessNode, net *lntest.NetworkHarness) { +func testTaprootSignOutputRawScriptSpend(ht *lntemp.HarnessTest, + alice *node.HarnessNode) { // For the next step, we need a public key. Let's use a special family // for this. - keyDesc, err := alice.WalletKitClient.DeriveNextKey( - ctxt, &walletrpc.KeyReq{KeyFamily: testTaprootKeyFamily}, - ) - require.NoError(t.t, err) + req := &walletrpc.KeyReq{KeyFamily: testTaprootKeyFamily} + keyDesc := alice.RPC.DeriveNextKey(req) leafSigningKey, err := btcec.ParsePubKey(keyDesc.RawKeyBytes) - require.NoError(t.t, err) + require.NoError(ht, err) // Let's create a taproot script output now. This is a hash lock with a // simple preimage of "foobar". - leaf1 := testScriptHashLock(t.t, []byte("foobar")) + leaf1 := testScriptHashLock(ht.T, []byte("foobar")) // Let's add a second script output as well to test the partial reveal. - leaf2 := testScriptSchnorrSig(t.t, leafSigningKey) + leaf2 := testScriptSchnorrSig(ht.T, leafSigningKey) inclusionProof := leaf1.TapHash() tapscript := input.TapscriptPartialReveal( dummyInternalKey, leaf2, inclusionProof[:], ) taprootKey, err := tapscript.TaprootKey() - require.NoError(t.t, err) + require.NoError(ht, err) // Send some coins to the generated tapscript address. p2trOutpoint, p2trPkScript := sendToTaprootOutput( - ctxt, t, net, alice, taprootKey, testAmount, + ht, alice, taprootKey, testAmount, ) // Spend the output again, this time back to a p2wkh address. p2wkhAddr, p2wkhPkScript := newAddrWithScript( - ctxt, t.t, alice, lnrpc.AddressType_WITNESS_PUBKEY_HASH, + ht, alice, lnrpc.AddressType_WITNESS_PUBKEY_HASH, ) // Create fee estimation for a p2tr input and p2wkh output. @@ -294,7 +274,7 @@ func testTaprootSignOutputRawScriptSpend(ctxt context.Context, t *harnessTest, }} var buf bytes.Buffer - require.NoError(t.t, tx.Serialize(&buf)) + require.NoError(ht, tx.Serialize(&buf)) utxoInfo := []*signrpc.TxOut{{ PkScript: p2trPkScript, @@ -304,66 +284,60 @@ func testTaprootSignOutputRawScriptSpend(ctxt context.Context, t *harnessTest, // Before we actually sign, we want to make sure that we get an error // when we try to sign for a Taproot output without specifying all UTXO // information. - _, err = alice.SignerClient.SignOutputRaw( - ctxt, &signrpc.SignReq{ - RawTxBytes: buf.Bytes(), - SignDescs: []*signrpc.SignDescriptor{{ - Output: utxoInfo[0], - InputIndex: 0, - KeyDesc: keyDesc, - Sighash: uint32(txscript.SigHashDefault), - WitnessScript: leaf2.Script, - SignMethod: signMethodTapscript, - }}, - }, - ) - require.Error(t.t, err) + signReq := &signrpc.SignReq{ + RawTxBytes: buf.Bytes(), + SignDescs: []*signrpc.SignDescriptor{{ + Output: utxoInfo[0], + InputIndex: 0, + KeyDesc: keyDesc, + Sighash: uint32(txscript.SigHashDefault), + WitnessScript: leaf2.Script, + SignMethod: signMethodTapscript, + }}, + } + err = alice.RPC.SignOutputRawErr(signReq) require.Contains( - t.t, err.Error(), "error signing taproot output, transaction "+ + ht, err.Error(), "error signing taproot output, transaction "+ "input 0 is missing its previous outpoint information", ) // We also want to make sure we get an error when we don't specify the // correct signing method. - _, err = alice.SignerClient.SignOutputRaw( - ctxt, &signrpc.SignReq{ - RawTxBytes: buf.Bytes(), - SignDescs: []*signrpc.SignDescriptor{{ - Output: utxoInfo[0], - InputIndex: 0, - KeyDesc: keyDesc, - Sighash: uint32(txscript.SigHashDefault), - WitnessScript: leaf2.Script, - }}, - PrevOutputs: utxoInfo, - }, - ) - require.Error(t.t, err) + signReq = &signrpc.SignReq{ + RawTxBytes: buf.Bytes(), + SignDescs: []*signrpc.SignDescriptor{{ + Output: utxoInfo[0], + InputIndex: 0, + KeyDesc: keyDesc, + Sighash: uint32(txscript.SigHashDefault), + WitnessScript: leaf2.Script, + }}, + PrevOutputs: utxoInfo, + } + err = alice.RPC.SignOutputRawErr(signReq) require.Contains( - t.t, err.Error(), "selected sign method witness_v0 is not "+ + ht, err.Error(), "selected sign method witness_v0 is not "+ "compatible with given pk script 5120", ) // Do the actual signing now. - signResp, err := alice.SignerClient.SignOutputRaw( - ctxt, &signrpc.SignReq{ - RawTxBytes: buf.Bytes(), - SignDescs: []*signrpc.SignDescriptor{{ - Output: utxoInfo[0], - InputIndex: 0, - KeyDesc: keyDesc, - Sighash: uint32(txscript.SigHashDefault), - WitnessScript: leaf2.Script, - SignMethod: signMethodTapscript, - }}, - PrevOutputs: utxoInfo, - }, - ) - require.NoError(t.t, err) + signReq = &signrpc.SignReq{ + RawTxBytes: buf.Bytes(), + SignDescs: []*signrpc.SignDescriptor{{ + Output: utxoInfo[0], + InputIndex: 0, + KeyDesc: keyDesc, + Sighash: uint32(txscript.SigHashDefault), + WitnessScript: leaf2.Script, + SignMethod: signMethodTapscript, + }}, + PrevOutputs: utxoInfo, + } + signResp := alice.RPC.SignOutputRaw(signReq) // We can now assemble the witness stack. controlBlockBytes, err := tapscript.ControlBlock.ToBytes() - require.NoError(t.t, err) + require.NoError(ht, err) tx.TxIn[0].Witness = wire.TxWitness{ signResp.RawSigs[0], @@ -374,7 +348,7 @@ func testTaprootSignOutputRawScriptSpend(ctxt context.Context, t *harnessTest, // Serialize, weigh and publish the TX now, then make sure the // coins are sent and confirmed to the final sweep destination address. publishTxAndConfirmSweep( - ctxt, t, net, alice, tx, estimatedWeight, + ht, alice, tx, estimatedWeight, &chainrpc.SpendRequest{ Outpoint: &chainrpc.Outpoint{ Hash: p2trOutpoint.Hash[:], @@ -389,18 +363,16 @@ func testTaprootSignOutputRawScriptSpend(ctxt context.Context, t *harnessTest, // testTaprootSignOutputRawKeySpendBip86 tests that a tapscript address can // also be spent using the key spend path through the SignOutputRaw RPC using a // BIP0086 key spend only commitment. -func testTaprootSignOutputRawKeySpendBip86(ctxt context.Context, - t *harnessTest, alice *lntest.HarnessNode, net *lntest.NetworkHarness) { +func testTaprootSignOutputRawKeySpendBip86(ht *lntemp.HarnessTest, + alice *node.HarnessNode) { // For the next step, we need a public key. Let's use a special family // for this. - keyDesc, err := alice.WalletKitClient.DeriveNextKey( - ctxt, &walletrpc.KeyReq{KeyFamily: testTaprootKeyFamily}, - ) - require.NoError(t.t, err) + req := &walletrpc.KeyReq{KeyFamily: testTaprootKeyFamily} + keyDesc := alice.RPC.DeriveNextKey(req) internalKey, err := btcec.ParsePubKey(keyDesc.RawKeyBytes) - require.NoError(t.t, err) + require.NoError(ht, err) // We want to make sure we can still use a tweaked key, even if it ends // up being essentially double tweaked because of the taproot root hash. @@ -413,12 +385,12 @@ func testTaprootSignOutputRawKeySpendBip86(ctxt context.Context, // Send some coins to the generated tapscript address. p2trOutpoint, p2trPkScript := sendToTaprootOutput( - ctxt, t, net, alice, taprootKey, testAmount, + ht, alice, taprootKey, testAmount, ) // Spend the output again, this time back to a p2wkh address. p2wkhAddr, p2wkhPkScript := newAddrWithScript( - ctxt, t.t, alice, lnrpc.AddressType_WITNESS_PUBKEY_HASH, + ht, alice, lnrpc.AddressType_WITNESS_PUBKEY_HASH, ) // Create fee estimation for a p2tr input and p2wkh output. @@ -440,27 +412,25 @@ func testTaprootSignOutputRawKeySpendBip86(ctxt context.Context, }} var buf bytes.Buffer - require.NoError(t.t, tx.Serialize(&buf)) + require.NoError(ht, tx.Serialize(&buf)) utxoInfo := []*signrpc.TxOut{{ PkScript: p2trPkScript, Value: testAmount, }} - signResp, err := alice.SignerClient.SignOutputRaw( - ctxt, &signrpc.SignReq{ - RawTxBytes: buf.Bytes(), - SignDescs: []*signrpc.SignDescriptor{{ - Output: utxoInfo[0], - InputIndex: 0, - KeyDesc: keyDesc, - SingleTweak: dummyKeyTweak[:], - Sighash: uint32(txscript.SigHashDefault), - SignMethod: signMethodBip86, - }}, - PrevOutputs: utxoInfo, - }, - ) - require.NoError(t.t, err) + signReq := &signrpc.SignReq{ + RawTxBytes: buf.Bytes(), + SignDescs: []*signrpc.SignDescriptor{{ + Output: utxoInfo[0], + InputIndex: 0, + KeyDesc: keyDesc, + SingleTweak: dummyKeyTweak[:], + Sighash: uint32(txscript.SigHashDefault), + SignMethod: signMethodBip86, + }}, + PrevOutputs: utxoInfo, + } + signResp := alice.RPC.SignOutputRaw(signReq) tx.TxIn[0].Witness = wire.TxWitness{ signResp.RawSigs[0], @@ -469,7 +439,7 @@ func testTaprootSignOutputRawKeySpendBip86(ctxt context.Context, // Serialize, weigh and publish the TX now, then make sure the // coins are sent and confirmed to the final sweep destination address. publishTxAndConfirmSweep( - ctxt, t, net, alice, tx, estimatedWeight, + ht, alice, tx, estimatedWeight, &chainrpc.SpendRequest{ Outpoint: &chainrpc.Outpoint{ Hash: p2trOutpoint.Hash[:], @@ -484,18 +454,16 @@ func testTaprootSignOutputRawKeySpendBip86(ctxt context.Context, // testTaprootSignOutputRawKeySpendRootHash tests that a tapscript address can // also be spent using the key spend path through the SignOutputRaw RPC using a // tapscript root hash. -func testTaprootSignOutputRawKeySpendRootHash(ctxt context.Context, - t *harnessTest, alice *lntest.HarnessNode, net *lntest.NetworkHarness) { +func testTaprootSignOutputRawKeySpendRootHash(ht *lntemp.HarnessTest, + alice *node.HarnessNode) { // For the next step, we need a public key. Let's use a special family // for this. - keyDesc, err := alice.WalletKitClient.DeriveNextKey( - ctxt, &walletrpc.KeyReq{KeyFamily: testTaprootKeyFamily}, - ) - require.NoError(t.t, err) + req := &walletrpc.KeyReq{KeyFamily: testTaprootKeyFamily} + keyDesc := alice.RPC.DeriveNextKey(req) internalKey, err := btcec.ParsePubKey(keyDesc.RawKeyBytes) - require.NoError(t.t, err) + require.NoError(ht, err) // We want to make sure we can still use a tweaked key, even if it ends // up being essentially double tweaked because of the taproot root hash. @@ -504,19 +472,19 @@ func testTaprootSignOutputRawKeySpendRootHash(ctxt context.Context, // Let's create a taproot script output now. This is a hash lock with a // simple preimage of "foobar". - leaf1 := testScriptHashLock(t.t, []byte("foobar")) + leaf1 := testScriptHashLock(ht.T, []byte("foobar")) rootHash := leaf1.TapHash() taprootKey := txscript.ComputeTaprootOutputKey(internalKey, rootHash[:]) // Send some coins to the generated tapscript address. p2trOutpoint, p2trPkScript := sendToTaprootOutput( - ctxt, t, net, alice, taprootKey, testAmount, + ht, alice, taprootKey, testAmount, ) // Spend the output again, this time back to a p2wkh address. p2wkhAddr, p2wkhPkScript := newAddrWithScript( - ctxt, t.t, alice, lnrpc.AddressType_WITNESS_PUBKEY_HASH, + ht, alice, lnrpc.AddressType_WITNESS_PUBKEY_HASH, ) // Create fee estimation for a p2tr input and p2wkh output. @@ -538,29 +506,26 @@ func testTaprootSignOutputRawKeySpendRootHash(ctxt context.Context, }} var buf bytes.Buffer - require.NoError(t.t, tx.Serialize(&buf)) + require.NoError(ht, tx.Serialize(&buf)) utxoInfo := []*signrpc.TxOut{{ PkScript: p2trPkScript, Value: testAmount, }} - signResp, err := alice.SignerClient.SignOutputRaw( - ctxt, &signrpc.SignReq{ - RawTxBytes: buf.Bytes(), - SignDescs: []*signrpc.SignDescriptor{{ - Output: utxoInfo[0], - InputIndex: 0, - KeyDesc: keyDesc, - SingleTweak: dummyKeyTweak[:], - Sighash: uint32(txscript.SigHashDefault), - TapTweak: rootHash[:], - SignMethod: signMethodRootHash, - }}, - PrevOutputs: utxoInfo, - }, - ) - require.NoError(t.t, err) - + signReq := &signrpc.SignReq{ + RawTxBytes: buf.Bytes(), + SignDescs: []*signrpc.SignDescriptor{{ + Output: utxoInfo[0], + InputIndex: 0, + KeyDesc: keyDesc, + SingleTweak: dummyKeyTweak[:], + Sighash: uint32(txscript.SigHashDefault), + TapTweak: rootHash[:], + SignMethod: signMethodRootHash, + }}, + PrevOutputs: utxoInfo, + } + signResp := alice.RPC.SignOutputRaw(signReq) tx.TxIn[0].Witness = wire.TxWitness{ signResp.RawSigs[0], } @@ -568,7 +533,7 @@ func testTaprootSignOutputRawKeySpendRootHash(ctxt context.Context, // Serialize, weigh and publish the TX now, then make sure the // coins are sent and confirmed to the final sweep destination address. publishTxAndConfirmSweep( - ctxt, t, net, alice, tx, estimatedWeight, + ht, alice, tx, estimatedWeight, &chainrpc.SpendRequest{ Outpoint: &chainrpc.Outpoint{ Hash: p2trOutpoint.Hash[:], @@ -582,8 +547,8 @@ func testTaprootSignOutputRawKeySpendRootHash(ctxt context.Context, // testTaprootMuSig2KeySpendBip86 tests that a combined MuSig2 key can also be // used as a BIP-0086 key spend only key. -func testTaprootMuSig2KeySpendBip86(ctxt context.Context, t *harnessTest, - alice *lntest.HarnessNode, net *lntest.NetworkHarness) { +func testTaprootMuSig2KeySpendBip86(ht *lntemp.HarnessTest, + alice *node.HarnessNode) { // We're not going to commit to a script. So our taproot tweak will be // empty and just specify the necessary flag. @@ -591,22 +556,20 @@ func testTaprootMuSig2KeySpendBip86(ctxt context.Context, t *harnessTest, KeySpendOnly: true, } - keyDesc1, keyDesc2, keyDesc3, allPubKeys := deriveSigningKeys( - ctxt, t, alice, - ) + keyDesc1, keyDesc2, keyDesc3, allPubKeys := deriveSigningKeys(ht, alice) _, taprootKey, sessResp1, sessResp2, sessResp3 := createMuSigSessions( - ctxt, t, alice, taprootTweak, keyDesc1, keyDesc2, keyDesc3, + ht, alice, taprootTweak, keyDesc1, keyDesc2, keyDesc3, allPubKeys, ) // Send some coins to the generated tapscript address. p2trOutpoint, p2trPkScript := sendToTaprootOutput( - ctxt, t, net, alice, taprootKey, testAmount, + ht, alice, taprootKey, testAmount, ) // Spend the output again, this time back to a p2wkh address. p2wkhAddr, p2wkhPkScript := newAddrWithScript( - ctxt, t.t, alice, lnrpc.AddressType_WITNESS_PUBKEY_HASH, + ht, alice, lnrpc.AddressType_WITNESS_PUBKEY_HASH, ) // Create fee estimation for a p2tr input and p2wkh output. @@ -628,7 +591,7 @@ func testTaprootMuSig2KeySpendBip86(ctxt context.Context, t *harnessTest, }} var buf bytes.Buffer - require.NoError(t.t, tx.Serialize(&buf)) + require.NoError(ht, tx.Serialize(&buf)) utxoInfo := []*signrpc.TxOut{{ PkScript: p2trPkScript, @@ -645,66 +608,58 @@ func testTaprootMuSig2KeySpendBip86(ctxt context.Context, t *harnessTest, sigHash, err := txscript.CalcTaprootSignatureHash( sighashes, txscript.SigHashDefault, tx, 0, prevOutputFetcher, ) - require.NoError(t.t, err) + require.NoError(ht, err) // Now that we have the transaction prepared, we need to start with the // signing. We simulate all three parties here, so we need to do // everything three times. But because we're going to use session 1 to // combine everything, we don't need its response, as it will store its // own signature. - _, err = alice.SignerClient.MuSig2Sign( - ctxt, &signrpc.MuSig2SignRequest{ - SessionId: sessResp1.SessionId, - MessageDigest: sigHash, - }, - ) - require.NoError(t.t, err) + signReq := &signrpc.MuSig2SignRequest{ + SessionId: sessResp1.SessionId, + MessageDigest: sigHash, + } + alice.RPC.MuSig2Sign(signReq) - signResp2, err := alice.SignerClient.MuSig2Sign( - ctxt, &signrpc.MuSig2SignRequest{ - SessionId: sessResp2.SessionId, - MessageDigest: sigHash, - Cleanup: true, - }, - ) - require.NoError(t.t, err) + signReq = &signrpc.MuSig2SignRequest{ + SessionId: sessResp2.SessionId, + MessageDigest: sigHash, + Cleanup: true, + } + signResp2 := alice.RPC.MuSig2Sign(signReq) - signResp3, err := alice.SignerClient.MuSig2Sign( - ctxt, &signrpc.MuSig2SignRequest{ - SessionId: sessResp3.SessionId, - MessageDigest: sigHash, - Cleanup: true, - }, - ) - require.NoError(t.t, err) + signReq = &signrpc.MuSig2SignRequest{ + SessionId: sessResp3.SessionId, + MessageDigest: sigHash, + Cleanup: true, + } + signResp3 := alice.RPC.MuSig2Sign(signReq) // Luckily only one of the signers needs to combine the signature, so // let's do that now. - combineReq1, err := alice.SignerClient.MuSig2CombineSig( - ctxt, &signrpc.MuSig2CombineSigRequest{ - SessionId: sessResp1.SessionId, - OtherPartialSignatures: [][]byte{ - signResp2.LocalPartialSignature, - signResp3.LocalPartialSignature, - }, + combineReq := &signrpc.MuSig2CombineSigRequest{ + SessionId: sessResp1.SessionId, + OtherPartialSignatures: [][]byte{ + signResp2.LocalPartialSignature, + signResp3.LocalPartialSignature, }, - ) - require.NoError(t.t, err) - require.Equal(t.t, true, combineReq1.HaveAllSignatures) - require.NotEmpty(t.t, combineReq1.FinalSignature) + } + combineResp := alice.RPC.MuSig2CombineSig(combineReq) + require.Equal(ht, true, combineResp.HaveAllSignatures) + require.NotEmpty(ht, combineResp.FinalSignature) - sig, err := schnorr.ParseSignature(combineReq1.FinalSignature) - require.NoError(t.t, err) - require.True(t.t, sig.Verify(sigHash, taprootKey)) + sig, err := schnorr.ParseSignature(combineResp.FinalSignature) + require.NoError(ht, err) + require.True(ht, sig.Verify(sigHash, taprootKey)) tx.TxIn[0].Witness = wire.TxWitness{ - combineReq1.FinalSignature, + combineResp.FinalSignature, } // Serialize, weigh and publish the TX now, then make sure the // coins are sent and confirmed to the final sweep destination address. publishTxAndConfirmSweep( - ctxt, t, net, alice, tx, estimatedWeight, + ht, alice, tx, estimatedWeight, &chainrpc.SpendRequest{ Outpoint: &chainrpc.Outpoint{ Hash: p2trOutpoint.Hash[:], @@ -718,35 +673,35 @@ func testTaprootMuSig2KeySpendBip86(ctxt context.Context, t *harnessTest, // testTaprootMuSig2KeySpendRootHash tests that a tapscript address can also be // spent using a MuSig2 combined key. -func testTaprootMuSig2KeySpendRootHash(ctxt context.Context, t *harnessTest, - alice *lntest.HarnessNode, net *lntest.NetworkHarness) { +func testTaprootMuSig2KeySpendRootHash(ht *lntemp.HarnessTest, + alice *node.HarnessNode) { // We're going to commit to a script as well. This is a hash lock with a // simple preimage of "foobar". We need to know this upfront so, we can // specify the taproot tweak with the root hash when creating the Musig2 // signing session. - leaf1 := testScriptHashLock(t.t, []byte("foobar")) + leaf1 := testScriptHashLock(ht.T, []byte("foobar")) rootHash := leaf1.TapHash() taprootTweak := &signrpc.TaprootTweakDesc{ ScriptRoot: rootHash[:], } keyDesc1, keyDesc2, keyDesc3, allPubKeys := deriveSigningKeys( - ctxt, t, alice, + ht, alice, ) _, taprootKey, sessResp1, sessResp2, sessResp3 := createMuSigSessions( - ctxt, t, alice, taprootTweak, keyDesc1, keyDesc2, keyDesc3, + ht, alice, taprootTweak, keyDesc1, keyDesc2, keyDesc3, allPubKeys, ) // Send some coins to the generated tapscript address. p2trOutpoint, p2trPkScript := sendToTaprootOutput( - ctxt, t, net, alice, taprootKey, testAmount, + ht, alice, taprootKey, testAmount, ) // Spend the output again, this time back to a p2wkh address. p2wkhAddr, p2wkhPkScript := newAddrWithScript( - ctxt, t.t, alice, lnrpc.AddressType_WITNESS_PUBKEY_HASH, + ht, alice, lnrpc.AddressType_WITNESS_PUBKEY_HASH, ) // Create fee estimation for a p2tr input and p2wkh output. @@ -768,7 +723,7 @@ func testTaprootMuSig2KeySpendRootHash(ctxt context.Context, t *harnessTest, }} var buf bytes.Buffer - require.NoError(t.t, tx.Serialize(&buf)) + require.NoError(ht, tx.Serialize(&buf)) utxoInfo := []*signrpc.TxOut{{ PkScript: p2trPkScript, @@ -785,66 +740,58 @@ func testTaprootMuSig2KeySpendRootHash(ctxt context.Context, t *harnessTest, sigHash, err := txscript.CalcTaprootSignatureHash( sighashes, txscript.SigHashDefault, tx, 0, prevOutputFetcher, ) - require.NoError(t.t, err) + require.NoError(ht, err) // Now that we have the transaction prepared, we need to start with the // signing. We simulate all three parties here, so we need to do // everything three times. But because we're going to use session 1 to // combine everything, we don't need its response, as it will store its // own signature. - _, err = alice.SignerClient.MuSig2Sign( - ctxt, &signrpc.MuSig2SignRequest{ - SessionId: sessResp1.SessionId, - MessageDigest: sigHash, - }, - ) - require.NoError(t.t, err) + req := &signrpc.MuSig2SignRequest{ + SessionId: sessResp1.SessionId, + MessageDigest: sigHash, + } + alice.RPC.MuSig2Sign(req) - signResp2, err := alice.SignerClient.MuSig2Sign( - ctxt, &signrpc.MuSig2SignRequest{ - SessionId: sessResp2.SessionId, - MessageDigest: sigHash, - Cleanup: true, - }, - ) - require.NoError(t.t, err) + req = &signrpc.MuSig2SignRequest{ + SessionId: sessResp2.SessionId, + MessageDigest: sigHash, + Cleanup: true, + } + signResp2 := alice.RPC.MuSig2Sign(req) - signResp3, err := alice.SignerClient.MuSig2Sign( - ctxt, &signrpc.MuSig2SignRequest{ - SessionId: sessResp3.SessionId, - MessageDigest: sigHash, - Cleanup: true, - }, - ) - require.NoError(t.t, err) + req = &signrpc.MuSig2SignRequest{ + SessionId: sessResp3.SessionId, + MessageDigest: sigHash, + Cleanup: true, + } + signResp3 := alice.RPC.MuSig2Sign(req) // Luckily only one of the signers needs to combine the signature, so // let's do that now. - combineReq1, err := alice.SignerClient.MuSig2CombineSig( - ctxt, &signrpc.MuSig2CombineSigRequest{ - SessionId: sessResp1.SessionId, - OtherPartialSignatures: [][]byte{ - signResp2.LocalPartialSignature, - signResp3.LocalPartialSignature, - }, + combineReq := &signrpc.MuSig2CombineSigRequest{ + SessionId: sessResp1.SessionId, + OtherPartialSignatures: [][]byte{ + signResp2.LocalPartialSignature, + signResp3.LocalPartialSignature, }, - ) - require.NoError(t.t, err) - require.Equal(t.t, true, combineReq1.HaveAllSignatures) - require.NotEmpty(t.t, combineReq1.FinalSignature) + } + combineResp := alice.RPC.MuSig2CombineSig(combineReq) + require.Equal(ht, true, combineResp.HaveAllSignatures) + require.NotEmpty(ht, combineResp.FinalSignature) - sig, err := schnorr.ParseSignature(combineReq1.FinalSignature) - require.NoError(t.t, err) - require.True(t.t, sig.Verify(sigHash, taprootKey)) + sig, err := schnorr.ParseSignature(combineResp.FinalSignature) + require.NoError(ht, err) + require.True(ht, sig.Verify(sigHash, taprootKey)) tx.TxIn[0].Witness = wire.TxWitness{ - combineReq1.FinalSignature, + combineResp.FinalSignature, } // Serialize, weigh and publish the TX now, then make sure the // coins are sent and confirmed to the final sweep destination address. publishTxAndConfirmSweep( - ctxt, t, net, alice, tx, estimatedWeight, + ht, alice, tx, estimatedWeight, &chainrpc.SpendRequest{ Outpoint: &chainrpc.Outpoint{ Hash: p2trOutpoint.Hash[:], @@ -858,24 +805,24 @@ func testTaprootMuSig2KeySpendRootHash(ctxt context.Context, t *harnessTest, // testTaprootMuSig2ScriptSpend tests that a tapscript address with an internal // key that is a MuSig2 combined key can also be spent using the script path. -func testTaprootMuSig2ScriptSpend(ctxt context.Context, t *harnessTest, - alice *lntest.HarnessNode, net *lntest.NetworkHarness) { +func testTaprootMuSig2ScriptSpend(ht *lntemp.HarnessTest, + alice *node.HarnessNode) { // We're going to commit to a script and spend the output using the // script. This is a hash lock with a simple preimage of "foobar". We // need to know this upfront so, we can specify the taproot tweak with // the root hash when creating the Musig2 signing session. - leaf1 := testScriptHashLock(t.t, []byte("foobar")) + leaf1 := testScriptHashLock(ht.T, []byte("foobar")) rootHash := leaf1.TapHash() taprootTweak := &signrpc.TaprootTweakDesc{ ScriptRoot: rootHash[:], } keyDesc1, keyDesc2, keyDesc3, allPubKeys := deriveSigningKeys( - ctxt, t, alice, + ht, alice, ) internalKey, taprootKey, _, _, _ := createMuSigSessions( - ctxt, t, alice, taprootTweak, keyDesc1, keyDesc2, keyDesc3, + ht, alice, taprootTweak, keyDesc1, keyDesc2, keyDesc3, allPubKeys, ) @@ -886,12 +833,12 @@ func testTaprootMuSig2ScriptSpend(ctxt context.Context, t *harnessTest, // Send some coins to the generated tapscript address. p2trOutpoint, p2trPkScript := sendToTaprootOutput( - ctxt, t, net, alice, taprootKey, testAmount, + ht, alice, taprootKey, testAmount, ) // Spend the output again, this time back to a p2wkh address. p2wkhAddr, p2wkhPkScript := newAddrWithScript( - ctxt, t.t, alice, lnrpc.AddressType_WITNESS_PUBKEY_HASH, + ht, alice, lnrpc.AddressType_WITNESS_PUBKEY_HASH, ) // Create fee estimation for a p2tr input and p2wkh output. @@ -916,7 +863,7 @@ func testTaprootMuSig2ScriptSpend(ctxt context.Context, t *harnessTest, // We can now assemble the witness stack. controlBlockBytes, err := tapscript.ControlBlock.ToBytes() - require.NoError(t.t, err) + require.NoError(ht, err) tx.TxIn[0].Witness = wire.TxWitness{ []byte("foobar"), @@ -927,7 +874,7 @@ func testTaprootMuSig2ScriptSpend(ctxt context.Context, t *harnessTest, // Serialize, weigh and publish the TX now, then make sure the // coins are sent and confirmed to the final sweep destination address. publishTxAndConfirmSweep( - ctxt, t, net, alice, tx, estimatedWeight, + ht, alice, tx, estimatedWeight, &chainrpc.SpendRequest{ Outpoint: &chainrpc.Outpoint{ Hash: p2trOutpoint.Hash[:], @@ -941,39 +888,37 @@ func testTaprootMuSig2ScriptSpend(ctxt context.Context, t *harnessTest, // testTaprootMuSig2CombinedLeafKeySpend tests that a MuSig2 combined key can be // used for an OP_CHECKSIG inside a tap script leaf spend. -func testTaprootMuSig2CombinedLeafKeySpend(ctxt context.Context, t *harnessTest, - alice *lntest.HarnessNode, net *lntest.NetworkHarness) { +func testTaprootMuSig2CombinedLeafKeySpend(ht *lntemp.HarnessTest, + alice *node.HarnessNode) { // We're using the combined MuSig2 key in a script leaf. So we need to // derive the combined key first, before we can build the script. keyDesc1, keyDesc2, keyDesc3, allPubKeys := deriveSigningKeys( - ctxt, t, alice, + ht, alice, ) - combineResp, err := alice.SignerClient.MuSig2CombineKeys( - ctxt, &signrpc.MuSig2CombineKeysRequest{ - AllSignerPubkeys: allPubKeys, - }, - ) - require.NoError(t.t, err) + req := &signrpc.MuSig2CombineKeysRequest{ + AllSignerPubkeys: allPubKeys, + } + combineResp := alice.RPC.MuSig2CombineKeys(req) combinedPubKey, err := schnorr.ParsePubKey(combineResp.CombinedKey) - require.NoError(t.t, err) + require.NoError(ht, err) // We're going to commit to a script and spend the output using the // script. This is just an OP_CHECKSIG with the combined MuSig2 public // key. - leaf := testScriptSchnorrSig(t.t, combinedPubKey) + leaf := testScriptSchnorrSig(ht.T, combinedPubKey) tapscript := input.TapscriptPartialReveal(dummyInternalKey, leaf, nil) taprootKey, err := tapscript.TaprootKey() - require.NoError(t.t, err) + require.NoError(ht, err) // Send some coins to the generated tapscript address. p2trOutpoint, p2trPkScript := sendToTaprootOutput( - ctxt, t, net, alice, taprootKey, testAmount, + ht, alice, taprootKey, testAmount, ) // Spend the output again, this time back to a p2wkh address. p2wkhAddr, p2wkhPkScript := newAddrWithScript( - ctxt, t.t, alice, lnrpc.AddressType_WITNESS_PUBKEY_HASH, + ht, alice, lnrpc.AddressType_WITNESS_PUBKEY_HASH, ) // Create fee estimation for a p2tr input and p2wkh output. @@ -997,7 +942,7 @@ func testTaprootMuSig2CombinedLeafKeySpend(ctxt context.Context, t *harnessTest, }} var buf bytes.Buffer - require.NoError(t.t, tx.Serialize(&buf)) + require.NoError(ht, tx.Serialize(&buf)) utxoInfo := []*signrpc.TxOut{{ PkScript: p2trPkScript, @@ -1006,9 +951,9 @@ func testTaprootMuSig2CombinedLeafKeySpend(ctxt context.Context, t *harnessTest, // Do the actual signing now. _, _, sessResp1, sessResp2, sessResp3 := createMuSigSessions( - ctxt, t, alice, nil, keyDesc1, keyDesc2, keyDesc3, allPubKeys, + ht, alice, nil, keyDesc1, keyDesc2, keyDesc3, allPubKeys, ) - require.NoError(t.t, err) + require.NoError(ht, err) // We now need to create the raw sighash of the transaction, as that // will be the message we're signing collaboratively. @@ -1021,95 +966,81 @@ func testTaprootMuSig2CombinedLeafKeySpend(ctxt context.Context, t *harnessTest, sighashes, txscript.SigHashDefault, tx, 0, prevOutputFetcher, leaf, ) - require.NoError(t.t, err) + require.NoError(ht, err) // Now that we have the transaction prepared, we need to start with the // signing. We simulate all three parties here, so we need to do // everything three times. But because we're going to use session 1 to // combine everything, we don't need its response, as it will store its // own signature. - _, err = alice.SignerClient.MuSig2Sign( - ctxt, &signrpc.MuSig2SignRequest{ - SessionId: sessResp1.SessionId, - MessageDigest: sigHash, - }, - ) - require.NoError(t.t, err) + signReq := &signrpc.MuSig2SignRequest{ + SessionId: sessResp1.SessionId, + MessageDigest: sigHash, + } + alice.RPC.MuSig2Sign(signReq) - signResp2, err := alice.SignerClient.MuSig2Sign( - ctxt, &signrpc.MuSig2SignRequest{ - SessionId: sessResp2.SessionId, - MessageDigest: sigHash, - Cleanup: true, - }, - ) - require.NoError(t.t, err) + signReq = &signrpc.MuSig2SignRequest{ + SessionId: sessResp2.SessionId, + MessageDigest: sigHash, + Cleanup: true, + } + signResp2 := alice.RPC.MuSig2Sign(signReq) // Before we have all partial signatures, we shouldn't get a final // signature back. - combineSigResp, err := alice.SignerClient.MuSig2CombineSig( - ctxt, &signrpc.MuSig2CombineSigRequest{ - SessionId: sessResp1.SessionId, - OtherPartialSignatures: [][]byte{ - signResp2.LocalPartialSignature, - }, + combineReq := &signrpc.MuSig2CombineSigRequest{ + SessionId: sessResp1.SessionId, + OtherPartialSignatures: [][]byte{ + signResp2.LocalPartialSignature, }, - ) - require.NoError(t.t, err) - require.False(t.t, combineSigResp.HaveAllSignatures) - require.Empty(t.t, combineSigResp.FinalSignature) + } + combineSigResp := alice.RPC.MuSig2CombineSig(combineReq) + require.False(ht, combineSigResp.HaveAllSignatures) + require.Empty(ht, combineSigResp.FinalSignature) - signResp3, err := alice.SignerClient.MuSig2Sign( - ctxt, &signrpc.MuSig2SignRequest{ - SessionId: sessResp3.SessionId, - MessageDigest: sigHash, - }, - ) - require.NoError(t.t, err) + signReq = &signrpc.MuSig2SignRequest{ + SessionId: sessResp3.SessionId, + MessageDigest: sigHash, + } + signResp3 := alice.RPC.MuSig2Sign(signReq) // We manually clean up session 3, just to make sure that works as well. - _, err = alice.SignerClient.MuSig2Cleanup( - ctxt, &signrpc.MuSig2CleanupRequest{ - SessionId: sessResp3.SessionId, - }, - ) - require.NoError(t.t, err) + cleanReq := &signrpc.MuSig2CleanupRequest{ + SessionId: sessResp3.SessionId, + } + alice.RPC.MuSig2Cleanup(cleanReq) // A second call to that cleaned up session should now fail with a // specific error. - _, err = alice.SignerClient.MuSig2Sign( - ctxt, &signrpc.MuSig2SignRequest{ - SessionId: sessResp3.SessionId, - MessageDigest: sigHash, - }, - ) - require.Error(t.t, err) - require.Contains(t.t, err.Error(), "not found") + signReq = &signrpc.MuSig2SignRequest{ + SessionId: sessResp3.SessionId, + MessageDigest: sigHash, + } + err = alice.RPC.MuSig2SignErr(signReq) + require.Contains(ht, err.Error(), "not found") // Luckily only one of the signers needs to combine the signature, so // let's do that now. - combineReq1, err := alice.SignerClient.MuSig2CombineSig( - ctxt, &signrpc.MuSig2CombineSigRequest{ - SessionId: sessResp1.SessionId, - OtherPartialSignatures: [][]byte{ - signResp3.LocalPartialSignature, - }, + combineReq = &signrpc.MuSig2CombineSigRequest{ + SessionId: sessResp1.SessionId, + OtherPartialSignatures: [][]byte{ + signResp3.LocalPartialSignature, }, - ) - require.NoError(t.t, err) - require.Equal(t.t, true, combineReq1.HaveAllSignatures) - require.NotEmpty(t.t, combineReq1.FinalSignature) + } + combineResp1 := alice.RPC.MuSig2CombineSig(combineReq) + require.Equal(ht, true, combineResp1.HaveAllSignatures) + require.NotEmpty(ht, combineResp1.FinalSignature) - sig, err := schnorr.ParseSignature(combineReq1.FinalSignature) - require.NoError(t.t, err) - require.True(t.t, sig.Verify(sigHash, combinedPubKey)) + sig, err := schnorr.ParseSignature(combineResp1.FinalSignature) + require.NoError(ht, err) + require.True(ht, sig.Verify(sigHash, combinedPubKey)) // We can now assemble the witness stack. controlBlockBytes, err := tapscript.ControlBlock.ToBytes() - require.NoError(t.t, err) + require.NoError(ht, err) tx.TxIn[0].Witness = wire.TxWitness{ - combineReq1.FinalSignature, + combineResp1.FinalSignature, leaf.Script, controlBlockBytes, } @@ -1117,7 +1048,7 @@ func testTaprootMuSig2CombinedLeafKeySpend(ctxt context.Context, t *harnessTest, // Serialize, weigh and publish the TX now, then make sure the // coins are sent and confirmed to the final sweep destination address. publishTxAndConfirmSweep( - ctxt, t, net, alice, tx, estimatedWeight, + ht, alice, tx, estimatedWeight, &chainrpc.SpendRequest{ Outpoint: &chainrpc.Outpoint{ Hash: p2trOutpoint.Hash[:], @@ -1131,67 +1062,64 @@ func testTaprootMuSig2CombinedLeafKeySpend(ctxt context.Context, t *harnessTest, // testTaprootImportTapscriptScriptSpend tests importing p2tr script addresses // using the script path with the full tree known. -func testTaprootImportTapscriptFullTree(ctxt context.Context, t *harnessTest, - alice *lntest.HarnessNode, net *lntest.NetworkHarness) { +func testTaprootImportTapscriptFullTree(ht *lntemp.HarnessTest, + alice *node.HarnessNode) { // For the next step, we need a public key. Let's use a special family // for this. - _, internalKey, derivationPath := deriveInternalKey(ctxt, t, alice) + _, internalKey, derivationPath := deriveInternalKey(ht, alice) // Let's create a taproot script output now. This is a hash lock with a // simple preimage of "foobar". - leaf1 := testScriptHashLock(t.t, []byte("foobar")) + leaf1 := testScriptHashLock(ht.T, []byte("foobar")) // Let's add a second script output as well to test the partial reveal. - leaf2 := testScriptSchnorrSig(t.t, internalKey) + leaf2 := testScriptSchnorrSig(ht.T, internalKey) tapscript := input.TapscriptFullTree(internalKey, leaf1, leaf2) tree := txscript.AssembleTaprootScriptTree(leaf1, leaf2) rootHash := tree.RootNode.TapHash() taprootKey, err := tapscript.TaprootKey() - require.NoError(t.t, err) + require.NoError(ht, err) // Import the scripts and make sure we get the same address back as we // calculated ourselves. - importResp, err := alice.WalletKitClient.ImportTapscript( - ctxt, &walletrpc.ImportTapscriptRequest{ - InternalPublicKey: schnorr.SerializePubKey(internalKey), - Script: &walletrpc.ImportTapscriptRequest_FullTree{ - FullTree: &walletrpc.TapscriptFullTree{ - AllLeaves: []*walletrpc.TapLeaf{{ - LeafVersion: uint32( - leaf1.LeafVersion, - ), - Script: leaf1.Script, - }, { - LeafVersion: uint32( - leaf2.LeafVersion, - ), - Script: leaf2.Script, - }}, - }, + req := &walletrpc.ImportTapscriptRequest{ + InternalPublicKey: schnorr.SerializePubKey(internalKey), + Script: &walletrpc.ImportTapscriptRequest_FullTree{ + FullTree: &walletrpc.TapscriptFullTree{ + AllLeaves: []*walletrpc.TapLeaf{{ + LeafVersion: uint32( + leaf1.LeafVersion, + ), + Script: leaf1.Script, + }, { + LeafVersion: uint32( + leaf2.LeafVersion, + ), + Script: leaf2.Script, + }}, }, }, - ) - require.NoError(t.t, err) + } + importResp := alice.RPC.ImportTapscript(req) calculatedAddr, err := btcutil.NewAddressTaproot( schnorr.SerializePubKey(taprootKey), harnessNetParams, ) - require.NoError(t.t, err) - require.Equal(t.t, calculatedAddr.String(), importResp.P2TrAddress) + require.NoError(ht, err) + require.Equal(ht, calculatedAddr.String(), importResp.P2TrAddress) // Send some coins to the generated tapscript address. p2trOutpoint, p2trPkScript := sendToTaprootOutput( - ctxt, t, net, alice, taprootKey, testAmount, + ht, alice, taprootKey, testAmount, ) - p2trOutputRPC := &lnrpc.OutPoint{ TxidBytes: p2trOutpoint.Hash[:], OutputIndex: p2trOutpoint.Index, } - assertWalletUnspent(t, alice, p2trOutputRPC, "imported") - assertAccountBalance(t.t, alice, "imported", testAmount, 0) + ht.AssertUTXOInWallet(alice, p2trOutputRPC, "imported") + ht.AssertWalletAccountBalance(alice, "imported", testAmount, 0) // Funding a PSBT from an imported script is not yet possible. So we // basically need to add all information manually for the wallet to be @@ -1201,26 +1129,26 @@ func testTaprootImportTapscriptFullTree(ctxt context.Context, t *harnessTest, PkScript: p2trPkScript, } clearWalletImportedTapscriptBalance( - ctxt, t, alice, utxo, p2trOutpoint, internalKey, derivationPath, + ht, alice, utxo, p2trOutpoint, internalKey, derivationPath, rootHash[:], ) } // testTaprootImportTapscriptPartialReveal tests importing p2tr script addresses // for which we only know part of the tree. -func testTaprootImportTapscriptPartialReveal(ctxt context.Context, - t *harnessTest, alice *lntest.HarnessNode, net *lntest.NetworkHarness) { +func testTaprootImportTapscriptPartialReveal(ht *lntemp.HarnessTest, + alice *node.HarnessNode) { // For the next step, we need a public key. Let's use a special family // for this. - _, internalKey, derivationPath := deriveInternalKey(ctxt, t, alice) + _, internalKey, derivationPath := deriveInternalKey(ht, alice) // Let's create a taproot script output now. This is a hash lock with a // simple preimage of "foobar". - leaf1 := testScriptHashLock(t.t, []byte("foobar")) + leaf1 := testScriptHashLock(ht.T, []byte("foobar")) // Let's add a second script output as well to test the partial reveal. - leaf2 := testScriptSchnorrSig(t.t, internalKey) + leaf2 := testScriptSchnorrSig(ht.T, internalKey) leaf2Hash := leaf2.TapHash() tapscript := input.TapscriptPartialReveal( @@ -1228,43 +1156,41 @@ func testTaprootImportTapscriptPartialReveal(ctxt context.Context, ) rootHash := tapscript.ControlBlock.RootHash(leaf1.Script) taprootKey, err := tapscript.TaprootKey() - require.NoError(t.t, err) + require.NoError(ht, err) // Import the scripts and make sure we get the same address back as we // calculated ourselves. - importResp, err := alice.WalletKitClient.ImportTapscript( - ctxt, &walletrpc.ImportTapscriptRequest{ - InternalPublicKey: schnorr.SerializePubKey(internalKey), - Script: &walletrpc.ImportTapscriptRequest_PartialReveal{ - PartialReveal: &walletrpc.TapscriptPartialReveal{ - RevealedLeaf: &walletrpc.TapLeaf{ - LeafVersion: uint32(leaf1.LeafVersion), - Script: leaf1.Script, - }, - FullInclusionProof: leaf2Hash[:], + req := &walletrpc.ImportTapscriptRequest{ + InternalPublicKey: schnorr.SerializePubKey(internalKey), + Script: &walletrpc.ImportTapscriptRequest_PartialReveal{ + PartialReveal: &walletrpc.TapscriptPartialReveal{ + RevealedLeaf: &walletrpc.TapLeaf{ + LeafVersion: uint32(leaf1.LeafVersion), + Script: leaf1.Script, }, + FullInclusionProof: leaf2Hash[:], }, }, - ) - require.NoError(t.t, err) + } + importResp := alice.RPC.ImportTapscript(req) calculatedAddr, err := btcutil.NewAddressTaproot( schnorr.SerializePubKey(taprootKey), harnessNetParams, ) - require.NoError(t.t, err) - require.Equal(t.t, calculatedAddr.String(), importResp.P2TrAddress) + require.NoError(ht, err) + require.Equal(ht, calculatedAddr.String(), importResp.P2TrAddress) // Send some coins to the generated tapscript address. p2trOutpoint, p2trPkScript := sendToTaprootOutput( - ctxt, t, net, alice, taprootKey, testAmount, + ht, alice, taprootKey, testAmount, ) p2trOutputRPC := &lnrpc.OutPoint{ TxidBytes: p2trOutpoint.Hash[:], OutputIndex: p2trOutpoint.Index, } - assertWalletUnspent(t, alice, p2trOutputRPC, "imported") - assertAccountBalance(t.t, alice, "imported", testAmount, 0) + ht.AssertUTXOInWallet(alice, p2trOutputRPC, "imported") + ht.AssertWalletAccountBalance(alice, "imported", testAmount, 0) // Funding a PSBT from an imported script is not yet possible. So we // basically need to add all information manually for the wallet to be @@ -1274,58 +1200,56 @@ func testTaprootImportTapscriptPartialReveal(ctxt context.Context, PkScript: p2trPkScript, } clearWalletImportedTapscriptBalance( - ctxt, t, alice, utxo, p2trOutpoint, internalKey, derivationPath, + ht, alice, utxo, p2trOutpoint, internalKey, derivationPath, rootHash, ) } // testTaprootImportTapscriptRootHashOnly tests importing p2tr script addresses // for which we only know the root hash. -func testTaprootImportTapscriptRootHashOnly(ctxt context.Context, - t *harnessTest, alice *lntest.HarnessNode, net *lntest.NetworkHarness) { +func testTaprootImportTapscriptRootHashOnly(ht *lntemp.HarnessTest, + alice *node.HarnessNode) { // For the next step, we need a public key. Let's use a special family // for this. - _, internalKey, derivationPath := deriveInternalKey(ctxt, t, alice) + _, internalKey, derivationPath := deriveInternalKey(ht, alice) // Let's create a taproot script output now. This is a hash lock with a // simple preimage of "foobar". - leaf1 := testScriptHashLock(t.t, []byte("foobar")) + leaf1 := testScriptHashLock(ht.T, []byte("foobar")) rootHash := leaf1.TapHash() tapscript := input.TapscriptRootHashOnly(internalKey, rootHash[:]) taprootKey, err := tapscript.TaprootKey() - require.NoError(t.t, err) + require.NoError(ht, err) // Import the scripts and make sure we get the same address back as we // calculated ourselves. - importResp, err := alice.WalletKitClient.ImportTapscript( - ctxt, &walletrpc.ImportTapscriptRequest{ - InternalPublicKey: schnorr.SerializePubKey(internalKey), - Script: &walletrpc.ImportTapscriptRequest_RootHashOnly{ - RootHashOnly: rootHash[:], - }, + req := &walletrpc.ImportTapscriptRequest{ + InternalPublicKey: schnorr.SerializePubKey(internalKey), + Script: &walletrpc.ImportTapscriptRequest_RootHashOnly{ + RootHashOnly: rootHash[:], }, - ) - require.NoError(t.t, err) + } + importResp := alice.RPC.ImportTapscript(req) calculatedAddr, err := btcutil.NewAddressTaproot( schnorr.SerializePubKey(taprootKey), harnessNetParams, ) - require.NoError(t.t, err) - require.Equal(t.t, calculatedAddr.String(), importResp.P2TrAddress) + require.NoError(ht, err) + require.Equal(ht, calculatedAddr.String(), importResp.P2TrAddress) // Send some coins to the generated tapscript address. p2trOutpoint, p2trPkScript := sendToTaprootOutput( - ctxt, t, net, alice, taprootKey, testAmount, + ht, alice, taprootKey, testAmount, ) p2trOutputRPC := &lnrpc.OutPoint{ TxidBytes: p2trOutpoint.Hash[:], OutputIndex: p2trOutpoint.Index, } - assertWalletUnspent(t, alice, p2trOutputRPC, "imported") - assertAccountBalance(t.t, alice, "imported", testAmount, 0) + ht.AssertUTXOInWallet(alice, p2trOutputRPC, "imported") + ht.AssertWalletAccountBalance(alice, "imported", testAmount, 0) // Funding a PSBT from an imported script is not yet possible. So we // basically need to add all information manually for the wallet to be @@ -1335,58 +1259,56 @@ func testTaprootImportTapscriptRootHashOnly(ctxt context.Context, PkScript: p2trPkScript, } clearWalletImportedTapscriptBalance( - ctxt, t, alice, utxo, p2trOutpoint, internalKey, derivationPath, + ht, alice, utxo, p2trOutpoint, internalKey, derivationPath, rootHash[:], ) } // testTaprootImportTapscriptFullKey tests importing p2tr script addresses for // which we only know the full Taproot key. -func testTaprootImportTapscriptFullKey(ctxt context.Context, t *harnessTest, - alice *lntest.HarnessNode, net *lntest.NetworkHarness) { +func testTaprootImportTapscriptFullKey(ht *lntemp.HarnessTest, + alice *node.HarnessNode) { // For the next step, we need a public key. Let's use a special family // for this. - _, internalKey, derivationPath := deriveInternalKey(ctxt, t, alice) + _, internalKey, derivationPath := deriveInternalKey(ht, alice) // Let's create a taproot script output now. This is a hash lock with a // simple preimage of "foobar". - leaf1 := testScriptHashLock(t.t, []byte("foobar")) + leaf1 := testScriptHashLock(ht.T, []byte("foobar")) tapscript := input.TapscriptFullTree(internalKey, leaf1) rootHash := leaf1.TapHash() taprootKey, err := tapscript.TaprootKey() - require.NoError(t.t, err) + require.NoError(ht, err) // Import the scripts and make sure we get the same address back as we // calculated ourselves. - importResp, err := alice.WalletKitClient.ImportTapscript( - ctxt, &walletrpc.ImportTapscriptRequest{ - InternalPublicKey: schnorr.SerializePubKey(taprootKey), - Script: &walletrpc.ImportTapscriptRequest_FullKeyOnly{ - FullKeyOnly: true, - }, + req := &walletrpc.ImportTapscriptRequest{ + InternalPublicKey: schnorr.SerializePubKey(taprootKey), + Script: &walletrpc.ImportTapscriptRequest_FullKeyOnly{ + FullKeyOnly: true, }, - ) - require.NoError(t.t, err) + } + importResp := alice.RPC.ImportTapscript(req) calculatedAddr, err := btcutil.NewAddressTaproot( schnorr.SerializePubKey(taprootKey), harnessNetParams, ) - require.NoError(t.t, err) - require.Equal(t.t, calculatedAddr.String(), importResp.P2TrAddress) + require.NoError(ht, err) + require.Equal(ht, calculatedAddr.String(), importResp.P2TrAddress) // Send some coins to the generated tapscript address. p2trOutpoint, p2trPkScript := sendToTaprootOutput( - ctxt, t, net, alice, taprootKey, testAmount, + ht, alice, taprootKey, testAmount, ) p2trOutputRPC := &lnrpc.OutPoint{ TxidBytes: p2trOutpoint.Hash[:], OutputIndex: p2trOutpoint.Index, } - assertWalletUnspent(t, alice, p2trOutputRPC, "imported") - assertAccountBalance(t.t, alice, "imported", testAmount, 0) + ht.AssertUTXOInWallet(alice, p2trOutputRPC, "imported") + ht.AssertWalletAccountBalance(alice, "imported", testAmount, 0) // Funding a PSBT from an imported script is not yet possible. So we // basically need to add all information manually for the wallet to be @@ -1396,20 +1318,20 @@ func testTaprootImportTapscriptFullKey(ctxt context.Context, t *harnessTest, PkScript: p2trPkScript, } clearWalletImportedTapscriptBalance( - ctxt, t, alice, utxo, p2trOutpoint, internalKey, derivationPath, + ht, alice, utxo, p2trOutpoint, internalKey, derivationPath, rootHash[:], ) } // clearWalletImportedTapscriptBalance manually assembles and then attempts to // sign a TX to sweep funds from an imported tapscript address. -func clearWalletImportedTapscriptBalance(ctx context.Context, t *harnessTest, - node *lntest.HarnessNode, utxo *wire.TxOut, outPoint wire.OutPoint, +func clearWalletImportedTapscriptBalance(ht *lntemp.HarnessTest, + hn *node.HarnessNode, utxo *wire.TxOut, outPoint wire.OutPoint, internalKey *btcec.PublicKey, derivationPath []uint32, rootHash []byte) { _, sweepPkScript := newAddrWithScript( - ctx, t.t, node, lnrpc.AddressType_WITNESS_PUBKEY_HASH, + ht, hn, lnrpc.AddressType_WITNESS_PUBKEY_HASH, ) output := &wire.TxOut{ @@ -1420,7 +1342,7 @@ func clearWalletImportedTapscriptBalance(ctx context.Context, t *harnessTest, []*wire.OutPoint{&outPoint}, []*wire.TxOut{output}, 2, 0, []uint32{0}, ) - require.NoError(t.t, err) + require.NoError(ht, err) // We have everything we need to know to sign the PSBT. in := &packet.Inputs[0] @@ -1437,44 +1359,38 @@ func clearWalletImportedTapscriptBalance(ctx context.Context, t *harnessTest, in.WitnessUtxo = utxo var buf bytes.Buffer - require.NoError(t.t, packet.Serialize(&buf)) + require.NoError(ht, packet.Serialize(&buf)) // Sign the manually funded PSBT now. - signResp, err := node.WalletKitClient.SignPsbt( - ctx, &walletrpc.SignPsbtRequest{ - FundedPsbt: buf.Bytes(), - }, - ) - require.NoError(t.t, err) + signResp := hn.RPC.SignPsbt(&walletrpc.SignPsbtRequest{ + FundedPsbt: buf.Bytes(), + }) signedPacket, err := psbt.NewFromRawBytes( bytes.NewReader(signResp.SignedPsbt), false, ) - require.NoError(t.t, err) + require.NoError(ht, err) // We should be able to finalize the PSBT and extract the sweep TX now. err = psbt.MaybeFinalizeAll(signedPacket) - require.NoError(t.t, err) + require.NoError(ht, err) sweepTx, err := psbt.Extract(signedPacket) - require.NoError(t.t, err) + require.NoError(ht, err) buf.Reset() err = sweepTx.Serialize(&buf) - require.NoError(t.t, err) + require.NoError(ht, err) // Publish the sweep transaction and then mine it as well. - _, err = node.WalletKitClient.PublishTransaction( - ctx, &walletrpc.Transaction{ - TxHex: buf.Bytes(), - }, - ) - require.NoError(t.t, err) + hn.RPC.PublishTransaction(&walletrpc.Transaction{ + TxHex: buf.Bytes(), + }) // Mine one block which should contain the sweep transaction. - block := mineBlocks(t, t.lndHarness, 1, 1)[0] + block := ht.MineBlocksAndAssertNumTxes(1, 1)[0] sweepTxHash := sweepTx.TxHash() - assertTxInBlock(t, block, &sweepTxHash) + ht.Miner.AssertTxInBlock(block, &sweepTxHash) } // testScriptHashLock returns a simple bitcoin script that locks the funds to @@ -1504,53 +1420,45 @@ func testScriptSchnorrSig(t *testing.T, } // newAddrWithScript returns a new address and its pkScript. -func newAddrWithScript(ctx context.Context, t *testing.T, - node *lntest.HarnessNode, addrType lnrpc.AddressType) (btcutil.Address, - []byte) { +func newAddrWithScript(ht *lntemp.HarnessTest, node *node.HarnessNode, + addrType lnrpc.AddressType) (btcutil.Address, []byte) { - newAddrResp, err := node.NewAddress(ctx, &lnrpc.NewAddressRequest{ + p2wkhResp := node.RPC.NewAddress(&lnrpc.NewAddressRequest{ Type: addrType, }) - require.NoError(t, err) - - addr, err := btcutil.DecodeAddress( - newAddrResp.Address, harnessNetParams, + p2wkhAddr, err := btcutil.DecodeAddress( + p2wkhResp.Address, harnessNetParams, ) - require.NoError(t, err) + require.NoError(ht, err) - pkScript, err := txscript.PayToAddrScript(addr) - require.NoError(t, err) + p2wkhPkScript, err := txscript.PayToAddrScript(p2wkhAddr) + require.NoError(ht, err) - return addr, pkScript + return p2wkhAddr, p2wkhPkScript } // sendToTaprootOutput sends coins to a p2tr output of the given taproot key and // mines a block to confirm the coins. -func sendToTaprootOutput(ctx context.Context, t *harnessTest, - net *lntest.NetworkHarness, node *lntest.HarnessNode, +func sendToTaprootOutput(ht *lntemp.HarnessTest, hn *node.HarnessNode, taprootKey *btcec.PublicKey, amt int64) (wire.OutPoint, []byte) { tapScriptAddr, err := btcutil.NewAddressTaproot( schnorr.SerializePubKey(taprootKey), harnessNetParams, ) - require.NoError(t.t, err) + require.NoError(ht, err) p2trPkScript, err := txscript.PayToAddrScript(tapScriptAddr) - require.NoError(t.t, err) + require.NoError(ht, err) // Send some coins to the generated tapscript address. - _, err = node.SendCoins(ctx, &lnrpc.SendCoinsRequest{ + req := &lnrpc.SendCoinsRequest{ Addr: tapScriptAddr.String(), Amount: amt, - }) - require.NoError(t.t, err) + } + hn.RPC.SendCoins(req) // Wait until the TX is found in the mempool. - txid, err := waitForTxInMempool(net.Miner.Client, minerMempoolTimeout) - require.NoError(t.t, err) - - p2trOutputIndex := getOutputIndex( - t, net.Miner, txid, tapScriptAddr.String(), - ) + txid := ht.Miner.AssertNumTxsInMempool(1)[0] + p2trOutputIndex := ht.GetOutputIndex(txid, tapScriptAddr.String()) p2trOutpoint := wire.OutPoint{ Hash: *txid, Index: uint32(p2trOutputIndex), @@ -1559,14 +1467,11 @@ func sendToTaprootOutput(ctx context.Context, t *harnessTest, // Make sure the transaction is recognized by our wallet and has the // correct output type. var outputDetail *lnrpc.OutputDetail - walletTxns, err := node.GetTransactions( - ctx, &lnrpc.GetTransactionsRequest{ - StartHeight: 0, - EndHeight: -1, - }, - ) - require.NoError(t.t, err) - require.NotEmpty(t.t, walletTxns.Transactions) + walletTxns := hn.RPC.GetTransactions(&lnrpc.GetTransactionsRequest{ + StartHeight: 0, + EndHeight: -1, + }) + require.NotEmpty(ht, walletTxns.Transactions) for _, tx := range walletTxns.Transactions { if tx.TxHash != txid.String() { continue @@ -1581,14 +1486,14 @@ func sendToTaprootOutput(ctx context.Context, t *harnessTest, break } } - require.NotNil(t.t, outputDetail, "transaction not found in wallet") + require.NotNil(ht, outputDetail, "transaction not found in wallet") require.Equal( - t.t, lnrpc.OutputScriptType_SCRIPT_TYPE_WITNESS_V1_TAPROOT, + ht, lnrpc.OutputScriptType_SCRIPT_TYPE_WITNESS_V1_TAPROOT, outputDetail.OutputType, ) // Clear the mempool. - mineBlocks(t, net, 1, 1) + ht.MineBlocksAndAssertNumTxes(1, 1) return p2trOutpoint, p2trPkScript } @@ -1597,158 +1502,134 @@ func sendToTaprootOutput(ctx context.Context, t *harnessTest, // after checking its weight against an estimate. After asserting the given // spend request, the given sweep address' balance is verified to be seen as // funds belonging to the wallet. -func publishTxAndConfirmSweep(ctx context.Context, t *harnessTest, - net *lntest.NetworkHarness, node *lntest.HarnessNode, tx *wire.MsgTx, - estimatedWeight int64, spendRequest *chainrpc.SpendRequest, - sweepAddr string) { +func publishTxAndConfirmSweep(ht *lntemp.HarnessTest, node *node.HarnessNode, + tx *wire.MsgTx, estimatedWeight int64, + spendRequest *chainrpc.SpendRequest, sweepAddr string) { // Before we publish the tx that spends the p2tr transaction, we want to // register a spend listener that we expect to fire after mining the // block. - _, currentHeight, err := net.Miner.Client.GetBestBlock() - require.NoError(t.t, err) + _, currentHeight := ht.Miner.GetBestBlock() // For a Taproot output we cannot leave the outpoint empty. Let's make // sure the API returns the correct error here. - spendClient, err := node.ChainClient.RegisterSpendNtfn( - ctx, &chainrpc.SpendRequest{ - Script: spendRequest.Script, - HeightHint: uint32(currentHeight), - }, - ) - require.NoError(t.t, err) + req := &chainrpc.SpendRequest{ + Script: spendRequest.Script, + HeightHint: uint32(currentHeight), + } + spendClient := node.RPC.RegisterSpendNtfn(req) // The error is only thrown when trying to read a message. - _, err = spendClient.Recv() + _, err := spendClient.Recv() require.Contains( - t.t, err.Error(), + ht, err.Error(), "cannot register witness v1 spend request without outpoint", ) // Now try again, this time with the outpoint set. - spendClient, err = node.ChainClient.RegisterSpendNtfn( - ctx, &chainrpc.SpendRequest{ - Outpoint: spendRequest.Outpoint, - Script: spendRequest.Script, - HeightHint: uint32(currentHeight), - }, - ) - require.NoError(t.t, err) + req = &chainrpc.SpendRequest{ + Outpoint: spendRequest.Outpoint, + Script: spendRequest.Script, + HeightHint: uint32(currentHeight), + } + spendClient = node.RPC.RegisterSpendNtfn(req) var buf bytes.Buffer - require.NoError(t.t, tx.Serialize(&buf)) + require.NoError(ht, tx.Serialize(&buf)) // Since Schnorr signatures are fixed size, we must be able to estimate // the size of this transaction exactly. txWeight := blockchain.GetTransactionWeight(btcutil.NewTx(tx)) - require.Equal(t.t, estimatedWeight, txWeight) + require.Equal(ht, estimatedWeight, txWeight) - _, err = node.WalletKitClient.PublishTransaction( - ctx, &walletrpc.Transaction{ - TxHex: buf.Bytes(), - }, - ) - require.NoError(t.t, err) + txReq := &walletrpc.Transaction{ + TxHex: buf.Bytes(), + } + node.RPC.PublishTransaction(txReq) // Make sure the coins sent to the address are confirmed correctly, // including the confirmation notification. - confirmAddress(ctx, t, net, node, sweepAddr) + confirmAddress(ht, node, sweepAddr) // We now expect our spend event to go through. spendMsg, err := spendClient.Recv() - require.NoError(t.t, err) + require.NoError(ht, err) spend := spendMsg.GetSpend() - require.NotNil(t.t, spend) - require.Equal(t.t, spend.SpendingHeight, uint32(currentHeight+1)) + require.NotNil(ht, spend) + require.Equal(ht, spend.SpendingHeight, uint32(currentHeight+1)) } // confirmAddress makes sure that a transaction in the mempool spends funds to // the given address. It also checks that a confirmation notification for the // address is triggered when the transaction is mined. -func confirmAddress(ctx context.Context, t *harnessTest, - net *lntest.NetworkHarness, node *lntest.HarnessNode, +func confirmAddress(ht *lntemp.HarnessTest, hn *node.HarnessNode, addrString string) { // Wait until the tx that sends to the address is found. - txid, err := waitForTxInMempool(net.Miner.Client, minerMempoolTimeout) - require.NoError(t.t, err) + txid := ht.Miner.AssertNumTxsInMempool(1)[0] // Wait until bob has seen the tx and considers it as owned. - addrOutputIndex := getOutputIndex(t, net.Miner, txid, addrString) + addrOutputIndex := ht.GetOutputIndex(txid, addrString) op := &lnrpc.OutPoint{ TxidBytes: txid[:], OutputIndex: uint32(addrOutputIndex), } - assertWalletUnspent(t, node, op, "") + ht.AssertUTXOInWallet(hn, op, "") // Before we confirm the transaction, let's register a confirmation // listener for it, which we expect to fire after mining a block. parsedAddr, err := btcutil.DecodeAddress(addrString, harnessNetParams) - require.NoError(t.t, err) + require.NoError(ht, err) addrPkScript, err := txscript.PayToAddrScript(parsedAddr) - require.NoError(t.t, err) + require.NoError(ht, err) - _, currentHeight, err := net.Miner.Client.GetBestBlock() - require.NoError(t.t, err) - - // We'll register for a conf notification, and also request the block - // that included it as well. - confClient, err := node.ChainClient.RegisterConfirmationsNtfn( - ctx, &chainrpc.ConfRequest{ - Script: addrPkScript, - Txid: txid[:], - HeightHint: uint32(currentHeight), - NumConfs: 1, - IncludeBlock: true, - }, - ) - require.NoError(t.t, err) + _, currentHeight := ht.Miner.GetBestBlock() + req := &chainrpc.ConfRequest{ + Script: addrPkScript, + Txid: txid[:], + HeightHint: uint32(currentHeight), + NumConfs: 1, + IncludeBlock: true, + } + confClient := hn.RPC.RegisterConfirmationsNtfn(req) // Mine another block to clean up the mempool. - mineBlocks(t, net, 1, 1) + ht.MineBlocksAndAssertNumTxes(1, 1) // We now expect our confirmation to go through, and also that the // block was specified. confMsg, err := confClient.Recv() - require.NoError(t.t, err) + require.NoError(ht, err) conf := confMsg.GetConf() - require.NotNil(t.t, conf) - require.Equal(t.t, conf.BlockHeight, uint32(currentHeight+1)) - require.NotNil(t.t, conf.RawBlock) + require.NotNil(ht, conf) + require.Equal(ht, conf.BlockHeight, uint32(currentHeight+1)) + require.NotNil(ht, conf.RawBlock) // We should also be able to decode the raw block. var blk wire.MsgBlock - require.NoError(t.t, blk.Deserialize(bytes.NewReader(conf.RawBlock))) + require.NoError(ht, blk.Deserialize(bytes.NewReader(conf.RawBlock))) } // deriveSigningKeys derives three signing keys and returns their descriptors, // as well as the public keys in the Schnorr serialized format. -func deriveSigningKeys(ctx context.Context, t *harnessTest, - node *lntest.HarnessNode) (*signrpc.KeyDescriptor, +func deriveSigningKeys(ht *lntemp.HarnessTest, + node *node.HarnessNode) (*signrpc.KeyDescriptor, *signrpc.KeyDescriptor, *signrpc.KeyDescriptor, [][]byte) { // For muSig2 we need multiple keys. We derive three of them from the // same wallet, just so we know we can also sign for them again. - keyDesc1, err := node.WalletKitClient.DeriveNextKey( - ctx, &walletrpc.KeyReq{KeyFamily: testTaprootKeyFamily}, - ) - require.NoError(t.t, err) + req := &walletrpc.KeyReq{KeyFamily: testTaprootKeyFamily} + keyDesc1 := node.RPC.DeriveNextKey(req) pubKey1, err := btcec.ParsePubKey(keyDesc1.RawKeyBytes) - require.NoError(t.t, err) + require.NoError(ht, err) - keyDesc2, err := node.WalletKitClient.DeriveNextKey( - ctx, &walletrpc.KeyReq{KeyFamily: testTaprootKeyFamily}, - ) - require.NoError(t.t, err) + keyDesc2 := node.RPC.DeriveNextKey(req) pubKey2, err := btcec.ParsePubKey(keyDesc2.RawKeyBytes) - require.NoError(t.t, err) + require.NoError(ht, err) - keyDesc3, err := node.WalletKitClient.DeriveNextKey( - ctx, &walletrpc.KeyReq{KeyFamily: testTaprootKeyFamily}, - ) - require.NoError(t.t, err) + keyDesc3 := node.RPC.DeriveNextKey(req) pubKey3, err := btcec.ParsePubKey(keyDesc3.RawKeyBytes) - require.NoError(t.t, err) + require.NoError(ht, err) // Now that we have all three keys we can create three sessions, one // for each of the signers. This would of course normally not happen on @@ -1766,21 +1647,19 @@ func deriveSigningKeys(ctx context.Context, t *harnessTest, // combined into a single key. The same node is used for the three signing // participants but a separate key is generated for each session. So the result // should be the same as if it were three different nodes. -func createMuSigSessions(ctx context.Context, t *harnessTest, - node *lntest.HarnessNode, taprootTweak *signrpc.TaprootTweakDesc, +func createMuSigSessions(ht *lntemp.HarnessTest, node *node.HarnessNode, + taprootTweak *signrpc.TaprootTweakDesc, keyDesc1, keyDesc2, keyDesc3 *signrpc.KeyDescriptor, allPubKeys [][]byte) (*btcec.PublicKey, *btcec.PublicKey, *signrpc.MuSig2SessionResponse, *signrpc.MuSig2SessionResponse, *signrpc.MuSig2SessionResponse) { - sessResp1, err := node.SignerClient.MuSig2CreateSession( - ctx, &signrpc.MuSig2SessionRequest{ - KeyLoc: keyDesc1.KeyLoc, - AllSignerPubkeys: allPubKeys, - TaprootTweak: taprootTweak, - }, - ) - require.NoError(t.t, err) + req := &signrpc.MuSig2SessionRequest{ + KeyLoc: keyDesc1.KeyLoc, + AllSignerPubkeys: allPubKeys, + TaprootTweak: taprootTweak, + } + sessResp1 := node.RPC.MuSig2CreateSession(req) // Now that we have the three keys in a combined form, we want to make // sure the tweaking for the taproot key worked correctly. We first need @@ -1789,7 +1668,7 @@ func createMuSigSessions(ctx context.Context, t *harnessTest, // tapHash of the script root hash. We should arrive at the same result // as the API. combinedKey, err := schnorr.ParsePubKey(sessResp1.CombinedKey) - require.NoError(t.t, err) + require.NoError(ht, err) // When combining the key without creating a session, we expect the same // combined key to be created. @@ -1804,7 +1683,7 @@ func createMuSigSessions(ctx context.Context, t *harnessTest, internalKey, err = schnorr.ParsePubKey( sessResp1.TaprootInternalKey, ) - require.NoError(t.t, err) + require.NoError(ht, err) // We now know the taproot key. The session with the tweak // applied should produce the same key! @@ -1812,127 +1691,96 @@ func createMuSigSessions(ctx context.Context, t *harnessTest, internalKey, taprootTweak.ScriptRoot, ) require.Equal( - t.t, schnorr.SerializePubKey(expectedCombinedKey), + ht, schnorr.SerializePubKey(expectedCombinedKey), schnorr.SerializePubKey(combinedKey), ) } // We should also get the same keys when just calling the // MuSig2CombineKeys RPC. - combineResp, err := node.SignerClient.MuSig2CombineKeys( - ctx, &signrpc.MuSig2CombineKeysRequest{ - AllSignerPubkeys: allPubKeys, - TaprootTweak: taprootTweak, - }, - ) - require.NoError(t.t, err) + combineReq := &signrpc.MuSig2CombineKeysRequest{ + AllSignerPubkeys: allPubKeys, + TaprootTweak: taprootTweak, + } + combineResp := node.RPC.MuSig2CombineKeys(combineReq) require.Equal( - t.t, schnorr.SerializePubKey(expectedCombinedKey), + ht, schnorr.SerializePubKey(expectedCombinedKey), combineResp.CombinedKey, ) require.Equal( - t.t, schnorr.SerializePubKey(internalKey), + ht, schnorr.SerializePubKey(internalKey), combineResp.TaprootInternalKey, ) // Everything is good so far, let's continue with creating the signing // session for the other two participants. - sessResp2, err := node.SignerClient.MuSig2CreateSession( - ctx, &signrpc.MuSig2SessionRequest{ - KeyLoc: keyDesc2.KeyLoc, - AllSignerPubkeys: allPubKeys, - OtherSignerPublicNonces: [][]byte{ - sessResp1.LocalPublicNonces, - }, - TaprootTweak: taprootTweak, + req = &signrpc.MuSig2SessionRequest{ + KeyLoc: keyDesc2.KeyLoc, + AllSignerPubkeys: allPubKeys, + OtherSignerPublicNonces: [][]byte{ + sessResp1.LocalPublicNonces, }, - ) - require.NoError(t.t, err) - require.Equal(t.t, sessResp1.CombinedKey, sessResp2.CombinedKey) + TaprootTweak: taprootTweak, + } + sessResp2 := node.RPC.MuSig2CreateSession(req) + require.Equal(ht, sessResp1.CombinedKey, sessResp2.CombinedKey) - sessResp3, err := node.SignerClient.MuSig2CreateSession( - ctx, &signrpc.MuSig2SessionRequest{ - KeyLoc: keyDesc3.KeyLoc, - AllSignerPubkeys: allPubKeys, - OtherSignerPublicNonces: [][]byte{ - sessResp1.LocalPublicNonces, - sessResp2.LocalPublicNonces, - }, - TaprootTweak: taprootTweak, + req = &signrpc.MuSig2SessionRequest{ + KeyLoc: keyDesc3.KeyLoc, + AllSignerPubkeys: allPubKeys, + OtherSignerPublicNonces: [][]byte{ + sessResp1.LocalPublicNonces, + sessResp2.LocalPublicNonces, }, - ) - require.NoError(t.t, err) - require.Equal(t.t, sessResp2.CombinedKey, sessResp3.CombinedKey) - require.Equal(t.t, true, sessResp3.HaveAllNonces) + TaprootTweak: taprootTweak, + } + sessResp3 := node.RPC.MuSig2CreateSession(req) + require.NoError(ht, err) + require.Equal(ht, sessResp2.CombinedKey, sessResp3.CombinedKey) + require.Equal(ht, true, sessResp3.HaveAllNonces) // We need to distribute the rest of the nonces. - nonceResp1, err := node.SignerClient.MuSig2RegisterNonces( - ctx, &signrpc.MuSig2RegisterNoncesRequest{ - SessionId: sessResp1.SessionId, - OtherSignerPublicNonces: [][]byte{ - sessResp2.LocalPublicNonces, - sessResp3.LocalPublicNonces, - }, + nonceReq := &signrpc.MuSig2RegisterNoncesRequest{ + SessionId: sessResp1.SessionId, + OtherSignerPublicNonces: [][]byte{ + sessResp2.LocalPublicNonces, + sessResp3.LocalPublicNonces, }, - ) - require.NoError(t.t, err) - require.Equal(t.t, true, nonceResp1.HaveAllNonces) + } + nonceResp1 := node.RPC.MuSig2RegisterNonces(nonceReq) + require.True(ht, nonceResp1.HaveAllNonces) - nonceResp2, err := node.SignerClient.MuSig2RegisterNonces( - ctx, &signrpc.MuSig2RegisterNoncesRequest{ - SessionId: sessResp2.SessionId, - OtherSignerPublicNonces: [][]byte{ - sessResp3.LocalPublicNonces, - }, + nonceReq = &signrpc.MuSig2RegisterNoncesRequest{ + SessionId: sessResp2.SessionId, + OtherSignerPublicNonces: [][]byte{ + sessResp3.LocalPublicNonces, }, - ) - require.NoError(t.t, err) - require.Equal(t.t, true, nonceResp2.HaveAllNonces) + } + nonceResp2 := node.RPC.MuSig2RegisterNonces(nonceReq) + require.True(ht, nonceResp2.HaveAllNonces) return internalKey, combinedKey, sessResp1, sessResp2, sessResp3 } -// assertTaprootDeliveryUsed returns true if a Taproot addr was used in the -// co-op close transaction. -func assertTaprootDeliveryUsed(net *lntest.NetworkHarness, - t *harnessTest, closingTxid *chainhash.Hash) bool { - - tx, err := net.Miner.Client.GetRawTransaction(closingTxid) - require.NoError(t.t, err, "unable to get closing tx") - - for _, txOut := range tx.MsgTx().TxOut { - if !txscript.IsPayToTaproot(txOut.PkScript) { - return false - } - } - - return true -} - // testTaprootCoopClose asserts that if both peers signal ShutdownAnySegwit, // then a taproot closing addr is used. Otherwise, we shouldn't expect one to // be used. -func testTaprootCoopClose(net *lntest.NetworkHarness, t *harnessTest) { +func testTaprootCoopClose(ht *lntemp.HarnessTest) { // We'll start by making two new nodes, and funding a channel between // them. - carol := net.NewNode(t.t, "Carol", nil) - defer shutdownAndAssert(net, t, carol) + carol := ht.NewNode("Carol", nil) + ht.FundCoins(btcutil.SatoshiPerBitcoin, carol) - net.SendCoins(t.t, btcutil.SatoshiPerBitcoin, carol) - - dave := net.NewNode(t.t, "Dave", nil) - defer shutdownAndAssert(net, t, dave) - - net.EnsureConnected(t.t, carol, dave) + dave := ht.NewNode("Dave", nil) + ht.EnsureConnected(carol, dave) chanAmt := funding.MaxBtcFundingAmount pushAmt := btcutil.Amount(100000) satPerVbyte := btcutil.Amount(1) // We'll now open a channel between Carol and Dave. - chanPoint := openChannelAndAssert( - t, net, carol, dave, - lntest.OpenChannelParams{ + chanPoint := ht.OpenChannel( + carol, dave, lntemp.OpenChannelParams{ Amt: chanAmt, PushAmt: pushAmt, SatPerVByte: satPerVbyte, @@ -1940,24 +1788,34 @@ func testTaprootCoopClose(net *lntest.NetworkHarness, t *harnessTest) { ) // We'll now close out the channel and obtain the closing TXID. - closingTxid := closeChannelAndAssert(t, net, carol, chanPoint, false) + closingTxid := ht.CloseChannel(carol, chanPoint) + + // assertTaprootDeliveryUsed returns true if a Taproot addr was used in + // the co-op close transaction. + assertTaprootDeliveryUsed := func(closingTxid *chainhash.Hash) bool { + tx := ht.Miner.GetRawTransaction(closingTxid) + for _, txOut := range tx.MsgTx().TxOut { + if !txscript.IsPayToTaproot(txOut.PkScript) { + return false + } + } + + return true + } // We expect that the closing transaction only has P2TR addresses. - require.True(t.t, assertTaprootDeliveryUsed(net, t, closingTxid), + require.True(ht, assertTaprootDeliveryUsed(closingTxid), "taproot addr not used!") // Now we'll bring Eve into the mix, Eve is running older software that // doesn't understand Taproot. eveArgs := []string{"--protocol.no-any-segwit"} - eve := net.NewNode(t.t, "Eve", eveArgs) - defer shutdownAndAssert(net, t, eve) - - net.EnsureConnected(t.t, carol, eve) + eve := ht.NewNode("Eve", eveArgs) + ht.EnsureConnected(carol, eve) // We'll now open up a chanel again between Carol and Eve. - chanPoint = openChannelAndAssert( - t, net, carol, eve, - lntest.OpenChannelParams{ + chanPoint = ht.OpenChannel( + carol, eve, lntemp.OpenChannelParams{ Amt: chanAmt, PushAmt: pushAmt, SatPerVByte: satPerVbyte, @@ -1966,7 +1824,7 @@ func testTaprootCoopClose(net *lntest.NetworkHarness, t *harnessTest) { // We'll now close out this channel and expect that no Taproot // addresses are used in the co-op close transaction. - closingTxid = closeChannelAndAssert(t, net, carol, chanPoint, false) - require.False(t.t, assertTaprootDeliveryUsed(net, t, closingTxid), + closingTxid = ht.CloseChannel(carol, chanPoint) + require.False(ht, assertTaprootDeliveryUsed(closingTxid), "taproot addr shouldn't be used!") } diff --git a/lntest/itest/lnd_test_list_on_test.go b/lntest/itest/lnd_test_list_on_test.go index 5d26440ac..0783688bd 100644 --- a/lntest/itest/lnd_test_list_on_test.go +++ b/lntest/itest/lnd_test_list_on_test.go @@ -4,140 +4,8 @@ package itest var allTestCases = []*testCase{ - { - name: "multiple channel creation and update subscription", - test: testBasicChannelCreationAndUpdates, - }, - { - name: "derive shared key", - test: testDeriveSharedKey, - }, - { - name: "sign output raw", - test: testSignOutputRaw, - }, - { - name: "sign verify message", - test: testSignVerifyMessage, - }, - { - name: "async payments benchmark", - test: testAsyncPayments, - }, { name: "async bidirectional payments", test: testBidirectionalAsyncPayments, }, - { - name: "switch circuit persistence", - test: testSwitchCircuitPersistence, - }, - { - name: "switch offline delivery", - test: testSwitchOfflineDelivery, - }, - { - name: "switch offline delivery persistence", - test: testSwitchOfflineDeliveryPersistence, - }, - { - name: "switch offline delivery outgoing offline", - test: testSwitchOfflineDeliveryOutgoingOffline, - }, - { - name: "cpfp", - test: testCPFP, - }, - { - name: "psbt channel funding", - test: testPsbtChanFunding, - }, - { - name: "sign psbt", - test: testSignPsbt, - }, - { - name: "sendtoroute multi path payment", - test: testSendToRouteMultiPath, - }, - { - name: "sendtoroute amp", - test: testSendToRouteAMP, - }, - { - name: "sendpayment amp", - test: testSendPaymentAMP, - }, - { - name: "sendpayment amp invoice", - test: testSendPaymentAMPInvoice, - }, - { - name: "sendpayment amp invoice repeat", - test: testSendPaymentAMPInvoiceRepeat, - }, - { - name: "send multi path payment", - test: testSendMultiPathPayment, - }, - { - name: "forward interceptor", - test: testForwardInterceptorBasic, - }, - { - name: "forward interceptor dedup htlcs", - test: testForwardInterceptorDedupHtlc, - }, - { - name: "wallet import account", - test: testWalletImportAccount, - }, - { - name: "wallet import pubkey", - test: testWalletImportPubKey, - }, - { - name: "remote signer", - test: testRemoteSigner, - }, - { - name: "taproot", - test: testTaproot, - }, - { - name: "zero conf channel open", - test: testZeroConfChannelOpen, - }, - { - name: "option scid alias", - test: testOptionScidAlias, - }, - { - name: "scid alias channel update", - test: testUpdateChannelPolicyScidAlias, - }, - { - name: "scid alias upgrade", - test: testOptionScidUpgrade, - }, - { - name: "nonstd sweep", - test: testNonstdSweep, - }, - { - name: "taproot coop close", - test: testTaprootCoopClose, - }, - { - name: "trackpayments", - test: testTrackPayments, - }, - { - name: "open channel fee policy", - test: testOpenChannelUpdateFeePolicy, - }, - { - name: "custom messaging", - test: testCustomMessage, - }, } diff --git a/lntest/itest/lnd_trackpayments_test.go b/lntest/itest/lnd_trackpayments_test.go index 8819b95cc..2554471ef 100644 --- a/lntest/itest/lnd_trackpayments_test.go +++ b/lntest/itest/lnd_trackpayments_test.go @@ -1,102 +1,89 @@ package itest import ( - "context" "encoding/hex" + "time" "github.com/btcsuite/btcd/btcutil" "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" ) // testTrackPayments tests whether a client that calls the TrackPayments api // receives payment updates. -func testTrackPayments(net *lntest.NetworkHarness, t *harnessTest) { +func testTrackPayments(ht *lntemp.HarnessTest) { // Open a channel between alice and bob. - net.EnsureConnected(t.t, net.Alice, net.Bob) - channel := openChannelAndAssert( - t, net, net.Alice, net.Bob, - lntest.OpenChannelParams{ + alice, bob := ht.Alice, ht.Bob + channel := ht.OpenChannel( + alice, bob, lntemp.OpenChannelParams{ Amt: btcutil.Amount(300000), }, ) - defer closeChannelAndAssert(t, net, net.Alice, channel, true) - - err := net.Alice.WaitForNetworkChannelOpen(channel) - require.NoError(t.t, err, "unable to wait for channel to open") - - ctxb := context.Background() - ctxt, cancelTracker := context.WithCancel(ctxb) - defer cancelTracker() // Call the TrackPayments api to listen for payment updates. - tracker, err := net.Alice.RouterClient.TrackPayments( - ctxt, - &routerrpc.TrackPaymentsRequest{ - NoInflightUpdates: false, - }, - ) - require.NoError(t.t, err, "failed to call TrackPayments successfully.") + req := &routerrpc.TrackPaymentsRequest{ + NoInflightUpdates: false, + } + tracker := alice.RPC.TrackPayments(req) // Create an invoice from bob. var amountMsat int64 = 1000 - invoiceResp, err := net.Bob.AddInvoice( - ctxb, + invoiceResp := bob.RPC.AddInvoice( &lnrpc.Invoice{ ValueMsat: amountMsat, }, ) - require.NoError(t.t, err, "unable to add invoice.") - - invoice, err := net.Bob.LookupInvoice( - ctxb, - &lnrpc.PaymentHash{ - RHashStr: hex.EncodeToString(invoiceResp.RHash), - }, - ) - require.NoError(t.t, err, "unable to find invoice.") + invoice := bob.RPC.LookupInvoice(invoiceResp.RHash) // Send payment from alice to bob. - paymentClient, err := net.Alice.RouterClient.SendPaymentV2( - ctxb, + paymentClient := alice.RPC.SendPayment( &routerrpc.SendPaymentRequest{ PaymentRequest: invoice.PaymentRequest, TimeoutSeconds: 60, }, ) - require.NoError(t.t, err, "unable to send payment.") // Make sure the payment doesn't error due to invalid parameters or so. - _, err = paymentClient.Recv() - require.NoError(t.t, err, "unable to get payment update.") + _, err := paymentClient.Recv() + require.NoError(ht, err, "unable to get payment update.") // Assert the first payment update is an inflight update. update1, err := tracker.Recv() - require.NoError(t.t, err, "unable to receive payment update 1.") + require.NoError(ht, err, "unable to receive payment update 1.") require.Equal( - t.t, lnrpc.PaymentFailureReason_FAILURE_REASON_NONE, + ht, lnrpc.PaymentFailureReason_FAILURE_REASON_NONE, update1.FailureReason, ) - require.Equal(t.t, lnrpc.Payment_IN_FLIGHT, update1.Status) - require.Equal(t.t, invoice.PaymentRequest, update1.PaymentRequest) - require.Equal(t.t, amountMsat, update1.ValueMsat) + require.Equal(ht, lnrpc.Payment_IN_FLIGHT, update1.Status) + require.Equal(ht, invoice.PaymentRequest, update1.PaymentRequest) + require.Equal(ht, amountMsat, update1.ValueMsat) // Assert the second payment update is a payment success update. update2, err := tracker.Recv() - require.NoError(t.t, err, "unable to receive payment update 2.") + require.NoError(ht, err, "unable to receive payment update 2.") require.Equal( - t.t, lnrpc.PaymentFailureReason_FAILURE_REASON_NONE, + ht, lnrpc.PaymentFailureReason_FAILURE_REASON_NONE, update2.FailureReason, ) - require.Equal(t.t, lnrpc.Payment_SUCCEEDED, update2.Status) - require.Equal(t.t, invoice.PaymentRequest, update2.PaymentRequest) - require.Equal(t.t, amountMsat, update2.ValueMsat) + require.Equal(ht, lnrpc.Payment_SUCCEEDED, update2.Status) + require.Equal(ht, invoice.PaymentRequest, update2.PaymentRequest) + require.Equal(ht, amountMsat, update2.ValueMsat) require.Equal( - t.t, hex.EncodeToString(invoice.RPreimage), + ht, hex.EncodeToString(invoice.RPreimage), update2.PaymentPreimage, ) + + // 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) + + ht.CloseChannel(alice, channel) } diff --git a/lntest/itest/lnd_wallet_import_test.go b/lntest/itest/lnd_wallet_import_test.go index ad8ad1b6b..51eb62b8d 100644 --- a/lntest/itest/lnd_wallet_import_test.go +++ b/lntest/itest/lnd_wallet_import_test.go @@ -2,10 +2,6 @@ package itest import ( "bytes" - "context" - "crypto/rand" - "fmt" - "math" "testing" "time" @@ -20,8 +16,8 @@ import ( "github.com/lightningnetwork/lnd/funding" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/walletrpc" - "github.com/lightningnetwork/lnd/lntest" - "github.com/lightningnetwork/lnd/lntest/wait" + "github.com/lightningnetwork/lnd/lntemp" + "github.com/lightningnetwork/lnd/lntemp/node" "github.com/lightningnetwork/lnd/lnwallet" "github.com/stretchr/testify/require" ) @@ -55,30 +51,27 @@ func walletToLNAddrType(t *testing.T, // newExternalAddr generates a new external address of an imported account for a // pair of nodes, where one acts as the funder and the other as the signer. -func newExternalAddr(t *testing.T, funder, signer *lntest.HarnessNode, +func newExternalAddr(ht *lntemp.HarnessTest, funder, signer *node.HarnessNode, importedAccount string, addrType walletrpc.AddressType) string { // We'll generate a new address for Carol from Dave's node to receive // and fund a new channel. - ctxb := context.Background() - ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) - defer cancel() - funderResp, err := funder.NewAddress(ctxt, &lnrpc.NewAddressRequest{ - Type: walletToLNAddrType(t, addrType), + req := &lnrpc.NewAddressRequest{ + Type: walletToLNAddrType(ht.T, addrType), Account: importedAccount, - }) - require.NoError(t, err) + } + funderResp := funder.RPC.NewAddress(req) - // Carol also needs to generate the address for the sake of this test to - // be able to sign the channel funding input. - signerResp, err := signer.NewAddress(ctxt, &lnrpc.NewAddressRequest{ - Type: walletToLNAddrType(t, addrType), - }) - require.NoError(t, err) + // Carol also needs to generate the address for the sake of this test + // to be able to sign the channel funding input. + req = &lnrpc.NewAddressRequest{ + Type: walletToLNAddrType(ht.T, addrType), + } + signerResp := signer.RPC.NewAddress(req) // Sanity check that the generated addresses match. - require.Equal(t, funderResp.Address, signerResp.Address) - assertExternalAddrType(t, funderResp.Address, addrType) + require.Equal(ht, funderResp.Address, signerResp.Address) + assertExternalAddrType(ht.T, funderResp.Address, addrType) return funderResp.Address } @@ -130,108 +123,21 @@ func assertOutputScriptType(t *testing.T, expType txscript.ScriptClass, spew.Sdump(tx)) } -// assertAccountBalance asserts that the unconfirmed and confirmed balance for -// the given account is satisfied by the WalletBalance and ListUnspent RPCs. The -// unconfirmed balance is not checked for neutrino nodes. -func assertAccountBalance(t *testing.T, node *lntest.HarnessNode, account string, - confirmedBalance, unconfirmedBalance int64) { - - err := wait.NoError(func() error { - balanceResp, err := node.WalletBalance( - context.Background(), &lnrpc.WalletBalanceRequest{}, - ) - if err != nil { - return err - } - require.Contains(t, balanceResp.AccountBalance, account) - accountBalance := balanceResp.AccountBalance[account] - - // Check confirmed balance. - if accountBalance.ConfirmedBalance != confirmedBalance { - return fmt.Errorf("expected confirmed balance %v, "+ - "got %v", confirmedBalance, - accountBalance.ConfirmedBalance) - } - listUtxosReq := &lnrpc.ListUnspentRequest{ - MinConfs: 1, - MaxConfs: math.MaxInt32, - Account: account, - } - confirmedUtxosResp, err := node.ListUnspent( - context.Background(), listUtxosReq, - ) - if err != nil { - return err - } - var totalConfirmedVal int64 - for _, utxo := range confirmedUtxosResp.Utxos { - totalConfirmedVal += utxo.AmountSat - } - if totalConfirmedVal != confirmedBalance { - return fmt.Errorf("expected total confirmed utxo "+ - "balance %v, got %v", confirmedBalance, - totalConfirmedVal) - } - - // Skip unconfirmed balance checks for neutrino nodes. - if node.Cfg.BackendCfg.Name() == lntest.NeutrinoBackendName { - return nil - } - - // Check unconfirmed balance. - if accountBalance.UnconfirmedBalance != unconfirmedBalance { - return fmt.Errorf("expected unconfirmed balance %v, "+ - "got %v", unconfirmedBalance, - accountBalance.UnconfirmedBalance) - } - listUtxosReq.MinConfs = 0 - listUtxosReq.MaxConfs = 0 - unconfirmedUtxosResp, err := node.ListUnspent( - context.Background(), listUtxosReq, - ) - require.NoError(t, err) - var totalUnconfirmedVal int64 - for _, utxo := range unconfirmedUtxosResp.Utxos { - totalUnconfirmedVal += utxo.AmountSat - } - if totalUnconfirmedVal != unconfirmedBalance { - return fmt.Errorf("expected total unconfirmed utxo "+ - "balance %v, got %v", unconfirmedBalance, - totalUnconfirmedVal) - } - - return nil - }, defaultTimeout) - require.NoError(t, err) -} - // psbtSendFromImportedAccount attempts to fund a PSBT from the given imported // account, originating from the source node to the destination. -func psbtSendFromImportedAccount(t *harnessTest, srcNode, destNode, - signer *lntest.HarnessNode, account string, +func psbtSendFromImportedAccount(ht *lntemp.HarnessTest, srcNode, destNode, + signer *node.HarnessNode, account string, accountAddrType walletrpc.AddressType) { - ctxb := context.Background() - - ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) - defer cancel() - balanceResp, err := srcNode.WalletBalance( - ctxt, &lnrpc.WalletBalanceRequest{}, - ) - require.NoError(t.t, err) - require.Contains(t.t, balanceResp.AccountBalance, account) + balanceResp := srcNode.RPC.WalletBalance() + require.Contains(ht, balanceResp.AccountBalance, account) confBalance := balanceResp.AccountBalance[account].ConfirmedBalance destAmt := confBalance - 10000 - ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout) - defer cancel() - destAddrResp, err := destNode.NewAddress(ctxt, &lnrpc.NewAddressRequest{ + destAddrResp := destNode.RPC.NewAddress(&lnrpc.NewAddressRequest{ Type: lnrpc.AddressType_WITNESS_PUBKEY_HASH, }) - require.NoError(t.t, err) - ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout) - defer cancel() fundReq := &walletrpc.FundPsbtRequest{ Template: &walletrpc.FundPsbtRequest_Raw{ Raw: &walletrpc.TxTemplate{ @@ -245,29 +151,20 @@ func psbtSendFromImportedAccount(t *harnessTest, srcNode, destNode, }, Account: account, } - fundResp, err := srcNode.WalletKitClient.FundPsbt(ctxt, fundReq) - require.NoError(t.t, err) + fundResp := srcNode.RPC.FundPsbt(fundReq) // Have Carol sign the PSBT input since Dave doesn't have any private // key information. - ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout) - defer cancel() finalizeReq := &walletrpc.FinalizePsbtRequest{ FundedPsbt: fundResp.FundedPsbt, } - finalizeResp, err := signer.WalletKitClient.FinalizePsbt( - ctxt, finalizeReq, - ) - require.NoError(t.t, err) + finalizeResp := signer.RPC.FinalizePsbt(finalizeReq) // With the PSBT signed, we can broadcast the resulting transaction. - ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout) - defer cancel() publishReq := &walletrpc.Transaction{ TxHex: finalizeResp.RawFinalTx, } - _, err = srcNode.WalletKitClient.PublishTransaction(ctxt, publishReq) - require.NoError(t.t, err) + srcNode.RPC.PublishTransaction(publishReq) // Carol's balance from Dave's perspective should update accordingly. var ( @@ -304,7 +201,7 @@ func psbtSendFromImportedAccount(t *harnessTest, srcNode, destNode, expChangeScriptType = txscript.WitnessV1TaprootTy default: - t.Fatalf("unsupported addr type %v", accountAddrType) + ht.Fatalf("unsupported addr type %v", accountAddrType) } changeUtxoAmt := confBalance - destAmt - expTxFee @@ -314,16 +211,20 @@ func psbtSendFromImportedAccount(t *harnessTest, srcNode, destNode, if account == defaultImportedAccount { accountWithBalance = defaultAccount } - assertAccountBalance(t.t, srcNode, accountWithBalance, 0, changeUtxoAmt) - _ = mineBlocks(t, t.lndHarness, 1, 1) - assertAccountBalance(t.t, srcNode, accountWithBalance, changeUtxoAmt, 0) + ht.AssertWalletAccountBalance( + srcNode, accountWithBalance, 0, changeUtxoAmt, + ) + ht.MineBlocksAndAssertNumTxes(1, 1) + ht.AssertWalletAccountBalance( + srcNode, accountWithBalance, changeUtxoAmt, 0, + ) // Finally, assert that the transaction has the expected change address // type based on the account. var tx wire.MsgTx - err = tx.Deserialize(bytes.NewReader(finalizeResp.RawFinalTx)) - require.NoError(t.t, err) - assertOutputScriptType(t.t, expChangeScriptType, &tx, changeUtxoAmt) + err := tx.Deserialize(bytes.NewReader(finalizeResp.RawFinalTx)) + require.NoError(ht, err) + assertOutputScriptType(ht.T, expChangeScriptType, &tx, changeUtxoAmt) } // fundChanAndCloseFromImportedAccount attempts to a fund a channel from the @@ -331,21 +232,14 @@ func psbtSendFromImportedAccount(t *harnessTest, srcNode, destNode, // node. To ensure the channel is operational before closing it, a test payment // is made. Several balance assertions are made along the way for the sake of // correctness. -func fundChanAndCloseFromImportedAccount(t *harnessTest, srcNode, destNode, - signer *lntest.HarnessNode, account string, +func fundChanAndCloseFromImportedAccount(ht *lntemp.HarnessTest, srcNode, + destNode, signer *node.HarnessNode, account string, accountAddrType walletrpc.AddressType, utxoAmt, chanSize int64) { - ctxb := context.Background() - // Retrieve the current confirmed balance to make some assertions later // on. - ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) - defer cancel() - balanceResp, err := srcNode.WalletBalance( - ctxt, &lnrpc.WalletBalanceRequest{}, - ) - require.NoError(t.t, err) - require.Contains(t.t, balanceResp.AccountBalance, account) + balanceResp := srcNode.RPC.WalletBalance() + require.Contains(ht, balanceResp.AccountBalance, account) accountConfBalance := balanceResp. AccountBalance[account].ConfirmedBalance defaultAccountConfBalance := balanceResp. @@ -353,31 +247,23 @@ func fundChanAndCloseFromImportedAccount(t *harnessTest, srcNode, destNode, // Now, start the channel funding process. We'll need to connect both // nodes first. - t.lndHarness.EnsureConnected(t.t, srcNode, destNode) + ht.EnsureConnected(srcNode, destNode) // The source node will then fund the channel through a PSBT shim. - var pendingChanID [32]byte - _, err = rand.Read(pendingChanID[:]) - require.NoError(t.t, err) - - ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout) - defer cancel() - chanUpdates, rawPsbt, err := openChannelPsbt( - ctxt, srcNode, destNode, lntest.OpenChannelParams{ + pendingChanID := ht.Random32Bytes() + chanUpdates, rawPsbt := ht.OpenChannelPsbt( + srcNode, destNode, lntemp.OpenChannelParams{ Amt: btcutil.Amount(chanSize), FundingShim: &lnrpc.FundingShim{ Shim: &lnrpc.FundingShim_PsbtShim{ PsbtShim: &lnrpc.PsbtShim{ - PendingChanId: pendingChanID[:], + PendingChanId: pendingChanID, }, }, }, }, ) - require.NoError(t.t, err) - ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout) - defer cancel() fundReq := &walletrpc.FundPsbtRequest{ Template: &walletrpc.FundPsbtRequest_Psbt{ Psbt: rawPsbt, @@ -387,49 +273,40 @@ func fundChanAndCloseFromImportedAccount(t *harnessTest, srcNode, destNode, }, Account: account, } - fundResp, err := srcNode.WalletKitClient.FundPsbt(ctxt, fundReq) - require.NoError(t.t, err) + fundResp := srcNode.RPC.FundPsbt(fundReq) - _, err = srcNode.FundingStateStep(ctxb, &lnrpc.FundingTransitionMsg{ + srcNode.RPC.FundingStateStep(&lnrpc.FundingTransitionMsg{ Trigger: &lnrpc.FundingTransitionMsg_PsbtVerify{ PsbtVerify: &lnrpc.FundingPsbtVerify{ - PendingChanId: pendingChanID[:], + PendingChanId: pendingChanID, FundedPsbt: fundResp.FundedPsbt, }, }, }) - require.NoError(t.t, err) // Now that we have a PSBT to fund the channel, our signer needs to sign // it. - ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout) - defer cancel() finalizeReq := &walletrpc.FinalizePsbtRequest{ FundedPsbt: fundResp.FundedPsbt, } - finalizeResp, err := signer.WalletKitClient.FinalizePsbt(ctxt, finalizeReq) - require.NoError(t.t, err) + finalizeResp := signer.RPC.FinalizePsbt(finalizeReq) // The source node can then submit the signed PSBT and complete the // channel funding process. - _, err = srcNode.FundingStateStep(ctxb, &lnrpc.FundingTransitionMsg{ + srcNode.RPC.FundingStateStep(&lnrpc.FundingTransitionMsg{ Trigger: &lnrpc.FundingTransitionMsg_PsbtFinalize{ PsbtFinalize: &lnrpc.FundingPsbtFinalize{ - PendingChanId: pendingChanID[:], + PendingChanId: pendingChanID, SignedPsbt: finalizeResp.SignedPsbt, }, }, }) - require.NoError(t.t, err) // We should receive a notification of the channel funding transaction // being broadcast. - ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout) - defer cancel() - 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) // Mine enough blocks to announce the channel to the network, making // balance assertions along the way. @@ -467,11 +344,11 @@ func fundChanAndCloseFromImportedAccount(t *harnessTest, srcNode, destNode, expChangeScriptType = txscript.WitnessV1TaprootTy default: - t.Fatalf("unsupported addr type %v", accountAddrType) + ht.Fatalf("unsupported addr type %v", accountAddrType) } chanChangeUtxoAmt := utxoAmt - chanSize - expChanTxFee txHash, err := chainhash.NewHash(upd.ChanPending.Txid) - require.NoError(t.t, err) + require.NoError(ht, err) // If we're spending from the default imported account, then any change // outputs produced are moved to the default wallet account, so we @@ -479,35 +356,35 @@ func fundChanAndCloseFromImportedAccount(t *harnessTest, srcNode, destNode, var confBalanceAfterChan int64 if account == defaultImportedAccount { confBalanceAfterChan = defaultAccountConfBalance - assertAccountBalance(t.t, srcNode, account, 0, 0) - assertAccountBalance( - t.t, srcNode, defaultAccount, defaultAccountConfBalance, + ht.AssertWalletAccountBalance(srcNode, account, 0, 0) + ht.AssertWalletAccountBalance( + srcNode, defaultAccount, defaultAccountConfBalance, chanChangeUtxoAmt, ) - block := mineBlocks(t, t.lndHarness, 6, 1)[0] - assertTxInBlock(t, block, txHash) + block := ht.MineBlocksAndAssertNumTxes(6, 1)[0] + ht.Miner.AssertTxInBlock(block, txHash) confBalanceAfterChan += chanChangeUtxoAmt - assertAccountBalance(t.t, srcNode, account, 0, 0) - assertAccountBalance( - t.t, srcNode, defaultAccount, confBalanceAfterChan, 0, + ht.AssertWalletAccountBalance(srcNode, account, 0, 0) + ht.AssertWalletAccountBalance( + srcNode, defaultAccount, confBalanceAfterChan, 0, ) } else { // Otherwise, all interactions remain within Carol's imported // account. confBalanceAfterChan = accountConfBalance - utxoAmt - assertAccountBalance( - t.t, srcNode, account, confBalanceAfterChan, + ht.AssertWalletAccountBalance( + srcNode, account, confBalanceAfterChan, chanChangeUtxoAmt, ) - block := mineBlocks(t, t.lndHarness, 6, 1)[0] - assertTxInBlock(t, block, txHash) + block := ht.MineBlocksAndAssertNumTxes(6, 1)[0] + ht.Miner.AssertTxInBlock(block, txHash) confBalanceAfterChan += chanChangeUtxoAmt - assertAccountBalance( - t.t, srcNode, account, confBalanceAfterChan, 0, + ht.AssertWalletAccountBalance( + srcNode, account, confBalanceAfterChan, 0, ) } @@ -515,8 +392,10 @@ func fundChanAndCloseFromImportedAccount(t *harnessTest, srcNode, destNode, // based on the account. var tx wire.MsgTx err = tx.Deserialize(bytes.NewReader(finalizeResp.RawFinalTx)) - require.NoError(t.t, err) - assertOutputScriptType(t.t, expChangeScriptType, &tx, chanChangeUtxoAmt) + require.NoError(ht, err) + assertOutputScriptType( + ht.T, expChangeScriptType, &tx, chanChangeUtxoAmt, + ) // Wait for the channel to be announced by both parties. chanPoint := &lnrpc.ChannelPoint{ @@ -525,29 +404,30 @@ func fundChanAndCloseFromImportedAccount(t *harnessTest, srcNode, destNode, }, OutputIndex: upd.ChanPending.OutputIndex, } - err = srcNode.WaitForNetworkChannelOpen(chanPoint) - require.NoError(t.t, err) - err = destNode.WaitForNetworkChannelOpen(chanPoint) - require.NoError(t.t, err) + ht.AssertTopologyChannelOpen(srcNode, chanPoint) + ht.AssertTopologyChannelOpen(destNode, chanPoint) // Send a test payment to ensure the channel is operating as normal. const invoiceAmt = 100000 - ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout) - defer cancel() - resp, err := destNode.AddInvoice(ctxt, &lnrpc.Invoice{ + invoice := &lnrpc.Invoice{ Memo: "psbt import chan", Value: invoiceAmt, - }) - require.NoError(t.t, err) + } + resp := destNode.RPC.AddInvoice(invoice) - err = completePaymentRequests( - srcNode, srcNode.RouterClient, - []string{resp.PaymentRequest}, true, - ) - require.NoError(t.t, err) + ht.CompletePaymentRequests(srcNode, []string{resp.PaymentRequest}) + + // TODO(yy): remove the sleep once the following bug is fixed. When the + // payment is reported as settled by srcNode, 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) // Now that we've confirmed the opened channel works, we'll close it. - closeChannelAndAssert(t, t.lndHarness, srcNode, chanPoint, false) + ht.CloseChannel(srcNode, chanPoint) // Since the channel still had funds left on the source node's side, // they must've been redeemed after the close. Without a pre-negotiated @@ -557,17 +437,17 @@ func fundChanAndCloseFromImportedAccount(t *harnessTest, srcNode, destNode, balanceFromClosedChan := chanSize - invoiceAmt - chanCloseTxFee if account == defaultImportedAccount { - assertAccountBalance(t.t, srcNode, account, 0, 0) - assertAccountBalance( - t.t, srcNode, defaultAccount, + ht.AssertWalletAccountBalance(srcNode, account, 0, 0) + ht.AssertWalletAccountBalance( + srcNode, defaultAccount, confBalanceAfterChan+balanceFromClosedChan, 0, ) } else { - assertAccountBalance( - t.t, srcNode, account, confBalanceAfterChan, 0, + ht.AssertWalletAccountBalance( + srcNode, account, confBalanceAfterChan, 0, ) - assertAccountBalance( - t.t, srcNode, defaultAccount, balanceFromClosedChan, 0, + ht.AssertWalletAccountBalance( + srcNode, defaultAccount, balanceFromClosedChan, 0, ) } } @@ -575,18 +455,20 @@ func fundChanAndCloseFromImportedAccount(t *harnessTest, srcNode, destNode, // testWalletImportAccount tests that an imported account can fund transactions // and channels through PSBTs, by having one node (the one with the imported // account) craft the transactions and another node act as the signer. -func testWalletImportAccount(net *lntest.NetworkHarness, t *harnessTest) { +func testWalletImportAccount(ht *lntemp.HarnessTest) { testCases := []struct { name string addrType walletrpc.AddressType }{ { - name: "standard BIP-0049", - addrType: walletrpc.AddressType_NESTED_WITNESS_PUBKEY_HASH, + name: "standard BIP-0049", + addrType: walletrpc. + AddressType_NESTED_WITNESS_PUBKEY_HASH, }, { - name: "lnd BIP-0049 variant", - addrType: walletrpc.AddressType_HYBRID_NESTED_WITNESS_PUBKEY_HASH, + name: "lnd BIP-0049 variant", + addrType: walletrpc. + AddressType_HYBRID_NESTED_WITNESS_PUBKEY_HASH, }, { name: "standard BIP-0084", @@ -600,23 +482,24 @@ func testWalletImportAccount(net *lntest.NetworkHarness, t *harnessTest) { for _, tc := range testCases { tc := tc - 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) { + success := ht.Run(tc.name, func(tt *testing.T) { + testFunc := func(ht *lntemp.HarnessTest) { + testWalletImportAccountScenario( + ht, tc.addrType, + ) + } - testWalletImportAccountScenario( - net, t, tc.addrType, - ) - }, + 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", )) break @@ -624,115 +507,103 @@ func testWalletImportAccount(net *lntest.NetworkHarness, t *harnessTest) { } } -func testWalletImportAccountScenario(net *lntest.NetworkHarness, t *harnessTest, +func testWalletImportAccountScenario(ht *lntemp.HarnessTest, addrType walletrpc.AddressType) { // We'll start our test by having two nodes, Carol and Dave. Carol's // default wallet account will be imported into Dave's node. - carol := net.NewNode(t.t, "carol", nil) - defer shutdownAndAssert(net, t, carol) + // + // NOTE: we won't use standby nodes here since the test will change + // each of the node's wallet state. + carol := ht.NewNode("carol", nil) + dave := ht.NewNode("dave", nil) - dave := net.NewNode(t.t, "dave", nil) - defer shutdownAndAssert(net, t, dave) - - runWalletImportAccountScenario(net, t, addrType, carol, dave) + runWalletImportAccountScenario(ht, addrType, carol, dave) } -func runWalletImportAccountScenario(net *lntest.NetworkHarness, t *harnessTest, - addrType walletrpc.AddressType, carol, dave *lntest.HarnessNode) { +func runWalletImportAccountScenario(ht *lntemp.HarnessTest, + addrType walletrpc.AddressType, carol, dave *node.HarnessNode) { - ctxb := context.Background() const utxoAmt int64 = btcutil.SatoshiPerBitcoin - ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) - defer cancel() listReq := &walletrpc.ListAccountsRequest{ Name: "default", AddressType: addrType, } - listResp, err := carol.WalletKitClient.ListAccounts(ctxt, listReq) - require.NoError(t.t, err) - require.Equal(t.t, len(listResp.Accounts), 1) + listResp := carol.RPC.ListAccounts(listReq) + require.Len(ht, listResp.Accounts, 1) carolAccount := listResp.Accounts[0] - ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout) - defer cancel() const importedAccount = "carol" importReq := &walletrpc.ImportAccountRequest{ Name: importedAccount, ExtendedPublicKey: carolAccount.ExtendedPublicKey, AddressType: addrType, } - _, err = dave.WalletKitClient.ImportAccount(ctxt, importReq) - require.NoError(t.t, err) + dave.RPC.ImportAccount(importReq) // We'll generate an address for Carol from Dave's node to receive some // funds. externalAddr := newExternalAddr( - t.t, dave, carol, importedAccount, addrType, + ht, dave, carol, importedAccount, addrType, ) // Send coins to Carol's address and confirm them, making sure the // balance updates accordingly. - ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout) - defer cancel() - _, err = net.Alice.SendCoins(ctxt, &lnrpc.SendCoinsRequest{ + alice := ht.Alice + req := &lnrpc.SendCoinsRequest{ Addr: externalAddr, Amount: utxoAmt, SatPerByte: 1, - }) - require.NoError(t.t, err) + } + alice.RPC.SendCoins(req) - assertAccountBalance(t.t, dave, importedAccount, 0, utxoAmt) - _ = mineBlocks(t, net, 1, 1) - assertAccountBalance(t.t, dave, importedAccount, utxoAmt, 0) + ht.AssertWalletAccountBalance(dave, importedAccount, 0, utxoAmt) + ht.MineBlocksAndAssertNumTxes(1, 1) + ht.AssertWalletAccountBalance(dave, importedAccount, utxoAmt, 0) // To ensure that Dave can use Carol's account as watch-only, we'll // construct a PSBT that sends funds to Alice, which we'll then hand // over to Carol to sign. psbtSendFromImportedAccount( - t, dave, net.Alice, carol, importedAccount, addrType, + ht, dave, alice, carol, importedAccount, addrType, ) // We'll generate a new address for Carol from Dave's node to receive // and fund a new channel. externalAddr = newExternalAddr( - t.t, dave, carol, importedAccount, addrType, + ht, dave, carol, importedAccount, addrType, ) // Retrieve the current confirmed balance of the imported account for // some assertions we'll make later on. - ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout) - defer cancel() - balanceResp, err := dave.WalletBalance( - ctxt, &lnrpc.WalletBalanceRequest{}, - ) - require.NoError(t.t, err) - require.Contains(t.t, balanceResp.AccountBalance, importedAccount) - confBalance := balanceResp.AccountBalance[importedAccount].ConfirmedBalance + balanceResp := dave.RPC.WalletBalance() + require.Contains(ht, balanceResp.AccountBalance, importedAccount) + confBalance := balanceResp.AccountBalance[importedAccount]. + ConfirmedBalance // Send coins to Carol's address and confirm them, making sure the // balance updates accordingly. - ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout) - defer cancel() - _, err = net.Alice.SendCoins(ctxt, &lnrpc.SendCoinsRequest{ + req = &lnrpc.SendCoinsRequest{ Addr: externalAddr, Amount: utxoAmt, SatPerByte: 1, - }) - require.NoError(t.t, err) + } + alice.RPC.SendCoins(req) - assertAccountBalance(t.t, dave, importedAccount, confBalance, utxoAmt) - _ = mineBlocks(t, net, 1, 1) - assertAccountBalance( - t.t, dave, importedAccount, confBalance+utxoAmt, 0, + ht.AssertWalletAccountBalance( + dave, importedAccount, confBalance, utxoAmt, + ) + ht.MineBlocksAndAssertNumTxes(1, 1) + ht.AssertWalletAccountBalance( + dave, importedAccount, confBalance+utxoAmt, 0, ) // Now that we have enough funds, it's time to fund the channel, make a // test payment, and close it. This contains several balance assertions // along the way. fundChanAndCloseFromImportedAccount( - t, dave, net.Alice, carol, importedAccount, addrType, utxoAmt, + ht, dave, alice, carol, importedAccount, addrType, utxoAmt, int64(funding.MaxBtcFundingAmount), ) } @@ -740,14 +611,15 @@ func runWalletImportAccountScenario(net *lntest.NetworkHarness, t *harnessTest, // testWalletImportPubKey tests that an imported public keys can fund // transactions and channels through PSBTs, by having one node (the one with the // imported account) craft the transactions and another node act as the signer. -func testWalletImportPubKey(net *lntest.NetworkHarness, t *harnessTest) { +func testWalletImportPubKey(ht *lntemp.HarnessTest) { testCases := []struct { name string addrType walletrpc.AddressType }{ { - name: "BIP-0049", - addrType: walletrpc.AddressType_NESTED_WITNESS_PUBKEY_HASH, + name: "BIP-0049", + addrType: walletrpc. + AddressType_NESTED_WITNESS_PUBKEY_HASH, }, { name: "BIP-0084", @@ -761,23 +633,24 @@ func testWalletImportPubKey(net *lntest.NetworkHarness, t *harnessTest) { for _, tc := range testCases { tc := tc - 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) { + success := ht.Run(tc.name, func(tt *testing.T) { + testFunc := func(ht *lntemp.HarnessTest) { + testWalletImportPubKeyScenario( + ht, tc.addrType, + ) + } - testWalletImportPubKeyScenario( - net, t, tc.addrType, - ) - }, + 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", )) break @@ -785,18 +658,18 @@ func testWalletImportPubKey(net *lntest.NetworkHarness, t *harnessTest) { } } -func testWalletImportPubKeyScenario(net *lntest.NetworkHarness, t *harnessTest, +func testWalletImportPubKeyScenario(ht *lntemp.HarnessTest, addrType walletrpc.AddressType) { - ctxb := context.Background() const utxoAmt int64 = btcutil.SatoshiPerBitcoin + alice := ht.Alice // We'll start our test by having two nodes, Carol and Dave. - carol := net.NewNode(t.t, "carol", nil) - defer shutdownAndAssert(net, t, carol) - - dave := net.NewNode(t.t, "dave", nil) - defer shutdownAndAssert(net, t, dave) + // + // NOTE: we won't use standby nodes here since the test will change + // each of the node's wallet state. + carol := ht.NewNode("carol", nil) + dave := ht.NewNode("dave", nil) // We'll define a helper closure that we'll use throughout the test to // generate a new address of the given type from Carol's perspective, @@ -806,30 +679,27 @@ func testWalletImportPubKeyScenario(net *lntest.NetworkHarness, t *harnessTest, // Retrieve Carol's account public key for the corresponding // address type. - ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) - defer cancel() listReq := &walletrpc.ListAccountsRequest{ Name: "default", AddressType: addrType, } - listResp, err := carol.WalletKitClient.ListAccounts( - ctxt, listReq, - ) - require.NoError(t.t, err) - require.Equal(t.t, len(listResp.Accounts), 1) + listResp := carol.RPC.ListAccounts(listReq) + require.Len(ht, listResp.Accounts, 1) p2wkhAccount := listResp.Accounts[0] // Derive the external address at the given index. accountPubKey, err := hdkeychain.NewKeyFromString( p2wkhAccount.ExtendedPublicKey, ) - require.NoError(t.t, err) + require.NoError(ht, err) externalAccountExtKey, err := accountPubKey.Derive(0) - require.NoError(t.t, err) - externalAddrExtKey, err := externalAccountExtKey.Derive(keyIndex) - require.NoError(t.t, err) + require.NoError(ht, err) + externalAddrExtKey, err := externalAccountExtKey.Derive( + keyIndex, + ) + require.NoError(ht, err) externalAddrPubKey, err := externalAddrExtKey.ECPubKey() - require.NoError(t.t, err) + require.NoError(ht, err) // Serialize as 32-byte x-only pubkey for Taproot addresses. serializedPubKey := externalAddrPubKey.SerializeCompressed() @@ -840,44 +710,34 @@ func testWalletImportPubKeyScenario(net *lntest.NetworkHarness, t *harnessTest, } // Import the public key into Dave. - ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout) - defer cancel() importReq := &walletrpc.ImportPublicKeyRequest{ PublicKey: serializedPubKey, AddressType: addrType, } - _, err = dave.WalletKitClient.ImportPublicKey(ctxt, importReq) - require.NoError(t.t, err) + dave.RPC.ImportPublicKey(importReq) // We'll also generate the same address for Carol, as it'll be // required later when signing. - ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout) - defer cancel() - carolAddrResp, err := carol.NewAddress( - ctxt, &lnrpc.NewAddressRequest{ - Type: walletToLNAddrType(t.t, addrType), - }, - ) - require.NoError(t.t, err) + carolAddrResp := carol.RPC.NewAddress(&lnrpc.NewAddressRequest{ + Type: walletToLNAddrType(ht.T, addrType), + }) // Send coins to Carol's address and confirm them, making sure // the balance updates accordingly. - ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout) - defer cancel() - _, err = net.Alice.SendCoins(ctxt, &lnrpc.SendCoinsRequest{ + req := &lnrpc.SendCoinsRequest{ Addr: carolAddrResp.Address, Amount: utxoAmt, SatPerByte: 1, - }) - require.NoError(t.t, err) + } + alice.RPC.SendCoins(req) - assertAccountBalance( - t.t, dave, defaultImportedAccount, prevConfBalance, + ht.AssertWalletAccountBalance( + dave, defaultImportedAccount, prevConfBalance, prevUnconfBalance+utxoAmt, ) - _ = mineBlocks(t, net, 1, 1) - assertAccountBalance( - t.t, dave, defaultImportedAccount, + ht.MineBlocksAndAssertNumTxes(1, 1) + ht.AssertWalletAccountBalance( + dave, defaultImportedAccount, prevConfBalance+utxoAmt, prevUnconfBalance, ) } @@ -890,22 +750,15 @@ func testWalletImportPubKeyScenario(net *lntest.NetworkHarness, t *harnessTest, // construct a PSBT that sends funds to Alice, which we'll then hand // over to Carol to sign. psbtSendFromImportedAccount( - t, dave, net.Alice, carol, defaultImportedAccount, addrType, + ht, dave, alice, carol, defaultImportedAccount, addrType, ) // We'll now attempt to fund a channel. // // We'll have Carol generate another external address, which we'll // import into Dave again. - ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) - defer cancel() - balanceResp, err := dave.WalletBalance( - ctxt, &lnrpc.WalletBalanceRequest{}, - ) - require.NoError(t.t, err) - require.Contains( - t.t, balanceResp.AccountBalance, defaultImportedAccount, - ) + balanceResp := dave.RPC.WalletBalance() + require.Contains(ht, balanceResp.AccountBalance, defaultImportedAccount) confBalance := balanceResp. AccountBalance[defaultImportedAccount].ConfirmedBalance importPubKey(1, confBalance, 0) @@ -914,7 +767,7 @@ func testWalletImportPubKeyScenario(net *lntest.NetworkHarness, t *harnessTest, // test payment, and close it. This contains several balance assertions // along the way. fundChanAndCloseFromImportedAccount( - t, dave, net.Alice, carol, defaultImportedAccount, addrType, + ht, dave, alice, carol, defaultImportedAccount, addrType, utxoAmt, int64(funding.MaxBtcFundingAmount), ) } diff --git a/lntest/itest/lnd_zero_conf_test.go b/lntest/itest/lnd_zero_conf_test.go index f41c03408..f630bfdea 100644 --- a/lntest/itest/lnd_zero_conf_test.go +++ b/lntest/itest/lnd_zero_conf_test.go @@ -1,7 +1,6 @@ package itest import ( - "context" "testing" "time" @@ -10,7 +9,9 @@ import ( "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/lightningnetwork/lnd/lntemp/rpc" "github.com/lightningnetwork/lnd/lntest/wait" "github.com/lightningnetwork/lnd/lnwire" "github.com/stretchr/testify/require" @@ -18,9 +19,7 @@ import ( // testZeroConfChannelOpen tests that opening a zero-conf channel works and // sending payments also works. -func testZeroConfChannelOpen(net *lntest.NetworkHarness, t *harnessTest) { - ctxb := context.Background() - +func testZeroConfChannelOpen(ht *lntemp.HarnessTest) { // Since option-scid-alias is opt-in, the provided harness nodes will // not have the feature bit set. Also need to set anchors as those are // default-off in itests. @@ -30,93 +29,65 @@ func testZeroConfChannelOpen(net *lntest.NetworkHarness, t *harnessTest) { "--protocol.anchors", } - carol := net.NewNode(t.t, "Carol", scidAliasArgs) - defer shutdownAndAssert(net, t, carol) + bob := ht.Bob + carol := ht.NewNode("Carol", scidAliasArgs) + ht.EnsureConnected(bob, carol) // We'll open a regular public channel between Bob and Carol here. - net.EnsureConnected(t.t, net.Bob, carol) - chanAmt := btcutil.Amount(1_000_000) - - fundingPoint := openChannelAndAssert( - t, net, net.Bob, carol, - lntest.OpenChannelParams{ - Amt: chanAmt, - }, - ) - - // Wait for both Bob and Carol to view the channel as active. - err := net.Bob.WaitForNetworkChannelOpen(fundingPoint) - require.NoError(t.t, err, "bob didn't report channel") - err = carol.WaitForNetworkChannelOpen(fundingPoint) - require.NoError(t.t, err, "carol didn't report channel") + p := lntemp.OpenChannelParams{ + Amt: chanAmt, + } + chanPoint := ht.OpenChannel(bob, carol, p) // Spin-up Dave so Carol can open a zero-conf channel to him. - dave := net.NewNode(t.t, "Dave", scidAliasArgs) - defer shutdownAndAssert(net, t, dave) + dave := ht.NewNode("Dave", scidAliasArgs) // We'll give Carol some coins in order to fund the channel. - net.SendCoins(t.t, btcutil.SatoshiPerBitcoin, carol) + ht.FundCoins(btcutil.SatoshiPerBitcoin, carol) // Ensure that both Carol and Dave are connected. - net.EnsureConnected(t.t, carol, dave) + ht.EnsureConnected(carol, dave) // Setup a ChannelAcceptor for Dave. - ctxc, cancel := context.WithCancel(ctxb) - acceptStream, err := dave.ChannelAcceptor(ctxc) - require.NoError(t.t, err) - go acceptChannel(t.t, true, acceptStream) + acceptStream, cancel := dave.RPC.ChannelAcceptor() + go acceptChannel(ht.T, true, acceptStream) // Open a private zero-conf anchors channel of 1M satoshis. - params := lntest.OpenChannelParams{ + params := lntemp.OpenChannelParams{ Amt: chanAmt, Private: true, CommitmentType: lnrpc.CommitmentType_ANCHORS, ZeroConf: true, } - chanOpenUpdate := openChannelStream(t, net, carol, dave, params) + stream := ht.OpenChannelAssertStream(carol, dave, params) // Remove the ChannelAcceptor. cancel() // We should receive the OpenStatusUpdate_ChanOpen update without // having to mine any blocks. - fundingPoint2, err := net.WaitForChannelOpen(chanOpenUpdate) - require.NoError(t.t, err, "error while waiting for channel open") + fundingPoint2 := ht.WaitForChannelOpenEvent(stream) - err = carol.WaitForNetworkChannelOpen(fundingPoint2) - require.NoError(t.t, err, "carol didn't report channel") - err = dave.WaitForNetworkChannelOpen(fundingPoint2) - require.NoError(t.t, err, "dave didn't report channel") + ht.AssertTopologyChannelOpen(carol, fundingPoint2) + ht.AssertTopologyChannelOpen(dave, fundingPoint2) // Attempt to send a 10K satoshi payment from Carol to Dave. daveInvoiceParams := &lnrpc.Invoice{ Value: int64(10_000), Private: true, } - daveInvoiceResp, err := dave.AddInvoice( - ctxb, daveInvoiceParams, - ) - require.NoError(t.t, err, "unable to add invoice") - _ = sendAndAssertSuccess( - t, carol, &routerrpc.SendPaymentRequest{ - PaymentRequest: daveInvoiceResp.PaymentRequest, - TimeoutSeconds: 60, - FeeLimitMsat: noFeeLimitMsat, - }, + daveInvoiceResp := dave.RPC.AddInvoice(daveInvoiceParams) + ht.CompletePaymentRequests( + carol, []string{daveInvoiceResp.PaymentRequest}, ) // Now attempt to send a multi-hop payment from Bob to Dave. This tests // that Dave issues an invoice with an alias SCID that Carol knows and // uses to forward to Dave. - daveInvoiceResp2, err := dave.AddInvoice(ctxb, daveInvoiceParams) - require.NoError(t.t, err, "unable to add invoice") - _ = sendAndAssertSuccess( - t, net.Bob, &routerrpc.SendPaymentRequest{ - PaymentRequest: daveInvoiceResp2.PaymentRequest, - TimeoutSeconds: 60, - FeeLimitMsat: noFeeLimitMsat, - }, + daveInvoiceResp2 := dave.RPC.AddInvoice(daveInvoiceParams) + ht.CompletePaymentRequests( + bob, []string{daveInvoiceResp2.PaymentRequest}, ) // Check that Dave has a zero-conf alias SCID in the graph. @@ -124,126 +95,99 @@ func testZeroConfChannelOpen(net *lntest.NetworkHarness, t *harnessTest) { IncludeUnannounced: true, } - err = waitForZeroConfGraphChange(ctxb, dave, descReq, true) - require.NoError(t.t, err) + err := waitForZeroConfGraphChange(dave, descReq, true) + require.NoError(ht, err) // We'll now confirm the zero-conf channel between Carol and Dave and // assert that sending is still possible. - block := mineBlocks(t, net, 6, 1)[0] + block := ht.MineBlocksAndAssertNumTxes(6, 1)[0] // Dave should still have the alias edge in his db. - err = waitForZeroConfGraphChange(ctxb, dave, descReq, true) - require.NoError(t.t, err) + err = waitForZeroConfGraphChange(dave, descReq, true) + require.NoError(ht, err) - fundingTxID, err := lnrpc.GetChanPointFundingTxid(fundingPoint2) - require.NoError(t.t, err, "unable to get txid") + fundingTxID := ht.GetChanPointFundingTxid(fundingPoint2) - assertTxInBlock(t, block, fundingTxID) + ht.Miner.AssertTxInBlock(block, fundingTxID) - daveInvoiceResp3, err := dave.AddInvoice( - ctxb, daveInvoiceParams, - ) - require.NoError(t.t, err, "unable to add invoice") - _ = sendAndAssertSuccess( - t, net.Bob, &routerrpc.SendPaymentRequest{ - PaymentRequest: daveInvoiceResp3.PaymentRequest, - TimeoutSeconds: 60, - FeeLimitMsat: noFeeLimitMsat, - }, + daveInvoiceResp3 := dave.RPC.AddInvoice(daveInvoiceParams) + ht.CompletePaymentRequests( + bob, []string{daveInvoiceResp3.PaymentRequest}, ) // Eve will now initiate a zero-conf channel with Carol. This tests // that the ChannelUpdates sent are correct since they will be // referring to different alias SCIDs. - eve := net.NewNode(t.t, "Eve", scidAliasArgs) - defer shutdownAndAssert(net, t, eve) - - net.EnsureConnected(t.t, eve, carol) + eve := ht.NewNode("Eve", scidAliasArgs) + ht.EnsureConnected(eve, carol) // Give Eve some coins to fund the channel. - net.SendCoins(t.t, btcutil.SatoshiPerBitcoin, eve) + ht.FundCoins(btcutil.SatoshiPerBitcoin, eve) // Setup a ChannelAcceptor. - ctxc, cancel = context.WithCancel(ctxb) - acceptStream, err = carol.ChannelAcceptor(ctxc) - require.NoError(t.t, err) - go acceptChannel(t.t, true, acceptStream) + acceptStream, cancel = carol.RPC.ChannelAcceptor() + go acceptChannel(ht.T, true, acceptStream) // We'll open a public zero-conf anchors channel of 1M satoshis. params.Private = false - chanOpenUpdate2 := openChannelStream(t, net, eve, carol, params) + stream = ht.OpenChannelAssertStream(eve, carol, params) // Remove the ChannelAcceptor. cancel() // Wait to receive the OpenStatusUpdate_ChanOpen update. - fundingPoint3, err := net.WaitForChannelOpen(chanOpenUpdate2) - require.NoError(t.t, err, "error while waiting for channel open") + fundingPoint3 := ht.WaitForChannelOpenEvent(stream) - err = eve.WaitForNetworkChannelOpen(fundingPoint3) - require.NoError(t.t, err, "eve didn't report channel") - err = carol.WaitForNetworkChannelOpen(fundingPoint3) - require.NoError(t.t, err, "carol didn't report channel") + ht.AssertTopologyChannelOpen(eve, fundingPoint3) + ht.AssertTopologyChannelOpen(carol, fundingPoint3) // Attempt to send a 20K satoshi payment from Eve to Dave. daveInvoiceParams.Value = int64(20_000) - daveInvoiceResp4, err := dave.AddInvoice( - ctxb, daveInvoiceParams, - ) - require.NoError(t.t, err, "unable to add invoice") - _ = sendAndAssertSuccess( - t, eve, &routerrpc.SendPaymentRequest{ - PaymentRequest: daveInvoiceResp4.PaymentRequest, - TimeoutSeconds: 60, - FeeLimitMsat: noFeeLimitMsat, - }, + daveInvoiceResp4 := dave.RPC.AddInvoice(daveInvoiceParams) + ht.CompletePaymentRequests( + eve, []string{daveInvoiceResp4.PaymentRequest}, ) // Assert that Eve has stored the zero-conf alias in her graph. - err = waitForZeroConfGraphChange(ctxb, eve, descReq, true) - require.NoError(t.t, err) + err = waitForZeroConfGraphChange(eve, descReq, true) + require.NoError(ht, err, "expected to not receive error") // We'll confirm the zero-conf channel between Eve and Carol and assert // that sending is still possible. - block = mineBlocks(t, net, 6, 1)[0] + block = ht.MineBlocksAndAssertNumTxes(6, 1)[0] - fundingTxID, err = lnrpc.GetChanPointFundingTxid(fundingPoint3) - require.NoError(t.t, err, "unable to get txid") - - assertTxInBlock(t, block, fundingTxID) + fundingTxID = ht.GetChanPointFundingTxid(fundingPoint3) + ht.Miner.AssertTxInBlock(block, fundingTxID) // Wait until Eve's ZeroConf channel is replaced by the confirmed SCID // in her graph. - err = waitForZeroConfGraphChange(ctxb, eve, descReq, false) - require.NoError(t.t, err, "expected to not receive error") + err = waitForZeroConfGraphChange(eve, descReq, false) + require.NoError(ht, err, "expected to not receive error") // Attempt to send a 6K satoshi payment from Dave to Eve. eveInvoiceParams := &lnrpc.Invoice{ Value: int64(6_000), Private: true, } - eveInvoiceResp, err := eve.AddInvoice(ctxb, eveInvoiceParams) - require.NoError(t.t, err, "unable to add invoice") + eveInvoiceResp := eve.RPC.AddInvoice(eveInvoiceParams) // Assert that route hints is empty since the channel is public. - payReq, err := eve.DecodePayReq(ctxb, &lnrpc.PayReqString{ - PayReq: eveInvoiceResp.PaymentRequest, - }) - require.NoError(t.t, err) - require.True(t.t, len(payReq.RouteHints) == 0) + payReq := eve.RPC.DecodePayReq(eveInvoiceResp.PaymentRequest) + require.Len(ht, payReq.RouteHints, 0) - _ = sendAndAssertSuccess( - t, dave, &routerrpc.SendPaymentRequest{ - PaymentRequest: eveInvoiceResp.PaymentRequest, - TimeoutSeconds: 60, - FeeLimitMsat: noFeeLimitMsat, - }, + // Make sure Dave is aware of this channel and send the payment. + ht.AssertTopologyChannelOpen(dave, fundingPoint3) + ht.CompletePaymentRequests( + dave, []string{eveInvoiceResp.PaymentRequest}, ) + + // Close standby node's channels. + ht.CloseChannel(bob, chanPoint) } // testOptionScidAlias checks that opening an option_scid_alias channel-type // channel or w/o the channel-type works properly. -func testOptionScidAlias(net *lntest.NetworkHarness, t *harnessTest) { +func testOptionScidAlias(ht *lntemp.HarnessTest) { type scidTestCase struct { name string @@ -274,10 +218,10 @@ func testOptionScidAlias(net *lntest.NetworkHarness, t *harnessTest) { for _, testCase := range testCases { testCase := testCase - success := t.t.Run(testCase.name, func(t *testing.T) { - h := newHarnessTest(t, net) + success := ht.Run(testCase.name, func(t *testing.T) { + st := ht.Subtest(t) optionScidAliasScenario( - net, h, testCase.chantype, testCase.private, + st, testCase.chantype, testCase.private, ) }) if !success { @@ -286,110 +230,89 @@ func testOptionScidAlias(net *lntest.NetworkHarness, t *harnessTest) { } } -func optionScidAliasScenario(net *lntest.NetworkHarness, t *harnessTest, - chantype, private bool) { - - ctxb := context.Background() - +func optionScidAliasScenario(ht *lntemp.HarnessTest, chantype, private bool) { // Option-scid-alias is opt-in, as is anchors. scidAliasArgs := []string{ "--protocol.option-scid-alias", "--protocol.anchors", } - carol := net.NewNode(t.t, "Carol", scidAliasArgs) - defer shutdownAndAssert(net, t, carol) - - dave := net.NewNode(t.t, "Dave", scidAliasArgs) - defer shutdownAndAssert(net, t, dave) + bob := ht.Bob + carol := ht.NewNode("Carol", scidAliasArgs) + dave := ht.NewNode("Dave", scidAliasArgs) // Ensure Bob, Carol are connected. - net.EnsureConnected(t.t, net.Bob, carol) + ht.EnsureConnected(bob, carol) // Ensure Carol, Dave are connected. - net.EnsureConnected(t.t, carol, dave) + ht.EnsureConnected(carol, dave) // Give Carol some coins so she can open the channel. - net.SendCoins(t.t, btcutil.SatoshiPerBitcoin, carol) + ht.FundCoins(btcutil.SatoshiPerBitcoin, carol) chanAmt := btcutil.Amount(1_000_000) - params := lntest.OpenChannelParams{ + params := lntemp.OpenChannelParams{ Amt: chanAmt, Private: private, CommitmentType: lnrpc.CommitmentType_ANCHORS, ScidAlias: chantype, } - fundingPoint := openChannelAndAssert(t, net, carol, dave, params) + fundingPoint := ht.OpenChannel(carol, dave, params) + // Make sure Bob knows this channel if it's public. if !private { - err := net.Bob.WaitForNetworkChannelOpen(fundingPoint) - require.NoError(t.t, err, "bob didn't report channel") + ht.AssertTopologyChannelOpen(bob, fundingPoint) } - err := carol.WaitForNetworkChannelOpen(fundingPoint) - require.NoError(t.t, err, "carol didn't report channel") - err = dave.WaitForNetworkChannelOpen(fundingPoint) - require.NoError(t.t, err, "dave didn't report channel") - // Assert that a payment from Carol to Dave works as expected. daveInvoiceParams := &lnrpc.Invoice{ Value: int64(10_000), Private: true, } - daveInvoiceResp, err := dave.AddInvoice(ctxb, daveInvoiceParams) - require.NoError(t.t, err) - _ = sendAndAssertSuccess( - t, carol, &routerrpc.SendPaymentRequest{ - PaymentRequest: daveInvoiceResp.PaymentRequest, - TimeoutSeconds: 60, - FeeLimitMsat: noFeeLimitMsat, - }, + daveInvoiceResp := dave.RPC.AddInvoice(daveInvoiceParams) + ht.CompletePaymentRequests( + carol, []string{daveInvoiceResp.PaymentRequest}, ) // We'll now open a regular public channel between Bob and Carol and // assert that Bob can pay Dave. We'll also assert that the invoice // Dave issues has the startingAlias as a hop hint. - fundingPoint2 := openChannelAndAssert( - t, net, net.Bob, carol, - lntest.OpenChannelParams{ - Amt: chanAmt, - }, - ) + p := lntemp.OpenChannelParams{ + Amt: chanAmt, + } + fundingPoint2 := ht.OpenChannel(bob, carol, p) - err = net.Bob.WaitForNetworkChannelOpen(fundingPoint2) - require.NoError(t.t, err) - err = carol.WaitForNetworkChannelOpen(fundingPoint2) - require.NoError(t.t, err) + defer func() { + // TODO(yy): remove the sleep once the following bug is fixed. + // When the payment is reported as settled by Bob, 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) + + // Close standby node's channels. + ht.CloseChannel(bob, fundingPoint2) + }() // Wait until Dave receives the Bob<->Carol channel. - err = dave.WaitForNetworkChannelOpen(fundingPoint2) - require.NoError(t.t, err) + ht.AssertTopologyChannelOpen(dave, fundingPoint2) - daveInvoiceResp2, err := dave.AddInvoice(ctxb, daveInvoiceParams) - require.NoError(t.t, err) - davePayReq := &lnrpc.PayReqString{ - PayReq: daveInvoiceResp2.PaymentRequest, - } - - decodedReq, err := dave.DecodePayReq(ctxb, davePayReq) - require.NoError(t.t, err) + daveInvoiceResp2 := dave.RPC.AddInvoice(daveInvoiceParams) + decodedReq := dave.RPC.DecodePayReq(daveInvoiceResp2.PaymentRequest) if !private { - require.Equal(t.t, 0, len(decodedReq.RouteHints)) + require.Len(ht, decodedReq.RouteHints, 0) payReq := daveInvoiceResp2.PaymentRequest - _ = sendAndAssertSuccess( - t, net.Bob, &routerrpc.SendPaymentRequest{ - PaymentRequest: payReq, - TimeoutSeconds: 60, - FeeLimitMsat: noFeeLimitMsat, - }, - ) + ht.CompletePaymentRequests(bob, []string{payReq}) return } - require.Equal(t.t, 1, len(decodedReq.RouteHints)) - require.Equal(t.t, 1, len(decodedReq.RouteHints[0].HopHints)) + require.Len(ht, decodedReq.RouteHints, 1) + require.Len(ht, decodedReq.RouteHints[0].HopHints, 1) startingAlias := lnwire.ShortChannelID{ BlockHeight: 16_000_000, @@ -398,14 +321,10 @@ func optionScidAliasScenario(net *lntest.NetworkHarness, t *harnessTest, } daveHopHint := decodedReq.RouteHints[0].HopHints[0].ChanId - require.Equal(t.t, startingAlias.ToUint64(), daveHopHint) + require.Equal(ht, startingAlias.ToUint64(), daveHopHint) - _ = sendAndAssertSuccess( - t, net.Bob, &routerrpc.SendPaymentRequest{ - PaymentRequest: daveInvoiceResp2.PaymentRequest, - TimeoutSeconds: 60, - FeeLimitMsat: noFeeLimitMsat, - }, + ht.CompletePaymentRequests( + bob, []string{daveInvoiceResp2.PaymentRequest}, ) } @@ -413,15 +332,11 @@ func optionScidAliasScenario(net *lntest.NetworkHarness, t *harnessTest, // the graph after confirmation or not. The expect argument denotes whether the // zero-conf is expected in the graph or not. There should always be at least // one channel of the passed HarnessNode, zero-conf or not. -func waitForZeroConfGraphChange(ctxb context.Context, n *lntest.HarnessNode, +func waitForZeroConfGraphChange(hn *node.HarnessNode, req *lnrpc.ChannelGraphRequest, expect bool) error { return wait.NoError(func() error { - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - graph, err := n.DescribeGraph(ctxt, req) - if err != nil { - return err - } + graph := hn.RPC.DescribeGraph(req) if expect { // If we expect a zero-conf channel, we'll assert that @@ -447,8 +362,8 @@ func waitForZeroConfGraphChange(ctxb context.Context, n *lntest.HarnessNode, // Check if we are party to the zero-conf // channel. - if e.Node1Pub == n.PubKeyStr || - e.Node2Pub == n.PubKeyStr { + if e.Node1Pub == hn.PubKeyStr || + e.Node2Pub == hn.PubKeyStr { return nil } @@ -474,8 +389,8 @@ func waitForZeroConfGraphChange(ctxb context.Context, n *lntest.HarnessNode, } // If we are part of this channel, exit gracefully. - if e.Node1Pub == n.PubKeyStr || - e.Node2Pub == n.PubKeyStr { + if e.Node1Pub == hn.PubKeyStr || + e.Node2Pub == hn.PubKeyStr { return nil } @@ -490,9 +405,7 @@ func waitForZeroConfGraphChange(ctxb context.Context, n *lntest.HarnessNode, // testUpdateChannelPolicyScidAlias checks that option-scid-alias, zero-conf // channel-types, and option-scid-alias feature-bit-only channels have the // expected graph and that payments work when updating the channel policy. -func testUpdateChannelPolicyScidAlias(net *lntest.NetworkHarness, - t *harnessTest) { - +func testUpdateChannelPolicyScidAlias(ht *lntemp.HarnessTest) { tests := []struct { name string @@ -530,10 +443,11 @@ func testUpdateChannelPolicyScidAlias(net *lntest.NetworkHarness, for _, test := range tests { test := test - success := t.t.Run(test.name, func(t *testing.T) { - ht := newHarnessTest(t, net) + success := ht.Run(test.name, func(t *testing.T) { + st := ht.Subtest(t) + testPrivateUpdateAlias( - net, ht, test.zeroConf, test.scidAliasType, + st, test.zeroConf, test.scidAliasType, test.private, ) }) @@ -543,17 +457,13 @@ func testUpdateChannelPolicyScidAlias(net *lntest.NetworkHarness, } } -func testPrivateUpdateAlias(net *lntest.NetworkHarness, t *harnessTest, +func testPrivateUpdateAlias(ht *lntemp.HarnessTest, zeroConf, scidAliasType, private bool) { - ctxb := context.Background() - defer ctxb.Done() - // We'll create a new node Eve that will not have option-scid-alias // channels. - eve := net.NewNode(t.t, "Eve", nil) - net.SendCoins(t.t, btcutil.SatoshiPerBitcoin, eve) - defer shutdownAndAssert(net, t, eve) + eve := ht.NewNode("Eve", nil) + ht.FundCoins(btcutil.SatoshiPerBitcoin, eve) // Since option-scid-alias is opt-in we'll need to specify the protocol // arguments when creating a new node. @@ -562,52 +472,39 @@ func testPrivateUpdateAlias(net *lntest.NetworkHarness, t *harnessTest, "--protocol.zero-conf", "--protocol.anchors", } - - carol := net.NewNode(t.t, "Carol", scidAliasArgs) - defer shutdownAndAssert(net, t, carol) + carol := ht.NewNode("Carol", scidAliasArgs) // Spin-up Dave who will have an option-scid-alias feature-bit-only or // channel-type channel with Carol. - dave := net.NewNode(t.t, "Dave", scidAliasArgs) - defer shutdownAndAssert(net, t, dave) + dave := ht.NewNode("Dave", scidAliasArgs) // We'll give Carol some coins in order to fund the channel. - net.SendCoins(t.t, btcutil.SatoshiPerBitcoin, carol) + ht.FundCoins(btcutil.SatoshiPerBitcoin, carol) // Ensure that Carol and Dave are connected. - net.EnsureConnected(t.t, carol, dave) + ht.EnsureConnected(carol, dave) // We'll open a regular public channel between Eve and Carol here. Eve // will be the one receiving the onion-encrypted ChannelUpdate. - net.EnsureConnected(t.t, eve, carol) + ht.EnsureConnected(eve, carol) chanAmt := btcutil.Amount(1_000_000) - fundingPoint := openChannelAndAssert( - t, net, eve, carol, - lntest.OpenChannelParams{ - Amt: chanAmt, - PushAmt: chanAmt / 2, - }, - ) - defer closeChannelAndAssert(t, net, eve, fundingPoint, false) + p := lntemp.OpenChannelParams{ + Amt: chanAmt, + PushAmt: chanAmt / 2, + } + fundingPoint := ht.OpenChannel(eve, carol, p) - // Wait for all to view the channel as active. - err := eve.WaitForNetworkChannelOpen(fundingPoint) - require.NoError(t.t, err, "eve didn't report channel") - err = carol.WaitForNetworkChannelOpen(fundingPoint) - require.NoError(t.t, err, "carol didn't report channel") - err = dave.WaitForNetworkChannelOpen(fundingPoint) - require.NoError(t.t, err, "dave didn't report channel") + // Make sure Dave has seen this public channel. + ht.AssertTopologyChannelOpen(dave, fundingPoint) // Setup a ChannelAcceptor for Dave. - ctxc, cancel := context.WithCancel(ctxb) - acceptStream, err := dave.ChannelAcceptor(ctxc) - require.NoError(t.t, err) - go acceptChannel(t.t, zeroConf, acceptStream) + acceptStream, cancel := dave.RPC.ChannelAcceptor() + go acceptChannel(ht.T, zeroConf, acceptStream) // Open a private channel, optionally specifying a channel-type. - params := lntest.OpenChannelParams{ + params := lntemp.OpenChannelParams{ Amt: chanAmt, Private: private, CommitmentType: lnrpc.CommitmentType_ANCHORS, @@ -615,26 +512,11 @@ func testPrivateUpdateAlias(net *lntest.NetworkHarness, t *harnessTest, ScidAlias: scidAliasType, PushAmt: chanAmt / 2, } - chanOpenUpdate := openChannelStream(t, net, carol, dave, params) + fundingPoint2 := ht.OpenChannelNoAnnounce(carol, dave, params) // Remove the ChannelAcceptor. cancel() - if !zeroConf { - // If this is not a zero-conf channel, mine a single block to - // confirm the channel. - _ = mineBlocks(t, net, 1, 1) - } - - // Wait for both Carol and Dave to see the channel as open. - fundingPoint2, err := net.WaitForChannelOpen(chanOpenUpdate) - require.NoError(t.t, err, "error while waiting for channel open") - - err = carol.WaitForNetworkChannelOpen(fundingPoint2) - require.NoError(t.t, err, "carol didn't report channel") - err = dave.WaitForNetworkChannelOpen(fundingPoint2) - require.NoError(t.t, err, "dave didn't report channel") - // Carol will now update the channel edge policy for her channel with // Dave. baseFeeMSat := 33000 @@ -648,9 +530,7 @@ func testPrivateUpdateAlias(net *lntest.NetworkHarness, t *harnessTest, ChanPoint: fundingPoint2, }, } - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - _, err = carol.UpdateChannelPolicy(ctxt, updateFeeReq) - require.NoError(t.t, err, "unable to update chan policy") + carol.RPC.UpdateChannelPolicy(updateFeeReq) expectedPolicy := &lnrpc.RoutingPolicy{ FeeBaseMsat: int64(baseFeeMSat), @@ -661,9 +541,8 @@ func testPrivateUpdateAlias(net *lntest.NetworkHarness, t *harnessTest, } // Assert that Dave receives Carol's policy update. - assertChannelPolicyUpdate( - t.t, dave, carol.PubKeyStr, expectedPolicy, fundingPoint2, - true, + ht.AssertChannelPolicyUpdate( + dave, carol, expectedPolicy, fundingPoint2, true, ) // Have Dave also update his policy. @@ -677,9 +556,7 @@ func testPrivateUpdateAlias(net *lntest.NetworkHarness, t *harnessTest, ChanPoint: fundingPoint2, }, } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - _, err = dave.UpdateChannelPolicy(ctxt, updateFeeReq) - require.NoError(t.t, err, "unable to update chan policy") + dave.RPC.UpdateChannelPolicy(updateFeeReq) expectedPolicy = &lnrpc.RoutingPolicy{ FeeBaseMsat: int64(baseFeeMSat), @@ -690,9 +567,8 @@ func testPrivateUpdateAlias(net *lntest.NetworkHarness, t *harnessTest, } // Assert that Carol receives Dave's policy update. - assertChannelPolicyUpdate( - t.t, carol, dave.PubKeyStr, expectedPolicy, fundingPoint2, - true, + ht.AssertChannelPolicyUpdate( + carol, dave, expectedPolicy, fundingPoint2, true, ) // Assert that if Dave disables the channel, Carol sees it. @@ -700,16 +576,11 @@ func testPrivateUpdateAlias(net *lntest.NetworkHarness, t *harnessTest, ChanPoint: fundingPoint2, Action: routerrpc.ChanStatusAction_DISABLE, } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - _, err = dave.RouterClient.UpdateChanStatus(ctxt, disableReq) - require.NoError(t.t, err) + dave.RPC.UpdateChanStatus(disableReq) - davePolicy := getChannelPolicies( - t, carol, dave.PubKeyStr, fundingPoint2, - )[0] - davePolicy.Disabled = true - assertChannelPolicyUpdate( - t.t, carol, dave.PubKeyStr, davePolicy, fundingPoint2, true, + expectedPolicy.Disabled = true + ht.AssertChannelPolicyUpdate( + carol, dave, expectedPolicy, fundingPoint2, true, ) // Assert that if Dave enables the channel, Carol sees it. @@ -717,13 +588,11 @@ func testPrivateUpdateAlias(net *lntest.NetworkHarness, t *harnessTest, ChanPoint: fundingPoint2, Action: routerrpc.ChanStatusAction_ENABLE, } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - _, err = dave.RouterClient.UpdateChanStatus(ctxt, enableReq) - require.NoError(t.t, err) + dave.RPC.UpdateChanStatus(enableReq) - davePolicy.Disabled = false - assertChannelPolicyUpdate( - t.t, carol, dave.PubKeyStr, davePolicy, fundingPoint2, true, + expectedPolicy.Disabled = false + ht.AssertChannelPolicyUpdate( + carol, dave, expectedPolicy, fundingPoint2, true, ) // Create an invoice for Carol to pay. @@ -731,27 +600,16 @@ func testPrivateUpdateAlias(net *lntest.NetworkHarness, t *harnessTest, Value: int64(10_000), Private: true, } - daveInvoiceResp, err := dave.AddInvoice(ctxb, invoiceParams) - require.NoError(t.t, err, "unable to add invoice") + daveInvoiceResp := dave.RPC.AddInvoice(invoiceParams) // Carol will attempt to send Dave an HTLC. payReqs := []string{daveInvoiceResp.PaymentRequest} - require.NoError( - t.t, completePaymentRequests( - carol, carol.RouterClient, payReqs, true, - ), "unable to send payment", - ) + ht.CompletePaymentRequests(carol, payReqs) // Now Eve will create an invoice that Dave will pay. - eveInvoiceResp, err := eve.AddInvoice(ctxb, invoiceParams) - require.NoError(t.t, err, "unable to add invoice") - + eveInvoiceResp := eve.RPC.AddInvoice(invoiceParams) payReqs = []string{eveInvoiceResp.PaymentRequest} - require.NoError( - t.t, completePaymentRequests( - dave, dave.RouterClient, payReqs, true, - ), "unable to send payment", - ) + ht.CompletePaymentRequests(dave, payReqs) // If this is a public channel, it won't be included in the hop hints, // so we'll mine enough for 6 confs here. We only expect a tx in the @@ -761,18 +619,19 @@ func testPrivateUpdateAlias(net *lntest.NetworkHarness, t *harnessTest, if zeroConf { expectTx = 1 } - _ = mineBlocks(t, net, 6, expectTx) + ht.MineBlocksAndAssertNumTxes(6, expectTx) // Sleep here so that the edge can be deleted and re-inserted. // This is necessary since the edge may have a policy for the // peer that is "correct" but has an invalid signature from the // PoV of BOLT#7. + // + // TODO(yy): further investigate this sleep. time.Sleep(time.Second * 5) } // Dave creates an invoice that Eve will pay. - daveInvoiceResp2, err := dave.AddInvoice(ctxb, invoiceParams) - require.NoError(t.t, err, "unable to add invoice") + daveInvoiceResp2 := dave.RPC.AddInvoice(invoiceParams) // Carol then updates the channel policy again. feeRate = int64(2) @@ -784,9 +643,7 @@ func testPrivateUpdateAlias(net *lntest.NetworkHarness, t *harnessTest, ChanPoint: fundingPoint2, }, } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - _, err = carol.UpdateChannelPolicy(ctxt, updateFeeReq) - require.NoError(t.t, err, "unable to update chan policy") + carol.RPC.UpdateChannelPolicy(updateFeeReq) expectedPolicy = &lnrpc.RoutingPolicy{ FeeBaseMsat: int64(baseFeeMSat), @@ -797,38 +654,26 @@ func testPrivateUpdateAlias(net *lntest.NetworkHarness, t *harnessTest, } // Assert Dave receives Carol's policy update. - assertChannelPolicyUpdate( - t.t, dave, carol.PubKeyStr, expectedPolicy, fundingPoint2, - true, + ht.AssertChannelPolicyUpdate( + dave, carol, expectedPolicy, fundingPoint2, true, ) // If the channel is public, check that Eve receives Carol's policy // update. if !private { - assertChannelPolicyUpdate( - t.t, eve, carol.PubKeyStr, expectedPolicy, - fundingPoint2, true, + ht.AssertChannelPolicyUpdate( + eve, carol, expectedPolicy, fundingPoint2, true, ) } // Eve will pay Dave's invoice and should use the updated base fee. payReqs = []string{daveInvoiceResp2.PaymentRequest} - require.NoError( - t.t, completePaymentRequests( - eve, eve.RouterClient, payReqs, true, - ), "unable to send payment", - ) + ht.CompletePaymentRequests(eve, payReqs) // Eve will issue an invoice that Dave will pay. - eveInvoiceResp2, err := eve.AddInvoice(ctxb, invoiceParams) - require.NoError(t.t, err, "unable to add invoice") - + eveInvoiceResp2 := eve.RPC.AddInvoice(invoiceParams) payReqs = []string{eveInvoiceResp2.PaymentRequest} - require.NoError( - t.t, completePaymentRequests( - dave, dave.RouterClient, payReqs, true, - ), "unable to send payment", - ) + ht.CompletePaymentRequests(dave, payReqs) // If this is a private channel, we'll mine 6 blocks here to test the // funding manager logic that deals with ChannelUpdates. If this is not @@ -838,62 +683,45 @@ func testPrivateUpdateAlias(net *lntest.NetworkHarness, t *harnessTest, if zeroConf { expectTx = 1 } - _ = mineBlocks(t, net, 6, expectTx) + ht.MineBlocksAndAssertNumTxes(6, expectTx) } // Dave will issue an invoice and Eve will pay it. - daveInvoiceResp3, err := dave.AddInvoice(ctxb, invoiceParams) - require.NoError(t.t, err, "unable to add invoice") - + daveInvoiceResp3 := dave.RPC.AddInvoice(invoiceParams) payReqs = []string{daveInvoiceResp3.PaymentRequest} - require.NoError( - t.t, completePaymentRequests( - eve, eve.RouterClient, payReqs, true, - ), "unable to send payment", - ) + ht.CompletePaymentRequests(eve, payReqs) // Carol will disable the channel, assert that Dave sees it and Eve as // well if the channel is public. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - _, err = carol.RouterClient.UpdateChanStatus(ctxt, disableReq) - require.NoError(t.t, err) + carol.RPC.UpdateChanStatus(disableReq) - carolPolicy := getChannelPolicies( - t, dave, carol.PubKeyStr, fundingPoint2, - )[0] - carolPolicy.Disabled = true - assertChannelPolicyUpdate( - t.t, dave, carol.PubKeyStr, carolPolicy, fundingPoint2, true, + expectedPolicy.Disabled = true + ht.AssertChannelPolicyUpdate( + dave, carol, expectedPolicy, fundingPoint2, true, ) if !private { - assertChannelPolicyUpdate( - t.t, eve, carol.PubKeyStr, carolPolicy, fundingPoint2, - true, + ht.AssertChannelPolicyUpdate( + eve, carol, expectedPolicy, fundingPoint2, true, ) } // Carol will enable the channel, assert the same as above. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - _, err = carol.RouterClient.UpdateChanStatus(ctxt, enableReq) - require.NoError(t.t, err) - - carolPolicy.Disabled = false - assertChannelPolicyUpdate( - t.t, dave, carol.PubKeyStr, carolPolicy, fundingPoint2, true, + carol.RPC.UpdateChanStatus(enableReq) + expectedPolicy.Disabled = false + ht.AssertChannelPolicyUpdate( + dave, carol, expectedPolicy, fundingPoint2, true, ) if !private { - assertChannelPolicyUpdate( - t.t, eve, carol.PubKeyStr, carolPolicy, fundingPoint2, - true, + ht.AssertChannelPolicyUpdate( + eve, carol, expectedPolicy, fundingPoint2, true, ) } // Dave will issue an invoice and Eve should pay it after Carol updates // her channel policy. - daveInvoiceResp4, err := dave.AddInvoice(ctxb, invoiceParams) - require.NoError(t.t, err, "unable to add invoice") + daveInvoiceResp4 := dave.RPC.AddInvoice(invoiceParams) feeRate = int64(3) updateFeeReq = &lnrpc.PolicyUpdateRequest{ @@ -904,9 +732,7 @@ func testPrivateUpdateAlias(net *lntest.NetworkHarness, t *harnessTest, ChanPoint: fundingPoint2, }, } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - _, err = carol.UpdateChannelPolicy(ctxt, updateFeeReq) - require.NoError(t.t, err, "unable to update chan policy") + carol.RPC.UpdateChannelPolicy(updateFeeReq) expectedPolicy = &lnrpc.RoutingPolicy{ FeeBaseMsat: int64(baseFeeMSat), @@ -917,90 +743,67 @@ func testPrivateUpdateAlias(net *lntest.NetworkHarness, t *harnessTest, } // Assert Dave and optionally Eve receives Carol's update. - assertChannelPolicyUpdate( - t.t, dave, carol.PubKeyStr, expectedPolicy, fundingPoint2, - true, + ht.AssertChannelPolicyUpdate( + dave, carol, expectedPolicy, fundingPoint2, true, ) if !private { - assertChannelPolicyUpdate( - t.t, eve, carol.PubKeyStr, expectedPolicy, - fundingPoint2, true, + ht.AssertChannelPolicyUpdate( + eve, carol, expectedPolicy, fundingPoint2, true, ) } payReqs = []string{daveInvoiceResp4.PaymentRequest} - require.NoError( - t.t, completePaymentRequests( - eve, eve.RouterClient, payReqs, true, - ), "unable to send payment", - ) + ht.CompletePaymentRequests(eve, payReqs) } // testOptionScidUpgrade tests that toggling the option-scid-alias feature bit // correctly upgrades existing channels. -func testOptionScidUpgrade(net *lntest.NetworkHarness, t *harnessTest) { - ctxb := context.Background() +func testOptionScidUpgrade(ht *lntemp.HarnessTest) { + bob := ht.Bob // Start carol with anchors only. carolArgs := []string{ "--protocol.anchors", } - carol := net.NewNode(t.t, "carol", carolArgs) + carol := ht.NewNode("carol", carolArgs) // Start dave with anchors + scid-alias. daveArgs := []string{ "--protocol.anchors", "--protocol.option-scid-alias", } - dave := net.NewNode(t.t, "dave", daveArgs) - defer shutdownAndAssert(net, t, dave) + dave := ht.NewNode("dave", daveArgs) // Give carol some coins. - net.SendCoins(t.t, btcutil.SatoshiPerBitcoin, carol) + ht.FundCoins(btcutil.SatoshiPerBitcoin, carol) // Ensure carol and are connected. - net.EnsureConnected(t.t, carol, dave) + ht.EnsureConnected(carol, dave) chanAmt := btcutil.Amount(1_000_000) - fundingPoint := openChannelAndAssert( - t, net, carol, dave, - lntest.OpenChannelParams{ - Amt: chanAmt, - PushAmt: chanAmt / 2, - Private: true, - }, - ) - defer closeChannelAndAssert(t, net, carol, fundingPoint, false) - - err := carol.WaitForNetworkChannelOpen(fundingPoint) - require.NoError(t.t, err) - err = dave.WaitForNetworkChannelOpen(fundingPoint) - require.NoError(t.t, err) + p := lntemp.OpenChannelParams{ + Amt: chanAmt, + PushAmt: chanAmt / 2, + Private: true, + } + ht.OpenChannel(carol, dave, p) // Bob will open a channel to Carol now. - net.EnsureConnected(t.t, net.Bob, carol) + ht.EnsureConnected(bob, carol) - fundingPoint2 := openChannelAndAssert( - t, net, net.Bob, carol, - lntest.OpenChannelParams{ - Amt: chanAmt, - }, - ) + p = lntemp.OpenChannelParams{ + Amt: chanAmt, + } + fundingPoint2 := ht.OpenChannel(bob, carol, p) - err = net.Bob.WaitForNetworkChannelOpen(fundingPoint2) - require.NoError(t.t, err) - err = carol.WaitForNetworkChannelOpen(fundingPoint2) - require.NoError(t.t, err) - err = dave.WaitForNetworkChannelOpen(fundingPoint2) - require.NoError(t.t, err) + // Make sure Dave knows this channel. + ht.AssertTopologyChannelOpen(dave, fundingPoint2) // Carol will now set the option-scid-alias feature bit and restart. carolArgs = append(carolArgs, "--protocol.option-scid-alias") - carol.SetExtraArgs(carolArgs) - err = net.RestartNode(carol, nil) - require.NoError(t.t, err) + ht.RestartNodeWithExtraArgs(carol, carolArgs) // Dave will create an invoice for Carol to pay, it should contain an // alias in the hop hints. @@ -1014,21 +817,9 @@ func testOptionScidUpgrade(net *lntest.NetworkHarness, t *harnessTest) { var startingAlias lnwire.ShortChannelID startingAlias.BlockHeight = 16_000_000 - err = wait.Predicate(func() bool { - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - invoiceResp, err := dave.AddInvoice(ctxt, daveParams) - if err != nil { - return false - } - - payReq := &lnrpc.PayReqString{ - PayReq: invoiceResp.PaymentRequest, - } - - decodedReq, err := dave.DecodePayReq(ctxb, payReq) - if err != nil { - return false - } + err := wait.Predicate(func() bool { + invoiceResp := dave.RPC.AddInvoice(daveParams) + decodedReq := dave.RPC.DecodePayReq(invoiceResp.PaymentRequest) if len(decodedReq.RouteHints) != 1 { return false @@ -1046,25 +837,49 @@ func testOptionScidUpgrade(net *lntest.NetworkHarness, t *harnessTest) { return false }, defaultTimeout) - require.NoError(t.t, err) + require.NoError(ht, err) // Carol should be able to pay it. - _ = sendAndAssertSuccess( - t, carol, &routerrpc.SendPaymentRequest{ - PaymentRequest: daveInvoice.PaymentRequest, - TimeoutSeconds: 60, - FeeLimitMsat: noFeeLimitMsat, - }, - ) + ht.CompletePaymentRequests(carol, []string{daveInvoice.PaymentRequest}) - daveInvoice2, err := dave.AddInvoice(ctxb, daveParams) - require.NoError(t.t, err) + // TODO(yy): remove this connection once the following bug is fixed. + // When Carol restarts, she will try to make a persistent connection to + // Bob. Meanwhile, Bob will also make a conn request as he notices the + // connection is broken. If they make these conn requests at the same + // time, they both have an outbound conn request, and will close the + // inbound conn they receives, which ends up in no conn. + ht.EnsureConnected(bob, carol) - _ = sendAndAssertSuccess( - t, net.Bob, &routerrpc.SendPaymentRequest{ - PaymentRequest: daveInvoice2.PaymentRequest, - TimeoutSeconds: 60, - FeeLimitMsat: noFeeLimitMsat, - }, - ) + daveInvoice2 := dave.RPC.AddInvoice(daveParams) + ht.CompletePaymentRequests(bob, []string{daveInvoice2.PaymentRequest}) + + // TODO(yy): remove the sleep once the following bug is fixed. When + // the payment is reported as settled by Bob, 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) + + // Close standby node's channels. + ht.CloseChannel(bob, fundingPoint2) +} + +// acceptChannel is used to accept a single channel that comes across. This +// should be run in a goroutine and is used to test nodes with the zero-conf +// feature bit. +func acceptChannel(t *testing.T, zeroConf bool, stream rpc.AcceptorClient) { + t.Helper() + + req, err := stream.Recv() + require.NoError(t, err) + + resp := &lnrpc.ChannelAcceptResponse{ + Accept: true, + PendingChanId: req.PendingChanId, + ZeroConf: zeroConf, + } + err = stream.Send(resp) + require.NoError(t, err) } diff --git a/lntest/itest/utils.go b/lntest/itest/utils.go index dd03ac40b..4995a0beb 100644 --- a/lntest/itest/utils.go +++ b/lntest/itest/utils.go @@ -5,20 +5,16 @@ import ( "crypto/rand" "fmt" "io" - "testing" "time" "github.com/btcsuite/btcd/btcutil" - "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/rpcclient" - "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" "github.com/lightningnetwork/lnd/lntemp" - "github.com/lightningnetwork/lnd/lntemp/rpc" "github.com/lightningnetwork/lnd/lntest" "github.com/lightningnetwork/lnd/lntest/wait" "github.com/lightningnetwork/lnd/lnwallet" @@ -470,49 +466,3 @@ func findTxAtHeight(t *harnessTest, height int32, return nil } - -// getOutputIndex returns the output index of the given address in the given -// transaction. -func getOutputIndex(t *harnessTest, miner *lntest.HarnessMiner, - txid *chainhash.Hash, addr string) int { - - t.t.Helper() - - // We'll then extract the raw transaction from the mempool in order to - // determine the index of the p2tr output. - tx, err := miner.Client.GetRawTransaction(txid) - require.NoError(t.t, err) - - p2trOutputIndex := -1 - for i, txOut := range tx.MsgTx().TxOut { - _, addrs, _, err := txscript.ExtractPkScriptAddrs( - txOut.PkScript, miner.ActiveNet, - ) - require.NoError(t.t, err) - - if addrs[0].String() == addr { - p2trOutputIndex = i - } - } - require.Greater(t.t, p2trOutputIndex, -1) - - return p2trOutputIndex -} - -// acceptChannel is used to accept a single channel that comes across. This -// should be run in a goroutine and is used to test nodes with the zero-conf -// feature bit. -func acceptChannel(t *testing.T, zeroConf bool, stream rpc.AcceptorClient) { - t.Helper() - - req, err := stream.Recv() - require.NoError(t, err) - - resp := &lnrpc.ChannelAcceptResponse{ - Accept: true, - PendingChanId: req.PendingChanId, - ZeroConf: zeroConf, - } - err = stream.Send(resp) - require.NoError(t, err) -} diff --git a/lnwallet/channel.go b/lnwallet/channel.go index d68d38b78..83a146be0 100644 --- a/lnwallet/channel.go +++ b/lnwallet/channel.go @@ -3566,9 +3566,12 @@ func (lc *LightningChannel) validateCommitmentSanity(theirLogCounter, // balance to satisfy the final evaluated HTLC's. switch { case int64(ourBalance) < 0: - return ErrBelowChanReserve + return fmt.Errorf("%w: negative local balance", + ErrBelowChanReserve) + case int64(theirBalance) < 0: - return ErrBelowChanReserve + return fmt.Errorf("%w: negative remote balance", + ErrBelowChanReserve) } // Ensure that the fee being applied is enough to be relayed across the @@ -3580,17 +3583,25 @@ func (lc *LightningChannel) validateCommitmentSanity(theirLogCounter, // If the added HTLCs will decrease the balance, make sure they won't // dip the local and remote balances below the channel reserves. + ourReserve := lnwire.NewMSatFromSatoshis( + lc.channelState.LocalChanCfg.ChanReserve, + ) + theirReserve := lnwire.NewMSatFromSatoshis( + lc.channelState.RemoteChanCfg.ChanReserve, + ) + switch { - case ourBalance < ourInitialBalance && - ourBalance < lnwire.NewMSatFromSatoshis( - lc.channelState.LocalChanCfg.ChanReserve): + case ourBalance < ourInitialBalance && ourBalance < ourReserve: + lc.log.Debugf("Funds below chan reserve: ourBalance=%v, "+ + "ourReserve=%v", ourBalance, ourReserve) + return fmt.Errorf("%w: our balance below chan reserve", + ErrBelowChanReserve) - return ErrBelowChanReserve - case theirBalance < theirInitialBalance && - theirBalance < lnwire.NewMSatFromSatoshis( - lc.channelState.RemoteChanCfg.ChanReserve): - - return ErrBelowChanReserve + case theirBalance < theirInitialBalance && theirBalance < theirReserve: + lc.log.Debugf("Funds below chan reserve: theirBalance=%v, "+ + "theirReserve=%v", theirBalance, theirReserve) + return fmt.Errorf("%w: their balance below chan reserve", + ErrBelowChanReserve) } // validateUpdates take a set of updates, and validates them against diff --git a/lnwallet/channel_test.go b/lnwallet/channel_test.go index 1ed6e6e3e..d3cec2d26 100644 --- a/lnwallet/channel_test.go +++ b/lnwallet/channel_test.go @@ -2737,10 +2737,7 @@ func TestAddHTLCNegativeBalance(t *testing.T) { htlcAmt = lnwire.NewMSatFromSatoshis(2 * btcutil.SatoshiPerBitcoin) htlc, _ := createHTLC(numHTLCs+1, htlcAmt) _, err = aliceChannel.AddHTLC(htlc, nil) - if err != ErrBelowChanReserve { - t.Fatalf("expected balance below channel reserve, instead "+ - "got: %v", err) - } + require.ErrorIs(t, err, ErrBelowChanReserve) } // assertNoChanSyncNeeded is a helper function that asserts that upon restart, @@ -5642,10 +5639,8 @@ func TestDesyncHTLCs(t *testing.T) { // balance is unavailable. htlcAmt = lnwire.NewMSatFromSatoshis(1 * btcutil.SatoshiPerBitcoin) htlc, _ = createHTLC(1, htlcAmt) - if _, err = aliceChannel.AddHTLC(htlc, nil); err != ErrBelowChanReserve { - t.Fatalf("expected ErrInsufficientBalance, instead received: %v", - err) - } + _, err = aliceChannel.AddHTLC(htlc, nil) + require.ErrorIs(t, err, ErrBelowChanReserve) // Now do a state transition, which will ACK the FailHTLC, making Alice // able to add the new HTLC. @@ -6063,14 +6058,11 @@ func TestChanReserve(t *testing.T) { htlc, _ = createHTLC(bobIndex, htlcAmt) bobIndex++ _, err := bobChannel.AddHTLC(htlc, nil) - if err != ErrBelowChanReserve { - t.Fatalf("expected ErrBelowChanReserve, instead received: %v", err) - } + require.ErrorIs(t, err, ErrBelowChanReserve) // Alice will reject this htlc upon receiving the htlc. - if _, err := aliceChannel.ReceiveHTLC(htlc); err != ErrBelowChanReserve { - t.Fatalf("expected ErrBelowChanReserve, instead received: %v", err) - } + _, err = aliceChannel.ReceiveHTLC(htlc) + require.ErrorIs(t, err, ErrBelowChanReserve) // We must setup the channels again, since a violation of the channel // constraints leads to channel shutdown. @@ -6105,14 +6097,11 @@ func TestChanReserve(t *testing.T) { htlc, _ = createHTLC(aliceIndex, htlcAmt) aliceIndex++ _, err = aliceChannel.AddHTLC(htlc, nil) - if err != ErrBelowChanReserve { - t.Fatalf("expected ErrBelowChanReserve, instead received: %v", err) - } + require.ErrorIs(t, err, ErrBelowChanReserve) // Likewise, Bob will reject receiving the htlc because of the same reason. - if _, err := bobChannel.ReceiveHTLC(htlc); err != ErrBelowChanReserve { - t.Fatalf("expected ErrBelowChanReserve, instead received: %v", err) - } + _, err = bobChannel.ReceiveHTLC(htlc) + require.ErrorIs(t, err, ErrBelowChanReserve) // We must setup the channels again, since a violation of the channel // constraints leads to channel shutdown. @@ -6218,22 +6207,15 @@ func TestChanReserveRemoteInitiator(t *testing.T) { // Bob should refuse to add this HTLC, since he realizes it will create // an invalid commitment. _, err = bobChannel.AddHTLC(htlc, nil) - if err != ErrBelowChanReserve { - t.Fatalf("expected ErrBelowChanReserve, instead received: %v", - err) - } + require.ErrorIs(t, err, ErrBelowChanReserve) // Of course Alice will also not have enough balance to add it herself. _, err = aliceChannel.AddHTLC(htlc, nil) - if err != ErrBelowChanReserve { - t.Fatalf("expected ErrBelowChanReserve, instead received: %v", - err) - } + require.ErrorIs(t, err, ErrBelowChanReserve) // Same for Alice, she should refuse to accept this second HTLC. - if _, err := aliceChannel.ReceiveHTLC(htlc); err != ErrBelowChanReserve { - t.Fatalf("expected ErrBelowChanReserve, instead received: %v", err) - } + _, err = aliceChannel.ReceiveHTLC(htlc) + require.ErrorIs(t, err, ErrBelowChanReserve) } // TestChanReserveLocalInitiatorDustHtlc tests that fee the initiator must pay @@ -6276,9 +6258,7 @@ func TestChanReserveLocalInitiatorDustHtlc(t *testing.T) { // Alice should realize that the fee she must pay to add this HTLC to // the local commitment would take her below the channel reserve. _, err = aliceChannel.AddHTLC(htlc, nil) - if err != ErrBelowChanReserve { - t.Fatalf("expected ErrBelowChanReserve, instead received: %v", err) - } + require.ErrorIs(t, err, ErrBelowChanReserve) } // TestMinHTLC tests that the ErrBelowMinHTLC error is thrown if an HTLC is added