Skip to content

Commit

Permalink
chain: wait for bitcoind to finish loading blocks during startup
Browse files Browse the repository at this point in the history
We sometimes see this error in test,
```
--- FAIL: TestBitcoindEvents (7.72s)
    --- FAIL: TestBitcoindEvents/Events_via_RPC_Polling (1.00s)
        bitcoind_events_test.go:480:
            	Error Trace:	/home/runner/work/btcwallet/btcwallet/chain/bitcoind_events_test.go:480
            	            				/home/runner/work/btcwallet/btcwallet/chain/bitcoind_events_test.go:49
            	Error:      	Received unexpected error:
            	            	-28: Loading block index…
            	Test:       	TestBitcoindEvents/Events_via_RPC_Polling
FAIL
```
which can also happen in real life if `bitcoind` is still loading
blocks. This commit adds a retry logic to wait for `bitcoind` to finish
its startup.
  • Loading branch information
yyforyongyu committed Oct 17, 2023
1 parent 506e948 commit 148ac23
Showing 1 changed file with 65 additions and 1 deletion.
66 changes: 65 additions & 1 deletion chain/bitcoind_conn.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package chain

import (
"errors"
"fmt"
"net"
"strings"
"sync"
"sync/atomic"
"time"

"github.com/btcsuite/btcd/btcjson"
"github.com/btcsuite/btcd/chaincfg"
Expand Down Expand Up @@ -38,8 +41,21 @@ const (
// errBlockPrunedStr is the error message returned by bitcoind upon
// calling GetBlock on a pruned block.
errBlockPrunedStr = "Block not available (pruned data)"

// errStillLoadingCode is the error code returned when an RPC request
// is made but bitcoind is still in the process of loading or verifying
// blocks.
errStillLoadingCode = "-28"

// bitcoindStartTimeout is the time we wait for bitcoind to finish
// loading and verifying blocks and become ready to serve RPC requests.
bitcoindStartTimeout = 30 * time.Second
)

// ErrBitcoindStartTimeout is returned when the bitcoind daemon fails to load
// and verify blocks under 30s during startup.
var ErrBitcoindStartTimeout = errors.New("bitcoind start timeout")

// BitcoindConfig contains all of the parameters required to establish a
// connection to a bitcoind's RPC.
type BitcoindConfig struct {
Expand Down Expand Up @@ -311,9 +327,57 @@ func (c *BitcoindConn) sendTxToClients() {
}
}

// getBlockHashDuringStartup is used to call the getblockhash RPC during
// startup. It catches the case where bitcoind is still in the process of
// loading blocks, which returns the following error,
// - "-28: Loading block index..."
// - "-28: Verifying blocks..."
// In this case we'd retry every second until we time out after 30 seconds.
func getBlockHashDuringStartup(
client *rpcclient.Client) (*chainhash.Hash, error) {

hash, err := client.GetBlockHash(0)

// Exit early if there's no error.
if err == nil {
return hash, nil
}

// If the error doesn't start with "-28", it's an unexpected error so
// we exit with it.
if !strings.Contains(err.Error(), errStillLoadingCode) {
return nil, err
}

// Starts the timeout ticker(30s).
timeout := time.After(bitcoindStartTimeout)

// Otherwise, we'd retry calling getblockhash or time out after 30s.
for {
select {
case <-timeout:
return nil, ErrBitcoindStartTimeout

// Retry every second.
case <-time.After(1 * time.Second):
hash, err = client.GetBlockHash(0)
// If there's no error, we return the hash.
if err == nil {
return hash, nil
}

// Otherwise, retry until we time out. We also check if
// the error returned here is still expected.
if !strings.Contains(err.Error(), errStillLoadingCode) {
return nil, err
}
}
}
}

// getCurrentNet returns the network on which the bitcoind node is running.
func getCurrentNet(client *rpcclient.Client) (wire.BitcoinNet, error) {
hash, err := client.GetBlockHash(0)
hash, err := getBlockHashDuringStartup(client)
if err != nil {
return 0, err
}
Expand Down

0 comments on commit 148ac23

Please sign in to comment.