lnd/lntemp/harness_miner.go
yyforyongyu ab62109865
lntemp+lntest: always wait for nodes sync after mining blocks
In this commit, we introduce two mining methods, `MineBlocks` and
`MineBlocksAndAssertNumTxes`, to `HarnessTest`. These methods are
different from the miner's methods as they would 1) mine the blocks
slowly and 2) assert all active nodes have synced. The miner's methods
are kept for future tests such as mining blocks really fast and see the
behavior of `lnd`.
2022-10-27 01:11:34 +08:00

379 lines
10 KiB
Go

package lntemp
import (
"bytes"
"context"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"testing"
"time"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/integration/rpctest"
"github.com/btcsuite/btcd/rpcclient"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/lntest"
"github.com/lightningnetwork/lnd/lntest/wait"
"github.com/stretchr/testify/require"
)
const (
// minerLogFilename is the default log filename for the miner node.
minerLogFilename = "output_btcd_miner.log"
// minerLogDir is the default log dir for the miner node.
minerLogDir = ".minerlogs"
// slowMineDelay defines a wait period between mining new blocks.
slowMineDelay = 100 * time.Millisecond
)
var harnessNetParams = &chaincfg.RegressionNetParams
type HarnessMiner struct {
*testing.T
*rpctest.Harness
// runCtx is a context with cancel method. It's used to signal when the
// node needs to quit, and used as the parent context when spawning
runCtx context.Context
cancel context.CancelFunc
// logPath is the directory path of the miner's logs.
logPath string
// logFilename is the saved log filename of the miner node.
logFilename string
}
// NewMiner creates a new miner using btcd backend with the default log file
// dir and name.
func NewMiner(ctxt context.Context, t *testing.T) *HarnessMiner {
return newMiner(ctxt, t, minerLogDir, minerLogFilename)
}
// newMiner creates a new miner using btcd's rpctest.
func newMiner(ctxb context.Context, t *testing.T, minerDirName,
logFilename string) *HarnessMiner {
handler := &rpcclient.NotificationHandlers{}
btcdBinary := lntest.GetBtcdBinary()
baseLogPath := fmt.Sprintf("%s/%s", lntest.GetLogDir(), minerDirName)
args := []string{
"--rejectnonstd",
"--txindex",
"--nowinservice",
"--nobanning",
"--debuglevel=debug",
"--logdir=" + baseLogPath,
"--trickleinterval=100ms",
// Don't disconnect if a reply takes too long.
"--nostalldetect",
}
miner, err := rpctest.New(harnessNetParams, handler, args, btcdBinary)
require.NoError(t, err, "unable to create mining node")
ctxt, cancel := context.WithCancel(ctxb)
return &HarnessMiner{
T: t,
Harness: miner,
runCtx: ctxt,
cancel: cancel,
logPath: baseLogPath,
logFilename: logFilename,
}
}
// saveLogs copies the node logs and save it to the file specified by
// h.logFilename.
func (h *HarnessMiner) saveLogs() {
// After shutting down the miner, we'll make a copy of the log files
// before deleting the temporary log dir.
path := fmt.Sprintf("%s/%s", h.logPath, harnessNetParams.Name)
files, err := ioutil.ReadDir(path)
require.NoError(h, err, "unable to read log directory")
for _, file := range files {
newFilename := strings.Replace(
file.Name(), "btcd.log", h.logFilename, 1,
)
copyPath := fmt.Sprintf("%s/../%s", h.logPath, newFilename)
logFile := fmt.Sprintf("%s/%s", path, file.Name())
err := CopyFile(filepath.Clean(copyPath), logFile)
require.NoError(h, err, "unable to copy file")
}
err = os.RemoveAll(h.logPath)
require.NoErrorf(h, err, "cannot remove dir %s", h.logPath)
}
// Stop shuts down the miner and saves its logs.
func (h *HarnessMiner) Stop() {
h.cancel()
require.NoError(h, h.TearDown(), "tear down miner got error")
h.saveLogs()
}
// GetBestBlock makes a RPC request to miner and asserts.
func (h *HarnessMiner) GetBestBlock() (*chainhash.Hash, int32) {
blockHash, height, err := h.Client.GetBestBlock()
require.NoError(h, err, "failed to GetBestBlock")
return blockHash, height
}
// GetRawMempool makes a RPC call to the miner's GetRawMempool and
// asserts.
func (h *HarnessMiner) GetRawMempool() []*chainhash.Hash {
mempool, err := h.Client.GetRawMempool()
require.NoError(h, err, "unable to get mempool")
return mempool
}
// GenerateBlocks mine 'num' of blocks and returns them.
func (h *HarnessMiner) GenerateBlocks(num uint32) []*chainhash.Hash {
blockHashes, err := h.Client.Generate(num)
require.NoError(h, err, "unable to generate blocks")
require.Len(h, blockHashes, int(num), "wrong num of blocks generated")
return blockHashes
}
// GetBlock gets a block using its block hash.
func (h *HarnessMiner) GetBlock(blockHash *chainhash.Hash) *wire.MsgBlock {
block, err := h.Client.GetBlock(blockHash)
require.NoError(h, err, "unable to get block")
return block
}
// MineBlocks mine 'num' of blocks and check that blocks are present in
// node blockchain.
func (h *HarnessMiner) MineBlocks(num uint32) []*wire.MsgBlock {
blocks := make([]*wire.MsgBlock, num)
blockHashes := h.GenerateBlocks(num)
for i, blockHash := range blockHashes {
block := h.GetBlock(blockHash)
blocks[i] = block
}
return blocks
}
// AssertNumTxsInMempool polls until finding the desired number of transactions
// in the provided miner's mempool. It will asserrt if this number is not met
// after the given timeout.
func (h *HarnessMiner) AssertNumTxsInMempool(n int) []*chainhash.Hash {
var (
mem []*chainhash.Hash
err error
)
err = wait.NoError(func() error {
// We require the RPC call to be succeeded and won't wait for
// it as it's an unexpected behavior.
mem = h.GetRawMempool()
if len(mem) == n {
return nil
}
return fmt.Errorf("want %v, got %v in mempool: %v",
n, len(mem), mem)
}, lntest.MinerMempoolTimeout)
require.NoError(h, err, "assert tx in mempool timeout")
return mem
}
// AssertTxInBlock asserts that a given txid can be found in the passed block.
func (h *HarnessMiner) AssertTxInBlock(block *wire.MsgBlock,
txid *chainhash.Hash) {
blockTxes := make([]chainhash.Hash, 0)
for _, tx := range block.Transactions {
sha := tx.TxHash()
blockTxes = append(blockTxes, sha)
if bytes.Equal(txid[:], sha[:]) {
return
}
}
require.Failf(h, "tx was not included in block", "tx:%v, block has:%v",
txid, blockTxes)
}
// MineBlocksAndAssertNumTxes mine 'num' of blocks and check that blocks are
// present in node blockchain. numTxs should be set to the number of
// transactions (excluding the coinbase) we expect to be included in the first
// mined block.
func (h *HarnessMiner) MineBlocksAndAssertNumTxes(num uint32,
numTxs int) []*wire.MsgBlock {
// If we expect transactions to be included in the blocks we'll mine,
// we wait here until they are seen in the miner's mempool.
txids := h.AssertNumTxsInMempool(numTxs)
// Mine blocks.
blocks := h.MineBlocks(num)
// Finally, assert that all the transactions were included in the first
// block.
for _, txid := range txids {
h.AssertTxInBlock(blocks[0], txid)
}
return blocks
}
// GetRawTransaction makes a RPC call to the miner's GetRawTransaction and
// asserts.
func (h *HarnessMiner) GetRawTransaction(txid *chainhash.Hash) *btcutil.Tx {
tx, err := h.Client.GetRawTransaction(txid)
require.NoErrorf(h, err, "failed to get raw tx: %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
err := wait.NoError(func() error {
// We require the RPC call to be succeeded and won't wait for
// it as it's an unexpected behavior.
mempool := h.GetRawMempool()
if len(mempool) == 0 {
return fmt.Errorf("empty mempool")
}
for _, memTx := range mempool {
// Check the values are equal.
if *memTx == *txid {
return nil
}
}
return fmt.Errorf("txid %v not found in mempool: %v", txid,
mempool)
}, lntest.MinerMempoolTimeout)
require.NoError(h, err, "timeout checking mempool")
return msgTx
}
// SendOutputsWithoutChange uses the miner to send the given outputs using the
// specified fee rate and returns the txid.
func (h *HarnessMiner) SendOutputsWithoutChange(outputs []*wire.TxOut,
feeRate btcutil.Amount) *chainhash.Hash {
txid, err := h.Harness.SendOutputsWithoutChange(
outputs, feeRate,
)
require.NoErrorf(h, err, "failed to send output")
return txid
}
// CreateTransaction uses the miner to create a transaction using the given
// outputs using the specified fee rate and returns the transaction.
func (h *HarnessMiner) CreateTransaction(outputs []*wire.TxOut,
feeRate btcutil.Amount) *wire.MsgTx {
tx, err := h.Harness.CreateTransaction(outputs, feeRate, false)
require.NoErrorf(h, err, "failed to create transaction")
return tx
}
// SendOutput creates, signs, and finally broadcasts a transaction spending
// the harness' available mature coinbase outputs to create the new output.
func (h *HarnessMiner) SendOutput(newOutput *wire.TxOut,
feeRate btcutil.Amount) *chainhash.Hash {
hash, err := h.Harness.SendOutputs([]*wire.TxOut{newOutput}, feeRate)
require.NoErrorf(h, err, "failed to send outputs")
return hash
}
// MineBlocksSlow mines 'num' of blocks. Between each mined block an artificial
// delay is introduced to give all network participants time to catch up.
func (h *HarnessMiner) MineBlocksSlow(num uint32) []*wire.MsgBlock {
blocks := make([]*wire.MsgBlock, num)
blockHashes := make([]*chainhash.Hash, 0, num)
for i := uint32(0); i < num; i++ {
generatedHashes := h.GenerateBlocks(1)
blockHashes = append(blockHashes, generatedHashes...)
time.Sleep(slowMineDelay)
}
for i, blockHash := range blockHashes {
block, err := h.Client.GetBlock(blockHash)
require.NoError(h, err, "get blocks")
blocks[i] = block
}
return blocks
}
// AssertOutpointInMempool asserts a given outpoint can be found in the mempool.
func (h *HarnessMiner) AssertOutpointInMempool(op wire.OutPoint) *wire.MsgTx {
var msgTx *wire.MsgTx
err := wait.NoError(func() error {
// We require the RPC call to be succeeded and won't wait for
// it as it's an unexpected behavior.
mempool := h.GetRawMempool()
if len(mempool) == 0 {
return fmt.Errorf("empty mempool")
}
for _, txid := range mempool {
// We require the RPC call to be succeeded and won't
// wait for it as it's an unexpected behavior.
tx := h.GetRawTransaction(txid)
msgTx = tx.MsgTx()
for _, txIn := range msgTx.TxIn {
if txIn.PreviousOutPoint == op {
return nil
}
}
}
return fmt.Errorf("outpoint %v not found in mempool", op)
}, lntest.MinerMempoolTimeout)
require.NoError(h, err, "timeout checking mempool")
return msgTx
}
// GetNumTxsFromMempool polls until finding the desired number of transactions
// in the miner's mempool and returns the full transactions to the caller.
func (h *HarnessMiner) GetNumTxsFromMempool(n int) []*wire.MsgTx {
txids := h.AssertNumTxsInMempool(n)
var txes []*wire.MsgTx
for _, txid := range txids {
tx := h.GetRawTransaction(txid)
txes = append(txes, tx.MsgTx())
}
return txes
}