From e5815dd8ddaa900f5b5b7cf30d492de4e75d5ef8 Mon Sep 17 00:00:00 2001 From: Wisdom Arerosuoghene Date: Wed, 20 Oct 2021 18:39:39 +0100 Subject: [PATCH] allow Wallet impls provide tip change tracking where possible --- client/asset/dcr/dcr.go | 29 ++++++++++++++++++++--------- client/asset/dcr/rpcwallet.go | 12 ++++++++++++ client/asset/dcr/wallet.go | 8 ++++++++ 3 files changed, 40 insertions(+), 9 deletions(-) diff --git a/client/asset/dcr/dcr.go b/client/asset/dcr/dcr.go index ac187e55b2..a100f6a2f7 100644 --- a/client/asset/dcr/dcr.go +++ b/client/asset/dcr/dcr.go @@ -537,11 +537,17 @@ func (dcr *ExchangeWallet) Connect(ctx context.Context) (*sync.WaitGroup, error) success = true // All good, don't disconnect the wallet when this method returns. + // NotifyOnTipChange will return false if the wallet does not support + // tip change notification. We'll use dcr.monitorBlocks below if so. + monitoringBlocks := dcr.wallet.NotifyOnTipChange(ctx, dcr.handleTipChange) + var wg sync.WaitGroup wg.Add(1) go func() { defer wg.Done() - dcr.monitorBlocks(ctx) + if !monitoringBlocks { + dcr.monitorBlocks(ctx) + } dcr.shutdown() }() return &wg, nil @@ -2691,16 +2697,21 @@ func (dcr *ExchangeWallet) checkForNewBlocks() { defer cancel() newTip, err := dcr.getBestBlock(ctx) if err != nil { - go dcr.tipChange(fmt.Errorf("failed to get best block: %w", err)) + dcr.handleTipChange(nil, 0, fmt.Errorf("failed to get best block: %w", err)) return } - // This method is called frequently. Don't hold write lock - // unless tip has changed. dcr.tipMtx.RLock() sameTip := dcr.currentTip.hash.IsEqual(newTip.hash) dcr.tipMtx.RUnlock() - if sameTip { + if !sameTip { + dcr.handleTipChange(newTip.hash, newTip.height, nil) + } +} + +func (dcr *ExchangeWallet) handleTipChange(newTipHash *chainhash.Hash, newTipHeight int64, err error) { + if err != nil { + go dcr.tipChange(err) return } @@ -2708,8 +2719,8 @@ func (dcr *ExchangeWallet) checkForNewBlocks() { defer dcr.tipMtx.Unlock() prevTip := dcr.currentTip - dcr.currentTip = newTip - dcr.log.Debugf("tip change: %d (%s) => %d (%s)", prevTip.height, prevTip.hash, newTip.height, newTip.hash) + dcr.currentTip = &block{newTipHeight, newTipHash} + dcr.log.Debugf("tip change: %d (%s) => %d (%s)", prevTip.height, prevTip.hash, newTipHeight, newTipHash) go dcr.tipChange(nil) // Search for contract redemption in new blocks if there @@ -2765,7 +2776,7 @@ func (dcr *ExchangeWallet) checkForNewBlocks() { if aBlock.Confirmations > -1 { // Found the mainchain ancestor of previous tip. startHeight = int64(aBlock.Height) - dcr.log.Debugf("reorg detected from height %d to %d", aBlock.Height, newTip.height) + dcr.log.Debugf("reorg detected from height %d to %d", aBlock.Height, newTipHeight) break } if aBlock.Height == 0 { @@ -2785,7 +2796,7 @@ func (dcr *ExchangeWallet) checkForNewBlocks() { // Run the redemption search from the startHeight determined above up // till the current tip height. - go dcr.findRedemptionsInBlockRange(startHeight, newTip.height, contractOutpoints) + go dcr.findRedemptionsInBlockRange(startHeight, newTipHeight, contractOutpoints) } func (dcr *ExchangeWallet) getBestBlock(ctx context.Context) (*block, error) { diff --git a/client/asset/dcr/rpcwallet.go b/client/asset/dcr/rpcwallet.go index 15a532ce5a..7f1a0e92fc 100644 --- a/client/asset/dcr/rpcwallet.go +++ b/client/asset/dcr/rpcwallet.go @@ -257,6 +257,18 @@ func (w *rpcWallet) Network(ctx context.Context) (wire.CurrencyNet, error) { return net, translateRPCCancelErr(err) } +// NotifyOnTipChange registers a callback function that the should be invoked +// when the wallet sees new mainchain blocks. The return value indicates if this +// notification can be provided. +// Part of the Wallet interface. +func (w *rpcWallet) NotifyOnTipChange(ctx context.Context, cb TipChangeCallback) bool { + // TODO: Could use the ctx provided to poll getbestblock and detect tip changes + // in order to invoke the provided callback. This is currently already being handled + // by the dcr ExchangeWallet to accomodate other implementations that may also not + // have an out-of-the-box provision for block change notifications. + return false +} + // AccountOwnsAddress uses the validateaddress rpc to check if the provided // address belongs to the specified account. // Part of the Wallet interface. diff --git a/client/asset/dcr/wallet.go b/client/asset/dcr/wallet.go index 3e50df8f36..a5e3353289 100644 --- a/client/asset/dcr/wallet.go +++ b/client/asset/dcr/wallet.go @@ -42,6 +42,8 @@ func RegisterCustomWallet(constructor WalletConstructor, info *asset.WalletInfo) chaincfg.TestNet3Params() } +type TipChangeCallback func(*chainhash.Hash, int64, error) + type Wallet interface { // Connect establishes a connection to the wallet. Connect(ctx context.Context) error @@ -51,6 +53,12 @@ type Wallet interface { Disconnected() bool // Network returns the network of the connected wallet. Network(ctx context.Context) (wire.CurrencyNet, error) + // NotifyOnTipChange registers a callback function that the should be + // invoked when the wallet sees new mainchain blocks. The return value + // indicates if this notification can be provided. Where this tip change + // notification is unimplemented, monitorBlocks should be used to track + // tip changes. + NotifyOnTipChange(ctx context.Context, cb TipChangeCallback) bool // AccountOwnsAddress checks if the provided address belongs to the // specified account. AccountOwnsAddress(ctx context.Context, account, address string) (bool, error)