lnd.xprv/lntest/itest/lnd_onchain_test.go
yyforyongyu 9d0d88ac21
lnrpc+lncli: deprecate sat_per_byte and add sat_per_vbyte
This commit deprecates/replaces the old field `sat_per_byte` with
`sat_per_vbyte`. While the old field suggests sat per byte, it’s
actually using sat per virtual byte. We use the Hidden param to hide all
the deprecated flags. These flags won't show up in help menu onwards,
while stay valid that can be passed from cli. Thus bash scripts
referencing these fields won't be broken.
2021-03-26 17:16:40 +08:00

363 lines
11 KiB
Go

package itest
import (
"bytes"
"context"
"fmt"
"strings"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcutil"
"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/lnwallet"
"github.com/lightningnetwork/lnd/sweep"
"github.com/stretchr/testify/require"
)
// testCPFP ensures that the daemon can bump an unconfirmed transaction's fee
// 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) {
// Skip this test for neutrino, as it's not aware of mempool
// transactions.
if net.BackendCfg.Name() == "neutrino" {
t.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()
ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
err := net.SendCoins(ctxt, btcutil.SatoshiPerBitcoin, net.Alice)
if err != nil {
t.Fatalf("unable to send coins to alice: %v", err)
}
// Create an address for Bob to send the coins to.
addrReq := &lnrpc.NewAddressRequest{
Type: lnrpc.AddressType_WITNESS_PUBKEY_HASH,
}
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
resp, err := net.Bob.NewAddress(ctxt, addrReq)
if err != nil {
t.Fatalf("unable to get new address for bob: %v", err)
}
// Send the coins from Alice to Bob. We should expect a transaction to
// be broadcast and seen in the mempool.
sendReq := &lnrpc.SendCoinsRequest{
Addr: resp.Address,
Amount: btcutil.SatoshiPerBitcoin,
}
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
if _, err = net.Alice.SendCoins(ctxt, sendReq); err != nil {
t.Fatalf("unable to send coins to bob: %v", err)
}
txid, err := waitForTxInMempool(net.Miner.Node, minerMempoolTimeout)
if err != nil {
t.Fatalf("expected one mempool transaction: %v", err)
}
// We'll then extract the raw transaction from the mempool in order to
// determine the index of Bob's output.
tx, err := net.Miner.Node.GetRawTransaction(txid)
if err != nil {
t.Fatalf("unable to extract raw transaction from mempool: %v",
err)
}
bobOutputIdx := -1
for i, txOut := range tx.MsgTx().TxOut {
_, addrs, _, err := txscript.ExtractPkScriptAddrs(
txOut.PkScript, net.Miner.ActiveNet,
)
if err != nil {
t.Fatalf("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")
}
// Wait until bob has seen the tx and considers it as owned.
op := &lnrpc.OutPoint{
TxidBytes: txid[:],
OutputIndex: uint32(bobOutputIdx),
}
assertWalletUnspent(t, net.Bob, op)
// We'll attempt to bump the fee of this transaction by performing a
// CPFP from Alice's point of view.
bumpFeeReq := &walletrpc.BumpFeeRequest{
Outpoint: op,
SatPerVbyte: uint64(
sweep.DefaultMaxFeeRate.FeePerKVByte() / 2000,
),
}
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
_, err = net.Bob.WalletKitClient.BumpFee(ctxt, bumpFeeReq)
if err != nil {
t.Fatalf("unable to bump fee: %v", err)
}
// We should now expect to see two transactions within the mempool, a
// parent and its child.
_, err = waitForNTxsInMempool(net.Miner.Node, 2, minerMempoolTimeout)
if err != nil {
t.Fatalf("expected two mempool transactions: %v", err)
}
// 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 := net.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))
}
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)
}
// Mine a block to clean up the unconfirmed transactions.
mineBlocks(t, net, 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 := net.Bob.WalletKitClient.PendingSweeps(ctxt, req)
if err != nil {
return fmt.Errorf("unable to retrieve bob's pending "+
"sweeps: %v", err)
}
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())
}
}
// testAnchorReservedValue tests that we won't allow sending transactions when
// that would take the value we reserve for anchor fee bumping out of our
// wallet.
func testAnchorReservedValue(net *lntest.NetworkHarness, t *harnessTest) {
// Start two nodes supporting anchor channels.
args := commitTypeAnchors.Args()
alice, err := net.NewNode("Alice", args)
require.NoError(t.t, err)
defer shutdownAndAssert(net, t, alice)
bob, err := net.NewNode("Bob", args)
require.NoError(t.t, err)
defer shutdownAndAssert(net, t, bob)
ctxb := context.Background()
ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
err = net.ConnectNodes(ctxt, alice, bob)
require.NoError(t.t, err)
// Send just enough coins for Alice to open a channel without a change output.
const (
chanAmt = 1000000
feeEst = 8000
)
ctxt, _ = context.WithTimeout(context.Background(), defaultTimeout)
err = net.SendCoins(ctxt, chanAmt+feeEst, alice)
require.NoError(t.t, err)
// Alice opens a channel that would consume all the funds in her
// wallet, without a change output. This should not be allowed.
resErr := lnwallet.ErrReservedValueInvalidated.Error()
ctxt, _ = context.WithTimeout(context.Background(), defaultTimeout)
_, err = net.OpenChannel(
ctxt, alice, bob,
lntest.OpenChannelParams{
Amt: chanAmt,
},
)
if err == nil || !strings.Contains(err.Error(), resErr) {
t.Fatalf("expected failure, got: %v", err)
}
// Alice opens a smaller channel. This works since it will have a
// change output.
ctxt, _ = context.WithTimeout(context.Background(), defaultTimeout)
aliceChanPoint := openChannelAndAssert(
ctxt, t, net, alice, bob,
lntest.OpenChannelParams{
Amt: chanAmt / 2,
},
)
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
err = alice.WaitForNetworkChannelOpen(ctxt, aliceChanPoint)
require.NoError(t.t, err)
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
err = bob.WaitForNetworkChannelOpen(ctxt, aliceChanPoint)
require.NoError(t.t, err)
// Alice tries to send all coins to an internal address. This is
// allowed, since the final wallet balance will still be above the
// reserved value.
addrReq := &lnrpc.NewAddressRequest{
Type: lnrpc.AddressType_WITNESS_PUBKEY_HASH,
}
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
resp, err := alice.NewAddress(ctxt, addrReq)
require.NoError(t.t, err)
sweepReq := &lnrpc.SendCoinsRequest{
Addr: resp.Address,
SendAll: true,
}
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
_, err = alice.SendCoins(ctxt, sweepReq)
require.NoError(t.t, err)
block := mineBlocks(t, net, 1, 1)[0]
// The sweep transaction should have exactly one input, the change from
// the previous SendCoins call.
sweepTx := block.Transactions[1]
if len(sweepTx.TxIn) != 1 {
t.Fatalf("expected 1 inputs instead have %v", len(sweepTx.TxIn))
}
// It should have a single output.
if len(sweepTx.TxOut) != 1 {
t.Fatalf("expected 1 output instead have %v", len(sweepTx.TxOut))
}
// Wait for Alice to see her balance as confirmed.
waitForConfirmedBalance := func() int64 {
var balance int64
err := wait.NoError(func() error {
req := &lnrpc.WalletBalanceRequest{}
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
resp, err := alice.WalletBalance(ctxt, req)
if err != nil {
return err
}
if resp.TotalBalance == 0 {
return fmt.Errorf("no balance")
}
if resp.UnconfirmedBalance > 0 {
return fmt.Errorf("unconfirmed balance")
}
balance = resp.TotalBalance
return nil
}, defaultTimeout)
require.NoError(t.t, err)
return balance
}
_ = waitForConfirmedBalance()
// Alice tries to send all funds to an external address, the reserved
// value must stay in her wallet.
minerAddr, err := net.Miner.NewAddress()
require.NoError(t.t, err)
sweepReq = &lnrpc.SendCoinsRequest{
Addr: minerAddr.String(),
SendAll: true,
}
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
_, err = alice.SendCoins(ctxt, sweepReq)
require.NoError(t.t, err)
// We'll mine a block which should include the sweep transaction we
// generated above.
block = mineBlocks(t, net, 1, 1)[0]
// The sweep transaction should have exactly one inputs as we only had
// the the single output from above in the wallet.
sweepTx = block.Transactions[1]
if len(sweepTx.TxIn) != 1 {
t.Fatalf("expected 1 inputs instead have %v", len(sweepTx.TxIn))
}
// It should have two outputs, one being the miner address, the other
// one being the reserve going back to our wallet.
if len(sweepTx.TxOut) != 2 {
t.Fatalf("expected 2 outputs instead have %v", len(sweepTx.TxOut))
}
// The reserved value is now back in Alice's wallet.
aliceBalance := waitForConfirmedBalance()
// Alice closes channel, should now be allowed to send everything to an
// external address.
closeChannelAndAssert(ctxt, t, net, alice, aliceChanPoint, false)
newBalance := waitForConfirmedBalance()
if newBalance <= aliceBalance {
t.Fatalf("Alice's balance did not increase after channel close")
}
// We'll wait for the balance to reflect that the channel has been
// closed and the funds are in the wallet.
sweepReq = &lnrpc.SendCoinsRequest{
Addr: minerAddr.String(),
SendAll: true,
}
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
_, err = alice.SendCoins(ctxt, sweepReq)
require.NoError(t.t, err)
// We'll mine a block which should include the sweep transaction we
// generated above.
block = mineBlocks(t, net, 1, 1)[0]
// The sweep transaction should have two inputs, the change output from
// the previous sweep, and the output from the coop closed channel.
sweepTx = block.Transactions[1]
if len(sweepTx.TxIn) != 2 {
t.Fatalf("expected 2 inputs instead have %v", len(sweepTx.TxIn))
}
// It should have a single output.
if len(sweepTx.TxOut) != 1 {
t.Fatalf("expected 1 output instead have %v", len(sweepTx.TxOut))
}
}