From 341393b1ebb9cafad9afdd50fc364f586e77d166 Mon Sep 17 00:00:00 2001 From: Martin Boehm Date: Mon, 26 Mar 2018 15:17:44 +0200 Subject: [PATCH 1/2] Define error ErrBlockNotFound and implement it in btc --- bchain/coins/btc/bitcoinrpc.go | 20 ++++++++++++++++++++ bchain/types.go | 13 ++++++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/bchain/coins/btc/bitcoinrpc.go b/bchain/coins/btc/bitcoinrpc.go index a85f24ca..b9607472 100644 --- a/bchain/coins/btc/bitcoinrpc.go +++ b/bchain/coins/btc/bitcoinrpc.go @@ -331,6 +331,11 @@ func (b *BitcoinRPC) GetBlockChainInfo() (string, error) { return res.Result.Chain, nil } +func isErrBlockNotFound(err error) bool { + return err.Error() == "Block not found" || + err.Error() == "Block height out of range" +} + // GetBlockHash returns hash of block in best-block-chain at given height. func (b *BitcoinRPC) GetBlockHash(height uint32) (string, error) { glog.V(1).Info("rpc: getblockhash ", height) @@ -344,6 +349,9 @@ func (b *BitcoinRPC) GetBlockHash(height uint32) (string, error) { return "", errors.Annotatef(err, "height %v", height) } if res.Error != nil { + if isErrBlockNotFound(res.Error) { + return "", bchain.ErrBlockNotFound + } return "", errors.Annotatef(res.Error, "height %v", height) } return res.Result, nil @@ -363,6 +371,9 @@ func (b *BitcoinRPC) GetBlockHeader(hash string) (*bchain.BlockHeader, error) { return nil, errors.Annotatef(err, "hash %v", hash) } if res.Error != nil { + if isErrBlockNotFound(res.Error) { + return nil, bchain.ErrBlockNotFound + } return nil, errors.Annotatef(res.Error, "hash %v", hash) } return &res.Result, nil @@ -423,6 +434,9 @@ func (b *BitcoinRPC) GetBlockRaw(hash string) ([]byte, error) { return nil, errors.Annotatef(err, "hash %v", hash) } if res.Error != nil { + if isErrBlockNotFound(res.Error) { + return nil, bchain.ErrBlockNotFound + } return nil, errors.Annotatef(res.Error, "hash %v", hash) } return hex.DecodeString(res.Result) @@ -443,6 +457,9 @@ func (b *BitcoinRPC) GetBlockList(hash string) (*bchain.Block, error) { return nil, errors.Annotatef(err, "hash %v", hash) } if res.Error != nil { + if isErrBlockNotFound(res.Error) { + return nil, bchain.ErrBlockNotFound + } return nil, errors.Annotatef(res.Error, "hash %v", hash) } @@ -475,6 +492,9 @@ func (b *BitcoinRPC) GetBlockFull(hash string) (*bchain.Block, error) { return nil, errors.Annotatef(err, "hash %v", hash) } if res.Error != nil { + if isErrBlockNotFound(res.Error) { + return nil, bchain.ErrBlockNotFound + } return nil, errors.Annotatef(res.Error, "hash %v", hash) } return &res.Result, nil diff --git a/bchain/types.go b/bchain/types.go index 64a72a2c..e482ee39 100644 --- a/bchain/types.go +++ b/bchain/types.go @@ -1,6 +1,17 @@ package bchain -import "fmt" +import ( + "errors" + "fmt" +) + +// errors with specific meaning returned by blockchain rpc +var ( + // ErrBlockNotFound is returned when block is not found + // either unknown hash or too high height + // can be returned from GetBlockHash, GetBlockHeader, GetBlock + ErrBlockNotFound = errors.New("Block not found") +) type ScriptSig struct { // Asm string `json:"asm"` From b1694b4a61af95d16c759465f654d68036f1b069 Mon Sep 17 00:00:00 2001 From: Martin Boehm Date: Mon, 26 Mar 2018 16:44:54 +0200 Subject: [PATCH 2/2] Support sync of blockchains without block.next hash --- bchain/coins/btc/bitcoinrpc.go | 13 ++++++++--- db/sync.go | 40 +++++++++++++++++++--------------- 2 files changed, 32 insertions(+), 21 deletions(-) diff --git a/bchain/coins/btc/bitcoinrpc.go b/bchain/coins/btc/bitcoinrpc.go index b9607472..9b8a2659 100644 --- a/bchain/coins/btc/bitcoinrpc.go +++ b/bchain/coins/btc/bitcoinrpc.go @@ -331,9 +331,9 @@ func (b *BitcoinRPC) GetBlockChainInfo() (string, error) { return res.Result.Chain, nil } -func isErrBlockNotFound(err error) bool { - return err.Error() == "Block not found" || - err.Error() == "Block height out of range" +func isErrBlockNotFound(err *bchain.RPCError) bool { + return err.Message == "Block not found" || + err.Message == "Block height out of range" } // GetBlockHash returns hash of block in best-block-chain at given height. @@ -381,6 +381,13 @@ func (b *BitcoinRPC) GetBlockHeader(hash string) (*bchain.BlockHeader, error) { // GetBlock returns block with given hash. func (b *BitcoinRPC) GetBlock(hash string, height uint32) (*bchain.Block, error) { + var err error + if hash == "" && height > 0 { + hash, err = b.GetBlockHash(height) + if err != nil { + return nil, err + } + } if !b.ParseBlocks { return b.GetBlockFull(hash) } diff --git a/db/sync.go b/db/sync.go index b749fe29..c1e6b9e2 100644 --- a/db/sync.go +++ b/db/sync.go @@ -4,7 +4,6 @@ import ( "blockbook/bchain" "blockbook/common" "os" - "strings" "sync" "sync/atomic" "time" @@ -20,6 +19,7 @@ type SyncWorker struct { syncWorkers, syncChunk int dryRun bool startHeight uint32 + startHash string chanOsSignal chan os.Signal metrics *common.Metrics } @@ -67,10 +67,6 @@ func (w *SyncWorker) ResyncIndex(onNewBlock func(hash string)) error { return err } -func isError(err error, s string) bool { - return strings.Contains(err.Error(), s) -} - func (w *SyncWorker) resyncIndex(onNewBlock func(hash string)) error { remote, err := w.chain.GetBestBlockHash() if err != nil { @@ -94,7 +90,7 @@ func (w *SyncWorker) resyncIndex(onNewBlock func(hash string)) error { header, err = w.chain.GetBlockHeader(local) forked := false if err != nil { - if isError(err, "Block not found") { + if err == bchain.ErrBlockNotFound { forked = true } else { return err @@ -132,16 +128,15 @@ func (w *SyncWorker) resyncIndex(onNewBlock func(hash string)) error { } } - var hash string if header != nil { glog.Info("resync: local is behind") - hash = header.Next + w.startHash = header.Next w.startHeight = localBestHeight } else { // If the local block is missing, we're indexing from the genesis block // or from the start block specified by flags glog.Info("resync: genesis from block ", w.startHeight) - hash, err = w.chain.GetBlockHash(w.startHeight) + w.startHash, err = w.chain.GetBlockHash(w.startHeight) if err != nil { return err } @@ -166,15 +161,15 @@ func (w *SyncWorker) resyncIndex(onNewBlock func(hash string)) error { } } - return w.connectBlocks(hash, onNewBlock) + return w.connectBlocks(onNewBlock) } -func (w *SyncWorker) connectBlocks(hash string, onNewBlock func(hash string)) error { +func (w *SyncWorker) connectBlocks(onNewBlock func(hash string)) error { bch := make(chan blockResult, 8) done := make(chan struct{}) defer close(done) - go w.getBlockChain(hash, bch, done) + go w.getBlockChain(bch, done) var lastRes blockResult for res := range bch { @@ -276,7 +271,7 @@ func (w *SyncWorker) connectBlockChunk(lower, higher uint32) error { connected, err := w.isBlockConnected(higher) if err != nil || connected { // if higher is over the best block, continue with lower block, otherwise return error - if isError(err, "Block height out of range") { + if err != bchain.ErrBlockNotFound { return err } } @@ -327,7 +322,7 @@ func (w *SyncWorker) ConnectBlocksParallelInChunks(lower, higher uint32) error { } err := w.connectBlockChunk(low, high) if err != nil { - if isError(err, "Block height out of range") || isError(err, "Block not found") { + if err == bchain.ErrBlockNotFound { break } glog.Fatalf("connectBlocksParallel %d-%d %v", low, high, err) @@ -348,7 +343,7 @@ func (w *SyncWorker) isBlockConnected(height uint32) (bool, error) { if err != nil { return false, err } - remote, err := w.db.GetBlockHash(height) + remote, err := w.chain.GetBlockHash(height) if err != nil { return false, err } @@ -363,21 +358,30 @@ type blockResult struct { err error } -func (w *SyncWorker) getBlockChain(hash string, out chan blockResult, done chan struct{}) { +func (w *SyncWorker) getBlockChain(out chan blockResult, done chan struct{}) { defer close(out) - for hash != "" { + hash := w.startHash + height := w.startHeight + + // some coins do not return Next hash + // must loop until error + for { select { case <-done: return default: } - block, err := w.chain.GetBlock(hash, 0) + block, err := w.chain.GetBlock(hash, height) if err != nil { + if err == bchain.ErrBlockNotFound { + break + } out <- blockResult{err: err} return } hash = block.Next + height++ out <- blockResult{block: block} } }