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 13, 2023
1 parent 5d168f9 commit 8df9fb9
Showing 1 changed file with 55 additions and 1 deletion.
56 changes: 55 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 @@ -40,6 +43,10 @@ const (
errBlockPrunedStr = "Block not available (pruned data)"
)

// 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 @@ -306,9 +313,56 @@ 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) {

errCode := "-28"
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(), errCode) {
return nil, err
}

// Otherwise, we'd retry calling getblockhash or time out after 30s.
for {
select {
// Timeout after 30s.
case <-time.After(30 * time.Second):
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(), errCode) {
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 8df9fb9

Please sign in to comment.