diff --git a/chain/bitcoind_client.go b/chain/bitcoind_client.go index 2e42e7d..2023b4d 100644 --- a/chain/bitcoind_client.go +++ b/chain/bitcoind_client.go @@ -174,6 +174,20 @@ func (c *BitcoindClient) GetBlockHeaderVerbose( return c.chainConn.client.GetBlockHeaderVerbose(hash) } +// IsCurrent returns whether the chain backend considers its view of the network +// as "current". +func (c *BitcoindClient) IsCurrent() bool { + bestHash, _, err := c.GetBestBlock() + if err != nil { + return false + } + bestHeader, err := c.GetBlockHeader(bestHash) + if err != nil { + return false + } + return bestHeader.Timestamp.After(time.Now().Add(-isCurrentDelta)) +} + // GetRawTransactionVerbose returns a transaction from the tx hash. func (c *BitcoindClient) GetRawTransactionVerbose( hash *chainhash.Hash) (*btcjson.TxRawResult, error) { diff --git a/chain/interface.go b/chain/interface.go index 885f632..da23e14 100644 --- a/chain/interface.go +++ b/chain/interface.go @@ -10,6 +10,11 @@ import ( "github.com/btcsuite/btcwallet/wtxmgr" ) +// isCurrentDelta is the delta duration we'll use from the present time to +// determine if a backend is considered "current", i.e. synced to the tip of +// the chain. +const isCurrentDelta = 2 * time.Hour + // BackEnds returns a list of the available back ends. // TODO: Refactor each into a driver and use dynamic registration. func BackEnds() []string { @@ -31,6 +36,7 @@ type Interface interface { GetBlock(*chainhash.Hash) (*wire.MsgBlock, error) GetBlockHash(int64) (*chainhash.Hash, error) GetBlockHeader(*chainhash.Hash) (*wire.BlockHeader, error) + IsCurrent() bool FilterBlocks(*FilterBlocksRequest) (*FilterBlocksResponse, error) BlockStamp() (*waddrmgr.BlockStamp, error) SendRawTransaction(*wire.MsgTx, bool) (*chainhash.Hash, error) diff --git a/chain/neutrino.go b/chain/neutrino.go index c87e89e..d74a6a1 100644 --- a/chain/neutrino.go +++ b/chain/neutrino.go @@ -157,6 +157,12 @@ func (s *NeutrinoClient) GetBlockHeader( return s.CS.GetBlockHeader(blockHash) } +// IsCurrent returns whether the chain backend considers its view of the network +// as "current". +func (s *NeutrinoClient) IsCurrent() bool { + return s.CS.IsCurrent() +} + // SendRawTransaction replicates the RPC client's SendRawTransaction command. func (s *NeutrinoClient) SendRawTransaction(tx *wire.MsgTx, allowHighFees bool) ( *chainhash.Hash, error) { diff --git a/chain/rpc.go b/chain/rpc.go index cea23ba..af9bbf5 100644 --- a/chain/rpc.go +++ b/chain/rpc.go @@ -140,6 +140,20 @@ func (c *RPCClient) Stop() { c.quitMtx.Unlock() } +// IsCurrent returns whether the chain backend considers its view of the network +// as "current". +func (c *RPCClient) IsCurrent() bool { + bestHash, _, err := c.GetBestBlock() + if err != nil { + return false + } + bestHeader, err := c.GetBlockHeader(bestHash) + if err != nil { + return false + } + return bestHeader.Timestamp.After(time.Now().Add(-isCurrentDelta)) +} + // Rescan wraps the normal Rescan command with an additional paramter that // allows us to map an oupoint to the address in the chain that it pays to. // This is useful when using BIP 158 filters as they include the prev pkScript diff --git a/wallet/mock.go b/wallet/mock.go index a626515..09b5e65 100644 --- a/wallet/mock.go +++ b/wallet/mock.go @@ -41,6 +41,10 @@ func (m *mockChainClient) GetBlockHeader(*chainhash.Hash) (*wire.BlockHeader, return nil, nil } +func (m *mockChainClient) IsCurrent() bool { + return false +} + func (m *mockChainClient) FilterBlocks(*chain.FilterBlocksRequest) ( *chain.FilterBlocksResponse, error) { return nil, nil