lntemp+itest: refactor wallet import tests

This commit is contained in:
yyforyongyu 2022-08-11 14:01:08 +08:00
parent 0176d58b85
commit 9000994b7b
No known key found for this signature in database
GPG key ID: 9BCD95C4FF296868
3 changed files with 188 additions and 345 deletions

View file

@ -469,4 +469,12 @@ var allTestCasesTemp = []*lntemp.TestCase{
Name: "taproot", Name: "taproot",
TestFunc: testTaproot, TestFunc: testTaproot,
}, },
{
Name: "wallet import account",
TestFunc: testWalletImportAccount,
},
{
Name: "wallet import pubkey",
TestFunc: testWalletImportPubKey,
},
} }

View file

@ -12,14 +12,6 @@ var allTestCases = []*testCase{
name: "async bidirectional payments", name: "async bidirectional payments",
test: testBidirectionalAsyncPayments, test: testBidirectionalAsyncPayments,
}, },
{
name: "wallet import account",
test: testWalletImportAccount,
},
{
name: "wallet import pubkey",
test: testWalletImportPubKey,
},
{ {
name: "remote signer", name: "remote signer",
test: testRemoteSigner, test: testRemoteSigner,

View file

@ -2,10 +2,6 @@ package itest
import ( import (
"bytes" "bytes"
"context"
"crypto/rand"
"fmt"
"math"
"testing" "testing"
"time" "time"
@ -20,8 +16,8 @@ import (
"github.com/lightningnetwork/lnd/funding" "github.com/lightningnetwork/lnd/funding"
"github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/walletrpc" "github.com/lightningnetwork/lnd/lnrpc/walletrpc"
"github.com/lightningnetwork/lnd/lntest" "github.com/lightningnetwork/lnd/lntemp"
"github.com/lightningnetwork/lnd/lntest/wait" "github.com/lightningnetwork/lnd/lntemp/node"
"github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet"
"github.com/stretchr/testify/require" "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 // 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. // 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 { importedAccount string, addrType walletrpc.AddressType) string {
// We'll generate a new address for Carol from Dave's node to receive // We'll generate a new address for Carol from Dave's node to receive
// and fund a new channel. // and fund a new channel.
ctxb := context.Background() req := &lnrpc.NewAddressRequest{
ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) Type: walletToLNAddrType(ht.T, addrType),
defer cancel()
funderResp, err := funder.NewAddress(ctxt, &lnrpc.NewAddressRequest{
Type: walletToLNAddrType(t, addrType),
Account: importedAccount, 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 // Carol also needs to generate the address for the sake of this test
// be able to sign the channel funding input. // to be able to sign the channel funding input.
signerResp, err := signer.NewAddress(ctxt, &lnrpc.NewAddressRequest{ req = &lnrpc.NewAddressRequest{
Type: walletToLNAddrType(t, addrType), Type: walletToLNAddrType(ht.T, addrType),
}) }
require.NoError(t, err) signerResp := signer.RPC.NewAddress(req)
// Sanity check that the generated addresses match. // Sanity check that the generated addresses match.
require.Equal(t, funderResp.Address, signerResp.Address) require.Equal(ht, funderResp.Address, signerResp.Address)
assertExternalAddrType(t, funderResp.Address, addrType) assertExternalAddrType(ht.T, funderResp.Address, addrType)
return funderResp.Address return funderResp.Address
} }
@ -130,108 +123,21 @@ func assertOutputScriptType(t *testing.T, expType txscript.ScriptClass,
spew.Sdump(tx)) 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 // psbtSendFromImportedAccount attempts to fund a PSBT from the given imported
// account, originating from the source node to the destination. // account, originating from the source node to the destination.
func psbtSendFromImportedAccount(t *harnessTest, srcNode, destNode, func psbtSendFromImportedAccount(ht *lntemp.HarnessTest, srcNode, destNode,
signer *lntest.HarnessNode, account string, signer *node.HarnessNode, account string,
accountAddrType walletrpc.AddressType) { accountAddrType walletrpc.AddressType) {
ctxb := context.Background() balanceResp := srcNode.RPC.WalletBalance()
require.Contains(ht, balanceResp.AccountBalance, account)
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)
confBalance := balanceResp.AccountBalance[account].ConfirmedBalance confBalance := balanceResp.AccountBalance[account].ConfirmedBalance
destAmt := confBalance - 10000 destAmt := confBalance - 10000
ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout) destAddrResp := destNode.RPC.NewAddress(&lnrpc.NewAddressRequest{
defer cancel()
destAddrResp, err := destNode.NewAddress(ctxt, &lnrpc.NewAddressRequest{
Type: lnrpc.AddressType_WITNESS_PUBKEY_HASH, Type: lnrpc.AddressType_WITNESS_PUBKEY_HASH,
}) })
require.NoError(t.t, err)
ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout)
defer cancel()
fundReq := &walletrpc.FundPsbtRequest{ fundReq := &walletrpc.FundPsbtRequest{
Template: &walletrpc.FundPsbtRequest_Raw{ Template: &walletrpc.FundPsbtRequest_Raw{
Raw: &walletrpc.TxTemplate{ Raw: &walletrpc.TxTemplate{
@ -245,29 +151,20 @@ func psbtSendFromImportedAccount(t *harnessTest, srcNode, destNode,
}, },
Account: account, Account: account,
} }
fundResp, err := srcNode.WalletKitClient.FundPsbt(ctxt, fundReq) fundResp := srcNode.RPC.FundPsbt(fundReq)
require.NoError(t.t, err)
// Have Carol sign the PSBT input since Dave doesn't have any private // Have Carol sign the PSBT input since Dave doesn't have any private
// key information. // key information.
ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout)
defer cancel()
finalizeReq := &walletrpc.FinalizePsbtRequest{ finalizeReq := &walletrpc.FinalizePsbtRequest{
FundedPsbt: fundResp.FundedPsbt, FundedPsbt: fundResp.FundedPsbt,
} }
finalizeResp, err := signer.WalletKitClient.FinalizePsbt( finalizeResp := signer.RPC.FinalizePsbt(finalizeReq)
ctxt, finalizeReq,
)
require.NoError(t.t, err)
// With the PSBT signed, we can broadcast the resulting transaction. // With the PSBT signed, we can broadcast the resulting transaction.
ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout)
defer cancel()
publishReq := &walletrpc.Transaction{ publishReq := &walletrpc.Transaction{
TxHex: finalizeResp.RawFinalTx, TxHex: finalizeResp.RawFinalTx,
} }
_, err = srcNode.WalletKitClient.PublishTransaction(ctxt, publishReq) srcNode.RPC.PublishTransaction(publishReq)
require.NoError(t.t, err)
// Carol's balance from Dave's perspective should update accordingly. // Carol's balance from Dave's perspective should update accordingly.
var ( var (
@ -304,7 +201,7 @@ func psbtSendFromImportedAccount(t *harnessTest, srcNode, destNode,
expChangeScriptType = txscript.WitnessV1TaprootTy expChangeScriptType = txscript.WitnessV1TaprootTy
default: default:
t.Fatalf("unsupported addr type %v", accountAddrType) ht.Fatalf("unsupported addr type %v", accountAddrType)
} }
changeUtxoAmt := confBalance - destAmt - expTxFee changeUtxoAmt := confBalance - destAmt - expTxFee
@ -314,16 +211,20 @@ func psbtSendFromImportedAccount(t *harnessTest, srcNode, destNode,
if account == defaultImportedAccount { if account == defaultImportedAccount {
accountWithBalance = defaultAccount accountWithBalance = defaultAccount
} }
assertAccountBalance(t.t, srcNode, accountWithBalance, 0, changeUtxoAmt) ht.AssertWalletAccountBalance(
_ = mineBlocks(t, t.lndHarness, 1, 1) srcNode, accountWithBalance, 0, changeUtxoAmt,
assertAccountBalance(t.t, srcNode, accountWithBalance, changeUtxoAmt, 0) )
ht.MineBlocksAndAssertNumTxes(1, 1)
ht.AssertWalletAccountBalance(
srcNode, accountWithBalance, changeUtxoAmt, 0,
)
// Finally, assert that the transaction has the expected change address // Finally, assert that the transaction has the expected change address
// type based on the account. // type based on the account.
var tx wire.MsgTx var tx wire.MsgTx
err = tx.Deserialize(bytes.NewReader(finalizeResp.RawFinalTx)) err := tx.Deserialize(bytes.NewReader(finalizeResp.RawFinalTx))
require.NoError(t.t, err) require.NoError(ht, err)
assertOutputScriptType(t.t, expChangeScriptType, &tx, changeUtxoAmt) assertOutputScriptType(ht.T, expChangeScriptType, &tx, changeUtxoAmt)
} }
// fundChanAndCloseFromImportedAccount attempts to a fund a channel from the // 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 // 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 // is made. Several balance assertions are made along the way for the sake of
// correctness. // correctness.
func fundChanAndCloseFromImportedAccount(t *harnessTest, srcNode, destNode, func fundChanAndCloseFromImportedAccount(ht *lntemp.HarnessTest, srcNode,
signer *lntest.HarnessNode, account string, destNode, signer *node.HarnessNode, account string,
accountAddrType walletrpc.AddressType, utxoAmt, chanSize int64) { accountAddrType walletrpc.AddressType, utxoAmt, chanSize int64) {
ctxb := context.Background()
// Retrieve the current confirmed balance to make some assertions later // Retrieve the current confirmed balance to make some assertions later
// on. // on.
ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) balanceResp := srcNode.RPC.WalletBalance()
defer cancel() require.Contains(ht, balanceResp.AccountBalance, account)
balanceResp, err := srcNode.WalletBalance(
ctxt, &lnrpc.WalletBalanceRequest{},
)
require.NoError(t.t, err)
require.Contains(t.t, balanceResp.AccountBalance, account)
accountConfBalance := balanceResp. accountConfBalance := balanceResp.
AccountBalance[account].ConfirmedBalance AccountBalance[account].ConfirmedBalance
defaultAccountConfBalance := balanceResp. defaultAccountConfBalance := balanceResp.
@ -353,31 +247,23 @@ func fundChanAndCloseFromImportedAccount(t *harnessTest, srcNode, destNode,
// Now, start the channel funding process. We'll need to connect both // Now, start the channel funding process. We'll need to connect both
// nodes first. // 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. // The source node will then fund the channel through a PSBT shim.
var pendingChanID [32]byte pendingChanID := ht.Random32Bytes()
_, err = rand.Read(pendingChanID[:]) chanUpdates, rawPsbt := ht.OpenChannelPsbt(
require.NoError(t.t, err) srcNode, destNode, lntemp.OpenChannelParams{
ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout)
defer cancel()
chanUpdates, rawPsbt, err := openChannelPsbt(
ctxt, srcNode, destNode, lntest.OpenChannelParams{
Amt: btcutil.Amount(chanSize), Amt: btcutil.Amount(chanSize),
FundingShim: &lnrpc.FundingShim{ FundingShim: &lnrpc.FundingShim{
Shim: &lnrpc.FundingShim_PsbtShim{ Shim: &lnrpc.FundingShim_PsbtShim{
PsbtShim: &lnrpc.PsbtShim{ PsbtShim: &lnrpc.PsbtShim{
PendingChanId: pendingChanID[:], PendingChanId: pendingChanID,
}, },
}, },
}, },
}, },
) )
require.NoError(t.t, err)
ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout)
defer cancel()
fundReq := &walletrpc.FundPsbtRequest{ fundReq := &walletrpc.FundPsbtRequest{
Template: &walletrpc.FundPsbtRequest_Psbt{ Template: &walletrpc.FundPsbtRequest_Psbt{
Psbt: rawPsbt, Psbt: rawPsbt,
@ -387,49 +273,40 @@ func fundChanAndCloseFromImportedAccount(t *harnessTest, srcNode, destNode,
}, },
Account: account, Account: account,
} }
fundResp, err := srcNode.WalletKitClient.FundPsbt(ctxt, fundReq) fundResp := srcNode.RPC.FundPsbt(fundReq)
require.NoError(t.t, err)
_, err = srcNode.FundingStateStep(ctxb, &lnrpc.FundingTransitionMsg{ srcNode.RPC.FundingStateStep(&lnrpc.FundingTransitionMsg{
Trigger: &lnrpc.FundingTransitionMsg_PsbtVerify{ Trigger: &lnrpc.FundingTransitionMsg_PsbtVerify{
PsbtVerify: &lnrpc.FundingPsbtVerify{ PsbtVerify: &lnrpc.FundingPsbtVerify{
PendingChanId: pendingChanID[:], PendingChanId: pendingChanID,
FundedPsbt: fundResp.FundedPsbt, FundedPsbt: fundResp.FundedPsbt,
}, },
}, },
}) })
require.NoError(t.t, err)
// Now that we have a PSBT to fund the channel, our signer needs to sign // Now that we have a PSBT to fund the channel, our signer needs to sign
// it. // it.
ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout)
defer cancel()
finalizeReq := &walletrpc.FinalizePsbtRequest{ finalizeReq := &walletrpc.FinalizePsbtRequest{
FundedPsbt: fundResp.FundedPsbt, FundedPsbt: fundResp.FundedPsbt,
} }
finalizeResp, err := signer.WalletKitClient.FinalizePsbt(ctxt, finalizeReq) finalizeResp := signer.RPC.FinalizePsbt(finalizeReq)
require.NoError(t.t, err)
// The source node can then submit the signed PSBT and complete the // The source node can then submit the signed PSBT and complete the
// channel funding process. // channel funding process.
_, err = srcNode.FundingStateStep(ctxb, &lnrpc.FundingTransitionMsg{ srcNode.RPC.FundingStateStep(&lnrpc.FundingTransitionMsg{
Trigger: &lnrpc.FundingTransitionMsg_PsbtFinalize{ Trigger: &lnrpc.FundingTransitionMsg_PsbtFinalize{
PsbtFinalize: &lnrpc.FundingPsbtFinalize{ PsbtFinalize: &lnrpc.FundingPsbtFinalize{
PendingChanId: pendingChanID[:], PendingChanId: pendingChanID,
SignedPsbt: finalizeResp.SignedPsbt, SignedPsbt: finalizeResp.SignedPsbt,
}, },
}, },
}) })
require.NoError(t.t, err)
// We should receive a notification of the channel funding transaction // We should receive a notification of the channel funding transaction
// being broadcast. // being broadcast.
ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout) updateResp := ht.ReceiveOpenChannelUpdate(chanUpdates)
defer cancel()
updateResp, err := receiveChanUpdate(ctxt, chanUpdates)
require.NoError(t.t, err)
upd, ok := updateResp.Update.(*lnrpc.OpenStatusUpdate_ChanPending) 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 // Mine enough blocks to announce the channel to the network, making
// balance assertions along the way. // balance assertions along the way.
@ -467,11 +344,11 @@ func fundChanAndCloseFromImportedAccount(t *harnessTest, srcNode, destNode,
expChangeScriptType = txscript.WitnessV1TaprootTy expChangeScriptType = txscript.WitnessV1TaprootTy
default: default:
t.Fatalf("unsupported addr type %v", accountAddrType) ht.Fatalf("unsupported addr type %v", accountAddrType)
} }
chanChangeUtxoAmt := utxoAmt - chanSize - expChanTxFee chanChangeUtxoAmt := utxoAmt - chanSize - expChanTxFee
txHash, err := chainhash.NewHash(upd.ChanPending.Txid) 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 // If we're spending from the default imported account, then any change
// outputs produced are moved to the default wallet account, so we // outputs produced are moved to the default wallet account, so we
@ -479,35 +356,35 @@ func fundChanAndCloseFromImportedAccount(t *harnessTest, srcNode, destNode,
var confBalanceAfterChan int64 var confBalanceAfterChan int64
if account == defaultImportedAccount { if account == defaultImportedAccount {
confBalanceAfterChan = defaultAccountConfBalance confBalanceAfterChan = defaultAccountConfBalance
assertAccountBalance(t.t, srcNode, account, 0, 0) ht.AssertWalletAccountBalance(srcNode, account, 0, 0)
assertAccountBalance( ht.AssertWalletAccountBalance(
t.t, srcNode, defaultAccount, defaultAccountConfBalance, srcNode, defaultAccount, defaultAccountConfBalance,
chanChangeUtxoAmt, chanChangeUtxoAmt,
) )
block := mineBlocks(t, t.lndHarness, 6, 1)[0] block := ht.MineBlocksAndAssertNumTxes(6, 1)[0]
assertTxInBlock(t, block, txHash) ht.Miner.AssertTxInBlock(block, txHash)
confBalanceAfterChan += chanChangeUtxoAmt confBalanceAfterChan += chanChangeUtxoAmt
assertAccountBalance(t.t, srcNode, account, 0, 0) ht.AssertWalletAccountBalance(srcNode, account, 0, 0)
assertAccountBalance( ht.AssertWalletAccountBalance(
t.t, srcNode, defaultAccount, confBalanceAfterChan, 0, srcNode, defaultAccount, confBalanceAfterChan, 0,
) )
} else { } else {
// Otherwise, all interactions remain within Carol's imported // Otherwise, all interactions remain within Carol's imported
// account. // account.
confBalanceAfterChan = accountConfBalance - utxoAmt confBalanceAfterChan = accountConfBalance - utxoAmt
assertAccountBalance( ht.AssertWalletAccountBalance(
t.t, srcNode, account, confBalanceAfterChan, srcNode, account, confBalanceAfterChan,
chanChangeUtxoAmt, chanChangeUtxoAmt,
) )
block := mineBlocks(t, t.lndHarness, 6, 1)[0] block := ht.MineBlocksAndAssertNumTxes(6, 1)[0]
assertTxInBlock(t, block, txHash) ht.Miner.AssertTxInBlock(block, txHash)
confBalanceAfterChan += chanChangeUtxoAmt confBalanceAfterChan += chanChangeUtxoAmt
assertAccountBalance( ht.AssertWalletAccountBalance(
t.t, srcNode, account, confBalanceAfterChan, 0, srcNode, account, confBalanceAfterChan, 0,
) )
} }
@ -515,8 +392,10 @@ func fundChanAndCloseFromImportedAccount(t *harnessTest, srcNode, destNode,
// based on the account. // based on the account.
var tx wire.MsgTx var tx wire.MsgTx
err = tx.Deserialize(bytes.NewReader(finalizeResp.RawFinalTx)) err = tx.Deserialize(bytes.NewReader(finalizeResp.RawFinalTx))
require.NoError(t.t, err) require.NoError(ht, err)
assertOutputScriptType(t.t, expChangeScriptType, &tx, chanChangeUtxoAmt) assertOutputScriptType(
ht.T, expChangeScriptType, &tx, chanChangeUtxoAmt,
)
// Wait for the channel to be announced by both parties. // Wait for the channel to be announced by both parties.
chanPoint := &lnrpc.ChannelPoint{ chanPoint := &lnrpc.ChannelPoint{
@ -525,29 +404,30 @@ func fundChanAndCloseFromImportedAccount(t *harnessTest, srcNode, destNode,
}, },
OutputIndex: upd.ChanPending.OutputIndex, OutputIndex: upd.ChanPending.OutputIndex,
} }
err = srcNode.WaitForNetworkChannelOpen(chanPoint) ht.AssertTopologyChannelOpen(srcNode, chanPoint)
require.NoError(t.t, err) ht.AssertTopologyChannelOpen(destNode, chanPoint)
err = destNode.WaitForNetworkChannelOpen(chanPoint)
require.NoError(t.t, err)
// Send a test payment to ensure the channel is operating as normal. // Send a test payment to ensure the channel is operating as normal.
const invoiceAmt = 100000 const invoiceAmt = 100000
ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout) invoice := &lnrpc.Invoice{
defer cancel()
resp, err := destNode.AddInvoice(ctxt, &lnrpc.Invoice{
Memo: "psbt import chan", Memo: "psbt import chan",
Value: invoiceAmt, Value: invoiceAmt,
}) }
require.NoError(t.t, err) resp := destNode.RPC.AddInvoice(invoice)
err = completePaymentRequests( ht.CompletePaymentRequests(srcNode, []string{resp.PaymentRequest})
srcNode, srcNode.RouterClient,
[]string{resp.PaymentRequest}, true, // TODO(yy): remove the sleep once the following bug is fixed. When the
) // payment is reported as settled by srcNode, it's expected the
require.NoError(t.t, err) // 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. // 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, // 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 // 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 balanceFromClosedChan := chanSize - invoiceAmt - chanCloseTxFee
if account == defaultImportedAccount { if account == defaultImportedAccount {
assertAccountBalance(t.t, srcNode, account, 0, 0) ht.AssertWalletAccountBalance(srcNode, account, 0, 0)
assertAccountBalance( ht.AssertWalletAccountBalance(
t.t, srcNode, defaultAccount, srcNode, defaultAccount,
confBalanceAfterChan+balanceFromClosedChan, 0, confBalanceAfterChan+balanceFromClosedChan, 0,
) )
} else { } else {
assertAccountBalance( ht.AssertWalletAccountBalance(
t.t, srcNode, account, confBalanceAfterChan, 0, srcNode, account, confBalanceAfterChan, 0,
) )
assertAccountBalance( ht.AssertWalletAccountBalance(
t.t, srcNode, defaultAccount, balanceFromClosedChan, 0, srcNode, defaultAccount, balanceFromClosedChan, 0,
) )
} }
} }
@ -575,7 +455,7 @@ func fundChanAndCloseFromImportedAccount(t *harnessTest, srcNode, destNode,
// testWalletImportAccount tests that an imported account can fund transactions // testWalletImportAccount tests that an imported account can fund transactions
// and channels through PSBTs, by having one node (the one with the imported // and channels through PSBTs, by having one node (the one with the imported
// account) craft the transactions and another node act as the signer. // 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 { testCases := []struct {
name string name string
addrType walletrpc.AddressType addrType walletrpc.AddressType
@ -600,23 +480,24 @@ func testWalletImportAccount(net *lntest.NetworkHarness, t *harnessTest) {
for _, tc := range testCases { for _, tc := range testCases {
tc := tc tc := tc
success := t.t.Run(tc.name, func(tt *testing.T) { success := ht.Run(tc.name, func(tt *testing.T) {
ht := newHarnessTest(tt, net) testFunc := func(ht *lntemp.HarnessTest) {
ht.RunTestCase(&testCase{ testWalletImportAccountScenario(
name: tc.name, ht, tc.addrType,
test: func(net1 *lntest.NetworkHarness, )
t1 *harnessTest) { }
testWalletImportAccountScenario( st := ht.Subtest(tt)
net, t, tc.addrType,
) st.RunTestCase(&lntemp.TestCase{
}, Name: tc.name,
TestFunc: testFunc,
}) })
}) })
if !success { if !success {
// Log failure time to help relate the lnd logs to the // Log failure time to help relate the lnd logs to the
// failure. // failure.
t.Logf("Failure time: %v", time.Now().Format( ht.Logf("Failure time: %v", time.Now().Format(
"2006-01-02 15:04:05.000", "2006-01-02 15:04:05.000",
)) ))
break break
@ -624,115 +505,98 @@ func testWalletImportAccount(net *lntest.NetworkHarness, t *harnessTest) {
} }
} }
func testWalletImportAccountScenario(net *lntest.NetworkHarness, t *harnessTest, func testWalletImportAccountScenario(ht *lntemp.HarnessTest,
addrType walletrpc.AddressType) { addrType walletrpc.AddressType) {
// We'll start our test by having two nodes, Carol and Dave. Carol's // We'll start our test by having two nodes, Carol and Dave. Carol's
// default wallet account will be imported into Dave's node. // 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) runWalletImportAccountScenario(ht, addrType, carol, dave)
defer shutdownAndAssert(net, t, dave)
runWalletImportAccountScenario(net, t, addrType, carol, dave)
} }
func runWalletImportAccountScenario(net *lntest.NetworkHarness, t *harnessTest, func runWalletImportAccountScenario(ht *lntemp.HarnessTest,
addrType walletrpc.AddressType, carol, dave *lntest.HarnessNode) { addrType walletrpc.AddressType, carol, dave *node.HarnessNode) {
ctxb := context.Background()
const utxoAmt int64 = btcutil.SatoshiPerBitcoin const utxoAmt int64 = btcutil.SatoshiPerBitcoin
ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout)
defer cancel()
listReq := &walletrpc.ListAccountsRequest{ listReq := &walletrpc.ListAccountsRequest{
Name: "default", Name: "default",
AddressType: addrType, AddressType: addrType,
} }
listResp, err := carol.WalletKitClient.ListAccounts(ctxt, listReq) listResp := carol.RPC.ListAccounts(listReq)
require.NoError(t.t, err) require.Len(ht, listResp.Accounts, 1)
require.Equal(t.t, len(listResp.Accounts), 1)
carolAccount := listResp.Accounts[0] carolAccount := listResp.Accounts[0]
ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout)
defer cancel()
const importedAccount = "carol" const importedAccount = "carol"
importReq := &walletrpc.ImportAccountRequest{ importReq := &walletrpc.ImportAccountRequest{
Name: importedAccount, Name: importedAccount,
ExtendedPublicKey: carolAccount.ExtendedPublicKey, ExtendedPublicKey: carolAccount.ExtendedPublicKey,
AddressType: addrType, AddressType: addrType,
} }
_, err = dave.WalletKitClient.ImportAccount(ctxt, importReq) dave.RPC.ImportAccount(importReq)
require.NoError(t.t, err)
// We'll generate an address for Carol from Dave's node to receive some // We'll generate an address for Carol from Dave's node to receive some
// funds. // funds.
externalAddr := newExternalAddr( 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 // Send coins to Carol's address and confirm them, making sure the
// balance updates accordingly. // balance updates accordingly.
ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout) alice := ht.Alice
defer cancel() req := &lnrpc.SendCoinsRequest{
_, err = net.Alice.SendCoins(ctxt, &lnrpc.SendCoinsRequest{
Addr: externalAddr, Addr: externalAddr,
Amount: utxoAmt, Amount: utxoAmt,
SatPerByte: 1, SatPerByte: 1,
}) }
require.NoError(t.t, err) alice.RPC.SendCoins(req)
assertAccountBalance(t.t, dave, importedAccount, 0, utxoAmt) ht.AssertWalletAccountBalance(dave, importedAccount, 0, utxoAmt)
_ = mineBlocks(t, net, 1, 1) ht.MineBlocksAndAssertNumTxes(1, 1)
assertAccountBalance(t.t, dave, importedAccount, utxoAmt, 0) ht.AssertWalletAccountBalance(dave, importedAccount, utxoAmt, 0)
// To ensure that Dave can use Carol's account as watch-only, we'll // 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 // construct a PSBT that sends funds to Alice, which we'll then hand
// over to Carol to sign. // over to Carol to sign.
psbtSendFromImportedAccount( 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 // We'll generate a new address for Carol from Dave's node to receive
// and fund a new channel. // and fund a new channel.
externalAddr = newExternalAddr( externalAddr = newExternalAddr(
t.t, dave, carol, importedAccount, addrType, ht, dave, carol, importedAccount, addrType,
) )
// Retrieve the current confirmed balance of the imported account for // Retrieve the current confirmed balance of the imported account for
// some assertions we'll make later on. // some assertions we'll make later on.
ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout) balanceResp := dave.RPC.WalletBalance()
defer cancel() require.Contains(ht, balanceResp.AccountBalance, importedAccount)
balanceResp, err := dave.WalletBalance(
ctxt, &lnrpc.WalletBalanceRequest{},
)
require.NoError(t.t, err)
require.Contains(t.t, balanceResp.AccountBalance, importedAccount)
confBalance := balanceResp.AccountBalance[importedAccount].ConfirmedBalance confBalance := balanceResp.AccountBalance[importedAccount].ConfirmedBalance
// Send coins to Carol's address and confirm them, making sure the // Send coins to Carol's address and confirm them, making sure the
// balance updates accordingly. // balance updates accordingly.
ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout) req = &lnrpc.SendCoinsRequest{
defer cancel()
_, err = net.Alice.SendCoins(ctxt, &lnrpc.SendCoinsRequest{
Addr: externalAddr, Addr: externalAddr,
Amount: utxoAmt, Amount: utxoAmt,
SatPerByte: 1, SatPerByte: 1,
}) }
require.NoError(t.t, err) alice.RPC.SendCoins(req)
assertAccountBalance(t.t, dave, importedAccount, confBalance, utxoAmt) ht.AssertWalletAccountBalance(dave, importedAccount, confBalance, utxoAmt)
_ = mineBlocks(t, net, 1, 1) ht.MineBlocksAndAssertNumTxes(1, 1)
assertAccountBalance( ht.AssertWalletAccountBalance(dave, importedAccount, confBalance+utxoAmt, 0)
t.t, dave, importedAccount, confBalance+utxoAmt, 0,
)
// Now that we have enough funds, it's time to fund the channel, make a // 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 // test payment, and close it. This contains several balance assertions
// along the way. // along the way.
fundChanAndCloseFromImportedAccount( fundChanAndCloseFromImportedAccount(
t, dave, net.Alice, carol, importedAccount, addrType, utxoAmt, ht, dave, alice, carol, importedAccount, addrType, utxoAmt,
int64(funding.MaxBtcFundingAmount), int64(funding.MaxBtcFundingAmount),
) )
} }
@ -740,7 +604,7 @@ func runWalletImportAccountScenario(net *lntest.NetworkHarness, t *harnessTest,
// testWalletImportPubKey tests that an imported public keys can fund // testWalletImportPubKey tests that an imported public keys can fund
// transactions and channels through PSBTs, by having one node (the one with the // 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. // 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 { testCases := []struct {
name string name string
addrType walletrpc.AddressType addrType walletrpc.AddressType
@ -761,23 +625,24 @@ func testWalletImportPubKey(net *lntest.NetworkHarness, t *harnessTest) {
for _, tc := range testCases { for _, tc := range testCases {
tc := tc tc := tc
success := t.t.Run(tc.name, func(tt *testing.T) { success := ht.Run(tc.name, func(tt *testing.T) {
ht := newHarnessTest(tt, net) testFunc := func(ht *lntemp.HarnessTest) {
ht.RunTestCase(&testCase{ testWalletImportPubKeyScenario(
name: tc.name, ht, tc.addrType,
test: func(net1 *lntest.NetworkHarness, )
t1 *harnessTest) { }
testWalletImportPubKeyScenario( st := ht.Subtest(tt)
net, t, tc.addrType,
) st.RunTestCase(&lntemp.TestCase{
}, Name: tc.name,
TestFunc: testFunc,
}) })
}) })
if !success { if !success {
// Log failure time to help relate the lnd logs to the // Log failure time to help relate the lnd logs to the
// failure. // failure.
t.Logf("Failure time: %v", time.Now().Format( ht.Logf("Failure time: %v", time.Now().Format(
"2006-01-02 15:04:05.000", "2006-01-02 15:04:05.000",
)) ))
break break
@ -785,18 +650,18 @@ func testWalletImportPubKey(net *lntest.NetworkHarness, t *harnessTest) {
} }
} }
func testWalletImportPubKeyScenario(net *lntest.NetworkHarness, t *harnessTest, func testWalletImportPubKeyScenario(ht *lntemp.HarnessTest,
addrType walletrpc.AddressType) { addrType walletrpc.AddressType) {
ctxb := context.Background()
const utxoAmt int64 = btcutil.SatoshiPerBitcoin const utxoAmt int64 = btcutil.SatoshiPerBitcoin
alice := ht.Alice
// We'll start our test by having two nodes, Carol and Dave. // We'll start our test by having two nodes, Carol and Dave.
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.
dave := net.NewNode(t.t, "dave", nil) carol := ht.NewNode("carol", nil)
defer shutdownAndAssert(net, t, dave) dave := ht.NewNode("dave", nil)
// We'll define a helper closure that we'll use throughout the test to // 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, // generate a new address of the given type from Carol's perspective,
@ -806,30 +671,25 @@ func testWalletImportPubKeyScenario(net *lntest.NetworkHarness, t *harnessTest,
// Retrieve Carol's account public key for the corresponding // Retrieve Carol's account public key for the corresponding
// address type. // address type.
ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout)
defer cancel()
listReq := &walletrpc.ListAccountsRequest{ listReq := &walletrpc.ListAccountsRequest{
Name: "default", Name: "default",
AddressType: addrType, AddressType: addrType,
} }
listResp, err := carol.WalletKitClient.ListAccounts( listResp := carol.RPC.ListAccounts(listReq)
ctxt, listReq, require.Len(ht, listResp.Accounts, 1)
)
require.NoError(t.t, err)
require.Equal(t.t, len(listResp.Accounts), 1)
p2wkhAccount := listResp.Accounts[0] p2wkhAccount := listResp.Accounts[0]
// Derive the external address at the given index. // Derive the external address at the given index.
accountPubKey, err := hdkeychain.NewKeyFromString( accountPubKey, err := hdkeychain.NewKeyFromString(
p2wkhAccount.ExtendedPublicKey, p2wkhAccount.ExtendedPublicKey,
) )
require.NoError(t.t, err) require.NoError(ht, err)
externalAccountExtKey, err := accountPubKey.Derive(0) externalAccountExtKey, err := accountPubKey.Derive(0)
require.NoError(t.t, err) require.NoError(ht, err)
externalAddrExtKey, err := externalAccountExtKey.Derive(keyIndex) externalAddrExtKey, err := externalAccountExtKey.Derive(keyIndex)
require.NoError(t.t, err) require.NoError(ht, err)
externalAddrPubKey, err := externalAddrExtKey.ECPubKey() externalAddrPubKey, err := externalAddrExtKey.ECPubKey()
require.NoError(t.t, err) require.NoError(ht, err)
// Serialize as 32-byte x-only pubkey for Taproot addresses. // Serialize as 32-byte x-only pubkey for Taproot addresses.
serializedPubKey := externalAddrPubKey.SerializeCompressed() serializedPubKey := externalAddrPubKey.SerializeCompressed()
@ -840,44 +700,34 @@ func testWalletImportPubKeyScenario(net *lntest.NetworkHarness, t *harnessTest,
} }
// Import the public key into Dave. // Import the public key into Dave.
ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout)
defer cancel()
importReq := &walletrpc.ImportPublicKeyRequest{ importReq := &walletrpc.ImportPublicKeyRequest{
PublicKey: serializedPubKey, PublicKey: serializedPubKey,
AddressType: addrType, AddressType: addrType,
} }
_, err = dave.WalletKitClient.ImportPublicKey(ctxt, importReq) dave.RPC.ImportPublicKey(importReq)
require.NoError(t.t, err)
// We'll also generate the same address for Carol, as it'll be // We'll also generate the same address for Carol, as it'll be
// required later when signing. // required later when signing.
ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout) carolAddrResp := carol.RPC.NewAddress(&lnrpc.NewAddressRequest{
defer cancel() Type: walletToLNAddrType(ht.T, addrType),
carolAddrResp, err := carol.NewAddress( })
ctxt, &lnrpc.NewAddressRequest{
Type: walletToLNAddrType(t.t, addrType),
},
)
require.NoError(t.t, err)
// Send coins to Carol's address and confirm them, making sure // Send coins to Carol's address and confirm them, making sure
// the balance updates accordingly. // the balance updates accordingly.
ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout) req := &lnrpc.SendCoinsRequest{
defer cancel()
_, err = net.Alice.SendCoins(ctxt, &lnrpc.SendCoinsRequest{
Addr: carolAddrResp.Address, Addr: carolAddrResp.Address,
Amount: utxoAmt, Amount: utxoAmt,
SatPerByte: 1, SatPerByte: 1,
}) }
require.NoError(t.t, err) alice.RPC.SendCoins(req)
assertAccountBalance( ht.AssertWalletAccountBalance(
t.t, dave, defaultImportedAccount, prevConfBalance, dave, defaultImportedAccount, prevConfBalance,
prevUnconfBalance+utxoAmt, prevUnconfBalance+utxoAmt,
) )
_ = mineBlocks(t, net, 1, 1) ht.MineBlocksAndAssertNumTxes(1, 1)
assertAccountBalance( ht.AssertWalletAccountBalance(
t.t, dave, defaultImportedAccount, dave, defaultImportedAccount,
prevConfBalance+utxoAmt, prevUnconfBalance, prevConfBalance+utxoAmt, prevUnconfBalance,
) )
} }
@ -890,22 +740,15 @@ func testWalletImportPubKeyScenario(net *lntest.NetworkHarness, t *harnessTest,
// construct a PSBT that sends funds to Alice, which we'll then hand // construct a PSBT that sends funds to Alice, which we'll then hand
// over to Carol to sign. // over to Carol to sign.
psbtSendFromImportedAccount( psbtSendFromImportedAccount(
t, dave, net.Alice, carol, defaultImportedAccount, addrType, ht, dave, alice, carol, defaultImportedAccount, addrType,
) )
// We'll now attempt to fund a channel. // We'll now attempt to fund a channel.
// //
// We'll have Carol generate another external address, which we'll // We'll have Carol generate another external address, which we'll
// import into Dave again. // import into Dave again.
ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) balanceResp := dave.RPC.WalletBalance()
defer cancel() require.Contains(ht, balanceResp.AccountBalance, defaultImportedAccount)
balanceResp, err := dave.WalletBalance(
ctxt, &lnrpc.WalletBalanceRequest{},
)
require.NoError(t.t, err)
require.Contains(
t.t, balanceResp.AccountBalance, defaultImportedAccount,
)
confBalance := balanceResp. confBalance := balanceResp.
AccountBalance[defaultImportedAccount].ConfirmedBalance AccountBalance[defaultImportedAccount].ConfirmedBalance
importPubKey(1, confBalance, 0) importPubKey(1, confBalance, 0)
@ -914,7 +757,7 @@ func testWalletImportPubKeyScenario(net *lntest.NetworkHarness, t *harnessTest,
// test payment, and close it. This contains several balance assertions // test payment, and close it. This contains several balance assertions
// along the way. // along the way.
fundChanAndCloseFromImportedAccount( fundChanAndCloseFromImportedAccount(
t, dave, net.Alice, carol, defaultImportedAccount, addrType, ht, dave, alice, carol, defaultImportedAccount, addrType,
utxoAmt, int64(funding.MaxBtcFundingAmount), utxoAmt, int64(funding.MaxBtcFundingAmount),
) )
} }