From 629258c7bc3eec887e2d52b1c39f948cadb3a916 Mon Sep 17 00:00:00 2001 From: Brian Stafford Date: Tue, 26 Oct 2021 06:00:47 -0500 Subject: [PATCH] add tests --- client/asset/btc/btc.go | 17 ++++++--- client/asset/btc/btc_test.go | 13 +++++-- client/asset/btc/spv_test.go | 71 ++++++++++++++++++++++++++++++++++++ 3 files changed, 92 insertions(+), 9 deletions(-) diff --git a/client/asset/btc/btc.go b/client/asset/btc/btc.go index 43fcbc47be..8818ccfae7 100644 --- a/client/asset/btc/btc.go +++ b/client/asset/btc/btc.go @@ -768,7 +768,7 @@ func (btc *ExchangeWallet) Connect(ctx context.Context) (*sync.WaitGroup, error) wg.Add(1) go func() { defer wg.Done() - btc.run(ctx) + btc.watchBlocks(ctx) btc.shutdown() }() return &wg, nil @@ -2384,9 +2384,9 @@ func (btc *ExchangeWallet) RegFeeConfirmations(_ context.Context, id dex.Bytes) return uint32(tx.Confirmations), nil } -// run pings for new blocks and runs the tipChange callback function when the -// block changes. -func (btc *ExchangeWallet) run(ctx context.Context) { +// watchBlocks pings for new blocks and runs the tipChange callback function +// when the block changes. +func (btc *ExchangeWallet) watchBlocks(ctx context.Context) { ticker := time.NewTicker(blockTicker) defer ticker.Stop() @@ -2409,7 +2409,7 @@ func (btc *ExchangeWallet) run(ctx context.Context) { // dequeuedBlock is where the queuedBlocks that time out will be sent for // broadcast. - var dequeuedBlock chan *block + dequeuedBlock := make(chan *block, 1) for { select { @@ -2423,6 +2423,10 @@ func (btc *ExchangeWallet) run(ctx context.Context) { return } + if queuedBlock != nil && *newTipHash == queuedBlock.block.hash { + continue + } + // This method is called frequently. Don't hold write lock // unless tip has changed. btc.tipMtx.RLock() @@ -2468,7 +2472,8 @@ func (btc *ExchangeWallet) run(ctx context.Context) { case dqBlock := <-dequeuedBlock: btc.log.Warnf("Reporting a block found in polling that the wallet apparently "+ - "never reported: %d %s. This may indicate a problem with the wallet.", dqBlock.height, dqBlock.hash) + "never reported: %d %s. If you see this message repeatedly, it may indicate "+ + "an issue with the wallet.", dqBlock.height, dqBlock.hash) btc.reportNewTip(ctx, dqBlock) case <-ctx.Done(): diff --git a/client/asset/btc/btc_test.go b/client/asset/btc/btc_test.go index f53c191b7b..f3a461592c 100644 --- a/client/asset/btc/btc_test.go +++ b/client/asset/btc/btc_test.go @@ -167,6 +167,7 @@ type testData struct { signTxErr error listUnspent []*ListUnspentResult listUnspentErr error + tipChanged chan struct{} // spv fetchInputInfoTx *wire.MsgTx @@ -195,6 +196,7 @@ func newTestData() *testData { getCFilterScripts: make(map[chainhash.Hash][][]byte), confsErr: WalletTransactionNotFound, checkpoints: make(map[outPoint]*scanCheckpoint), + tipChanged: make(chan struct{}, 1), } } @@ -567,7 +569,12 @@ func tNewWallet(segwit bool, walletType string) (*ExchangeWallet, *testData, fun data := newTestData() walletCfg := &asset.WalletConfig{ - TipChange: func(error) {}, + TipChange: func(error) { + select { + case data.tipChanged <- struct{}{}: + default: + } + }, } walletCtx, shutdown := context.WithCancel(tCtx) cfg := &BTCCloneCFG{ @@ -596,6 +603,7 @@ func tNewWallet(segwit bool, walletType string) (*ExchangeWallet, *testData, fun chainParams: &chaincfg.MainNetParams, wallet: &tBtcWallet{data}, cl: neutrinoClient, + tipChan: make(chan *block), chainClient: nil, acctNum: 0, txBlocks: data.dbBlockForTx, @@ -622,7 +630,7 @@ func tNewWallet(segwit bool, walletType string) (*ExchangeWallet, *testData, fun hash: *bestHash, } wallet.tipMtx.Unlock() - go wallet.run(walletCtx) + go wallet.watchBlocks(walletCtx) return wallet, data, shutdown, nil } @@ -2966,5 +2974,4 @@ func testTryRedemptionRequests(t *testing.T, segwit bool, walletType string) { } } } - } diff --git a/client/asset/btc/spv_test.go b/client/asset/btc/spv_test.go index fbee237db2..9773cfed90 100644 --- a/client/asset/btc/spv_test.go +++ b/client/asset/btc/spv_test.go @@ -699,3 +699,74 @@ func TestSendWithSubtract(t *testing.T) { t.Fatalf("test passed with fees > available error") } } + +func TestTryBlocksWithNotifier(t *testing.T) { + defaultWalletBlockAllowance := walletBlockAllowance + defaultBlockTicker := blockTicker + + walletBlockAllowance = 30 * time.Millisecond + blockTicker = 5 * time.Millisecond + + defer func() { + walletBlockAllowance = defaultWalletBlockAllowance + blockTicker = defaultBlockTicker + }() + + wallet, node, shutdown, _ := tNewWallet(true, walletTypeSPV) + defer shutdown() + + spv := wallet.node.(*spvWallet) + + getNote := func(timeout time.Duration) bool { + select { + case <-node.tipChanged: + return true + case <-time.After(timeout): + return false + } + } + + if getNote(walletBlockAllowance * 2) { + t.Fatalf("got a first block") + } + + var tipHeight int64 + addBlock := func() *block { + tipHeight++ + h, _ := node.addRawTx(tipHeight, dummyTx()) + return &block{tipHeight, *h} + } + + addBlock() + + // It should not come through on the block tick, since it will be cached. + if getNote(blockTicker * 2) { + t.Fatalf("got block that should've been cached") + } + + // But it will come through after the blockAllowance, printing a warning. + if !getNote(walletBlockAllowance * 2) { + t.Fatal("block didn't time out") + } + + // On the other hand, a wallet block should come through immediately. Not + // even waiting on the block tick. + spv.tipChan <- addBlock() + if !getNote(blockTicker / 2) { + t.Fatal("wallet block wasn't sent through") + } + + // If we do the same thing but make sure that a polled block is queued + // first, we should still see the block right away, and the queued block + // should be canceled. + blk := addBlock() + time.Sleep(blockTicker * 2) + spv.tipChan <- blk + if !getNote(blockTicker / 2) { + t.Fatal("wallet block wasn't sent through with polled block queued") + } + + if getNote(walletBlockAllowance * 2) { + t.Fatal("queued polled block that should have been canceled came through") + } +}