diff --git a/client/asset/dcr/dcr.go b/client/asset/dcr/dcr.go index c0805a2944..ff557bdffd 100644 --- a/client/asset/dcr/dcr.go +++ b/client/asset/dcr/dcr.go @@ -643,8 +643,8 @@ type ExchangeWallet struct { oracleFees map[uint64]feeStamped // conf target => fee rate oracleFailing bool - tipMtx sync.RWMutex - currentTip *block + handleTipMtx sync.Mutex + currentTip atomic.Value // *block // Coins returned by Fund are cached for quick reference. fundingMtx sync.RWMutex @@ -1064,13 +1064,11 @@ func (dcr *ExchangeWallet) Connect(ctx context.Context) (*sync.WaitGroup, error) } // Initialize the best block. - dcr.tipMtx.Lock() - dcr.currentTip, err = dcr.getBestBlock(ctx) - tip := dcr.currentTip - dcr.tipMtx.Unlock() + tip, err := dcr.getBestBlock(ctx) if err != nil { return nil, fmt.Errorf("error initializing best block for DCR: %w", err) } + dcr.currentTip.Store(tip) dcr.startingBlocks.Store(uint64(tip.height)) dbCM, err := dcr.startTxHistoryDB(ctx) @@ -3592,9 +3590,7 @@ func (dcr *ExchangeWallet) lookupTxOutput(ctx context.Context, txHash *chainhash // LockTimeExpired returns true if the specified locktime has expired, making it // possible to redeem the locked coins. func (dcr *ExchangeWallet) LockTimeExpired(ctx context.Context, lockTime time.Time) (bool, error) { - dcr.tipMtx.RLock() - blockHash := dcr.currentTip.hash - dcr.tipMtx.RUnlock() + blockHash := dcr.cachedBestBlock().hash hdr, err := dcr.wallet.GetBlockHeader(ctx, blockHash) if err != nil { return false, fmt.Errorf("unable to retrieve the block header: %w", err) @@ -3657,9 +3653,7 @@ func (dcr *ExchangeWallet) FindRedemption(ctx context.Context, coinID, _ dex.Byt if contractBlock == nil { dcr.findRedemptionsInMempool([]outPoint{contractOutpoint}) } else { - dcr.tipMtx.RLock() - bestBlock := dcr.currentTip - dcr.tipMtx.RUnlock() + bestBlock := dcr.cachedBestBlock() dcr.findRedemptionsInBlockRange(contractBlock.height, bestBlock.height, []outPoint{contractOutpoint}) } @@ -4833,11 +4827,8 @@ func (dcr *ExchangeWallet) SyncStatus() (ss *asset.SyncStatus, err error) { } if wasSynced := dcr.previouslySynced.Swap(synced); synced && !wasSynced { - dcr.tipMtx.RLock() - tip := dcr.currentTip - dcr.tipMtx.RUnlock() - - dcr.syncTxHistory(dcr.ctx, uint64(tip.height)) + tip := dcr.cachedBestBlock() + go dcr.syncTxHistory(dcr.ctx, uint64(tip.height)) } }() @@ -6630,11 +6621,7 @@ func (dcr *ExchangeWallet) monitorBlocks(ctx context.Context) { return } - dcr.tipMtx.RLock() - sameTip := dcr.currentTip.hash.IsEqual(newTip.hash) - dcr.tipMtx.RUnlock() - - if sameTip { + if dcr.cachedBestBlock().hash.IsEqual(newTip.hash) { return } @@ -6682,9 +6669,7 @@ func (dcr *ExchangeWallet) monitorBlocks(ctx context.Context) { // Mempool tx seen. dcr.emitBalance() - dcr.tipMtx.RLock() - tip := dcr.currentTip - dcr.tipMtx.RUnlock() + tip := dcr.cachedBestBlock() dcr.syncTxHistory(ctx, uint64(tip.height)) continue } @@ -6712,11 +6697,10 @@ func (dcr *ExchangeWallet) handleTipChange(ctx context.Context, newTipHash *chai } // Lock to avoid concurrent handleTipChange execution for simplicity. - dcr.tipMtx.Lock() - defer dcr.tipMtx.Unlock() + dcr.handleTipMtx.Lock() + defer dcr.handleTipMtx.Unlock() - prevTip := dcr.currentTip - dcr.currentTip = &block{newTipHeight, newTipHash} + prevTip := dcr.currentTip.Swap(&block{newTipHeight, newTipHash}).(*block) dcr.log.Tracef("tip change: %d (%s) => %d (%s)", prevTip.height, prevTip.hash, newTipHeight, newTipHash) @@ -6849,10 +6833,8 @@ func (dcr *ExchangeWallet) blockHeader(ctx context.Context, blockHash *chainhash return blockHeader, true, validMainchain, nil } -func (dcr *ExchangeWallet) cachedBestBlock() block { - dcr.tipMtx.RLock() - defer dcr.tipMtx.RUnlock() - return *dcr.currentTip +func (dcr *ExchangeWallet) cachedBestBlock() *block { + return dcr.currentTip.Load().(*block) } // wireBytes dumps the serialized transaction bytes. diff --git a/client/asset/dcr/dcr_test.go b/client/asset/dcr/dcr_test.go index 7caf8e9dd9..6634a7e117 100644 --- a/client/asset/dcr/dcr_test.go +++ b/client/asset/dcr/dcr_test.go @@ -162,9 +162,8 @@ func tNewWalletMonitorBlocks(monitorBlocks bool) (*ExchangeWallet, *tRPCClient, wallet.ctx = walletCtx // Initialize the best block. - wallet.tipMtx.Lock() - wallet.currentTip, _ = wallet.getBestBlock(walletCtx) - wallet.tipMtx.Unlock() + tip, _ := wallet.getBestBlock(walletCtx) + wallet.currentTip.Store(tip) if monitorBlocks { go wallet.monitorBlocks(walletCtx) @@ -4232,9 +4231,8 @@ func TestConfirmRedemption(t *testing.T) { spenderTx := makeRawTx(inputs, nil) node.blockchain.addRawTx(2, spenderTx) - wallet.tipMtx.Lock() - wallet.currentTip, _ = wallet.getBestBlock(wallet.ctx) - wallet.tipMtx.Unlock() + tip, _ := wallet.getBestBlock(wallet.ctx) + wallet.currentTip.Store(tip) txFn := func(doErr []bool) func() (*walletjson.GetTransactionResult, error) { var i int @@ -5007,7 +5005,7 @@ func TestRescanSync(t *testing.T) { defer shutdown() const tip = 1000 - wallet.currentTip = &block{height: tip} + wallet.currentTip.Store(&block{height: tip}) node.rawRes[methodSyncStatus], node.rawErr[methodSyncStatus] = json.Marshal(&walletjson.SyncStatusResult{ Synced: true, diff --git a/client/asset/dcr/externaltx.go b/client/asset/dcr/externaltx.go index b954c5ae40..8af83f9ea7 100644 --- a/client/asset/dcr/externaltx.go +++ b/client/asset/dcr/externaltx.go @@ -63,7 +63,7 @@ func (dcr *ExchangeWallet) lookupTxOutWithBlockFilters(ctx context.Context, op o tip, err := dcr.getBestBlock(ctx) if err != nil { dcr.log.Errorf("getbestblock error %v", err) - *tip = dcr.cachedBestBlock() + tip = dcr.cachedBestBlock() } var confs uint32 if tip.height >= outputBlock.height { // slight possibility that the cached tip height is behind the output's block height diff --git a/client/asset/dcr/native_wallet.go b/client/asset/dcr/native_wallet.go index e31294451c..c49ae2a25a 100644 --- a/client/asset/dcr/native_wallet.go +++ b/client/asset/dcr/native_wallet.go @@ -274,9 +274,7 @@ func (w *NativeWallet) transferAccount(ctx context.Context, toAcct string, fromA // birthdayBlockHeight performs a binary search for the last block with a // timestamp lower than the provided birthday. func (w *NativeWallet) birthdayBlockHeight(ctx context.Context, bday uint64) int32 { - w.tipMtx.RLock() - tipHeight := w.currentTip.height - w.tipMtx.RUnlock() + tipHeight := w.cachedBestBlock().height var err error firstBlockAfterBday := sort.Search(int(tipHeight), func(blockHeightI int) bool { if err != nil { // if we see any errors, just give up. diff --git a/client/asset/dcr/spv_test.go b/client/asset/dcr/spv_test.go index 1ff3b4d5c9..6b3081407b 100644 --- a/client/asset/dcr/spv_test.go +++ b/client/asset/dcr/spv_test.go @@ -956,12 +956,12 @@ func TestBirthdayBlockHeight(t *testing.T) { dcrw.makeBlocks(0, tipHeight) w := &NativeWallet{ ExchangeWallet: &ExchangeWallet{ - wallet: spvw, - log: dex.StdOutLogger("T", dex.LevelInfo), - currentTip: &block{height: tipHeight}, + wallet: spvw, + log: dex.StdOutLogger("T", dex.LevelInfo), }, spvw: spvw, } + w.currentTip.Store(&block{height: tipHeight}) if h := w.birthdayBlockHeight(tCtx, 5); h != 4 { t.Fatalf("expected block 4, got %d", h) @@ -982,12 +982,12 @@ func TestRescan(t *testing.T) { spvw, dcrw := tNewSpvWallet() w := &NativeWallet{ ExchangeWallet: &ExchangeWallet{ - wallet: spvw, - log: dex.StdOutLogger("T", dex.LevelInfo), - currentTip: &block{height: tipHeight}, + wallet: spvw, + log: dex.StdOutLogger("T", dex.LevelInfo), }, spvw: spvw, } + w.currentTip.Store(&block{height: tipHeight}) dcrw.makeBlocks(0, tipHeight)