Refactor sync code to own file
parent
a132f43112
commit
9bdb83deff
364
blockbook.go
364
blockbook.go
|
@ -6,7 +6,6 @@ import (
|
||||||
"flag"
|
"flag"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"sync"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -40,7 +39,7 @@ var (
|
||||||
|
|
||||||
dbPath = flag.String("path", "./data", "path to address index directory")
|
dbPath = flag.String("path", "./data", "path to address index directory")
|
||||||
|
|
||||||
blockHeight = flag.Int("blockheight", -1, "height of the starting block")
|
blockFrom = flag.Int("blockheight", -1, "height of the starting block")
|
||||||
blockUntil = flag.Int("blockuntil", -1, "height of the final block")
|
blockUntil = flag.Int("blockuntil", -1, "height of the final block")
|
||||||
rollbackHeight = flag.Int("rollback", -1, "rollback to the given height and quit")
|
rollbackHeight = flag.Int("rollback", -1, "rollback to the given height and quit")
|
||||||
|
|
||||||
|
@ -74,6 +73,7 @@ var (
|
||||||
chain *bchain.BitcoinRPC
|
chain *bchain.BitcoinRPC
|
||||||
mempool *bchain.Mempool
|
mempool *bchain.Mempool
|
||||||
index *db.RocksDB
|
index *db.RocksDB
|
||||||
|
syncWorker *db.SyncWorker
|
||||||
callbacksOnNewBlockHash []func(hash string)
|
callbacksOnNewBlockHash []func(hash string)
|
||||||
callbacksOnNewTxAddr []func(txid string, addr string)
|
callbacksOnNewTxAddr []func(txid string, addr string)
|
||||||
chanOsSignal chan os.Signal
|
chanOsSignal chan os.Signal
|
||||||
|
@ -140,8 +140,13 @@ func main() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
syncWorker, err = db.NewSyncWorker(index, chain, *syncWorkers, *syncChunk, uint32(*blockFrom), *dryRun, chanOsSignal)
|
||||||
|
if err != nil {
|
||||||
|
glog.Fatalf("NewSyncWorker %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
if *synchronize {
|
if *synchronize {
|
||||||
if err := resyncIndex(nil); err != nil {
|
if err := syncWorker.ResyncIndex(nil); err != nil {
|
||||||
glog.Error("resyncIndex ", err)
|
glog.Error("resyncIndex ", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -210,11 +215,11 @@ func main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if *blockHeight >= 0 {
|
if *blockFrom >= 0 {
|
||||||
if *blockUntil < 0 {
|
if *blockUntil < 0 {
|
||||||
*blockUntil = *blockHeight
|
*blockUntil = *blockFrom
|
||||||
}
|
}
|
||||||
height := uint32(*blockHeight)
|
height := uint32(*blockFrom)
|
||||||
until := uint32(*blockUntil)
|
until := uint32(*blockUntil)
|
||||||
address := *queryAddress
|
address := *queryAddress
|
||||||
|
|
||||||
|
@ -229,12 +234,7 @@ func main() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else if !*synchronize {
|
} else if !*synchronize {
|
||||||
if err = connectBlocksParallelInChunks(
|
if err = syncWorker.ConnectBlocksParallelInChunks(height, until); err != nil {
|
||||||
height,
|
|
||||||
until,
|
|
||||||
*syncChunk,
|
|
||||||
*syncWorkers,
|
|
||||||
); err != nil {
|
|
||||||
glog.Error("connectBlocksParallelInChunks ", err)
|
glog.Error("connectBlocksParallelInChunks ", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -281,7 +281,7 @@ func syncIndexLoop() {
|
||||||
glog.Info("syncIndexLoop starting")
|
glog.Info("syncIndexLoop starting")
|
||||||
// resync index about every 15 minutes if there are no chanSyncIndex requests, with debounce 1 second
|
// resync index about every 15 minutes if there are no chanSyncIndex requests, with debounce 1 second
|
||||||
tickAndDebounce(resyncIndexPeriodMs*time.Millisecond, debounceResyncIndexMs*time.Millisecond, chanSyncIndex, func() {
|
tickAndDebounce(resyncIndexPeriodMs*time.Millisecond, debounceResyncIndexMs*time.Millisecond, chanSyncIndex, func() {
|
||||||
if err := resyncIndex(onNewBlockHash); err != nil {
|
if err := syncWorker.ResyncIndex(onNewBlockHash); err != nil {
|
||||||
glog.Error("syncIndexLoop ", errors.ErrorStack(err))
|
glog.Error("syncIndexLoop ", errors.ErrorStack(err))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -355,341 +355,3 @@ func printResult(txid string, vout uint32, isOutput bool) error {
|
||||||
glog.Info(txid, vout, isOutput)
|
glog.Info(txid, vout, isOutput)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func resyncIndex(onNewBlock func(hash string)) error {
|
|
||||||
remote, err := chain.GetBestBlockHash()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
localBestHeight, local, err := index.GetBestBlock()
|
|
||||||
if err != nil {
|
|
||||||
local = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the locally indexed block is the same as the best block on the
|
|
||||||
// network, we're done.
|
|
||||||
if local == remote {
|
|
||||||
glog.Infof("resync: synced on %d %s", localBestHeight, local)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var header *bchain.BlockHeader
|
|
||||||
if local != "" {
|
|
||||||
// Is local tip on the best chain?
|
|
||||||
header, err = chain.GetBlockHeader(local)
|
|
||||||
forked := false
|
|
||||||
if err != nil {
|
|
||||||
if e, ok := err.(*bchain.RPCError); ok && e.Message == "Block not found" {
|
|
||||||
forked = true
|
|
||||||
} else {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if header.Confirmations < 0 {
|
|
||||||
forked = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if forked {
|
|
||||||
// find and disconnect forked blocks and then synchronize again
|
|
||||||
glog.Info("resync: local is forked")
|
|
||||||
var height uint32
|
|
||||||
for height = localBestHeight - 1; height >= 0; height-- {
|
|
||||||
local, err = index.GetBlockHash(height)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
remote, err = chain.GetBlockHash(height)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if local == remote {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
err = index.DisconnectBlocks(height+1, localBestHeight)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return resyncIndex(onNewBlock)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
startHeight := uint32(0)
|
|
||||||
var hash string
|
|
||||||
if header != nil {
|
|
||||||
glog.Info("resync: local is behind")
|
|
||||||
hash = header.Next
|
|
||||||
startHeight = localBestHeight
|
|
||||||
} else {
|
|
||||||
// If the local block is missing, we're indexing from the genesis block
|
|
||||||
// or from the start block specified by flags
|
|
||||||
if *blockHeight > 0 {
|
|
||||||
startHeight = uint32(*blockHeight)
|
|
||||||
}
|
|
||||||
glog.Info("resync: genesis from block ", startHeight)
|
|
||||||
hash, err = chain.GetBlockHash(startHeight)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if parallel operation is enabled and the number of blocks to be connected is large,
|
|
||||||
// use parallel routine to load majority of blocks
|
|
||||||
if *syncWorkers > 1 {
|
|
||||||
chainBestHeight, err := chain.GetBestBlockHeight()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if chainBestHeight-startHeight > uint32(*syncChunk) {
|
|
||||||
glog.Infof("resync: parallel sync of blocks %d-%d, using %d workers", startHeight, chainBestHeight, *syncWorkers)
|
|
||||||
err = connectBlocksParallel(
|
|
||||||
startHeight,
|
|
||||||
chainBestHeight,
|
|
||||||
*syncWorkers,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// after parallel load finish the sync using standard way,
|
|
||||||
// new blocks may have been created in the meantime
|
|
||||||
return resyncIndex(onNewBlock)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return connectBlocks(hash, onNewBlock)
|
|
||||||
}
|
|
||||||
|
|
||||||
func connectBlocks(hash string, onNewBlock func(hash string)) error {
|
|
||||||
bch := make(chan blockResult, 8)
|
|
||||||
done := make(chan struct{})
|
|
||||||
defer close(done)
|
|
||||||
|
|
||||||
go getBlockChain(hash, bch, done)
|
|
||||||
|
|
||||||
var lastRes blockResult
|
|
||||||
for res := range bch {
|
|
||||||
lastRes = res
|
|
||||||
if res.err != nil {
|
|
||||||
return res.err
|
|
||||||
}
|
|
||||||
err := index.ConnectBlock(res.block)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if onNewBlock != nil {
|
|
||||||
onNewBlock(res.block.Hash)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if lastRes.block != nil {
|
|
||||||
glog.Infof("resync: synced on %d %s", lastRes.block.Height, lastRes.block.Hash)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func connectBlocksParallel(
|
|
||||||
lower uint32,
|
|
||||||
higher uint32,
|
|
||||||
numWorkers int,
|
|
||||||
) error {
|
|
||||||
type hashHeight struct {
|
|
||||||
hash string
|
|
||||||
height uint32
|
|
||||||
}
|
|
||||||
var err error
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
hch := make(chan hashHeight, numWorkers)
|
|
||||||
running := make([]bool, numWorkers)
|
|
||||||
work := func(i int) {
|
|
||||||
defer wg.Done()
|
|
||||||
var err error
|
|
||||||
var block *bchain.Block
|
|
||||||
for hh := range hch {
|
|
||||||
running[i] = true
|
|
||||||
for {
|
|
||||||
block, err = chain.GetBlockWithoutHeader(hh.hash, hh.height)
|
|
||||||
if err != nil {
|
|
||||||
glog.Error("Connect block error ", err, ". Retrying...")
|
|
||||||
time.Sleep(time.Millisecond * 500)
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if *dryRun {
|
|
||||||
running[i] = false
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
err = index.ConnectBlock(block)
|
|
||||||
if err != nil {
|
|
||||||
glog.Error("Connect block ", hh.height, " ", hh.hash, " error ", err)
|
|
||||||
}
|
|
||||||
running[i] = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for i := 0; i < numWorkers; i++ {
|
|
||||||
wg.Add(1)
|
|
||||||
go work(i)
|
|
||||||
}
|
|
||||||
|
|
||||||
var hash string
|
|
||||||
ConnectLoop:
|
|
||||||
for h := lower; h <= higher; {
|
|
||||||
select {
|
|
||||||
case <-chanOsSignal:
|
|
||||||
// wait for the workers to finish block
|
|
||||||
i := 0
|
|
||||||
WaitAgain:
|
|
||||||
for ; i < 60; i++ {
|
|
||||||
for _, r := range running {
|
|
||||||
if r {
|
|
||||||
glog.Info("Waiting ", i, "s for workers to finish ", running)
|
|
||||||
time.Sleep(time.Millisecond * 1000)
|
|
||||||
continue WaitAgain
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
err = errors.Errorf("connectBlocksParallel interrupted at height %d", h)
|
|
||||||
break ConnectLoop
|
|
||||||
default:
|
|
||||||
hash, err = chain.GetBlockHash(h)
|
|
||||||
if err != nil {
|
|
||||||
glog.Error("GetBlockHash error ", err)
|
|
||||||
time.Sleep(time.Millisecond * 500)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
hch <- hashHeight{hash, h}
|
|
||||||
if h > 0 && h%1000 == 0 {
|
|
||||||
glog.Info("connecting block ", h, " ", hash)
|
|
||||||
}
|
|
||||||
h++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
close(hch)
|
|
||||||
wg.Wait()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func connectBlocksParallelInChunks(
|
|
||||||
lower uint32,
|
|
||||||
higher uint32,
|
|
||||||
chunkSize int,
|
|
||||||
numWorkers int,
|
|
||||||
) error {
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
|
|
||||||
work := func(i int) {
|
|
||||||
defer wg.Done()
|
|
||||||
|
|
||||||
offset := uint32(chunkSize * i)
|
|
||||||
stride := uint32(chunkSize * numWorkers)
|
|
||||||
|
|
||||||
for low := lower + offset; low <= higher; low += stride {
|
|
||||||
high := low + uint32(chunkSize-1)
|
|
||||||
if high > higher {
|
|
||||||
high = higher
|
|
||||||
}
|
|
||||||
err := connectBlockChunk(low, high)
|
|
||||||
if err != nil {
|
|
||||||
if e, ok := err.(*bchain.RPCError); ok && (e.Message == "Block height out of range" || e.Message == "Block not found") {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
glog.Fatalf("connectBlocksParallel %d-%d %v", low, high, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for i := 0; i < numWorkers; i++ {
|
|
||||||
wg.Add(1)
|
|
||||||
go work(i)
|
|
||||||
}
|
|
||||||
wg.Wait()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func connectBlockChunk(
|
|
||||||
lower uint32,
|
|
||||||
higher uint32,
|
|
||||||
) error {
|
|
||||||
connected, err := isBlockConnected(higher)
|
|
||||||
if err != nil || connected {
|
|
||||||
// if higher is over the best block, continue with lower block, otherwise return error
|
|
||||||
if e, ok := err.(*bchain.RPCError); !ok || e.Message != "Block height out of range" {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
height := lower
|
|
||||||
hash, err := chain.GetBlockHash(lower)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for height <= higher {
|
|
||||||
block, err := chain.GetBlock(hash)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
hash = block.Next
|
|
||||||
height = block.Height + 1
|
|
||||||
if *dryRun {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
err = index.ConnectBlock(block)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if block.Height%1000 == 0 {
|
|
||||||
glog.Info("connected block ", block.Height, " ", block.Hash)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func isBlockConnected(
|
|
||||||
height uint32,
|
|
||||||
) (bool, error) {
|
|
||||||
local, err := index.GetBlockHash(height)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
remote, err := chain.GetBlockHash(height)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
if local != remote {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type blockResult struct {
|
|
||||||
block *bchain.Block
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
func getBlockChain(
|
|
||||||
hash string,
|
|
||||||
out chan blockResult,
|
|
||||||
done chan struct{},
|
|
||||||
) {
|
|
||||||
defer close(out)
|
|
||||||
|
|
||||||
for hash != "" {
|
|
||||||
select {
|
|
||||||
case <-done:
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
block, err := chain.GetBlock(hash)
|
|
||||||
if err != nil {
|
|
||||||
out <- blockResult{err: err}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
hash = block.Next
|
|
||||||
out <- blockResult{block: block}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,349 @@
|
||||||
|
package db
|
||||||
|
|
||||||
|
import (
|
||||||
|
"blockbook/bchain"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/golang/glog"
|
||||||
|
"github.com/juju/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SyncWorker is handle to SyncWorker
|
||||||
|
type SyncWorker struct {
|
||||||
|
db *RocksDB
|
||||||
|
chain *bchain.BitcoinRPC
|
||||||
|
syncWorkers, syncChunk int
|
||||||
|
dryRun bool
|
||||||
|
startHeight uint32
|
||||||
|
chanOsSignal chan os.Signal
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSyncWorker creates new SyncWorker and returns its handle
|
||||||
|
func NewSyncWorker(db *RocksDB, chain *bchain.BitcoinRPC, syncWorkers, syncChunk int, minStartHeight uint32, dryRun bool, chanOsSignal chan os.Signal) (*SyncWorker, error) {
|
||||||
|
return &SyncWorker{
|
||||||
|
db: db,
|
||||||
|
chain: chain,
|
||||||
|
syncWorkers: syncWorkers,
|
||||||
|
syncChunk: syncChunk,
|
||||||
|
dryRun: dryRun,
|
||||||
|
startHeight: minStartHeight,
|
||||||
|
chanOsSignal: chanOsSignal,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResyncIndex synchronizes index to the top of the blockchain
|
||||||
|
// onNewBlock is called when new block is connected, but not in initial parallel sync
|
||||||
|
func (w *SyncWorker) ResyncIndex(onNewBlock func(hash string)) error {
|
||||||
|
remote, err := w.chain.GetBestBlockHash()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
localBestHeight, local, err := w.db.GetBestBlock()
|
||||||
|
if err != nil {
|
||||||
|
local = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the locally indexed block is the same as the best block on the
|
||||||
|
// network, we're done.
|
||||||
|
if local == remote {
|
||||||
|
glog.Infof("resync: synced on %d %s", localBestHeight, local)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var header *bchain.BlockHeader
|
||||||
|
if local != "" {
|
||||||
|
// Is local tip on the best chain?
|
||||||
|
header, err = w.chain.GetBlockHeader(local)
|
||||||
|
forked := false
|
||||||
|
if err != nil {
|
||||||
|
if e, ok := err.(*bchain.RPCError); ok && e.Message == "Block not found" {
|
||||||
|
forked = true
|
||||||
|
} else {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if header.Confirmations < 0 {
|
||||||
|
forked = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if forked {
|
||||||
|
// find and disconnect forked blocks and then synchronize again
|
||||||
|
glog.Info("resync: local is forked")
|
||||||
|
var height uint32
|
||||||
|
for height = localBestHeight - 1; height >= 0; height-- {
|
||||||
|
local, err = w.db.GetBlockHash(height)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
remote, err = w.chain.GetBlockHash(height)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if local == remote {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = w.db.DisconnectBlocks(height+1, localBestHeight)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return w.ResyncIndex(onNewBlock)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var hash string
|
||||||
|
if header != nil {
|
||||||
|
glog.Info("resync: local is behind")
|
||||||
|
hash = 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)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if parallel operation is enabled and the number of blocks to be connected is large,
|
||||||
|
// use parallel routine to load majority of blocks
|
||||||
|
if w.syncWorkers > 1 {
|
||||||
|
chainBestHeight, err := w.chain.GetBestBlockHeight()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if chainBestHeight-w.startHeight > uint32(w.syncChunk) {
|
||||||
|
glog.Infof("resync: parallel sync of blocks %d-%d, using %d workers", w.startHeight, chainBestHeight, w.syncWorkers)
|
||||||
|
err = w.connectBlocksParallel(w.startHeight, chainBestHeight)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// after parallel load finish the sync using standard way,
|
||||||
|
// new blocks may have been created in the meantime
|
||||||
|
return w.ResyncIndex(onNewBlock)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return w.connectBlocks(hash, onNewBlock)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *SyncWorker) connectBlocks(hash string, onNewBlock func(hash string)) error {
|
||||||
|
bch := make(chan blockResult, 8)
|
||||||
|
done := make(chan struct{})
|
||||||
|
defer close(done)
|
||||||
|
|
||||||
|
go w.getBlockChain(hash, bch, done)
|
||||||
|
|
||||||
|
var lastRes blockResult
|
||||||
|
for res := range bch {
|
||||||
|
lastRes = res
|
||||||
|
if res.err != nil {
|
||||||
|
return res.err
|
||||||
|
}
|
||||||
|
err := w.db.ConnectBlock(res.block)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if onNewBlock != nil {
|
||||||
|
onNewBlock(res.block.Hash)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if lastRes.block != nil {
|
||||||
|
glog.Infof("resync: synced on %d %s", lastRes.block.Height, lastRes.block.Hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *SyncWorker) connectBlocksParallel(lower, higher uint32) error {
|
||||||
|
type hashHeight struct {
|
||||||
|
hash string
|
||||||
|
height uint32
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
hch := make(chan hashHeight, w.syncWorkers)
|
||||||
|
running := make([]bool, w.syncWorkers)
|
||||||
|
work := func(i int) {
|
||||||
|
defer wg.Done()
|
||||||
|
var err error
|
||||||
|
var block *bchain.Block
|
||||||
|
for hh := range hch {
|
||||||
|
running[i] = true
|
||||||
|
for {
|
||||||
|
block, err = w.chain.GetBlockWithoutHeader(hh.hash, hh.height)
|
||||||
|
if err != nil {
|
||||||
|
glog.Error("Connect block error ", err, ". Retrying...")
|
||||||
|
time.Sleep(time.Millisecond * 500)
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if w.dryRun {
|
||||||
|
running[i] = false
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
err = w.db.ConnectBlock(block)
|
||||||
|
if err != nil {
|
||||||
|
glog.Error("Connect block ", hh.height, " ", hh.hash, " error ", err)
|
||||||
|
}
|
||||||
|
running[i] = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i := 0; i < w.syncWorkers; i++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go work(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
var hash string
|
||||||
|
ConnectLoop:
|
||||||
|
for h := lower; h <= higher; {
|
||||||
|
select {
|
||||||
|
case <-w.chanOsSignal:
|
||||||
|
// wait for the workers to finish block
|
||||||
|
i := 0
|
||||||
|
WaitAgain:
|
||||||
|
for ; i < 60; i++ {
|
||||||
|
for _, r := range running {
|
||||||
|
if r {
|
||||||
|
glog.Info("Waiting ", i, "s for workers to finish ", running)
|
||||||
|
time.Sleep(time.Millisecond * 1000)
|
||||||
|
continue WaitAgain
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
err = errors.Errorf("connectBlocksParallel interrupted at height %d", h)
|
||||||
|
break ConnectLoop
|
||||||
|
default:
|
||||||
|
hash, err = w.chain.GetBlockHash(h)
|
||||||
|
if err != nil {
|
||||||
|
glog.Error("GetBlockHash error ", err)
|
||||||
|
time.Sleep(time.Millisecond * 500)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
hch <- hashHeight{hash, h}
|
||||||
|
if h > 0 && h%1000 == 0 {
|
||||||
|
glog.Info("connecting block ", h, " ", hash)
|
||||||
|
}
|
||||||
|
h++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
close(hch)
|
||||||
|
wg.Wait()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
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 e, ok := err.(*bchain.RPCError); !ok || e.Message != "Block height out of range" {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
height := lower
|
||||||
|
hash, err := w.chain.GetBlockHash(lower)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for height <= higher {
|
||||||
|
block, err := w.chain.GetBlock(hash)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
hash = block.Next
|
||||||
|
height = block.Height + 1
|
||||||
|
if w.dryRun {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
err = w.db.ConnectBlock(block)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if block.Height%1000 == 0 {
|
||||||
|
glog.Info("connected block ", block.Height, " ", block.Hash)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConnectBlocksParallelInChunks connect blocks in chunks
|
||||||
|
func (w *SyncWorker) ConnectBlocksParallelInChunks(lower, higher uint32) error {
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
|
work := func(i int) {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
offset := uint32(w.syncChunk * i)
|
||||||
|
stride := uint32(w.syncChunk * w.syncWorkers)
|
||||||
|
|
||||||
|
for low := lower + offset; low <= higher; low += stride {
|
||||||
|
high := low + uint32(w.syncChunk-1)
|
||||||
|
if high > higher {
|
||||||
|
high = higher
|
||||||
|
}
|
||||||
|
err := w.connectBlockChunk(low, high)
|
||||||
|
if err != nil {
|
||||||
|
if e, ok := err.(*bchain.RPCError); ok && (e.Message == "Block height out of range" || e.Message == "Block not found") {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
glog.Fatalf("connectBlocksParallel %d-%d %v", low, high, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i := 0; i < w.syncWorkers; i++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go work(i)
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *SyncWorker) isBlockConnected(height uint32) (bool, error) {
|
||||||
|
local, err := w.db.GetBlockHash(height)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
remote, err := w.db.GetBlockHash(height)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if local != remote {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type blockResult struct {
|
||||||
|
block *bchain.Block
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *SyncWorker) getBlockChain(hash string, out chan blockResult, done chan struct{}) {
|
||||||
|
defer close(out)
|
||||||
|
|
||||||
|
for hash != "" {
|
||||||
|
select {
|
||||||
|
case <-done:
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
block, err := w.chain.GetBlock(hash)
|
||||||
|
if err != nil {
|
||||||
|
out <- blockResult{err: err}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
hash = block.Next
|
||||||
|
out <- blockResult{block: block}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue