From 030ae63be7fced96ec524b01f89ca894d4654f41 Mon Sep 17 00:00:00 2001 From: Raffe Date: Tue, 24 Sep 2024 12:00:58 +1000 Subject: [PATCH] Wait for Initial Block Download in core This avoids a problem where we get the current block height too early and end up following the entire blockchain. --- pkg/chaintracker/chainfollower.go | 29 +++++++++++++++++++++++++---- pkg/core/rpc.go | 9 +++++++++ pkg/dogecoin.go | 20 ++++++++++++++++++++ pkg/dogecoin/libdogecoin.go | 7 +++++++ pkg/dogecoin/mock.go | 4 ++++ 5 files changed, 65 insertions(+), 4 deletions(-) diff --git a/pkg/chaintracker/chainfollower.go b/pkg/chaintracker/chainfollower.go index 611a439..5333e84 100644 --- a/pkg/chaintracker/chainfollower.go +++ b/pkg/chaintracker/chainfollower.go @@ -10,10 +10,11 @@ import ( ) const ( - RETRY_DELAY = 5 * time.Second // for RPC and Database errors. - WRONG_CHAIN_DELAY = 5 * time.Minute // for "Wrong Chain" error (essentially stop) - CONFLICT_DELAY = 250 * time.Millisecond // for Database conflicts (concurrent transactions) - BLOCKS_PER_COMMIT = 10 // number of blocks per database commit. + RETRY_DELAY = 5 * time.Second // for RPC and Database errors. + WRONG_CHAIN_DELAY = 5 * time.Minute // for "Wrong Chain" error (essentially stop) + WAIT_INITIAL_BLOCK = 30 * time.Second // for Initial Block Download + CONFLICT_DELAY = 250 * time.Millisecond // for Database conflicts (concurrent transactions) + BLOCKS_PER_COMMIT = 10 // number of blocks per database commit. ) type ChainFollower struct { @@ -154,6 +155,14 @@ func (c *ChainFollower) fetchStartingPos() ChainPos { continue } c.chain = chain + // Wait for Core to be fully synced, otherwise fetchBlockCount will give + // us an early block and we'll follow the whole chain. + info := c.fetchBlockchainInfo() + if info.InitialBlockDownload { + log.Println("ChainFollower: waiting for Core initial block download") + c.sleepForRetry(nil, WAIT_INITIAL_BLOCK) + continue + } if state.BestBlockHash != "" { // Resume sync. // Make sure we're syncing the same blockchain as before. @@ -688,6 +697,18 @@ func (c *ChainFollower) fetchBlockHash(height int64) string { } } +func (c *ChainFollower) fetchBlockchainInfo() giga.RpcBlockchainInfo { + for { + info, err := c.l1.GetBlockchainInfo() + if err != nil { + log.Println("ChainFollower: error retrieving blockchain info (will retry):", err) + c.sleepForRetry(err, 0) + } else { + return info + } + } +} + func (c *ChainFollower) fetchBlockCount() int64 { for { count, err := c.l1.GetBlockCount() diff --git a/pkg/core/rpc.go b/pkg/core/rpc.go index 68d0825..d3b7acf 100755 --- a/pkg/core/rpc.go +++ b/pkg/core/rpc.go @@ -71,6 +71,10 @@ func (l *L1CoreRPC) request(method string, params []any, result any) error { if err != nil { return fmt.Errorf("json-rpc read response: %v", err) } + // check for error response + if res.StatusCode != 200 { + return fmt.Errorf("json-rpc error status: %v", res.StatusCode) + } // cannot use json.NewDecoder: "The decoder introduces its own buffering // and may read data from r beyond the JSON values requested." var rpcres rpcResponse @@ -149,6 +153,11 @@ func (l *L1CoreRPC) GetBlockCount() (blockCount int64, err error) { return } +func (l *L1CoreRPC) GetBlockchainInfo() (info giga.RpcBlockchainInfo, err error) { + err = l.request("getblockchaininfo", []any{}, &info) + return +} + func (l *L1CoreRPC) GetTransaction(txnHash string) (txn giga.RawTxn, err error) { decode := true // to get back JSON rather than HEX err = l.request("getrawtransaction", []any{txnHash, decode}, &txn) diff --git a/pkg/dogecoin.go b/pkg/dogecoin.go index 7d00319..248b7c8 100755 --- a/pkg/dogecoin.go +++ b/pkg/dogecoin.go @@ -26,6 +26,7 @@ type L1 interface { GetBlockHash(height int64) (string, error) GetBestBlockHash() (string, error) GetBlockCount() (int64, error) + GetBlockchainInfo() (RpcBlockchainInfo, error) GetTransaction(txnHash string) (RawTxn, error) Send(txnHex string) (txid string, err error) EstimateFee(confirmTarget int) (feePerKB CoinAmount, err error) @@ -171,3 +172,22 @@ type RpcBlockHeader struct { PreviousBlockHash string `json:"previousblockhash"` // (string) The hash of the previous block (hex) NextBlockHash string `json:"nextblockhash"` // (string) The hash of the next block (hex) } + +// RpcBlockchainInfo from Core +type RpcBlockchainInfo struct { + Chain string `json:"chain"` // (string) current network name (main, test, regtest) + Blocks int64 `json:"blocks"` // (numeric) the height of the most-work fully-validated chain. The genesis block has height 0 + Headers int64 `json:"headers"` // (numeric) the current number of headers we have validated + BestBlockHash string `json:"bestblockhash"` // (string) the hash of the currently best block + Difficulty float64 `json:"difficulty"` // (numeric) the current difficulty + MedianTime int64 `json:"mediantime"` // (numeric) median time for the current best block + VerificationProgress float64 `json:"verificationprogress"` // (numeric) estimate of verification progress [0..1] + InitialBlockDownload bool `json:"initialblockdownload"` // (boolean) (debug information) estimate of whether this node is in Initial Block Download mode + ChainWord string `json:"chainwork"` // (string) total amount of work in active chain, in hexadecimal + SizeOnDisk int64 `json:"size_on_disk"` // (numeric) the estimated size of the block and undo files on disk + Pruned bool `json:"pruned"` // (boolean) if the blocks are subject to pruning + PruneHeight int64 `json:"pruneheight"` // (numeric) lowest-height complete block stored (only present if pruning is enabled) + AutomaticPruning bool `json:"automatic_pruning"` // (boolean) whether automatic pruning is enabled (only present if pruning is enabled) + PruneTargetSize int64 `json:"prune_target_size"` // (numeric) the target size used by pruning (only present if automatic pruning is enabled) + +} diff --git a/pkg/dogecoin/libdogecoin.go b/pkg/dogecoin/libdogecoin.go index 4d51460..693af43 100644 --- a/pkg/dogecoin/libdogecoin.go +++ b/pkg/dogecoin/libdogecoin.go @@ -213,6 +213,13 @@ func (l L1Libdogecoin) GetBlockCount() (blockCount int64, err error) { return 0, fmt.Errorf("not implemented") } +func (l L1Libdogecoin) GetBlockchainInfo() (info giga.RpcBlockchainInfo, err error) { + if l.fallback != nil { + return l.fallback.GetBlockchainInfo() + } + return giga.RpcBlockchainInfo{}, fmt.Errorf("not implemented") +} + func (l L1Libdogecoin) GetTransaction(txnHash string) (txn giga.RawTxn, err error) { if l.fallback != nil { return l.fallback.GetTransaction(txnHash) diff --git a/pkg/dogecoin/mock.go b/pkg/dogecoin/mock.go index 808a31b..2ca3935 100755 --- a/pkg/dogecoin/mock.go +++ b/pkg/dogecoin/mock.go @@ -57,6 +57,10 @@ func (l L1Mock) GetBlockCount() (blockCount int64, err error) { return 100, nil } +func (l L1Mock) GetBlockchainInfo() (info giga.RpcBlockchainInfo, err error) { + return giga.RpcBlockchainInfo{}, fmt.Errorf("not implemented") +} + func (l L1Mock) GetTransaction(txnHash string) (txn giga.RawTxn, err error) { return giga.RawTxn{}, nil }