mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-03-03 17:26:57 +01:00
Merge pull request #9549 from yyforyongyu/fix-bitcond-test
Fix unit test flake `TestHistoricalConfDetailsTxIndex`
This commit is contained in:
commit
e3d9fcb5ac
8 changed files with 334 additions and 88 deletions
16
Makefile
16
Makefile
|
@ -276,18 +276,18 @@ unit-bench: $(BTCD_BIN)
|
|||
# FLAKE HUNTING
|
||||
# =============
|
||||
|
||||
#? flakehunter: Run the integration tests continuously until one fails
|
||||
flakehunter: build-itest
|
||||
#? flakehunter-itest: Run the integration tests continuously until one fails
|
||||
flakehunter-itest: build-itest
|
||||
@$(call print, "Flake hunting ${backend} integration tests.")
|
||||
while [ $$? -eq 0 ]; do make itest-only icase='${icase}' backend='${backend}'; done
|
||||
|
||||
#? flake-unit: Run the unit tests continuously until one fails
|
||||
flake-unit:
|
||||
@$(call print, "Flake hunting unit tests.")
|
||||
while [ $$? -eq 0 ]; do GOTRACEBACK=all $(UNIT) -count=1; done
|
||||
#? flakehunter-unit: Run the unit tests continuously until one fails
|
||||
flakehunter-unit:
|
||||
@$(call print, "Flake hunting unit test.")
|
||||
scripts/unit-test-flake-hunter.sh ${pkg} ${case}
|
||||
|
||||
#? flakehunter-parallel: Run the integration tests continuously until one fails, running up to ITEST_PARALLELISM test tranches in parallel (default 4)
|
||||
flakehunter-parallel:
|
||||
#? flakehunter-itest-parallel: Run the integration tests continuously until one fails, running up to ITEST_PARALLELISM test tranches in parallel (default 4)
|
||||
flakehunter-itest-parallel:
|
||||
@$(call print, "Flake hunting ${backend} integration tests in parallel.")
|
||||
while [ $$? -eq 0 ]; do make itest-parallel tranches=1 parallel=${ITEST_PARALLELISM} icase='${icase}' backend='${backend}'; done
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/integration/rpctest"
|
||||
"github.com/btcsuite/btcd/rpcclient"
|
||||
"github.com/btcsuite/btcwallet/chain"
|
||||
"github.com/lightningnetwork/lnd/blockcache"
|
||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||
|
@ -103,6 +104,54 @@ func syncNotifierWithMiner(t *testing.T, notifier *BitcoindNotifier,
|
|||
"err=%v, minerHeight=%v, bitcoindHeight=%v",
|
||||
err, minerHeight, bitcoindHeight)
|
||||
}
|
||||
|
||||
// Get the num of connections the miner has. We expect it to
|
||||
// have at least one connection with the chain backend.
|
||||
count, err := miner.Client.GetConnectionCount()
|
||||
require.NoError(t, err)
|
||||
if count != 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// Reconnect the miner and the chain backend.
|
||||
//
|
||||
// NOTE: The connection should have been made before we perform
|
||||
// the `syncNotifierWithMiner`. However, due to an unknown
|
||||
// reason, the miner may refuse to process the inbound
|
||||
// connection made by the bitcoind node, causing the connection
|
||||
// to fail. It's possible there's a bug in the handshake between
|
||||
// the two nodes.
|
||||
//
|
||||
// A normal flow is, bitcoind starts a v2 handshake flow, which
|
||||
// btcd will fail and disconnect. Upon seeing this
|
||||
// disconnection, bitcoind will try a v1 handshake and succeeds.
|
||||
// The failed flow is, upon seeing the v2 handshake, btcd
|
||||
// doesn't seem to perform the disconnect. Instead an EOF
|
||||
// websocket error is found.
|
||||
//
|
||||
// TODO(yy): Fix the above bug in `btcd`. This can be reproduced
|
||||
// using `make flakehunter-unit pkg=$pkg case=$case`, with,
|
||||
// `case=TestHistoricalConfDetailsNoTxIndex/rpc_polling_enabled`
|
||||
// `pkg=chainntnfs/bitcoindnotify`.
|
||||
// Also need to modify the temp dir logic so we can save the
|
||||
// debug logs.
|
||||
// This bug is likely to be fixed when we implement the
|
||||
// encrypted p2p conn, or when we properly fix the shutdown
|
||||
// issues in all our RPC conns.
|
||||
t.Log("Expected to the chain backend to have one conn with " +
|
||||
"the miner, instead it's disconnected!")
|
||||
|
||||
// We now ask the miner to add the chain backend back.
|
||||
host := fmt.Sprintf(
|
||||
"127.0.0.1:%s", notifier.chainParams.DefaultPort,
|
||||
)
|
||||
|
||||
// NOTE:AddNode must take a host that has the format
|
||||
// `host:port`, otherwise the default port will be used. Check
|
||||
// `normalizeAddress` in btcd for details.
|
||||
err = miner.Client.AddNode(host, rpcclient.ANAdd)
|
||||
require.NoError(t, err, "Failed to connect miner to the chain "+
|
||||
"backend")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -130,7 +179,7 @@ func testHistoricalConfDetailsTxIndex(t *testing.T, rpcPolling bool) {
|
|||
)
|
||||
|
||||
bitcoindConn := unittest.NewBitcoindBackend(
|
||||
t, unittest.NetParams, miner.P2PAddress(), true, rpcPolling,
|
||||
t, unittest.NetParams, miner, true, rpcPolling,
|
||||
)
|
||||
|
||||
hintCache := initHintCache(t)
|
||||
|
@ -140,8 +189,6 @@ func testHistoricalConfDetailsTxIndex(t *testing.T, rpcPolling bool) {
|
|||
t, bitcoindConn, hintCache, hintCache, blockCache,
|
||||
)
|
||||
|
||||
syncNotifierWithMiner(t, notifier, miner)
|
||||
|
||||
// A transaction unknown to the node should not be found within the
|
||||
// txindex even if it is enabled, so we should not proceed with any
|
||||
// fallback methods.
|
||||
|
@ -230,13 +277,15 @@ func testHistoricalConfDetailsNoTxIndex(t *testing.T, rpcpolling bool) {
|
|||
miner := unittest.NewMiner(t, unittest.NetParams, nil, true, 25)
|
||||
|
||||
bitcoindConn := unittest.NewBitcoindBackend(
|
||||
t, unittest.NetParams, miner.P2PAddress(), false, rpcpolling,
|
||||
t, unittest.NetParams, miner, false, rpcpolling,
|
||||
)
|
||||
|
||||
hintCache := initHintCache(t)
|
||||
blockCache := blockcache.NewBlockCache(10000)
|
||||
|
||||
notifier := setUpNotifier(t, bitcoindConn, hintCache, hintCache, blockCache)
|
||||
notifier := setUpNotifier(
|
||||
t, bitcoindConn, hintCache, hintCache, blockCache,
|
||||
)
|
||||
|
||||
// Since the node has its txindex disabled, we fall back to scanning the
|
||||
// chain manually. A transaction unknown to the network should not be
|
||||
|
@ -245,7 +294,11 @@ func testHistoricalConfDetailsNoTxIndex(t *testing.T, rpcpolling bool) {
|
|||
copy(unknownHash[:], bytes.Repeat([]byte{0x10}, 32))
|
||||
unknownConfReq, err := chainntnfs.NewConfRequest(&unknownHash, testScript)
|
||||
require.NoError(t, err, "unable to create conf request")
|
||||
broadcastHeight := syncNotifierWithMiner(t, notifier, miner)
|
||||
|
||||
// Get the current best height.
|
||||
_, broadcastHeight, err := miner.Client.GetBestBlock()
|
||||
require.NoError(t, err, "unable to retrieve miner's current height")
|
||||
|
||||
_, txStatus, err := notifier.historicalConfDetails(
|
||||
unknownConfReq, uint32(broadcastHeight), uint32(broadcastHeight),
|
||||
)
|
||||
|
|
|
@ -1932,7 +1932,7 @@ func TestInterfaces(t *testing.T, targetBackEnd string) {
|
|||
case "bitcoind":
|
||||
var bitcoindConn *chain.BitcoindConn
|
||||
bitcoindConn = unittest.NewBitcoindBackend(
|
||||
t, unittest.NetParams, p2pAddr, true, false,
|
||||
t, unittest.NetParams, miner, true, false,
|
||||
)
|
||||
newNotifier = func() (chainntnfs.TestChainNotifier, error) {
|
||||
return bitcoindnotify.New(
|
||||
|
@ -1944,7 +1944,7 @@ func TestInterfaces(t *testing.T, targetBackEnd string) {
|
|||
case "bitcoind-rpc-polling":
|
||||
var bitcoindConn *chain.BitcoindConn
|
||||
bitcoindConn = unittest.NewBitcoindBackend(
|
||||
t, unittest.NetParams, p2pAddr, true, true,
|
||||
t, unittest.NetParams, miner, true, true,
|
||||
)
|
||||
newNotifier = func() (chainntnfs.TestChainNotifier, error) {
|
||||
return bitcoindnotify.New(
|
||||
|
|
|
@ -317,6 +317,9 @@ The underlying functionality between those two options remain the same.
|
|||
now documented and
|
||||
[fixed](https://github.com/lightningnetwork/lnd/pull/9368).
|
||||
|
||||
* [Fixed](https://github.com/lightningnetwork/lnd/pull/9549) a long standing
|
||||
unit test flake found in the `chainntnfs/bitcoindnotify` package.
|
||||
|
||||
## Database
|
||||
|
||||
* [Migrate the mission control
|
||||
|
|
|
@ -4,11 +4,13 @@ import (
|
|||
"fmt"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/btcsuite/btcd/integration/rpctest"
|
||||
"github.com/btcsuite/btcd/rpcclient"
|
||||
"github.com/btcsuite/btcwallet/chain"
|
||||
"github.com/btcsuite/btcwallet/walletdb"
|
||||
"github.com/lightninglabs/neutrino"
|
||||
|
@ -37,9 +39,15 @@ func NewMiner(t *testing.T, netParams *chaincfg.Params, extraArgs []string,
|
|||
|
||||
t.Helper()
|
||||
|
||||
// Add the trickle interval argument to the extra args.
|
||||
trickle := fmt.Sprintf("--trickleinterval=%v", TrickleInterval)
|
||||
extraArgs = append(extraArgs, trickle)
|
||||
args := []string{
|
||||
"--nobanning",
|
||||
"--debuglevel=debug",
|
||||
fmt.Sprintf("--trickleinterval=%v", TrickleInterval),
|
||||
|
||||
// Don't disconnect if a reply takes too long.
|
||||
"--nostalldetect",
|
||||
}
|
||||
extraArgs = append(extraArgs, args...)
|
||||
|
||||
node, err := rpctest.New(netParams, nil, extraArgs, "")
|
||||
require.NoError(t, err, "unable to create backend node")
|
||||
|
@ -73,9 +81,10 @@ func NewMiner(t *testing.T, netParams *chaincfg.Params, extraArgs []string,
|
|||
// backend node should maintain a transaction index. The rpcpolling boolean
|
||||
// can be set to determine whether bitcoind's RPC polling interface should be
|
||||
// used for block and tx notifications or if its ZMQ interface should be used.
|
||||
// A connection to the newly spawned bitcoind node is returned.
|
||||
// A connection to the newly spawned bitcoind node is returned once the bitcoind
|
||||
// is synced to the miner's best height.
|
||||
func NewBitcoindBackend(t *testing.T, netParams *chaincfg.Params,
|
||||
minerAddr string, txindex, rpcpolling bool) *chain.BitcoindConn {
|
||||
miner *rpctest.Harness, txindex, rpcpolling bool) *chain.BitcoindConn {
|
||||
|
||||
t.Helper()
|
||||
|
||||
|
@ -88,29 +97,51 @@ func NewBitcoindBackend(t *testing.T, netParams *chaincfg.Params,
|
|||
zmqBlockHost := fmt.Sprintf("tcp://127.0.0.1:%d", zmqBlockPort)
|
||||
zmqTxHost := fmt.Sprintf("tcp://127.0.0.1:%d", zmqTxPort)
|
||||
|
||||
// TODO(yy): Make this configurable via `chain.BitcoindConfig` and
|
||||
// replace the default P2P port when set.
|
||||
p2pPort := port.NextAvailablePort()
|
||||
netParams.DefaultPort = fmt.Sprintf("%d", p2pPort)
|
||||
|
||||
args := []string{
|
||||
"-connect=" + minerAddr,
|
||||
"-datadir=" + tempBitcoindDir,
|
||||
"-regtest",
|
||||
"-rpcauth=weks:469e9bb14ab2360f8e226efed5ca6fd$507c670e800a95" +
|
||||
"284294edb5773b05544b220110063096c221be9933c82d38e1",
|
||||
fmt.Sprintf("-rpcport=%d", rpcPort),
|
||||
fmt.Sprintf("-bind=127.0.0.1:%d=onion", torBindPort),
|
||||
fmt.Sprintf("-port=%d", p2pPort),
|
||||
"-disablewallet",
|
||||
"-zmqpubrawblock=" + zmqBlockHost,
|
||||
"-zmqpubrawtx=" + zmqTxHost,
|
||||
|
||||
// whitelist localhost to speed up relay.
|
||||
"-whitelist=127.0.0.1",
|
||||
|
||||
// Disable v2 transport as btcd doesn't support it yet.
|
||||
//
|
||||
// TODO(yy): Remove this line once v2 conn is supported in
|
||||
// `btcd`.
|
||||
"-v2transport=0",
|
||||
}
|
||||
if txindex {
|
||||
args = append(args, "-txindex")
|
||||
}
|
||||
|
||||
bitcoind := exec.Command("bitcoind", args...)
|
||||
if err := bitcoind.Start(); err != nil {
|
||||
t.Fatalf("unable to start bitcoind: %v", err)
|
||||
}
|
||||
err := bitcoind.Start()
|
||||
require.NoError(t, err, "unable to start bitcoind")
|
||||
|
||||
t.Cleanup(func() {
|
||||
_ = bitcoind.Process.Kill()
|
||||
_ = bitcoind.Wait()
|
||||
// Kill `bitcoind` and assert there's no error.
|
||||
err = bitcoind.Process.Kill()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = bitcoind.Wait()
|
||||
if strings.Contains(err.Error(), "signal: killed") {
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
// Wait for the bitcoind instance to start up.
|
||||
|
@ -142,7 +173,7 @@ func NewBitcoindBackend(t *testing.T, netParams *chaincfg.Params,
|
|||
}
|
||||
|
||||
var conn *chain.BitcoindConn
|
||||
err := wait.NoError(func() error {
|
||||
err = wait.NoError(func() error {
|
||||
var err error
|
||||
conn, err = chain.NewBitcoindConn(cfg)
|
||||
if err != nil {
|
||||
|
@ -157,9 +188,131 @@ func NewBitcoindBackend(t *testing.T, netParams *chaincfg.Params,
|
|||
}
|
||||
t.Cleanup(conn.Stop)
|
||||
|
||||
// Assert that the connection with the miner is made.
|
||||
//
|
||||
// Create a new RPC client.
|
||||
rpcCfg := rpcclient.ConnConfig{
|
||||
Host: cfg.Host,
|
||||
User: cfg.User,
|
||||
Pass: cfg.Pass,
|
||||
DisableConnectOnNew: true,
|
||||
DisableAutoReconnect: false,
|
||||
DisableTLS: true,
|
||||
HTTPPostMode: true,
|
||||
}
|
||||
|
||||
rpcClient, err := rpcclient.New(&rpcCfg, nil)
|
||||
require.NoError(t, err, "failed to create RPC client")
|
||||
|
||||
// Connect to the miner node.
|
||||
err = rpcClient.AddNode(miner.P2PAddress(), rpcclient.ANAdd)
|
||||
require.NoError(t, err, "failed to connect to miner")
|
||||
|
||||
// Get the network info and assert the num of outbound connections is 1.
|
||||
err = wait.NoError(func() error {
|
||||
result, err := rpcClient.GetNetworkInfo()
|
||||
require.NoError(t, err)
|
||||
|
||||
if int(result.Connections) != 1 {
|
||||
return fmt.Errorf("want 1 conn, got %d",
|
||||
result.Connections)
|
||||
}
|
||||
|
||||
if int(result.ConnectionsOut) != 1 {
|
||||
return fmt.Errorf("want 1 outbound conn, got %d",
|
||||
result.Connections)
|
||||
}
|
||||
|
||||
return nil
|
||||
}, wait.DefaultTimeout)
|
||||
require.NoError(t, err, "timeout connecting to the miner")
|
||||
|
||||
// Assert the chain backend is synced to the miner.
|
||||
syncBitcoindWithMiner(t, rpcClient, miner, p2pPort)
|
||||
|
||||
// Tear down the rpc client.
|
||||
rpcClient.Shutdown()
|
||||
|
||||
return conn
|
||||
}
|
||||
|
||||
// syncBitcoindWithMiner waits until the bitcoind node is synced with the miner.
|
||||
func syncBitcoindWithMiner(t *testing.T, notifier *rpcclient.Client,
|
||||
miner *rpctest.Harness, p2pPort int) uint32 {
|
||||
|
||||
_, minerHeight, err := miner.Client.GetBestBlock()
|
||||
require.NoError(t, err, "unable to retrieve miner's current height")
|
||||
|
||||
timeout := time.After(10 * time.Second)
|
||||
for {
|
||||
info, err := notifier.GetBlockChainInfo()
|
||||
require.NoError(t, err)
|
||||
|
||||
bitcoindHeight := info.Blocks
|
||||
|
||||
t.Logf("miner height=%v, bitcoind height=%v", minerHeight,
|
||||
bitcoindHeight)
|
||||
|
||||
if bitcoindHeight == minerHeight {
|
||||
return uint32(bitcoindHeight)
|
||||
}
|
||||
|
||||
select {
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
case <-timeout:
|
||||
t.Fatalf("timed out in syncNotifierWithMiner, got "+
|
||||
"err=%v, minerHeight=%v, bitcoindHeight=%v",
|
||||
err, minerHeight, bitcoindHeight)
|
||||
}
|
||||
|
||||
// Get the num of connections the miner has. We expect it to
|
||||
// have at least one connection with the chain backend.
|
||||
count, err := miner.Client.GetConnectionCount()
|
||||
require.NoError(t, err)
|
||||
if count != 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// Reconnect the miner and the chain backend.
|
||||
//
|
||||
// NOTE: The connection should have been made before we perform
|
||||
// the `syncNotifierWithMiner`. However, due unknown reason, the
|
||||
// miner may refuse to process the inbound connection made by
|
||||
// the bitcoind node, causing the connection to fail. It's
|
||||
// possible there's a bug in the handshake between the two
|
||||
// nodes.
|
||||
//
|
||||
// A normal flow is, bitcoind starts a v2 handshake flow, which
|
||||
// btcd will fail and disconnect. Upon seeing this
|
||||
// disconnection, bitcoind will try a v1 handshake and succeeds.
|
||||
// The failed flow is, upon seeing the v2 handshake, btcd
|
||||
// doesn't seem to perform the disconnect. Instead an EOF
|
||||
// websocket error is found.
|
||||
//
|
||||
// TODO(yy): Fix the above bug in `btcd`. This can be reproduced
|
||||
// using `make flakehunter-unit pkg=$pkg case=$case`, with,
|
||||
// `case=TestHistoricalConfDetailsNoTxIndex/rpc_polling_enabled`
|
||||
// `pkg=chainntnfs/bitcoindnotify`.
|
||||
// Also need to modify the temp dir logic so we can save the
|
||||
// debug logs.
|
||||
// This bug is likely to be fixed when we implement the
|
||||
// encrypted p2p conn, or when we properly fix the shutdown
|
||||
// issues in all our RPC conns.
|
||||
t.Log("Expected to the chain backend to have one conn with " +
|
||||
"the miner, instead it's disconnected!")
|
||||
|
||||
// We now ask the miner to add the chain backend back.
|
||||
host := fmt.Sprintf("127.0.0.1:%d", p2pPort)
|
||||
|
||||
// NOTE:AddNode must take a host that has the format
|
||||
// `host:port`, otherwise the default port will be used. Check
|
||||
// `normalizeAddress` in btcd for details.
|
||||
err = miner.Client.AddNode(host, rpcclient.ANAdd)
|
||||
require.NoError(t, err, "Failed to connect miner to the chain "+
|
||||
"backend")
|
||||
}
|
||||
}
|
||||
|
||||
// NewNeutrinoBackend spawns a new neutrino node that connects to a miner at
|
||||
// the specified address.
|
||||
func NewNeutrinoBackend(t *testing.T, netParams *chaincfg.Params,
|
||||
|
|
|
@ -3352,8 +3352,7 @@ func runTests(t *testing.T, walletDriver *lnwallet.WalletDriver,
|
|||
case "bitcoind":
|
||||
// Start a bitcoind instance.
|
||||
chainConn := unittest.NewBitcoindBackend(
|
||||
t, unittest.NetParams, miningNode.P2PAddress(),
|
||||
true, false,
|
||||
t, unittest.NetParams, miningNode, true, false,
|
||||
)
|
||||
|
||||
// Create a btcwallet bitcoind client for both Alice and
|
||||
|
@ -3364,8 +3363,7 @@ func runTests(t *testing.T, walletDriver *lnwallet.WalletDriver,
|
|||
case "bitcoind-rpc-polling":
|
||||
// Start a bitcoind instance.
|
||||
chainConn := unittest.NewBitcoindBackend(
|
||||
t, unittest.NetParams, miningNode.P2PAddress(),
|
||||
true, true,
|
||||
t, unittest.NetParams, miningNode, true, true,
|
||||
)
|
||||
|
||||
// Create a btcwallet bitcoind client for both Alice and
|
||||
|
|
|
@ -160,9 +160,40 @@ func assertFilteredBlock(t *testing.T, fb *FilteredBlock, expectedHeight int32,
|
|||
|
||||
}
|
||||
|
||||
func testFilterBlockNotifications(node *rpctest.Harness,
|
||||
chainView FilteredChainView, chainViewInit chainViewInitFunc,
|
||||
t *testing.T) {
|
||||
// setupMinerAndChainView creates a miner node and initialize a chain view using
|
||||
// this miner node. It returns the miner and the chain view.
|
||||
func setupMinerAndChainView(t *testing.T, chainViewInit chainViewInitFunc) (
|
||||
*rpctest.Harness, FilteredChainView) {
|
||||
|
||||
// Initialize the harness around a btcd node which will serve as our
|
||||
// dedicated miner to generate blocks, cause re-orgs, etc. We'll set up
|
||||
// this node with a chain length of 125, so we have plenty of BTC to
|
||||
// play around with.
|
||||
miner := unittest.NewMiner(
|
||||
t, netParams, []string{"--txindex"}, true, 25,
|
||||
)
|
||||
rpcConfig := miner.RPCConfig()
|
||||
|
||||
_, bestHeight, err := miner.Client.GetBestBlock()
|
||||
require.NoError(t, err)
|
||||
|
||||
chainView, err := chainViewInit(t, rpcConfig, miner, bestHeight)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = chainView.Start()
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Cleanup(func() {
|
||||
require.NoError(t, chainView.Stop())
|
||||
})
|
||||
|
||||
return miner, chainView
|
||||
}
|
||||
|
||||
func testFilterBlockNotifications(t *testing.T,
|
||||
chainViewInit chainViewInitFunc) {
|
||||
|
||||
node, chainView := setupMinerAndChainView(t, chainViewInit)
|
||||
|
||||
// To start the test, we'll create to fresh outputs paying to the
|
||||
// private key that we generated above.
|
||||
|
@ -270,9 +301,8 @@ func testFilterBlockNotifications(node *rpctest.Harness,
|
|||
}
|
||||
}
|
||||
|
||||
func testUpdateFilterBackTrack(node *rpctest.Harness,
|
||||
chainView FilteredChainView, chainViewInit chainViewInitFunc,
|
||||
t *testing.T) {
|
||||
func testUpdateFilterBackTrack(t *testing.T, chainViewInit chainViewInitFunc) {
|
||||
node, chainView := setupMinerAndChainView(t, chainViewInit)
|
||||
|
||||
// To start, we'll create a fresh output paying to the height generated
|
||||
// above.
|
||||
|
@ -345,8 +375,8 @@ func testUpdateFilterBackTrack(node *rpctest.Harness,
|
|||
}
|
||||
}
|
||||
|
||||
func testFilterSingleBlock(node *rpctest.Harness, chainView FilteredChainView,
|
||||
chainViewInit chainViewInitFunc, t *testing.T) {
|
||||
func testFilterSingleBlock(t *testing.T, chainViewInit chainViewInitFunc) {
|
||||
node, chainView := setupMinerAndChainView(t, chainViewInit)
|
||||
|
||||
// In this test, we'll test the manual filtration of blocks, which can
|
||||
// be used by clients to manually rescan their sub-set of the UTXO set.
|
||||
|
@ -445,9 +475,10 @@ func testFilterSingleBlock(node *rpctest.Harness, chainView FilteredChainView,
|
|||
// testFilterBlockDisconnected triggers a reorg all the way back to genesis,
|
||||
// and a small 5 block reorg, ensuring the chainView notifies about
|
||||
// disconnected and connected blocks in the order we expect.
|
||||
func testFilterBlockDisconnected(node *rpctest.Harness,
|
||||
chainView FilteredChainView, chainViewInit chainViewInitFunc,
|
||||
t *testing.T) {
|
||||
func testFilterBlockDisconnected(t *testing.T,
|
||||
chainViewInit chainViewInitFunc) {
|
||||
|
||||
node, _ := setupMinerAndChainView(t, chainViewInit)
|
||||
|
||||
// Create a node that has a shorter chain than the main chain, so we
|
||||
// can trigger a reorg.
|
||||
|
@ -460,7 +491,7 @@ func testFilterBlockDisconnected(node *rpctest.Harness,
|
|||
|
||||
// Init a chain view that has this node as its block source.
|
||||
reorgView, err := chainViewInit(
|
||||
t, reorgNode.RPCConfig(), reorgNode.P2PAddress(), bestHeight,
|
||||
t, reorgNode.RPCConfig(), reorgNode, bestHeight,
|
||||
)
|
||||
require.NoError(t, err, "unable to create chain view")
|
||||
|
||||
|
@ -632,12 +663,11 @@ func testFilterBlockDisconnected(node *rpctest.Harness,
|
|||
}
|
||||
|
||||
type chainViewInitFunc func(t *testing.T, rpcInfo rpcclient.ConnConfig,
|
||||
p2pAddr string, bestHeight int32) (FilteredChainView, error)
|
||||
miner *rpctest.Harness, bestHeight int32) (FilteredChainView, error)
|
||||
|
||||
type testCase struct {
|
||||
name string
|
||||
test func(*rpctest.Harness, FilteredChainView, chainViewInitFunc,
|
||||
*testing.T)
|
||||
test func(*testing.T, chainViewInitFunc)
|
||||
}
|
||||
|
||||
var chainViewTests = []testCase{
|
||||
|
@ -666,12 +696,12 @@ var interfaceImpls = []struct {
|
|||
{
|
||||
name: "bitcoind_zmq",
|
||||
chainViewInit: func(t *testing.T, _ rpcclient.ConnConfig,
|
||||
p2pAddr string, bestHeight int32) (FilteredChainView,
|
||||
error) {
|
||||
miner *rpctest.Harness, bestHeight int32) (
|
||||
FilteredChainView, error) {
|
||||
|
||||
// Start a bitcoind instance.
|
||||
chainConn := unittest.NewBitcoindBackend(
|
||||
t, unittest.NetParams, p2pAddr, true,
|
||||
t, unittest.NetParams, miner, true,
|
||||
false,
|
||||
)
|
||||
blockCache := blockcache.NewBlockCache(10000)
|
||||
|
@ -686,12 +716,12 @@ var interfaceImpls = []struct {
|
|||
{
|
||||
name: "bitcoind_polling",
|
||||
chainViewInit: func(t *testing.T, _ rpcclient.ConnConfig,
|
||||
p2pAddr string, bestHeight int32) (FilteredChainView,
|
||||
error) {
|
||||
miner *rpctest.Harness, bestHeight int32) (
|
||||
FilteredChainView, error) {
|
||||
|
||||
// Wait for the bitcoind instance to start up.
|
||||
chainConn := unittest.NewBitcoindBackend(
|
||||
t, unittest.NetParams, p2pAddr, true,
|
||||
t, unittest.NetParams, miner, true,
|
||||
true,
|
||||
)
|
||||
blockCache := blockcache.NewBlockCache(10000)
|
||||
|
@ -700,14 +730,28 @@ var interfaceImpls = []struct {
|
|||
chainConn, blockCache,
|
||||
)
|
||||
|
||||
// When running in rpc polling mode, the `reorg` method
|
||||
// in `BitcoindClient`'s `ntfnHandler` may be invoked to
|
||||
// handle the last block received from the miner during
|
||||
// bitcoind's startup. This behavior will cause a block
|
||||
// disconnected and a block connected notifications to
|
||||
// be sent to the channels.
|
||||
//
|
||||
// TODO(yy): unify the chain backend logic and put
|
||||
// everything in `btcwallet/chain` instead. The only
|
||||
// place we use this chain view is in `graph/builder`,
|
||||
// in which we subscribe to `FilteredBlocks` and
|
||||
// `DisconnectedBlocks`.
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
return chainView, nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "p2p_neutrino",
|
||||
chainViewInit: func(t *testing.T, _ rpcclient.ConnConfig,
|
||||
p2pAddr string, bestHeight int32) (FilteredChainView,
|
||||
error) {
|
||||
miner *rpctest.Harness, bestHeight int32) (
|
||||
FilteredChainView, error) {
|
||||
|
||||
spvDir := t.TempDir()
|
||||
|
||||
|
@ -723,7 +767,7 @@ var interfaceImpls = []struct {
|
|||
DataDir: spvDir,
|
||||
Database: spvDatabase,
|
||||
ChainParams: *netParams,
|
||||
ConnectPeers: []string{p2pAddr},
|
||||
ConnectPeers: []string{miner.P2PAddress()},
|
||||
}
|
||||
|
||||
spvNode, err := neutrino.NewChainService(spvConfig)
|
||||
|
@ -776,8 +820,8 @@ var interfaceImpls = []struct {
|
|||
{
|
||||
name: "btcd_websockets",
|
||||
chainViewInit: func(_ *testing.T, config rpcclient.ConnConfig,
|
||||
p2pAddr string, bestHeight int32) (FilteredChainView,
|
||||
error) {
|
||||
_ *rpctest.Harness, _ int32) (
|
||||
FilteredChainView, error) {
|
||||
|
||||
blockCache := blockcache.NewBlockCache(10000)
|
||||
chainView, err := NewBtcdFilteredChainView(
|
||||
|
@ -793,42 +837,17 @@ var interfaceImpls = []struct {
|
|||
}
|
||||
|
||||
func TestFilteredChainView(t *testing.T) {
|
||||
// Initialize the harness around a btcd node which will serve as our
|
||||
// dedicated miner to generate blocks, cause re-orgs, etc. We'll set up
|
||||
// this node with a chain length of 125, so we have plenty of BTC to
|
||||
// play around with.
|
||||
miner := unittest.NewMiner(
|
||||
t, netParams, []string{"--txindex"}, true, 25,
|
||||
)
|
||||
|
||||
rpcConfig := miner.RPCConfig()
|
||||
p2pAddr := miner.P2PAddress()
|
||||
|
||||
for _, chainViewImpl := range interfaceImpls {
|
||||
t.Logf("Testing '%v' implementation of FilteredChainView",
|
||||
chainViewImpl.name)
|
||||
|
||||
_, bestHeight, err := miner.Client.GetBestBlock()
|
||||
if err != nil {
|
||||
t.Fatalf("error getting best block: %v", err)
|
||||
}
|
||||
|
||||
chainView, err := chainViewImpl.chainViewInit(
|
||||
t, rpcConfig, p2pAddr, bestHeight,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to make chain view: %v", err)
|
||||
}
|
||||
if err := chainView.Start(); err != nil {
|
||||
t.Fatalf("unable to start chain view: %v", err)
|
||||
}
|
||||
for _, chainViewTest := range chainViewTests {
|
||||
testName := fmt.Sprintf("%v: %v", chainViewImpl.name,
|
||||
chainViewTest.name)
|
||||
|
||||
success := t.Run(testName, func(t *testing.T) {
|
||||
chainViewTest.test(
|
||||
miner, chainView,
|
||||
chainViewImpl.chainViewInit, t,
|
||||
t, chainViewImpl.chainViewInit,
|
||||
)
|
||||
})
|
||||
|
||||
|
@ -837,8 +856,5 @@ func TestFilteredChainView(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
if err := chainView.Stop(); err != nil {
|
||||
t.Fatalf("unable to stop chain view: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
23
scripts/unit-test-flake-hunter.sh
Executable file
23
scripts/unit-test-flake-hunter.sh
Executable file
|
@ -0,0 +1,23 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Check if pkg and case variables are provided.
|
||||
if [ $# -lt 2 ] || [ $# -gt 3 ]; then
|
||||
echo "Usage: $0 <pkg> <case> [timeout]"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
pkg=$1
|
||||
case=$2
|
||||
timeout=${3:-30s} # Default to 30s if not provided.
|
||||
|
||||
counter=0
|
||||
|
||||
# Run the command in a loop until it fails.
|
||||
while output=$(go clean -testcache && make unit-debug log="stdlog trace" pkg=$pkg case=$case timeout=$timeout 2>&1); do
|
||||
((counter++))
|
||||
echo "Test $case passed, count: $counter"
|
||||
done
|
||||
|
||||
# Only log the output when it fails.
|
||||
echo "Test $case failed. Output:"
|
||||
echo "$output"
|
Loading…
Add table
Reference in a new issue