lntest: add CPFP integration test

This commit is contained in:
Wilmer Paulino 2019-06-03 16:12:20 -07:00
parent d41af9a65f
commit 638355b603
No known key found for this signature in database
GPG Key ID: 6DF57B9F9514972F
3 changed files with 174 additions and 8 deletions

@ -13733,6 +13733,10 @@ var testsCases = []*testCase{
name: "hold invoice sender persistence",
test: testHoldInvoicePersistence,
},
{
name: "cpfp",
test: testCPFP,
},
}
// TestLightningNetworkDaemon performs a series of integration tests amongst a

160
lntest/itest/onchain.go Normal file

@ -0,0 +1,160 @@
// +build rpctest
package itest
import (
"bytes"
"context"
"fmt"
"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/sweep"
)
// 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 reorg 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")
}
// We'll attempt to bump the fee of this transaction by performing a
// CPFP from Alice's point of view.
op := &lnrpc.OutPoint{
TxidBytes: txid[:],
OutputIndex: uint32(bobOutputIdx),
}
bumpFeeReq := &walletrpc.BumpFeeRequest{
Outpoint: op,
SatPerByte: uint32(sweep.DefaultMaxFeeRate.FeePerKVByte() / 1000),
}
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.SatPerByte != bumpFeeReq.SatPerByte {
t.Fatalf("expected sweep sat per byte %v, got %v",
bumpFeeReq.SatPerByte, pendingSweep.SatPerByte)
}
// 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 = lntest.WaitNoError(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())
}
}

@ -15,11 +15,6 @@ import (
"sync"
"time"
"golang.org/x/net/context"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
macaroon "gopkg.in/macaroon.v2"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
@ -29,7 +24,12 @@ import (
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/invoicesrpc"
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
"github.com/lightningnetwork/lnd/lnrpc/walletrpc"
"github.com/lightningnetwork/lnd/macaroons"
"golang.org/x/net/context"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
macaroon "gopkg.in/macaroon.v2"
)
var (
@ -250,9 +250,10 @@ type HarnessNode struct {
invoicesrpc.InvoicesClient
// RouterClient cannot be embedded, because a name collision would occur
// on the main rpc SendPayment.
// RouterClient and WalletKitClient cannot be embedded, because a name
// collision would occur with LightningClient.
RouterClient routerrpc.RouterClient
WalletKitClient walletrpc.WalletKitClient
}
// Assert *HarnessNode implements the lnrpc.LightningClient interface.
@ -503,6 +504,7 @@ func (hn *HarnessNode) initLightningClient(conn *grpc.ClientConn) error {
hn.LightningClient = lnrpc.NewLightningClient(conn)
hn.InvoicesClient = invoicesrpc.NewInvoicesClient(conn)
hn.RouterClient = routerrpc.NewRouterClient(conn)
hn.WalletKitClient = walletrpc.NewWalletKitClient(conn)
// Set the harness node's pubkey to what the node claims in GetInfo.
err := hn.FetchNodeInfo()