parent
b242506df5
commit
dc3ee197f9
@ -0,0 +1,34 @@ |
|||||||
|
package stagedstreamsync |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common" |
||||||
|
"github.com/ethereum/go-ethereum/event" |
||||||
|
"github.com/harmony-one/harmony/consensus/engine" |
||||||
|
"github.com/harmony-one/harmony/core/types" |
||||||
|
"github.com/harmony-one/harmony/p2p/stream/common/streammanager" |
||||||
|
syncproto "github.com/harmony-one/harmony/p2p/stream/protocols/sync" |
||||||
|
sttypes "github.com/harmony-one/harmony/p2p/stream/types" |
||||||
|
) |
||||||
|
|
||||||
|
type syncProtocol interface { |
||||||
|
GetCurrentBlockNumber(ctx context.Context, opts ...syncproto.Option) (uint64, sttypes.StreamID, error) |
||||||
|
GetBlocksByNumber(ctx context.Context, bns []uint64, opts ...syncproto.Option) ([]*types.Block, sttypes.StreamID, error) |
||||||
|
GetRawBlocksByNumber(ctx context.Context, bns []uint64, opts ...syncproto.Option) ([][]byte, [][]byte, sttypes.StreamID, error) |
||||||
|
GetBlockHashes(ctx context.Context, bns []uint64, opts ...syncproto.Option) ([]common.Hash, sttypes.StreamID, error) |
||||||
|
GetBlocksByHashes(ctx context.Context, hs []common.Hash, opts ...syncproto.Option) ([]*types.Block, sttypes.StreamID, error) |
||||||
|
|
||||||
|
RemoveStream(stID sttypes.StreamID) // If a stream delivers invalid data, remove the stream
|
||||||
|
StreamFailed(stID sttypes.StreamID, reason string) |
||||||
|
SubscribeAddStreamEvent(ch chan<- streammanager.EvtStreamAdded) event.Subscription |
||||||
|
NumStreams() int |
||||||
|
} |
||||||
|
|
||||||
|
type blockChain interface { |
||||||
|
engine.ChainReader |
||||||
|
Engine() engine.Engine |
||||||
|
|
||||||
|
InsertChain(chain types.Blocks, verifyHeaders bool) (int, error) |
||||||
|
WriteCommitSig(blockNum uint64, lastCommits []byte) error |
||||||
|
} |
@ -0,0 +1,156 @@ |
|||||||
|
package stagedstreamsync |
||||||
|
|
||||||
|
import ( |
||||||
|
"time" |
||||||
|
|
||||||
|
"github.com/harmony-one/harmony/core/types" |
||||||
|
"github.com/harmony-one/harmony/internal/utils" |
||||||
|
"github.com/rs/zerolog" |
||||||
|
) |
||||||
|
|
||||||
|
// lastMileCache keeps the last 50 number blocks in memory cache
|
||||||
|
const lastMileCap = 50 |
||||||
|
|
||||||
|
type ( |
||||||
|
// beaconHelper is the helper for the beacon downloader. The beaconHelper is only started
|
||||||
|
// when node is running on side chain, listening to beacon client pub-sub message and
|
||||||
|
// insert the latest blocks to the beacon chain.
|
||||||
|
beaconHelper struct { |
||||||
|
bc blockChain |
||||||
|
blockC <-chan *types.Block |
||||||
|
// TODO: refactor this hook to consensus module. We'd better put it in
|
||||||
|
// consensus module under a subscription.
|
||||||
|
insertHook func() |
||||||
|
|
||||||
|
lastMileCache *blocksByNumber |
||||||
|
insertC chan insertTask |
||||||
|
closeC chan struct{} |
||||||
|
logger zerolog.Logger |
||||||
|
} |
||||||
|
|
||||||
|
insertTask struct { |
||||||
|
doneC chan struct{} |
||||||
|
} |
||||||
|
) |
||||||
|
|
||||||
|
func newBeaconHelper(bc blockChain, blockC <-chan *types.Block, insertHook func()) *beaconHelper { |
||||||
|
return &beaconHelper{ |
||||||
|
bc: bc, |
||||||
|
blockC: blockC, |
||||||
|
insertHook: insertHook, |
||||||
|
lastMileCache: newBlocksByNumber(lastMileCap), |
||||||
|
insertC: make(chan insertTask, 1), |
||||||
|
closeC: make(chan struct{}), |
||||||
|
logger: utils.Logger().With(). |
||||||
|
Str("module", "downloader"). |
||||||
|
Str("sub-module", "beacon helper"). |
||||||
|
Logger(), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (bh *beaconHelper) start() { |
||||||
|
go bh.loop() |
||||||
|
} |
||||||
|
|
||||||
|
func (bh *beaconHelper) close() { |
||||||
|
close(bh.closeC) |
||||||
|
} |
||||||
|
|
||||||
|
func (bh *beaconHelper) loop() { |
||||||
|
t := time.NewTicker(10 * time.Second) |
||||||
|
defer t.Stop() |
||||||
|
for { |
||||||
|
select { |
||||||
|
case <-t.C: |
||||||
|
bh.insertAsync() |
||||||
|
|
||||||
|
case b, ok := <-bh.blockC: |
||||||
|
if !ok { |
||||||
|
return // blockC closed. Node exited
|
||||||
|
} |
||||||
|
if b == nil { |
||||||
|
continue |
||||||
|
} |
||||||
|
bh.lastMileCache.push(b) |
||||||
|
bh.insertAsync() |
||||||
|
|
||||||
|
case it := <-bh.insertC: |
||||||
|
inserted, bn, err := bh.insertLastMileBlocks() |
||||||
|
numBlocksInsertedBeaconHelperCounter.Add(float64(inserted)) |
||||||
|
if err != nil { |
||||||
|
bh.logger.Error().Err(err).Msg(WrapStagedSyncMsg("insert last mile blocks error")) |
||||||
|
continue |
||||||
|
} |
||||||
|
bh.logger.Info().Int("inserted", inserted). |
||||||
|
Uint64("end height", bn). |
||||||
|
Uint32("shard", bh.bc.ShardID()). |
||||||
|
Msg(WrapStagedSyncMsg("insert last mile blocks")) |
||||||
|
|
||||||
|
close(it.doneC) |
||||||
|
|
||||||
|
case <-bh.closeC: |
||||||
|
return |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// insertAsync triggers the insert last mile without blocking
|
||||||
|
func (bh *beaconHelper) insertAsync() { |
||||||
|
select { |
||||||
|
case bh.insertC <- insertTask{ |
||||||
|
doneC: make(chan struct{}), |
||||||
|
}: |
||||||
|
default: |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// insertSync triggers the insert last mile while blocking
|
||||||
|
func (bh *beaconHelper) insertSync() { |
||||||
|
task := insertTask{ |
||||||
|
doneC: make(chan struct{}), |
||||||
|
} |
||||||
|
bh.insertC <- task |
||||||
|
<-task.doneC |
||||||
|
} |
||||||
|
|
||||||
|
func (bh *beaconHelper) insertLastMileBlocks() (inserted int, bn uint64, err error) { |
||||||
|
bn = bh.bc.CurrentBlock().NumberU64() + 1 |
||||||
|
for { |
||||||
|
b := bh.getNextBlock(bn) |
||||||
|
if b == nil { |
||||||
|
bn-- |
||||||
|
return |
||||||
|
} |
||||||
|
// TODO: Instruct the beacon helper to verify signatures. This may require some forks
|
||||||
|
// in pub-sub message (add commit sigs in node.block.sync messages)
|
||||||
|
if _, err = bh.bc.InsertChain(types.Blocks{b}, true); err != nil { |
||||||
|
bn-- |
||||||
|
return |
||||||
|
} |
||||||
|
bh.logger.Info().Uint64("number", b.NumberU64()).Msg(WrapStagedSyncMsg("Inserted block from beacon pub-sub")) |
||||||
|
|
||||||
|
if bh.insertHook != nil { |
||||||
|
bh.insertHook() |
||||||
|
} |
||||||
|
inserted++ |
||||||
|
bn++ |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (bh *beaconHelper) getNextBlock(expBN uint64) *types.Block { |
||||||
|
for bh.lastMileCache.len() > 0 { |
||||||
|
b := bh.lastMileCache.pop() |
||||||
|
if b == nil { |
||||||
|
return nil |
||||||
|
} |
||||||
|
if b.NumberU64() < expBN { |
||||||
|
continue |
||||||
|
} |
||||||
|
if b.NumberU64() > expBN { |
||||||
|
bh.lastMileCache.push(b) |
||||||
|
return nil |
||||||
|
} |
||||||
|
return b |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
@ -0,0 +1,133 @@ |
|||||||
|
package stagedstreamsync |
||||||
|
|
||||||
|
import ( |
||||||
|
"sync" |
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common" |
||||||
|
"github.com/harmony-one/harmony/core/types" |
||||||
|
sttypes "github.com/harmony-one/harmony/p2p/stream/types" |
||||||
|
"github.com/pkg/errors" |
||||||
|
) |
||||||
|
|
||||||
|
type getBlocksByHashManager struct { |
||||||
|
hashes []common.Hash |
||||||
|
pendings map[common.Hash]struct{} |
||||||
|
results map[common.Hash]blockResult |
||||||
|
whitelist []sttypes.StreamID |
||||||
|
|
||||||
|
lock sync.Mutex |
||||||
|
} |
||||||
|
|
||||||
|
func newGetBlocksByHashManager(hashes []common.Hash, whitelist []sttypes.StreamID) *getBlocksByHashManager { |
||||||
|
return &getBlocksByHashManager{ |
||||||
|
hashes: hashes, |
||||||
|
pendings: make(map[common.Hash]struct{}), |
||||||
|
results: make(map[common.Hash]blockResult), |
||||||
|
whitelist: whitelist, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (m *getBlocksByHashManager) getNextHashes() ([]common.Hash, []sttypes.StreamID, error) { |
||||||
|
m.lock.Lock() |
||||||
|
defer m.lock.Unlock() |
||||||
|
|
||||||
|
num := m.numBlocksPerRequest() |
||||||
|
hashes := make([]common.Hash, 0, num) |
||||||
|
if len(m.whitelist) == 0 { |
||||||
|
return nil, nil, errors.New("empty white list") |
||||||
|
} |
||||||
|
|
||||||
|
for _, hash := range m.hashes { |
||||||
|
if len(hashes) == num { |
||||||
|
break |
||||||
|
} |
||||||
|
_, ok1 := m.pendings[hash] |
||||||
|
_, ok2 := m.results[hash] |
||||||
|
if !ok1 && !ok2 { |
||||||
|
hashes = append(hashes, hash) |
||||||
|
} |
||||||
|
} |
||||||
|
sts := make([]sttypes.StreamID, len(m.whitelist)) |
||||||
|
copy(sts, m.whitelist) |
||||||
|
return hashes, sts, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (m *getBlocksByHashManager) numBlocksPerRequest() int { |
||||||
|
val := divideCeil(len(m.hashes), len(m.whitelist)) |
||||||
|
if val < BlockByHashesLowerCap { |
||||||
|
val = BlockByHashesLowerCap |
||||||
|
} |
||||||
|
if val > BlockByHashesUpperCap { |
||||||
|
val = BlockByHashesUpperCap |
||||||
|
} |
||||||
|
return val |
||||||
|
} |
||||||
|
|
||||||
|
func (m *getBlocksByHashManager) numRequests() int { |
||||||
|
return divideCeil(len(m.hashes), m.numBlocksPerRequest()) |
||||||
|
} |
||||||
|
|
||||||
|
func (m *getBlocksByHashManager) addResult(hashes []common.Hash, blocks []*types.Block, stid sttypes.StreamID) { |
||||||
|
m.lock.Lock() |
||||||
|
defer m.lock.Unlock() |
||||||
|
|
||||||
|
for i, hash := range hashes { |
||||||
|
block := blocks[i] |
||||||
|
delete(m.pendings, hash) |
||||||
|
m.results[hash] = blockResult{ |
||||||
|
block: block, |
||||||
|
stid: stid, |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (m *getBlocksByHashManager) handleResultError(hashes []common.Hash, stid sttypes.StreamID) { |
||||||
|
m.lock.Lock() |
||||||
|
defer m.lock.Unlock() |
||||||
|
|
||||||
|
m.removeStreamID(stid) |
||||||
|
|
||||||
|
for _, hash := range hashes { |
||||||
|
delete(m.pendings, hash) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (m *getBlocksByHashManager) getResults() ([]*types.Block, []sttypes.StreamID, error) { |
||||||
|
m.lock.Lock() |
||||||
|
defer m.lock.Unlock() |
||||||
|
|
||||||
|
blocks := make([]*types.Block, 0, len(m.hashes)) |
||||||
|
stids := make([]sttypes.StreamID, 0, len(m.hashes)) |
||||||
|
for _, hash := range m.hashes { |
||||||
|
if m.results[hash].block == nil { |
||||||
|
return nil, nil, errors.New("SANITY: nil block found") |
||||||
|
} |
||||||
|
blocks = append(blocks, m.results[hash].block) |
||||||
|
stids = append(stids, m.results[hash].stid) |
||||||
|
} |
||||||
|
return blocks, stids, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (m *getBlocksByHashManager) isDone() bool { |
||||||
|
m.lock.Lock() |
||||||
|
defer m.lock.Unlock() |
||||||
|
|
||||||
|
return len(m.results) == len(m.hashes) |
||||||
|
} |
||||||
|
|
||||||
|
func (m *getBlocksByHashManager) removeStreamID(target sttypes.StreamID) { |
||||||
|
// O(n^2) complexity. But considering the whitelist size is small, should not
|
||||||
|
// have performance issue.
|
||||||
|
loop: |
||||||
|
for i, stid := range m.whitelist { |
||||||
|
if stid == target { |
||||||
|
if i == len(m.whitelist) { |
||||||
|
m.whitelist = m.whitelist[:i] |
||||||
|
} else { |
||||||
|
m.whitelist = append(m.whitelist[:i], m.whitelist[i+1:]...) |
||||||
|
} |
||||||
|
goto loop |
||||||
|
} |
||||||
|
} |
||||||
|
return |
||||||
|
} |
@ -0,0 +1,75 @@ |
|||||||
|
package stagedstreamsync |
||||||
|
|
||||||
|
import ( |
||||||
|
"sync" |
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common" |
||||||
|
sttypes "github.com/harmony-one/harmony/p2p/stream/types" |
||||||
|
) |
||||||
|
|
||||||
|
type ( |
||||||
|
blockHashResults struct { |
||||||
|
bns []uint64 |
||||||
|
results []map[sttypes.StreamID]common.Hash |
||||||
|
|
||||||
|
lock sync.Mutex |
||||||
|
} |
||||||
|
) |
||||||
|
|
||||||
|
func newBlockHashResults(bns []uint64) *blockHashResults { |
||||||
|
results := make([]map[sttypes.StreamID]common.Hash, 0, len(bns)) |
||||||
|
for range bns { |
||||||
|
results = append(results, make(map[sttypes.StreamID]common.Hash)) |
||||||
|
} |
||||||
|
return &blockHashResults{ |
||||||
|
bns: bns, |
||||||
|
results: results, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (res *blockHashResults) addResult(hashes []common.Hash, stid sttypes.StreamID) { |
||||||
|
res.lock.Lock() |
||||||
|
defer res.lock.Unlock() |
||||||
|
|
||||||
|
for i, h := range hashes { |
||||||
|
if h == emptyHash { |
||||||
|
return // nil block hash reached
|
||||||
|
} |
||||||
|
res.results[i][stid] = h |
||||||
|
} |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
func (res *blockHashResults) computeLongestHashChain() ([]common.Hash, []sttypes.StreamID) { |
||||||
|
var ( |
||||||
|
whitelist map[sttypes.StreamID]struct{} |
||||||
|
hashChain []common.Hash |
||||||
|
) |
||||||
|
for _, result := range res.results { |
||||||
|
hash, nextWl := countHashMaxVote(result, whitelist) |
||||||
|
if hash == emptyHash { |
||||||
|
break |
||||||
|
} |
||||||
|
hashChain = append(hashChain, hash) |
||||||
|
whitelist = nextWl |
||||||
|
} |
||||||
|
|
||||||
|
sts := make([]sttypes.StreamID, 0, len(whitelist)) |
||||||
|
for st := range whitelist { |
||||||
|
sts = append(sts, st) |
||||||
|
} |
||||||
|
return hashChain, sts |
||||||
|
} |
||||||
|
|
||||||
|
func (res *blockHashResults) numBlocksWithResults() int { |
||||||
|
res.lock.Lock() |
||||||
|
defer res.lock.Unlock() |
||||||
|
|
||||||
|
cnt := 0 |
||||||
|
for _, result := range res.results { |
||||||
|
if len(result) != 0 { |
||||||
|
cnt++ |
||||||
|
} |
||||||
|
} |
||||||
|
return cnt |
||||||
|
} |
@ -0,0 +1,172 @@ |
|||||||
|
package stagedstreamsync |
||||||
|
|
||||||
|
import ( |
||||||
|
"sync" |
||||||
|
|
||||||
|
sttypes "github.com/harmony-one/harmony/p2p/stream/types" |
||||||
|
"github.com/ledgerwatch/erigon-lib/kv" |
||||||
|
"github.com/rs/zerolog" |
||||||
|
) |
||||||
|
|
||||||
|
type BlockDownloadDetails struct { |
||||||
|
loopID int |
||||||
|
streamID sttypes.StreamID |
||||||
|
} |
||||||
|
|
||||||
|
// blockDownloadManager is the helper structure for get blocks request management
|
||||||
|
type blockDownloadManager struct { |
||||||
|
chain blockChain |
||||||
|
tx kv.RwTx |
||||||
|
|
||||||
|
targetBN uint64 |
||||||
|
requesting map[uint64]struct{} // block numbers that have been assigned to workers but not received
|
||||||
|
processing map[uint64]struct{} // block numbers received requests but not inserted
|
||||||
|
retries *prioritizedNumbers // requests where error happens
|
||||||
|
rq *resultQueue // result queue wait to be inserted into blockchain
|
||||||
|
bdd map[uint64]BlockDownloadDetails // details about how this block was downloaded
|
||||||
|
|
||||||
|
logger zerolog.Logger |
||||||
|
lock sync.Mutex |
||||||
|
} |
||||||
|
|
||||||
|
func newBlockDownloadManager(tx kv.RwTx, chain blockChain, targetBN uint64, logger zerolog.Logger) *blockDownloadManager { |
||||||
|
return &blockDownloadManager{ |
||||||
|
chain: chain, |
||||||
|
tx: tx, |
||||||
|
targetBN: targetBN, |
||||||
|
requesting: make(map[uint64]struct{}), |
||||||
|
processing: make(map[uint64]struct{}), |
||||||
|
retries: newPrioritizedNumbers(), |
||||||
|
rq: newResultQueue(), |
||||||
|
bdd: make(map[uint64]BlockDownloadDetails), |
||||||
|
logger: logger, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// GetNextBatch get the next block numbers batch
|
||||||
|
func (gbm *blockDownloadManager) GetNextBatch() []uint64 { |
||||||
|
gbm.lock.Lock() |
||||||
|
defer gbm.lock.Unlock() |
||||||
|
|
||||||
|
cap := BlocksPerRequest |
||||||
|
|
||||||
|
bns := gbm.getBatchFromRetries(cap) |
||||||
|
if len(bns) > 0 { |
||||||
|
cap -= len(bns) |
||||||
|
gbm.addBatchToRequesting(bns) |
||||||
|
} |
||||||
|
|
||||||
|
if gbm.availableForMoreTasks() { |
||||||
|
addBNs := gbm.getBatchFromUnprocessed(cap) |
||||||
|
gbm.addBatchToRequesting(addBNs) |
||||||
|
bns = append(bns, addBNs...) |
||||||
|
} |
||||||
|
|
||||||
|
return bns |
||||||
|
} |
||||||
|
|
||||||
|
// HandleRequestError handles the error result
|
||||||
|
func (gbm *blockDownloadManager) HandleRequestError(bns []uint64, err error, streamID sttypes.StreamID) { |
||||||
|
gbm.lock.Lock() |
||||||
|
defer gbm.lock.Unlock() |
||||||
|
|
||||||
|
// add requested block numbers to retries
|
||||||
|
for _, bn := range bns { |
||||||
|
delete(gbm.requesting, bn) |
||||||
|
gbm.retries.push(bn) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// HandleRequestResult handles get blocks result
|
||||||
|
func (gbm *blockDownloadManager) HandleRequestResult(bns []uint64, blockBytes [][]byte, sigBytes [][]byte, loopID int, streamID sttypes.StreamID) error { |
||||||
|
gbm.lock.Lock() |
||||||
|
defer gbm.lock.Unlock() |
||||||
|
|
||||||
|
for i, bn := range bns { |
||||||
|
delete(gbm.requesting, bn) |
||||||
|
if len(blockBytes[i]) <= 1 { |
||||||
|
gbm.retries.push(bn) |
||||||
|
} else { |
||||||
|
gbm.processing[bn] = struct{}{} |
||||||
|
gbm.bdd[bn] = BlockDownloadDetails{ |
||||||
|
loopID: loopID, |
||||||
|
streamID: streamID, |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// SetDownloadDetails sets the download details for a batch of blocks
|
||||||
|
func (gbm *blockDownloadManager) SetDownloadDetails(bns []uint64, loopID int, streamID sttypes.StreamID) error { |
||||||
|
gbm.lock.Lock() |
||||||
|
defer gbm.lock.Unlock() |
||||||
|
|
||||||
|
for _, bn := range bns { |
||||||
|
gbm.bdd[bn] = BlockDownloadDetails{ |
||||||
|
loopID: loopID, |
||||||
|
streamID: streamID, |
||||||
|
} |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// GetDownloadDetails returns the download details for a block
|
||||||
|
func (gbm *blockDownloadManager) GetDownloadDetails(blockNumber uint64) (loopID int, streamID sttypes.StreamID) { |
||||||
|
gbm.lock.Lock() |
||||||
|
defer gbm.lock.Unlock() |
||||||
|
|
||||||
|
return gbm.bdd[blockNumber].loopID, gbm.bdd[blockNumber].streamID |
||||||
|
} |
||||||
|
|
||||||
|
// getBatchFromRetries get the block number batch to be requested from retries.
|
||||||
|
func (gbm *blockDownloadManager) getBatchFromRetries(cap int) []uint64 { |
||||||
|
var ( |
||||||
|
requestBNs []uint64 |
||||||
|
curHeight = gbm.chain.CurrentBlock().NumberU64() |
||||||
|
) |
||||||
|
for cnt := 0; cnt < cap; cnt++ { |
||||||
|
bn := gbm.retries.pop() |
||||||
|
if bn == 0 { |
||||||
|
break // no more retries
|
||||||
|
} |
||||||
|
if bn <= curHeight { |
||||||
|
continue |
||||||
|
} |
||||||
|
requestBNs = append(requestBNs, bn) |
||||||
|
} |
||||||
|
return requestBNs |
||||||
|
} |
||||||
|
|
||||||
|
// getBatchFromUnprocessed returns a batch of block numbers to be requested from unprocessed.
|
||||||
|
func (gbm *blockDownloadManager) getBatchFromUnprocessed(cap int) []uint64 { |
||||||
|
var ( |
||||||
|
requestBNs []uint64 |
||||||
|
curHeight = gbm.chain.CurrentBlock().NumberU64() |
||||||
|
) |
||||||
|
bn := curHeight + 1 |
||||||
|
// TODO: this algorithm can be potentially optimized.
|
||||||
|
for cnt := 0; cnt < cap && bn <= gbm.targetBN; cnt++ { |
||||||
|
for bn <= gbm.targetBN { |
||||||
|
_, ok1 := gbm.requesting[bn] |
||||||
|
_, ok2 := gbm.processing[bn] |
||||||
|
if !ok1 && !ok2 { |
||||||
|
requestBNs = append(requestBNs, bn) |
||||||
|
bn++ |
||||||
|
break |
||||||
|
} |
||||||
|
bn++ |
||||||
|
} |
||||||
|
} |
||||||
|
return requestBNs |
||||||
|
} |
||||||
|
|
||||||
|
func (gbm *blockDownloadManager) availableForMoreTasks() bool { |
||||||
|
return gbm.rq.results.Len() < SoftQueueCap |
||||||
|
} |
||||||
|
|
||||||
|
func (gbm *blockDownloadManager) addBatchToRequesting(bns []uint64) { |
||||||
|
for _, bn := range bns { |
||||||
|
gbm.requesting[bn] = struct{}{} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,81 @@ |
|||||||
|
package stagedstreamsync |
||||||
|
|
||||||
|
import ( |
||||||
|
"time" |
||||||
|
|
||||||
|
"github.com/harmony-one/harmony/core/types" |
||||||
|
nodeconfig "github.com/harmony-one/harmony/internal/configs/node" |
||||||
|
) |
||||||
|
|
||||||
|
const ( |
||||||
|
BlocksPerRequest int = 10 // number of blocks for each request
|
||||||
|
BlocksPerInsertion int = 50 // number of blocks for each insert batch
|
||||||
|
BlockHashesPerRequest int = 20 // number of get block hashes for short range sync
|
||||||
|
BlockByHashesUpperCap int = 10 // number of get blocks by hashes upper cap
|
||||||
|
BlockByHashesLowerCap int = 3 // number of get blocks by hashes lower cap
|
||||||
|
|
||||||
|
LastMileBlocksThreshold int = 10 |
||||||
|
|
||||||
|
// SoftQueueCap is the soft cap of size in resultQueue. When the queue size is larger than this limit,
|
||||||
|
// no more request will be assigned to workers to wait for InsertChain to finish.
|
||||||
|
SoftQueueCap int = 100 |
||||||
|
|
||||||
|
// DefaultConcurrency is the default settings for concurrency
|
||||||
|
DefaultConcurrency = 4 |
||||||
|
|
||||||
|
// ShortRangeTimeout is the timeout for each short range sync, which allow short range sync
|
||||||
|
// to restart automatically when stuck in `getBlockHashes`
|
||||||
|
ShortRangeTimeout = 1 * time.Minute |
||||||
|
) |
||||||
|
|
||||||
|
type ( |
||||||
|
// Config is the downloader config
|
||||||
|
Config struct { |
||||||
|
// Only run stream sync protocol as a server.
|
||||||
|
// TODO: remove this when stream sync is fully up.
|
||||||
|
ServerOnly bool |
||||||
|
|
||||||
|
// parameters
|
||||||
|
Network nodeconfig.NetworkType |
||||||
|
Concurrency int // Number of concurrent sync requests
|
||||||
|
MinStreams int // Minimum number of streams to do sync
|
||||||
|
InitStreams int // Number of streams requirement for initial bootstrap
|
||||||
|
|
||||||
|
// stream manager config
|
||||||
|
SmSoftLowCap int |
||||||
|
SmHardLowCap int |
||||||
|
SmHiCap int |
||||||
|
SmDiscBatch int |
||||||
|
|
||||||
|
// config for beacon config
|
||||||
|
BHConfig *BeaconHelperConfig |
||||||
|
|
||||||
|
// log the stage progress
|
||||||
|
LogProgress bool |
||||||
|
} |
||||||
|
|
||||||
|
// BeaconHelperConfig is the extra config used for beaconHelper which uses
|
||||||
|
// pub-sub block message to do sync.
|
||||||
|
BeaconHelperConfig struct { |
||||||
|
BlockC <-chan *types.Block |
||||||
|
InsertHook func() |
||||||
|
} |
||||||
|
) |
||||||
|
|
||||||
|
func (c *Config) fixValues() { |
||||||
|
if c.Concurrency == 0 { |
||||||
|
c.Concurrency = DefaultConcurrency |
||||||
|
} |
||||||
|
if c.Concurrency > c.MinStreams { |
||||||
|
c.MinStreams = c.Concurrency |
||||||
|
} |
||||||
|
if c.MinStreams > c.InitStreams { |
||||||
|
c.InitStreams = c.MinStreams |
||||||
|
} |
||||||
|
if c.MinStreams > c.SmSoftLowCap { |
||||||
|
c.SmSoftLowCap = c.MinStreams |
||||||
|
} |
||||||
|
if c.MinStreams > c.SmHardLowCap { |
||||||
|
c.SmHardLowCap = c.MinStreams |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,87 @@ |
|||||||
|
package stagedstreamsync |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
) |
||||||
|
|
||||||
|
type ForwardOrder []SyncStageID |
||||||
|
type RevertOrder []SyncStageID |
||||||
|
type CleanUpOrder []SyncStageID |
||||||
|
|
||||||
|
var DefaultForwardOrder = ForwardOrder{ |
||||||
|
Heads, |
||||||
|
SyncEpoch, |
||||||
|
ShortRange, |
||||||
|
BlockBodies, |
||||||
|
// Stages below don't use Internet
|
||||||
|
States, |
||||||
|
Finish, |
||||||
|
} |
||||||
|
|
||||||
|
var DefaultRevertOrder = RevertOrder{ |
||||||
|
Finish, |
||||||
|
States, |
||||||
|
BlockBodies, |
||||||
|
ShortRange, |
||||||
|
SyncEpoch, |
||||||
|
Heads, |
||||||
|
} |
||||||
|
|
||||||
|
var DefaultCleanUpOrder = CleanUpOrder{ |
||||||
|
Finish, |
||||||
|
States, |
||||||
|
BlockBodies, |
||||||
|
ShortRange, |
||||||
|
SyncEpoch, |
||||||
|
Heads, |
||||||
|
} |
||||||
|
|
||||||
|
func DefaultStages(ctx context.Context, |
||||||
|
headsCfg StageHeadsCfg, |
||||||
|
seCfg StageEpochCfg, |
||||||
|
srCfg StageShortRangeCfg, |
||||||
|
bodiesCfg StageBodiesCfg, |
||||||
|
statesCfg StageStatesCfg, |
||||||
|
finishCfg StageFinishCfg, |
||||||
|
) []*Stage { |
||||||
|
|
||||||
|
handlerStageHeads := NewStageHeads(headsCfg) |
||||||
|
handlerStageShortRange := NewStageShortRange(srCfg) |
||||||
|
handlerStageEpochSync := NewStageEpoch(seCfg) |
||||||
|
handlerStageBodies := NewStageBodies(bodiesCfg) |
||||||
|
handlerStageStates := NewStageStates(statesCfg) |
||||||
|
handlerStageFinish := NewStageFinish(finishCfg) |
||||||
|
|
||||||
|
return []*Stage{ |
||||||
|
{ |
||||||
|
ID: Heads, |
||||||
|
Description: "Retrieve Chain Heads", |
||||||
|
Handler: handlerStageHeads, |
||||||
|
}, |
||||||
|
{ |
||||||
|
ID: SyncEpoch, |
||||||
|
Description: "Sync only Last Block of Epoch", |
||||||
|
Handler: handlerStageEpochSync, |
||||||
|
}, |
||||||
|
{ |
||||||
|
ID: ShortRange, |
||||||
|
Description: "Short Range Sync", |
||||||
|
Handler: handlerStageShortRange, |
||||||
|
}, |
||||||
|
{ |
||||||
|
ID: BlockBodies, |
||||||
|
Description: "Retrieve Block Bodies", |
||||||
|
Handler: handlerStageBodies, |
||||||
|
}, |
||||||
|
{ |
||||||
|
ID: States, |
||||||
|
Description: "Update Blockchain State", |
||||||
|
Handler: handlerStageStates, |
||||||
|
}, |
||||||
|
{ |
||||||
|
ID: Finish, |
||||||
|
Description: "Finalize Changes", |
||||||
|
Handler: handlerStageFinish, |
||||||
|
}, |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,303 @@ |
|||||||
|
package stagedstreamsync |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
"fmt" |
||||||
|
"time" |
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/event" |
||||||
|
"github.com/pkg/errors" |
||||||
|
"github.com/rs/zerolog" |
||||||
|
|
||||||
|
"github.com/harmony-one/harmony/core" |
||||||
|
"github.com/harmony-one/harmony/core/types" |
||||||
|
"github.com/harmony-one/harmony/crypto/bls" |
||||||
|
"github.com/harmony-one/harmony/internal/chain" |
||||||
|
nodeconfig "github.com/harmony-one/harmony/internal/configs/node" |
||||||
|
"github.com/harmony-one/harmony/internal/utils" |
||||||
|
"github.com/harmony-one/harmony/p2p" |
||||||
|
"github.com/harmony-one/harmony/p2p/stream/common/streammanager" |
||||||
|
"github.com/harmony-one/harmony/p2p/stream/protocols/sync" |
||||||
|
"github.com/harmony-one/harmony/shard" |
||||||
|
) |
||||||
|
|
||||||
|
type ( |
||||||
|
// Downloader is responsible for sync task of one shard
|
||||||
|
Downloader struct { |
||||||
|
bc blockChain |
||||||
|
syncProtocol syncProtocol |
||||||
|
bh *beaconHelper |
||||||
|
stagedSyncInstance *StagedStreamSync |
||||||
|
|
||||||
|
downloadC chan struct{} |
||||||
|
closeC chan struct{} |
||||||
|
ctx context.Context |
||||||
|
cancel func() |
||||||
|
|
||||||
|
config Config |
||||||
|
logger zerolog.Logger |
||||||
|
} |
||||||
|
) |
||||||
|
|
||||||
|
// NewDownloader creates a new downloader
|
||||||
|
func NewDownloader(host p2p.Host, bc core.BlockChain, config Config) *Downloader { |
||||||
|
config.fixValues() |
||||||
|
|
||||||
|
sp := sync.NewProtocol(sync.Config{ |
||||||
|
Chain: bc, |
||||||
|
Host: host.GetP2PHost(), |
||||||
|
Discovery: host.GetDiscovery(), |
||||||
|
ShardID: nodeconfig.ShardID(bc.ShardID()), |
||||||
|
Network: config.Network, |
||||||
|
|
||||||
|
SmSoftLowCap: config.SmSoftLowCap, |
||||||
|
SmHardLowCap: config.SmHardLowCap, |
||||||
|
SmHiCap: config.SmHiCap, |
||||||
|
DiscBatch: config.SmDiscBatch, |
||||||
|
}) |
||||||
|
|
||||||
|
host.AddStreamProtocol(sp) |
||||||
|
|
||||||
|
var bh *beaconHelper |
||||||
|
if config.BHConfig != nil && bc.ShardID() == shard.BeaconChainShardID { |
||||||
|
bh = newBeaconHelper(bc, config.BHConfig.BlockC, config.BHConfig.InsertHook) |
||||||
|
} |
||||||
|
|
||||||
|
logger := utils.Logger().With().Str("module", "StagedStreamSync").Uint32("ShardID", bc.ShardID()).Logger() |
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background()) |
||||||
|
|
||||||
|
//TODO: use mem db should be in config file
|
||||||
|
stagedSyncInstance, err := CreateStagedSync(ctx, bc, false, sp, config, logger, config.LogProgress) |
||||||
|
if err != nil { |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
return &Downloader{ |
||||||
|
bc: bc, |
||||||
|
syncProtocol: sp, |
||||||
|
bh: bh, |
||||||
|
stagedSyncInstance: stagedSyncInstance, |
||||||
|
|
||||||
|
downloadC: make(chan struct{}), |
||||||
|
closeC: make(chan struct{}), |
||||||
|
ctx: ctx, |
||||||
|
cancel: cancel, |
||||||
|
|
||||||
|
config: config, |
||||||
|
logger: logger, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Start starts the downloader
|
||||||
|
func (d *Downloader) Start() { |
||||||
|
go func() { |
||||||
|
d.waitForBootFinish() |
||||||
|
fmt.Printf("boot completed for shard %d, %d streams are connected\n", d.bc.ShardID(), d.syncProtocol.NumStreams()) |
||||||
|
d.loop() |
||||||
|
}() |
||||||
|
|
||||||
|
if d.bh != nil { |
||||||
|
d.bh.start() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Close closes the downloader
|
||||||
|
func (d *Downloader) Close() { |
||||||
|
close(d.closeC) |
||||||
|
d.cancel() |
||||||
|
|
||||||
|
if d.bh != nil { |
||||||
|
d.bh.close() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// DownloadAsync triggers the download async.
|
||||||
|
func (d *Downloader) DownloadAsync() { |
||||||
|
select { |
||||||
|
case d.downloadC <- struct{}{}: |
||||||
|
consensusTriggeredDownloadCounterVec.With(d.promLabels()).Inc() |
||||||
|
|
||||||
|
case <-time.After(100 * time.Millisecond): |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// NumPeers returns the number of peers connected of a specific shard.
|
||||||
|
func (d *Downloader) NumPeers() int { |
||||||
|
return d.syncProtocol.NumStreams() |
||||||
|
} |
||||||
|
|
||||||
|
// IsSyncing returns the current sync status
|
||||||
|
func (d *Downloader) SyncStatus() (bool, uint64, uint64) { |
||||||
|
syncing, target := d.stagedSyncInstance.status.get() |
||||||
|
if !syncing { |
||||||
|
target = d.bc.CurrentBlock().NumberU64() |
||||||
|
} |
||||||
|
return syncing, target, 0 |
||||||
|
} |
||||||
|
|
||||||
|
// SubscribeDownloadStarted subscribes download started
|
||||||
|
func (d *Downloader) SubscribeDownloadStarted(ch chan struct{}) event.Subscription { |
||||||
|
d.stagedSyncInstance.evtDownloadStartedSubscribed = true |
||||||
|
return d.stagedSyncInstance.evtDownloadStarted.Subscribe(ch) |
||||||
|
} |
||||||
|
|
||||||
|
// SubscribeDownloadFinished subscribes the download finished
|
||||||
|
func (d *Downloader) SubscribeDownloadFinished(ch chan struct{}) event.Subscription { |
||||||
|
d.stagedSyncInstance.evtDownloadFinishedSubscribed = true |
||||||
|
return d.stagedSyncInstance.evtDownloadFinished.Subscribe(ch) |
||||||
|
} |
||||||
|
|
||||||
|
// waitForBootFinish waits for stream manager to finish the initial discovery and have
|
||||||
|
// enough peers to start downloader
|
||||||
|
func (d *Downloader) waitForBootFinish() { |
||||||
|
evtCh := make(chan streammanager.EvtStreamAdded, 1) |
||||||
|
sub := d.syncProtocol.SubscribeAddStreamEvent(evtCh) |
||||||
|
defer sub.Unsubscribe() |
||||||
|
|
||||||
|
checkCh := make(chan struct{}, 1) |
||||||
|
trigger := func() { |
||||||
|
select { |
||||||
|
case checkCh <- struct{}{}: |
||||||
|
default: |
||||||
|
} |
||||||
|
} |
||||||
|
trigger() |
||||||
|
|
||||||
|
t := time.NewTicker(10 * time.Second) |
||||||
|
defer t.Stop() |
||||||
|
for { |
||||||
|
select { |
||||||
|
case <-t.C: |
||||||
|
trigger() |
||||||
|
|
||||||
|
case <-evtCh: |
||||||
|
trigger() |
||||||
|
|
||||||
|
case <-checkCh: |
||||||
|
if d.syncProtocol.NumStreams() >= d.config.InitStreams { |
||||||
|
return |
||||||
|
} |
||||||
|
case <-d.closeC: |
||||||
|
return |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (d *Downloader) loop() { |
||||||
|
ticker := time.NewTicker(10 * time.Second) |
||||||
|
defer ticker.Stop() |
||||||
|
initSync := d.bc.ShardID() != shard.BeaconChainShardID |
||||||
|
trigger := func() { |
||||||
|
select { |
||||||
|
case d.downloadC <- struct{}{}: |
||||||
|
case <-time.After(100 * time.Millisecond): |
||||||
|
} |
||||||
|
} |
||||||
|
go trigger() |
||||||
|
|
||||||
|
for { |
||||||
|
select { |
||||||
|
case <-ticker.C: |
||||||
|
go trigger() |
||||||
|
|
||||||
|
case <-d.downloadC: |
||||||
|
addedBN, err := d.stagedSyncInstance.doSync(d.ctx, initSync) |
||||||
|
if err != nil { |
||||||
|
//TODO: if there is a bad block which can't be resolved
|
||||||
|
if d.stagedSyncInstance.invalidBlock.Active { |
||||||
|
numTriedStreams := len(d.stagedSyncInstance.invalidBlock.StreamID) |
||||||
|
// if many streams couldn't solve it, then that's an unresolvable bad block
|
||||||
|
if numTriedStreams >= d.config.InitStreams { |
||||||
|
if !d.stagedSyncInstance.invalidBlock.IsLogged { |
||||||
|
fmt.Println("unresolvable bad block:", d.stagedSyncInstance.invalidBlock.Number) |
||||||
|
d.stagedSyncInstance.invalidBlock.IsLogged = true |
||||||
|
} |
||||||
|
//TODO: if we don't have any new or untried stream in the list, sleep or panic
|
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// If error happens, sleep 5 seconds and retry
|
||||||
|
d.logger.Error(). |
||||||
|
Err(err). |
||||||
|
Bool("initSync", initSync). |
||||||
|
Msg(WrapStagedSyncMsg("sync loop failed")) |
||||||
|
go func() { |
||||||
|
time.Sleep(5 * time.Second) |
||||||
|
trigger() |
||||||
|
}() |
||||||
|
time.Sleep(1 * time.Second) |
||||||
|
continue |
||||||
|
} |
||||||
|
d.logger.Info().Int("block added", addedBN). |
||||||
|
Uint64("current height", d.bc.CurrentBlock().NumberU64()). |
||||||
|
Bool("initSync", initSync). |
||||||
|
Uint32("shard", d.bc.ShardID()). |
||||||
|
Msg(WrapStagedSyncMsg("sync finished")) |
||||||
|
|
||||||
|
if addedBN != 0 { |
||||||
|
// If block number has been changed, trigger another sync
|
||||||
|
// and try to add last mile from pub-sub (blocking)
|
||||||
|
go trigger() |
||||||
|
if d.bh != nil { |
||||||
|
d.bh.insertSync() |
||||||
|
} |
||||||
|
} |
||||||
|
d.stagedSyncInstance.initSync = false |
||||||
|
initSync = false |
||||||
|
|
||||||
|
case <-d.closeC: |
||||||
|
return |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
var emptySigVerifyErr *sigVerifyErr |
||||||
|
|
||||||
|
type sigVerifyErr struct { |
||||||
|
err error |
||||||
|
} |
||||||
|
|
||||||
|
func (e *sigVerifyErr) Error() string { |
||||||
|
return fmt.Sprintf("[VerifyHeaderSignature] %v", e.err.Error()) |
||||||
|
} |
||||||
|
|
||||||
|
func verifyAndInsertBlocks(bc blockChain, blocks types.Blocks) (int, error) { |
||||||
|
for i, block := range blocks { |
||||||
|
if err := verifyAndInsertBlock(bc, block, blocks[i+1:]...); err != nil { |
||||||
|
return i, err |
||||||
|
} |
||||||
|
} |
||||||
|
return len(blocks), nil |
||||||
|
} |
||||||
|
|
||||||
|
func verifyAndInsertBlock(bc blockChain, block *types.Block, nextBlocks ...*types.Block) error { |
||||||
|
var ( |
||||||
|
sigBytes bls.SerializedSignature |
||||||
|
bitmap []byte |
||||||
|
err error |
||||||
|
) |
||||||
|
if len(nextBlocks) > 0 { |
||||||
|
// get commit sig from the next block
|
||||||
|
next := nextBlocks[0] |
||||||
|
sigBytes = next.Header().LastCommitSignature() |
||||||
|
bitmap = next.Header().LastCommitBitmap() |
||||||
|
} else { |
||||||
|
// get commit sig from current block
|
||||||
|
sigBytes, bitmap, err = chain.ParseCommitSigAndBitmap(block.GetCurrentCommitSig()) |
||||||
|
if err != nil { |
||||||
|
return errors.Wrap(err, "parse commitSigAndBitmap") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if err := bc.Engine().VerifyHeaderSignature(bc, block.Header(), sigBytes, bitmap); err != nil { |
||||||
|
return &sigVerifyErr{err} |
||||||
|
} |
||||||
|
if err := bc.Engine().VerifyHeader(bc, block.Header(), true); err != nil { |
||||||
|
return errors.Wrap(err, "[VerifyHeader]") |
||||||
|
} |
||||||
|
if _, err := bc.InsertChain(types.Blocks{block}, false); err != nil { |
||||||
|
return errors.Wrap(err, "[InsertChain]") |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
@ -0,0 +1,96 @@ |
|||||||
|
package stagedstreamsync |
||||||
|
|
||||||
|
import ( |
||||||
|
"github.com/harmony-one/abool" |
||||||
|
"github.com/harmony-one/harmony/core" |
||||||
|
"github.com/harmony-one/harmony/p2p" |
||||||
|
) |
||||||
|
|
||||||
|
// Downloaders is the set of downloaders
|
||||||
|
type Downloaders struct { |
||||||
|
ds map[uint32]*Downloader |
||||||
|
active *abool.AtomicBool |
||||||
|
|
||||||
|
config Config |
||||||
|
} |
||||||
|
|
||||||
|
// NewDownloaders creates Downloaders for sync of multiple blockchains
|
||||||
|
func NewDownloaders(host p2p.Host, bcs []core.BlockChain, config Config) *Downloaders { |
||||||
|
ds := make(map[uint32]*Downloader) |
||||||
|
|
||||||
|
for _, bc := range bcs { |
||||||
|
if bc == nil { |
||||||
|
continue |
||||||
|
} |
||||||
|
if _, ok := ds[bc.ShardID()]; ok { |
||||||
|
continue |
||||||
|
} |
||||||
|
ds[bc.ShardID()] = NewDownloader(host, bc, config) |
||||||
|
} |
||||||
|
return &Downloaders{ |
||||||
|
ds: ds, |
||||||
|
active: abool.New(), |
||||||
|
config: config, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Start starts the downloaders
|
||||||
|
func (ds *Downloaders) Start() { |
||||||
|
if ds.config.ServerOnly { |
||||||
|
// Run in server only mode. Do not start downloaders.
|
||||||
|
return |
||||||
|
} |
||||||
|
ds.active.Set() |
||||||
|
for _, d := range ds.ds { |
||||||
|
d.Start() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Close closes the downloaders
|
||||||
|
func (ds *Downloaders) Close() { |
||||||
|
if ds.config.ServerOnly { |
||||||
|
// Run in server only mode. Downloaders not started.
|
||||||
|
return |
||||||
|
} |
||||||
|
ds.active.UnSet() |
||||||
|
for _, d := range ds.ds { |
||||||
|
d.Close() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// DownloadAsync triggers a download
|
||||||
|
func (ds *Downloaders) DownloadAsync(shardID uint32) { |
||||||
|
d, ok := ds.ds[shardID] |
||||||
|
if !ok && d != nil { |
||||||
|
d.DownloadAsync() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// GetShardDownloader returns the downloader with the given shard ID
|
||||||
|
func (ds *Downloaders) GetShardDownloader(shardID uint32) *Downloader { |
||||||
|
return ds.ds[shardID] |
||||||
|
} |
||||||
|
|
||||||
|
// NumPeers returns the connected peers for each shard
|
||||||
|
func (ds *Downloaders) NumPeers() map[uint32]int { |
||||||
|
res := make(map[uint32]int) |
||||||
|
|
||||||
|
for sid, d := range ds.ds { |
||||||
|
res[sid] = d.NumPeers() |
||||||
|
} |
||||||
|
return res |
||||||
|
} |
||||||
|
|
||||||
|
// SyncStatus returns whether the given shard is doing syncing task and the target block number
|
||||||
|
func (ds *Downloaders) SyncStatus(shardID uint32) (bool, uint64, uint64) { |
||||||
|
d, ok := ds.ds[shardID] |
||||||
|
if !ok { |
||||||
|
return false, 0, 0 |
||||||
|
} |
||||||
|
return d.SyncStatus() |
||||||
|
} |
||||||
|
|
||||||
|
// IsActive returns whether the downloader is active
|
||||||
|
func (ds *Downloaders) IsActive() bool { |
||||||
|
return ds.active.IsSet() |
||||||
|
} |
@ -0,0 +1,56 @@ |
|||||||
|
package stagedstreamsync |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
) |
||||||
|
|
||||||
|
// Errors ...
|
||||||
|
var ( |
||||||
|
ErrRegistrationFail = WrapStagedSyncError("registration failed") |
||||||
|
ErrGetBlock = WrapStagedSyncError("get block failed") |
||||||
|
ErrGetBlockHash = WrapStagedSyncError("get block hash failed") |
||||||
|
ErrGetConsensusHashes = WrapStagedSyncError("get consensus hashes failed") |
||||||
|
ErrGenStateSyncTaskQueue = WrapStagedSyncError("generate state sync task queue failed") |
||||||
|
ErrDownloadBlocks = WrapStagedSyncError("get download blocks failed") |
||||||
|
ErrUpdateBlockAndStatus = WrapStagedSyncError("update block and status failed") |
||||||
|
ErrGenerateNewState = WrapStagedSyncError("get generate new state failed") |
||||||
|
ErrFetchBlockHashProgressFail = WrapStagedSyncError("fetch cache progress for block hashes stage failed") |
||||||
|
ErrFetchCachedBlockHashFail = WrapStagedSyncError("fetch cached block hashes failed") |
||||||
|
ErrNotEnoughBlockHashes = WrapStagedSyncError("peers haven't sent all requested block hashes") |
||||||
|
ErrRetrieveCachedProgressFail = WrapStagedSyncError("retrieving cache progress for block hashes stage failed") |
||||||
|
ErrRetrieveCachedHashProgressFail = WrapStagedSyncError("retrieving cache progress for block hashes stage failed") |
||||||
|
ErrSaveBlockHashesProgressFail = WrapStagedSyncError("saving progress for block hashes stage failed") |
||||||
|
ErrSaveCachedBlockHashesProgressFail = WrapStagedSyncError("saving cache progress for block hashes stage failed") |
||||||
|
ErrSavingCacheLastBlockHashFail = WrapStagedSyncError("saving cache last block hash for block hashes stage failed") |
||||||
|
ErrCachingBlockHashFail = WrapStagedSyncError("caching downloaded block hashes failed") |
||||||
|
ErrCommitTransactionFail = WrapStagedSyncError("failed to write db commit") |
||||||
|
ErrUnexpectedNumberOfBlocks = WrapStagedSyncError("unexpected number of block delivered") |
||||||
|
ErrSavingBodiesProgressFail = WrapStagedSyncError("saving progress for block bodies stage failed") |
||||||
|
ErrAddTasksToQueueFail = WrapStagedSyncError("cannot add task to queue") |
||||||
|
ErrSavingCachedBodiesProgressFail = WrapStagedSyncError("saving cache progress for blocks stage failed") |
||||||
|
ErrRetrievingCachedBodiesProgressFail = WrapStagedSyncError("retrieving cache progress for blocks stage failed") |
||||||
|
ErrNoConnectedPeers = WrapStagedSyncError("haven't connected to any peer yet!") |
||||||
|
ErrNotEnoughConnectedPeers = WrapStagedSyncError("not enough connected peers") |
||||||
|
ErrSaveStateProgressFail = WrapStagedSyncError("saving progress for block States stage failed") |
||||||
|
ErrPruningCursorCreationFail = WrapStagedSyncError("failed to create cursor for pruning") |
||||||
|
ErrInvalidBlockNumber = WrapStagedSyncError("invalid block number") |
||||||
|
ErrInvalidBlockBytes = WrapStagedSyncError("invalid block bytes to insert into chain") |
||||||
|
ErrAddTaskFailed = WrapStagedSyncError("cannot add task to queue") |
||||||
|
ErrNodeNotEnoughBlockHashes = WrapStagedSyncError("some of the nodes didn't provide all block hashes") |
||||||
|
ErrCachingBlocksFail = WrapStagedSyncError("caching downloaded block bodies failed") |
||||||
|
ErrSaveBlocksFail = WrapStagedSyncError("save downloaded block bodies failed") |
||||||
|
ErrStageNotFound = WrapStagedSyncError("stage not found") |
||||||
|
ErrSomeNodesNotReady = WrapStagedSyncError("some nodes are not ready") |
||||||
|
ErrSomeNodesBlockHashFail = WrapStagedSyncError("some nodes failed to download block hashes") |
||||||
|
ErrMaxPeerHeightFail = WrapStagedSyncError("get max peer height failed") |
||||||
|
) |
||||||
|
|
||||||
|
// WrapStagedSyncError wraps errors for staged sync and returns error object
|
||||||
|
func WrapStagedSyncError(context string) error { |
||||||
|
return fmt.Errorf("[STAGED_STREAM_SYNC]: %s", context) |
||||||
|
} |
||||||
|
|
||||||
|
// WrapStagedSyncMsg wraps message for staged sync and returns string
|
||||||
|
func WrapStagedSyncMsg(context string) string { |
||||||
|
return fmt.Sprintf("[STAGED_STREAM_SYNC]: %s", context) |
||||||
|
} |
@ -0,0 +1,114 @@ |
|||||||
|
package stagedstreamsync |
||||||
|
|
||||||
|
import ( |
||||||
|
"encoding/binary" |
||||||
|
"fmt" |
||||||
|
"math" |
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common" |
||||||
|
"github.com/harmony-one/harmony/core/types" |
||||||
|
sttypes "github.com/harmony-one/harmony/p2p/stream/types" |
||||||
|
"github.com/pkg/errors" |
||||||
|
) |
||||||
|
|
||||||
|
func marshalData(blockNumber uint64) []byte { |
||||||
|
return encodeBigEndian(blockNumber) |
||||||
|
} |
||||||
|
|
||||||
|
func unmarshalData(data []byte) (uint64, error) { |
||||||
|
if len(data) == 0 { |
||||||
|
return 0, nil |
||||||
|
} |
||||||
|
if len(data) < 8 { |
||||||
|
return 0, fmt.Errorf("value must be at least 8 bytes, got %d", len(data)) |
||||||
|
} |
||||||
|
return binary.BigEndian.Uint64(data[:8]), nil |
||||||
|
} |
||||||
|
|
||||||
|
func encodeBigEndian(n uint64) []byte { |
||||||
|
var v [8]byte |
||||||
|
binary.BigEndian.PutUint64(v[:], n) |
||||||
|
return v[:] |
||||||
|
} |
||||||
|
|
||||||
|
func divideCeil(x, y int) int { |
||||||
|
fVal := float64(x) / float64(y) |
||||||
|
return int(math.Ceil(fVal)) |
||||||
|
} |
||||||
|
|
||||||
|
// computeBlockNumberByMaxVote computes the target block number by max vote.
|
||||||
|
func computeBlockNumberByMaxVote(votes map[sttypes.StreamID]uint64) uint64 { |
||||||
|
var ( |
||||||
|
nm = make(map[uint64]int) |
||||||
|
res uint64 |
||||||
|
maxCnt int |
||||||
|
) |
||||||
|
for _, bn := range votes { |
||||||
|
_, ok := nm[bn] |
||||||
|
if !ok { |
||||||
|
nm[bn] = 0 |
||||||
|
} |
||||||
|
nm[bn]++ |
||||||
|
cnt := nm[bn] |
||||||
|
|
||||||
|
if cnt > maxCnt || (cnt == maxCnt && bn > res) { |
||||||
|
res = bn |
||||||
|
maxCnt = cnt |
||||||
|
} |
||||||
|
} |
||||||
|
return res |
||||||
|
} |
||||||
|
|
||||||
|
func checkGetBlockByHashesResult(blocks []*types.Block, hashes []common.Hash) error { |
||||||
|
if len(blocks) != len(hashes) { |
||||||
|
return errors.New("unexpected number of getBlocksByHashes result") |
||||||
|
} |
||||||
|
for i, block := range blocks { |
||||||
|
if block == nil { |
||||||
|
return errors.New("nil block found") |
||||||
|
} |
||||||
|
if block.Hash() != hashes[i] { |
||||||
|
return fmt.Errorf("unexpected block hash: %x / %x", block.Hash(), hashes[i]) |
||||||
|
} |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func countHashMaxVote(m map[sttypes.StreamID]common.Hash, whitelist map[sttypes.StreamID]struct{}) (common.Hash, map[sttypes.StreamID]struct{}) { |
||||||
|
var ( |
||||||
|
voteM = make(map[common.Hash]int) |
||||||
|
res common.Hash |
||||||
|
maxCnt = 0 |
||||||
|
) |
||||||
|
|
||||||
|
for st, h := range m { |
||||||
|
if len(whitelist) != 0 { |
||||||
|
if _, ok := whitelist[st]; !ok { |
||||||
|
continue |
||||||
|
} |
||||||
|
} |
||||||
|
if _, ok := voteM[h]; !ok { |
||||||
|
voteM[h] = 0 |
||||||
|
} |
||||||
|
voteM[h]++ |
||||||
|
if voteM[h] > maxCnt { |
||||||
|
maxCnt = voteM[h] |
||||||
|
res = h |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
nextWl := make(map[sttypes.StreamID]struct{}) |
||||||
|
for st, h := range m { |
||||||
|
if h != res { |
||||||
|
continue |
||||||
|
} |
||||||
|
if len(whitelist) != 0 { |
||||||
|
if _, ok := whitelist[st]; ok { |
||||||
|
nextWl[st] = struct{}{} |
||||||
|
} |
||||||
|
} else { |
||||||
|
nextWl[st] = struct{}{} |
||||||
|
} |
||||||
|
} |
||||||
|
return res, nextWl |
||||||
|
} |
@ -0,0 +1,98 @@ |
|||||||
|
package stagedstreamsync |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
|
||||||
|
prom "github.com/harmony-one/harmony/api/service/prometheus" |
||||||
|
"github.com/prometheus/client_golang/prometheus" |
||||||
|
) |
||||||
|
|
||||||
|
func init() { |
||||||
|
prom.PromRegistry().MustRegister( |
||||||
|
consensusTriggeredDownloadCounterVec, |
||||||
|
longRangeSyncedBlockCounterVec, |
||||||
|
longRangeFailInsertedBlockCounterVec, |
||||||
|
numShortRangeCounterVec, |
||||||
|
numFailedDownloadCounterVec, |
||||||
|
numBlocksInsertedShortRangeHistogramVec, |
||||||
|
numBlocksInsertedBeaconHelperCounter, |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
var ( |
||||||
|
consensusTriggeredDownloadCounterVec = prometheus.NewCounterVec( |
||||||
|
prometheus.CounterOpts{ |
||||||
|
Namespace: "hmy", |
||||||
|
Subsystem: "downloader", |
||||||
|
Name: "consensus_trigger", |
||||||
|
Help: "number of times consensus triggered download task", |
||||||
|
}, |
||||||
|
[]string{"ShardID"}, |
||||||
|
) |
||||||
|
|
||||||
|
longRangeSyncedBlockCounterVec = prometheus.NewCounterVec( |
||||||
|
prometheus.CounterOpts{ |
||||||
|
Namespace: "hmy", |
||||||
|
Subsystem: "downloader", |
||||||
|
Name: "num_blocks_synced_long_range", |
||||||
|
Help: "number of blocks synced in long range sync", |
||||||
|
}, |
||||||
|
[]string{"ShardID"}, |
||||||
|
) |
||||||
|
|
||||||
|
longRangeFailInsertedBlockCounterVec = prometheus.NewCounterVec( |
||||||
|
prometheus.CounterOpts{ |
||||||
|
Namespace: "hmy", |
||||||
|
Subsystem: "downloader", |
||||||
|
Name: "num_blocks_failed_long_range", |
||||||
|
Help: "number of blocks failed to insert into change in long range sync", |
||||||
|
}, |
||||||
|
[]string{"ShardID", "error"}, |
||||||
|
) |
||||||
|
|
||||||
|
numShortRangeCounterVec = prometheus.NewCounterVec( |
||||||
|
prometheus.CounterOpts{ |
||||||
|
Namespace: "hmy", |
||||||
|
Subsystem: "downloader", |
||||||
|
Name: "num_short_range", |
||||||
|
Help: "number of short range sync is triggered", |
||||||
|
}, |
||||||
|
[]string{"ShardID"}, |
||||||
|
) |
||||||
|
|
||||||
|
numFailedDownloadCounterVec = prometheus.NewCounterVec( |
||||||
|
prometheus.CounterOpts{ |
||||||
|
Namespace: "hmy", |
||||||
|
Subsystem: "downloader", |
||||||
|
Name: "failed_download", |
||||||
|
Help: "number of downloading is failed", |
||||||
|
}, |
||||||
|
[]string{"ShardID", "error"}, |
||||||
|
) |
||||||
|
|
||||||
|
numBlocksInsertedShortRangeHistogramVec = prometheus.NewHistogramVec( |
||||||
|
prometheus.HistogramOpts{ |
||||||
|
Namespace: "hmy", |
||||||
|
Subsystem: "downloader", |
||||||
|
Name: "num_blocks_inserted_short_range", |
||||||
|
Help: "number of blocks inserted for each short range sync", |
||||||
|
// Buckets: 0, 1, 2, 4, +INF (capped at 10)
|
||||||
|
Buckets: prometheus.ExponentialBuckets(0.5, 2, 5), |
||||||
|
}, |
||||||
|
[]string{"ShardID"}, |
||||||
|
) |
||||||
|
|
||||||
|
numBlocksInsertedBeaconHelperCounter = prometheus.NewCounter( |
||||||
|
prometheus.CounterOpts{ |
||||||
|
Namespace: "hmy", |
||||||
|
Subsystem: "downloader", |
||||||
|
Name: "num_blocks_inserted_beacon_helper", |
||||||
|
Help: "number of blocks inserted from beacon helper", |
||||||
|
}, |
||||||
|
) |
||||||
|
) |
||||||
|
|
||||||
|
func (d *Downloader) promLabels() prometheus.Labels { |
||||||
|
sid := d.bc.ShardID() |
||||||
|
return prometheus.Labels{"ShardID": fmt.Sprintf("%d", sid)} |
||||||
|
} |
@ -0,0 +1,30 @@ |
|||||||
|
package stagedstreamsync |
||||||
|
|
||||||
|
import ( |
||||||
|
"github.com/harmony-one/harmony/core" |
||||||
|
"github.com/harmony-one/harmony/p2p" |
||||||
|
) |
||||||
|
|
||||||
|
// StagedStreamSyncService is simply a adapter of downloaders, which support block synchronization
|
||||||
|
type StagedStreamSyncService struct { |
||||||
|
Downloaders *Downloaders |
||||||
|
} |
||||||
|
|
||||||
|
// NewService creates a new downloader service
|
||||||
|
func NewService(host p2p.Host, bcs []core.BlockChain, config Config) *StagedStreamSyncService { |
||||||
|
return &StagedStreamSyncService{ |
||||||
|
Downloaders: NewDownloaders(host, bcs, config), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Start starts the service
|
||||||
|
func (s *StagedStreamSyncService) Start() error { |
||||||
|
s.Downloaders.Start() |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// Stop stops the service
|
||||||
|
func (s *StagedStreamSyncService) Stop() error { |
||||||
|
s.Downloaders.Close() |
||||||
|
return nil |
||||||
|
} |
@ -0,0 +1,221 @@ |
|||||||
|
package stagedstreamsync |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
"sync" |
||||||
|
"time" |
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common" |
||||||
|
"github.com/harmony-one/harmony/core/types" |
||||||
|
syncProto "github.com/harmony-one/harmony/p2p/stream/protocols/sync" |
||||||
|
sttypes "github.com/harmony-one/harmony/p2p/stream/types" |
||||||
|
"github.com/pkg/errors" |
||||||
|
"github.com/rs/zerolog" |
||||||
|
) |
||||||
|
|
||||||
|
type srHelper struct { |
||||||
|
syncProtocol syncProtocol |
||||||
|
|
||||||
|
ctx context.Context |
||||||
|
config Config |
||||||
|
logger zerolog.Logger |
||||||
|
} |
||||||
|
|
||||||
|
func (sh *srHelper) getHashChain(bns []uint64) ([]common.Hash, []sttypes.StreamID, error) { |
||||||
|
results := newBlockHashResults(bns) |
||||||
|
|
||||||
|
var wg sync.WaitGroup |
||||||
|
wg.Add(sh.config.Concurrency) |
||||||
|
|
||||||
|
for i := 0; i != sh.config.Concurrency; i++ { |
||||||
|
go func(index int) { |
||||||
|
defer wg.Done() |
||||||
|
|
||||||
|
hashes, stid, err := sh.doGetBlockHashesRequest(bns) |
||||||
|
if err != nil { |
||||||
|
sh.logger.Warn().Err(err).Str("StreamID", string(stid)). |
||||||
|
Msg(WrapStagedSyncMsg("doGetBlockHashes return error")) |
||||||
|
return |
||||||
|
} |
||||||
|
results.addResult(hashes, stid) |
||||||
|
}(i) |
||||||
|
} |
||||||
|
wg.Wait() |
||||||
|
|
||||||
|
select { |
||||||
|
case <-sh.ctx.Done(): |
||||||
|
sh.logger.Info().Err(sh.ctx.Err()).Int("num blocks", results.numBlocksWithResults()). |
||||||
|
Msg(WrapStagedSyncMsg("short range sync get hashes timed out")) |
||||||
|
return nil, nil, sh.ctx.Err() |
||||||
|
default: |
||||||
|
} |
||||||
|
|
||||||
|
hashChain, wl := results.computeLongestHashChain() |
||||||
|
sh.logger.Info().Int("hashChain size", len(hashChain)).Int("whitelist", len(wl)). |
||||||
|
Msg(WrapStagedSyncMsg("computeLongestHashChain result")) |
||||||
|
return hashChain, wl, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (sh *srHelper) getBlocksChain(bns []uint64) ([]*types.Block, sttypes.StreamID, error) { |
||||||
|
return sh.doGetBlocksByNumbersRequest(bns) |
||||||
|
} |
||||||
|
|
||||||
|
func (sh *srHelper) getBlocksByHashes(hashes []common.Hash, whitelist []sttypes.StreamID) ([]*types.Block, []sttypes.StreamID, error) { |
||||||
|
ctx, cancel := context.WithCancel(sh.ctx) |
||||||
|
defer cancel() |
||||||
|
m := newGetBlocksByHashManager(hashes, whitelist) |
||||||
|
|
||||||
|
var ( |
||||||
|
wg sync.WaitGroup |
||||||
|
gErr error |
||||||
|
errLock sync.Mutex |
||||||
|
) |
||||||
|
|
||||||
|
concurrency := sh.config.Concurrency |
||||||
|
if concurrency > m.numRequests() { |
||||||
|
concurrency = m.numRequests() |
||||||
|
} |
||||||
|
|
||||||
|
wg.Add(concurrency) |
||||||
|
for i := 0; i != concurrency; i++ { |
||||||
|
go func(index int) { |
||||||
|
defer wg.Done() |
||||||
|
defer cancel() // it's ok to cancel context more than once
|
||||||
|
|
||||||
|
for { |
||||||
|
if m.isDone() { |
||||||
|
return |
||||||
|
} |
||||||
|
hashes, wl, err := m.getNextHashes() |
||||||
|
if err != nil { |
||||||
|
errLock.Lock() |
||||||
|
gErr = err |
||||||
|
errLock.Unlock() |
||||||
|
return |
||||||
|
} |
||||||
|
if len(hashes) == 0 { |
||||||
|
select { |
||||||
|
case <-time.After(200 * time.Millisecond): |
||||||
|
continue |
||||||
|
case <-ctx.Done(): |
||||||
|
return |
||||||
|
} |
||||||
|
} |
||||||
|
blocks, stid, err := sh.doGetBlocksByHashesRequest(ctx, hashes, wl) |
||||||
|
if err != nil { |
||||||
|
sh.logger.Warn().Err(err). |
||||||
|
Str("StreamID", string(stid)). |
||||||
|
Int("hashes", len(hashes)). |
||||||
|
Int("index", index). |
||||||
|
Msg(WrapStagedSyncMsg("getBlocksByHashes worker failed")) |
||||||
|
m.handleResultError(hashes, stid) |
||||||
|
} else { |
||||||
|
m.addResult(hashes, blocks, stid) |
||||||
|
} |
||||||
|
} |
||||||
|
}(i) |
||||||
|
} |
||||||
|
wg.Wait() |
||||||
|
|
||||||
|
if gErr != nil { |
||||||
|
return nil, nil, gErr |
||||||
|
} |
||||||
|
select { |
||||||
|
case <-sh.ctx.Done(): |
||||||
|
res, _, _ := m.getResults() |
||||||
|
sh.logger.Info().Err(sh.ctx.Err()).Int("num blocks", len(res)). |
||||||
|
Msg(WrapStagedSyncMsg("short range sync get blocks timed out")) |
||||||
|
return nil, nil, sh.ctx.Err() |
||||||
|
default: |
||||||
|
} |
||||||
|
|
||||||
|
return m.getResults() |
||||||
|
} |
||||||
|
|
||||||
|
func (sh *srHelper) checkPrerequisites() error { |
||||||
|
if sh.syncProtocol.NumStreams() < sh.config.Concurrency { |
||||||
|
return errors.New("not enough streams") |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (sh *srHelper) prepareBlockHashNumbers(curNumber uint64, count int) []uint64 { |
||||||
|
|
||||||
|
n := count |
||||||
|
if count > BlockHashesPerRequest { |
||||||
|
n = BlockHashesPerRequest |
||||||
|
} |
||||||
|
res := make([]uint64, 0, n) |
||||||
|
|
||||||
|
for bn := curNumber + 1; bn <= curNumber+uint64(n); bn++ { |
||||||
|
res = append(res, bn) |
||||||
|
} |
||||||
|
return res |
||||||
|
} |
||||||
|
|
||||||
|
func (sh *srHelper) doGetBlockHashesRequest(bns []uint64) ([]common.Hash, sttypes.StreamID, error) { |
||||||
|
ctx, cancel := context.WithTimeout(sh.ctx, 1*time.Second) |
||||||
|
defer cancel() |
||||||
|
|
||||||
|
hashes, stid, err := sh.syncProtocol.GetBlockHashes(ctx, bns) |
||||||
|
if err != nil { |
||||||
|
sh.logger.Warn().Err(err). |
||||||
|
Interface("block numbers", bns). |
||||||
|
Str("stream", string(stid)). |
||||||
|
Msg(WrapStagedSyncMsg("failed to doGetBlockHashesRequest")) |
||||||
|
return nil, stid, err |
||||||
|
} |
||||||
|
if len(hashes) != len(bns) { |
||||||
|
err := errors.New("unexpected get block hashes result delivered") |
||||||
|
sh.logger.Warn().Err(err). |
||||||
|
Str("stream", string(stid)). |
||||||
|
Msg(WrapStagedSyncMsg("failed to doGetBlockHashesRequest")) |
||||||
|
sh.syncProtocol.StreamFailed(stid, "unexpected get block hashes result delivered") |
||||||
|
return nil, stid, err |
||||||
|
} |
||||||
|
return hashes, stid, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (sh *srHelper) doGetBlocksByNumbersRequest(bns []uint64) ([]*types.Block, sttypes.StreamID, error) { |
||||||
|
ctx, cancel := context.WithTimeout(sh.ctx, 10*time.Second) |
||||||
|
defer cancel() |
||||||
|
|
||||||
|
blocks, stid, err := sh.syncProtocol.GetBlocksByNumber(ctx, bns) |
||||||
|
if err != nil { |
||||||
|
sh.logger.Warn().Err(err).Str("stream", string(stid)).Msg(WrapStagedSyncMsg("failed to doGetBlockHashesRequest")) |
||||||
|
return nil, stid, err |
||||||
|
} |
||||||
|
return blocks, stid, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (sh *srHelper) doGetBlocksByHashesRequest(ctx context.Context, hashes []common.Hash, wl []sttypes.StreamID) ([]*types.Block, sttypes.StreamID, error) { |
||||||
|
ctx, cancel := context.WithTimeout(sh.ctx, 10*time.Second) |
||||||
|
defer cancel() |
||||||
|
|
||||||
|
blocks, stid, err := sh.syncProtocol.GetBlocksByHashes(ctx, hashes, |
||||||
|
syncProto.WithWhitelist(wl)) |
||||||
|
if err != nil { |
||||||
|
sh.logger.Warn().Err(err).Str("stream", string(stid)).Msg("failed to getBlockByHashes") |
||||||
|
return nil, stid, err |
||||||
|
} |
||||||
|
if err := checkGetBlockByHashesResult(blocks, hashes); err != nil { |
||||||
|
sh.logger.Warn().Err(err).Str("stream", string(stid)).Msg(WrapStagedSyncMsg("failed to getBlockByHashes")) |
||||||
|
sh.syncProtocol.StreamFailed(stid, "failed to getBlockByHashes") |
||||||
|
return nil, stid, err |
||||||
|
} |
||||||
|
return blocks, stid, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (sh *srHelper) removeStreams(sts []sttypes.StreamID) { |
||||||
|
for _, st := range sts { |
||||||
|
sh.syncProtocol.RemoveStream(st) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// blameAllStreams only not to blame all whitelisted streams when the it's not the last block signature verification failed.
|
||||||
|
func (sh *srHelper) blameAllStreams(blocks types.Blocks, errIndex int, err error) bool { |
||||||
|
if errors.As(err, &emptySigVerifyErr) && errIndex == len(blocks)-1 { |
||||||
|
return false |
||||||
|
} |
||||||
|
return true |
||||||
|
} |
@ -0,0 +1,112 @@ |
|||||||
|
package stagedstreamsync |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
"errors" |
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common" |
||||||
|
sttypes "github.com/harmony-one/harmony/p2p/stream/types" |
||||||
|
"github.com/ledgerwatch/erigon-lib/kv" |
||||||
|
) |
||||||
|
|
||||||
|
type ExecFunc func(firstCycle bool, invalidBlockRevert bool, s *StageState, reverter Reverter, tx kv.RwTx) error |
||||||
|
|
||||||
|
type StageHandler interface { |
||||||
|
// Exec is the execution function for the stage to move forward.
|
||||||
|
// * firstCycle - is it the first cycle of syncing.
|
||||||
|
// * invalidBlockRevert - whether the execution is to solve the invalid block
|
||||||
|
// * s - is the current state of the stage and contains stage data.
|
||||||
|
// * reverter - if the stage needs to cause reverting, `reverter` methods can be used.
|
||||||
|
Exec(firstCycle bool, invalidBlockRevert bool, s *StageState, reverter Reverter, tx kv.RwTx) error |
||||||
|
|
||||||
|
// Revert is the reverting logic of the stage.
|
||||||
|
// * firstCycle - is it the first cycle of syncing.
|
||||||
|
// * u - contains information about the revert itself.
|
||||||
|
// * s - represents the state of this stage at the beginning of revert.
|
||||||
|
Revert(firstCycle bool, u *RevertState, s *StageState, tx kv.RwTx) error |
||||||
|
|
||||||
|
// CleanUp is the execution function for the stage to prune old data.
|
||||||
|
// * firstCycle - is it the first cycle of syncing.
|
||||||
|
// * p - is the current state of the stage and contains stage data.
|
||||||
|
CleanUp(firstCycle bool, p *CleanUpState, tx kv.RwTx) error |
||||||
|
|
||||||
|
// SetStageContext updates the context for stage
|
||||||
|
SetStageContext(ctx context.Context) |
||||||
|
} |
||||||
|
|
||||||
|
// Stage is a single sync stage in staged sync.
|
||||||
|
type Stage struct { |
||||||
|
// ID of the sync stage. Should not be empty and should be unique. It is recommended to prefix it with reverse domain to avoid clashes (`com.example.my-stage`).
|
||||||
|
ID SyncStageID |
||||||
|
// Handler handles the logic for the stage
|
||||||
|
Handler StageHandler |
||||||
|
// Description is a string that is shown in the logs.
|
||||||
|
Description string |
||||||
|
// DisabledDescription shows in the log with a message if the stage is disabled. Here, you can show which command line flags should be provided to enable the page.
|
||||||
|
DisabledDescription string |
||||||
|
// Disabled defines if the stage is disabled. It sets up when the stage is build by its `StageBuilder`.
|
||||||
|
Disabled bool |
||||||
|
} |
||||||
|
|
||||||
|
var ErrStopped = errors.New("stopped") |
||||||
|
var ErrRevert = errors.New("unwound") |
||||||
|
|
||||||
|
// StageState is the state of the stage.
|
||||||
|
type StageState struct { |
||||||
|
state *StagedStreamSync |
||||||
|
ID SyncStageID |
||||||
|
BlockNumber uint64 // BlockNumber is the current block number of the stage at the beginning of the state execution.
|
||||||
|
} |
||||||
|
|
||||||
|
func (s *StageState) LogPrefix() string { return s.state.LogPrefix() } |
||||||
|
|
||||||
|
func (s *StageState) CurrentStageProgress(db kv.Getter) (uint64, error) { |
||||||
|
return GetStageProgress(db, s.ID, s.state.isBeacon) |
||||||
|
} |
||||||
|
|
||||||
|
func (s *StageState) StageProgress(db kv.Getter, id SyncStageID) (uint64, error) { |
||||||
|
return GetStageProgress(db, id, s.state.isBeacon) |
||||||
|
} |
||||||
|
|
||||||
|
// Update updates the stage state (current block number) in the database. Can be called multiple times during stage execution.
|
||||||
|
func (s *StageState) Update(db kv.Putter, newBlockNum uint64) error { |
||||||
|
return SaveStageProgress(db, s.ID, s.state.isBeacon, newBlockNum) |
||||||
|
} |
||||||
|
func (s *StageState) UpdateCleanUp(db kv.Putter, blockNum uint64) error { |
||||||
|
return SaveStageCleanUpProgress(db, s.ID, s.state.isBeacon, blockNum) |
||||||
|
} |
||||||
|
|
||||||
|
// Reverter allows the stage to cause an revert.
|
||||||
|
type Reverter interface { |
||||||
|
// RevertTo begins staged sync revert to the specified block.
|
||||||
|
RevertTo(revertPoint uint64, invalidBlockNumber uint64, invalidBlockHash common.Hash, invalidBlockStreamID sttypes.StreamID) |
||||||
|
} |
||||||
|
|
||||||
|
// RevertState contains the information about revert.
|
||||||
|
type RevertState struct { |
||||||
|
ID SyncStageID |
||||||
|
RevertPoint uint64 // RevertPoint is the block to revert to.
|
||||||
|
state *StagedStreamSync |
||||||
|
} |
||||||
|
|
||||||
|
func (u *RevertState) LogPrefix() string { return u.state.LogPrefix() } |
||||||
|
|
||||||
|
// Done updates the DB state of the stage.
|
||||||
|
func (u *RevertState) Done(db kv.Putter) error { |
||||||
|
return SaveStageProgress(db, u.ID, u.state.isBeacon, u.RevertPoint) |
||||||
|
} |
||||||
|
|
||||||
|
type CleanUpState struct { |
||||||
|
ID SyncStageID |
||||||
|
ForwardProgress uint64 // progress of stage forward move
|
||||||
|
CleanUpProgress uint64 // progress of stage prune move. after sync cycle it become equal to ForwardProgress by Done() method
|
||||||
|
state *StagedStreamSync |
||||||
|
} |
||||||
|
|
||||||
|
func (s *CleanUpState) LogPrefix() string { return s.state.LogPrefix() + " CleanUp" } |
||||||
|
func (s *CleanUpState) Done(db kv.Putter) error { |
||||||
|
return SaveStageCleanUpProgress(db, s.ID, s.state.isBeacon, s.ForwardProgress) |
||||||
|
} |
||||||
|
func (s *CleanUpState) DoneAt(db kv.Putter, blockNum uint64) error { |
||||||
|
return SaveStageCleanUpProgress(db, s.ID, s.state.isBeacon, blockNum) |
||||||
|
} |
@ -0,0 +1,420 @@ |
|||||||
|
package stagedstreamsync |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
"fmt" |
||||||
|
"sync" |
||||||
|
"time" |
||||||
|
|
||||||
|
"github.com/harmony-one/harmony/core" |
||||||
|
"github.com/harmony-one/harmony/core/types" |
||||||
|
"github.com/harmony-one/harmony/internal/utils" |
||||||
|
sttypes "github.com/harmony-one/harmony/p2p/stream/types" |
||||||
|
"github.com/ledgerwatch/erigon-lib/kv" |
||||||
|
"github.com/pkg/errors" |
||||||
|
) |
||||||
|
|
||||||
|
type StageBodies struct { |
||||||
|
configs StageBodiesCfg |
||||||
|
} |
||||||
|
type StageBodiesCfg struct { |
||||||
|
ctx context.Context |
||||||
|
bc core.BlockChain |
||||||
|
db kv.RwDB |
||||||
|
blockDBs []kv.RwDB |
||||||
|
concurrency int |
||||||
|
protocol syncProtocol |
||||||
|
isBeacon bool |
||||||
|
logProgress bool |
||||||
|
} |
||||||
|
|
||||||
|
func NewStageBodies(cfg StageBodiesCfg) *StageBodies { |
||||||
|
return &StageBodies{ |
||||||
|
configs: cfg, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func NewStageBodiesCfg(ctx context.Context, bc core.BlockChain, db kv.RwDB, blockDBs []kv.RwDB, concurrency int, protocol syncProtocol, isBeacon bool, logProgress bool) StageBodiesCfg { |
||||||
|
return StageBodiesCfg{ |
||||||
|
ctx: ctx, |
||||||
|
bc: bc, |
||||||
|
db: db, |
||||||
|
blockDBs: blockDBs, |
||||||
|
concurrency: concurrency, |
||||||
|
protocol: protocol, |
||||||
|
isBeacon: isBeacon, |
||||||
|
logProgress: logProgress, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (b *StageBodies) SetStageContext(ctx context.Context) { |
||||||
|
b.configs.ctx = ctx |
||||||
|
} |
||||||
|
|
||||||
|
// Exec progresses Bodies stage in the forward direction
|
||||||
|
func (b *StageBodies) Exec(firstCycle bool, invalidBlockRevert bool, s *StageState, reverter Reverter, tx kv.RwTx) (err error) { |
||||||
|
|
||||||
|
useInternalTx := tx == nil |
||||||
|
|
||||||
|
if invalidBlockRevert { |
||||||
|
return b.redownloadBadBlock(s) |
||||||
|
} |
||||||
|
|
||||||
|
// for short range sync, skip this stage
|
||||||
|
if !s.state.initSync { |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
maxHeight := s.state.status.targetBN |
||||||
|
currentHead := b.configs.bc.CurrentBlock().NumberU64() |
||||||
|
if currentHead >= maxHeight { |
||||||
|
return nil |
||||||
|
} |
||||||
|
currProgress := uint64(0) |
||||||
|
targetHeight := s.state.currentCycle.TargetHeight |
||||||
|
// isBeacon := s.state.isBeacon
|
||||||
|
// isLastCycle := targetHeight >= maxHeight
|
||||||
|
|
||||||
|
if errV := CreateView(b.configs.ctx, b.configs.db, tx, func(etx kv.Tx) error { |
||||||
|
if currProgress, err = s.CurrentStageProgress(etx); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
return nil |
||||||
|
}); errV != nil { |
||||||
|
return errV |
||||||
|
} |
||||||
|
|
||||||
|
if currProgress == 0 { |
||||||
|
if err := b.cleanAllBlockDBs(); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
currProgress = currentHead |
||||||
|
} |
||||||
|
|
||||||
|
if currProgress >= targetHeight { |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// size := uint64(0)
|
||||||
|
startTime := time.Now() |
||||||
|
// startBlock := currProgress
|
||||||
|
if b.configs.logProgress { |
||||||
|
fmt.Print("\033[s") // save the cursor position
|
||||||
|
} |
||||||
|
|
||||||
|
if useInternalTx { |
||||||
|
var err error |
||||||
|
tx, err = b.configs.db.BeginRw(context.Background()) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
defer tx.Rollback() |
||||||
|
} |
||||||
|
|
||||||
|
// Fetch blocks from neighbors
|
||||||
|
s.state.gbm = newBlockDownloadManager(tx, b.configs.bc, targetHeight, s.state.logger) |
||||||
|
|
||||||
|
// Setup workers to fetch blocks from remote node
|
||||||
|
var wg sync.WaitGroup |
||||||
|
|
||||||
|
for i := 0; i != s.state.config.Concurrency; i++ { |
||||||
|
wg.Add(1) |
||||||
|
go b.runBlockWorkerLoop(s.state.gbm, &wg, i, startTime) |
||||||
|
} |
||||||
|
|
||||||
|
wg.Wait() |
||||||
|
|
||||||
|
if useInternalTx { |
||||||
|
if err := tx.Commit(); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// runBlockWorkerLoop creates a work loop for download blocks
|
||||||
|
func (b *StageBodies) runBlockWorkerLoop(gbm *blockDownloadManager, wg *sync.WaitGroup, loopID int, startTime time.Time) { |
||||||
|
|
||||||
|
currentBlock := int(b.configs.bc.CurrentBlock().NumberU64()) |
||||||
|
|
||||||
|
defer wg.Done() |
||||||
|
|
||||||
|
for { |
||||||
|
select { |
||||||
|
case <-b.configs.ctx.Done(): |
||||||
|
return |
||||||
|
default: |
||||||
|
} |
||||||
|
batch := gbm.GetNextBatch() |
||||||
|
if len(batch) == 0 { |
||||||
|
select { |
||||||
|
case <-b.configs.ctx.Done(): |
||||||
|
return |
||||||
|
case <-time.After(100 * time.Millisecond): |
||||||
|
return |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
blockBytes, sigBytes, stid, err := b.downloadRawBlocks(batch) |
||||||
|
if err != nil { |
||||||
|
if !errors.Is(err, context.Canceled) { |
||||||
|
b.configs.protocol.StreamFailed(stid, "downloadRawBlocks failed") |
||||||
|
} |
||||||
|
utils.Logger().Error(). |
||||||
|
Err(err). |
||||||
|
Str("stream", string(stid)). |
||||||
|
Interface("block numbers", batch). |
||||||
|
Msg(WrapStagedSyncMsg("downloadRawBlocks failed")) |
||||||
|
err = errors.Wrap(err, "request error") |
||||||
|
gbm.HandleRequestError(batch, err, stid) |
||||||
|
} else { |
||||||
|
if err = b.saveBlocks(gbm.tx, batch, blockBytes, sigBytes, loopID, stid); err != nil { |
||||||
|
panic("[STAGED_STREAM_SYNC] saving downloaded blocks to db failed.") |
||||||
|
} |
||||||
|
gbm.HandleRequestResult(batch, blockBytes, sigBytes, loopID, stid) |
||||||
|
if b.configs.logProgress { |
||||||
|
//calculating block download speed
|
||||||
|
dt := time.Now().Sub(startTime).Seconds() |
||||||
|
speed := float64(0) |
||||||
|
if dt > 0 { |
||||||
|
speed = float64(len(gbm.bdd)) / dt |
||||||
|
} |
||||||
|
blockSpeed := fmt.Sprintf("%.2f", speed) |
||||||
|
|
||||||
|
fmt.Print("\033[u\033[K") // restore the cursor position and clear the line
|
||||||
|
fmt.Println("downloaded blocks:", currentBlock+len(gbm.bdd), "/", int(gbm.targetBN), "(", blockSpeed, "blocks/s", ")") |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// redownloadBadBlock tries to redownload the bad block from other streams
|
||||||
|
func (b *StageBodies) redownloadBadBlock(s *StageState) error { |
||||||
|
|
||||||
|
batch := make([]uint64, 1) |
||||||
|
batch = append(batch, s.state.invalidBlock.Number) |
||||||
|
|
||||||
|
for { |
||||||
|
if b.configs.protocol.NumStreams() == 0 { |
||||||
|
return errors.Errorf("re-download bad block from all streams failed") |
||||||
|
} |
||||||
|
blockBytes, sigBytes, stid, err := b.downloadRawBlocks(batch) |
||||||
|
if err != nil { |
||||||
|
if !errors.Is(err, context.Canceled) { |
||||||
|
b.configs.protocol.StreamFailed(stid, "tried to re-download bad block from this stream, but downloadRawBlocks failed") |
||||||
|
} |
||||||
|
continue |
||||||
|
} |
||||||
|
isOneOfTheBadStreams := false |
||||||
|
for _, id := range s.state.invalidBlock.StreamID { |
||||||
|
if id == stid { |
||||||
|
b.configs.protocol.RemoveStream(stid) |
||||||
|
isOneOfTheBadStreams = true |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
if isOneOfTheBadStreams { |
||||||
|
continue |
||||||
|
} |
||||||
|
s.state.gbm.SetDownloadDetails(batch, 0, stid) |
||||||
|
if errU := b.configs.blockDBs[0].Update(context.Background(), func(tx kv.RwTx) error { |
||||||
|
if err = b.saveBlocks(tx, batch, blockBytes, sigBytes, 0, stid); err != nil { |
||||||
|
return errors.Errorf("[STAGED_STREAM_SYNC] saving re-downloaded bad block to db failed.") |
||||||
|
} |
||||||
|
return nil |
||||||
|
}); errU != nil { |
||||||
|
continue |
||||||
|
} |
||||||
|
break |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (b *StageBodies) downloadBlocks(bns []uint64) ([]*types.Block, sttypes.StreamID, error) { |
||||||
|
ctx, cancel := context.WithTimeout(b.configs.ctx, 10*time.Second) |
||||||
|
defer cancel() |
||||||
|
|
||||||
|
blocks, stid, err := b.configs.protocol.GetBlocksByNumber(ctx, bns) |
||||||
|
if err != nil { |
||||||
|
return nil, stid, err |
||||||
|
} |
||||||
|
if err := validateGetBlocksResult(bns, blocks); err != nil { |
||||||
|
return nil, stid, err |
||||||
|
} |
||||||
|
return blocks, stid, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (b *StageBodies) downloadRawBlocks(bns []uint64) ([][]byte, [][]byte, sttypes.StreamID, error) { |
||||||
|
ctx, cancel := context.WithTimeout(b.configs.ctx, 10*time.Second) |
||||||
|
defer cancel() |
||||||
|
|
||||||
|
return b.configs.protocol.GetRawBlocksByNumber(ctx, bns) |
||||||
|
} |
||||||
|
|
||||||
|
func validateGetBlocksResult(requested []uint64, result []*types.Block) error { |
||||||
|
if len(result) != len(requested) { |
||||||
|
return fmt.Errorf("unexpected number of blocks delivered: %v / %v", len(result), len(requested)) |
||||||
|
} |
||||||
|
for i, block := range result { |
||||||
|
if block != nil && block.NumberU64() != requested[i] { |
||||||
|
return fmt.Errorf("block with unexpected number delivered: %v / %v", block.NumberU64(), requested[i]) |
||||||
|
} |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// saveBlocks saves the blocks into db
|
||||||
|
func (b *StageBodies) saveBlocks(tx kv.RwTx, bns []uint64, blockBytes [][]byte, sigBytes [][]byte, loopID int, stid sttypes.StreamID) error { |
||||||
|
|
||||||
|
tx, err := b.configs.blockDBs[loopID].BeginRw(context.Background()) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
defer tx.Rollback() |
||||||
|
|
||||||
|
for i := uint64(0); i < uint64(len(blockBytes)); i++ { |
||||||
|
block := blockBytes[i] |
||||||
|
sig := sigBytes[i] |
||||||
|
if block == nil { |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
blkKey := marshalData(bns[i]) |
||||||
|
|
||||||
|
if err := tx.Put(BlocksBucket, blkKey, block); err != nil { |
||||||
|
utils.Logger().Error(). |
||||||
|
Err(err). |
||||||
|
Uint64("block height", bns[i]). |
||||||
|
Msg("[STAGED_STREAM_SYNC] adding block to db failed") |
||||||
|
return err |
||||||
|
} |
||||||
|
// sigKey := []byte("s" + string(bns[i]))
|
||||||
|
if err := tx.Put(BlockSignaturesBucket, blkKey, sig); err != nil { |
||||||
|
utils.Logger().Error(). |
||||||
|
Err(err). |
||||||
|
Uint64("block height", bns[i]). |
||||||
|
Msg("[STAGED_STREAM_SYNC] adding block sig to db failed") |
||||||
|
return err |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if err := tx.Commit(); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (b *StageBodies) saveProgress(s *StageState, progress uint64, tx kv.RwTx) (err error) { |
||||||
|
useInternalTx := tx == nil |
||||||
|
if useInternalTx { |
||||||
|
var err error |
||||||
|
tx, err = b.configs.db.BeginRw(context.Background()) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
defer tx.Rollback() |
||||||
|
} |
||||||
|
|
||||||
|
// save progress
|
||||||
|
if err = s.Update(tx, progress); err != nil { |
||||||
|
utils.Logger().Error(). |
||||||
|
Err(err). |
||||||
|
Msgf("[STAGED_SYNC] saving progress for block bodies stage failed") |
||||||
|
return ErrSavingBodiesProgressFail |
||||||
|
} |
||||||
|
|
||||||
|
if useInternalTx { |
||||||
|
if err := tx.Commit(); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (b *StageBodies) cleanBlocksDB(loopID int) (err error) { |
||||||
|
|
||||||
|
tx, errb := b.configs.blockDBs[loopID].BeginRw(b.configs.ctx) |
||||||
|
if errb != nil { |
||||||
|
return errb |
||||||
|
} |
||||||
|
defer tx.Rollback() |
||||||
|
|
||||||
|
// clean block bodies db
|
||||||
|
if err = tx.ClearBucket(BlocksBucket); err != nil { |
||||||
|
utils.Logger().Error(). |
||||||
|
Err(err). |
||||||
|
Msgf("[STAGED_STREAM_SYNC] clear blocks bucket after revert failed") |
||||||
|
return err |
||||||
|
} |
||||||
|
// clean block signatures db
|
||||||
|
if err = tx.ClearBucket(BlockSignaturesBucket); err != nil { |
||||||
|
utils.Logger().Error(). |
||||||
|
Err(err). |
||||||
|
Msgf("[STAGED_STREAM_SYNC] clear block signatures bucket after revert failed") |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
if err = tx.Commit(); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (b *StageBodies) cleanAllBlockDBs() (err error) { |
||||||
|
//clean all blocks DBs
|
||||||
|
for i := 0; i < b.configs.concurrency; i++ { |
||||||
|
if err := b.cleanBlocksDB(i); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (b *StageBodies) Revert(firstCycle bool, u *RevertState, s *StageState, tx kv.RwTx) (err error) { |
||||||
|
|
||||||
|
//clean all blocks DBs
|
||||||
|
if err := b.cleanAllBlockDBs(); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
useInternalTx := tx == nil |
||||||
|
if useInternalTx { |
||||||
|
tx, err = b.configs.db.BeginRw(b.configs.ctx) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
defer tx.Rollback() |
||||||
|
} |
||||||
|
// save progress
|
||||||
|
currentHead := b.configs.bc.CurrentBlock().NumberU64() |
||||||
|
if err = s.Update(tx, currentHead); err != nil { |
||||||
|
utils.Logger().Error(). |
||||||
|
Err(err). |
||||||
|
Msgf("[STAGED_SYNC] saving progress for block bodies stage after revert failed") |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
if err = u.Done(tx); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
if useInternalTx { |
||||||
|
if err = tx.Commit(); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (b *StageBodies) CleanUp(firstCycle bool, p *CleanUpState, tx kv.RwTx) (err error) { |
||||||
|
|
||||||
|
//clean all blocks DBs
|
||||||
|
if err := b.cleanAllBlockDBs(); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
@ -0,0 +1,198 @@ |
|||||||
|
package stagedstreamsync |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
|
||||||
|
"github.com/harmony-one/harmony/core" |
||||||
|
"github.com/harmony-one/harmony/internal/utils" |
||||||
|
"github.com/harmony-one/harmony/shard" |
||||||
|
"github.com/ledgerwatch/erigon-lib/kv" |
||||||
|
"github.com/pkg/errors" |
||||||
|
) |
||||||
|
|
||||||
|
type StageEpoch struct { |
||||||
|
configs StageEpochCfg |
||||||
|
} |
||||||
|
|
||||||
|
type StageEpochCfg struct { |
||||||
|
ctx context.Context |
||||||
|
bc core.BlockChain |
||||||
|
db kv.RwDB |
||||||
|
} |
||||||
|
|
||||||
|
func NewStageEpoch(cfg StageEpochCfg) *StageEpoch { |
||||||
|
return &StageEpoch{ |
||||||
|
configs: cfg, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func NewStageEpochCfg(ctx context.Context, bc core.BlockChain, db kv.RwDB) StageEpochCfg { |
||||||
|
return StageEpochCfg{ |
||||||
|
ctx: ctx, |
||||||
|
bc: bc, |
||||||
|
db: db, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (sr *StageEpoch) SetStageContext(ctx context.Context) { |
||||||
|
sr.configs.ctx = ctx |
||||||
|
} |
||||||
|
|
||||||
|
func (sr *StageEpoch) Exec(firstCycle bool, invalidBlockRevert bool, s *StageState, reverter Reverter, tx kv.RwTx) error { |
||||||
|
|
||||||
|
// no need to update epoch chain if we are redoing the stages because of bad block
|
||||||
|
if invalidBlockRevert { |
||||||
|
return nil |
||||||
|
} |
||||||
|
// for long range sync, skip this stage
|
||||||
|
if s.state.initSync { |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
if _, ok := sr.configs.bc.(*core.EpochChain); !ok { |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// doShortRangeSyncForEpochSync
|
||||||
|
n, err := sr.doShortRangeSyncForEpochSync(s) |
||||||
|
s.state.inserted = n |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
useInternalTx := tx == nil |
||||||
|
if useInternalTx { |
||||||
|
var err error |
||||||
|
tx, err = sr.configs.db.BeginRw(sr.configs.ctx) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
defer tx.Rollback() |
||||||
|
} |
||||||
|
|
||||||
|
if useInternalTx { |
||||||
|
if err := tx.Commit(); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (sr *StageEpoch) doShortRangeSyncForEpochSync(s *StageState) (int, error) { |
||||||
|
|
||||||
|
numShortRangeCounterVec.With(s.state.promLabels()).Inc() |
||||||
|
|
||||||
|
srCtx, cancel := context.WithTimeout(s.state.ctx, ShortRangeTimeout) |
||||||
|
defer cancel() |
||||||
|
|
||||||
|
//TODO: merge srHelper with StageEpochConfig
|
||||||
|
sh := &srHelper{ |
||||||
|
syncProtocol: s.state.protocol, |
||||||
|
ctx: srCtx, |
||||||
|
config: s.state.config, |
||||||
|
logger: utils.Logger().With().Str("mode", "epoch chain short range").Logger(), |
||||||
|
} |
||||||
|
|
||||||
|
if err := sh.checkPrerequisites(); err != nil { |
||||||
|
return 0, errors.Wrap(err, "prerequisite") |
||||||
|
} |
||||||
|
curBN := s.state.bc.CurrentBlock().NumberU64() |
||||||
|
bns := make([]uint64, 0, BlocksPerRequest) |
||||||
|
// in epoch chain, we have only the last block of each epoch, so, the current
|
||||||
|
// block's epoch number shows the last epoch we have. We should start
|
||||||
|
// from next epoch then
|
||||||
|
loopEpoch := s.state.bc.CurrentHeader().Epoch().Uint64() + 1 |
||||||
|
for len(bns) < BlocksPerRequest { |
||||||
|
blockNum := shard.Schedule.EpochLastBlock(loopEpoch) |
||||||
|
if blockNum > curBN { |
||||||
|
bns = append(bns, blockNum) |
||||||
|
} |
||||||
|
loopEpoch = loopEpoch + 1 |
||||||
|
} |
||||||
|
|
||||||
|
if len(bns) == 0 { |
||||||
|
return 0, nil |
||||||
|
} |
||||||
|
|
||||||
|
////////////////////////////////////////////////////////
|
||||||
|
hashChain, whitelist, err := sh.getHashChain(bns) |
||||||
|
if err != nil { |
||||||
|
return 0, errors.Wrap(err, "getHashChain") |
||||||
|
} |
||||||
|
if len(hashChain) == 0 { |
||||||
|
// short circuit for no sync is needed
|
||||||
|
return 0, nil |
||||||
|
} |
||||||
|
blocks, streamID, err := sh.getBlocksByHashes(hashChain, whitelist) |
||||||
|
if err != nil { |
||||||
|
utils.Logger().Warn().Err(err).Msg("epoch sync getBlocksByHashes failed") |
||||||
|
if !errors.Is(err, context.Canceled) { |
||||||
|
sh.removeStreams(whitelist) // Remote nodes cannot provide blocks with target hashes
|
||||||
|
} |
||||||
|
return 0, errors.Wrap(err, "epoch sync getBlocksByHashes") |
||||||
|
} |
||||||
|
///////////////////////////////////////////////////////
|
||||||
|
// TODO: check this
|
||||||
|
// blocks, streamID, err := sh.getBlocksChain(bns)
|
||||||
|
// if err != nil {
|
||||||
|
// return 0, errors.Wrap(err, "getHashChain")
|
||||||
|
// }
|
||||||
|
///////////////////////////////////////////////////////
|
||||||
|
if len(blocks) == 0 { |
||||||
|
// short circuit for no sync is needed
|
||||||
|
return 0, nil |
||||||
|
} |
||||||
|
|
||||||
|
n, err := s.state.bc.InsertChain(blocks, true) |
||||||
|
numBlocksInsertedShortRangeHistogramVec.With(s.state.promLabels()).Observe(float64(n)) |
||||||
|
if err != nil { |
||||||
|
utils.Logger().Info().Err(err).Int("blocks inserted", n).Msg("Insert block failed") |
||||||
|
sh.removeStreams(streamID) // Data provided by remote nodes is corrupted
|
||||||
|
return n, err |
||||||
|
} |
||||||
|
if n > 0 { |
||||||
|
utils.Logger().Info().Int("blocks inserted", n).Msg("Insert block success") |
||||||
|
} |
||||||
|
return n, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (sr *StageEpoch) Revert(firstCycle bool, u *RevertState, s *StageState, tx kv.RwTx) (err error) { |
||||||
|
useInternalTx := tx == nil |
||||||
|
if useInternalTx { |
||||||
|
tx, err = sr.configs.db.BeginRw(context.Background()) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
defer tx.Rollback() |
||||||
|
} |
||||||
|
|
||||||
|
if err = u.Done(tx); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
if useInternalTx { |
||||||
|
if err := tx.Commit(); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (sr *StageEpoch) CleanUp(firstCycle bool, p *CleanUpState, tx kv.RwTx) (err error) { |
||||||
|
useInternalTx := tx == nil |
||||||
|
if useInternalTx { |
||||||
|
tx, err = sr.configs.db.BeginRw(context.Background()) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
defer tx.Rollback() |
||||||
|
} |
||||||
|
|
||||||
|
if useInternalTx { |
||||||
|
if err = tx.Commit(); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
@ -0,0 +1,114 @@ |
|||||||
|
package stagedstreamsync |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
|
||||||
|
"github.com/ledgerwatch/erigon-lib/kv" |
||||||
|
) |
||||||
|
|
||||||
|
type StageFinish struct { |
||||||
|
configs StageFinishCfg |
||||||
|
} |
||||||
|
|
||||||
|
type StageFinishCfg struct { |
||||||
|
ctx context.Context |
||||||
|
db kv.RwDB |
||||||
|
} |
||||||
|
|
||||||
|
func NewStageFinish(cfg StageFinishCfg) *StageFinish { |
||||||
|
return &StageFinish{ |
||||||
|
configs: cfg, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func NewStageFinishCfg(ctx context.Context, db kv.RwDB) StageFinishCfg { |
||||||
|
return StageFinishCfg{ |
||||||
|
ctx: ctx, |
||||||
|
db: db, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (finish *StageFinish) SetStageContext(ctx context.Context) { |
||||||
|
finish.configs.ctx = ctx |
||||||
|
} |
||||||
|
|
||||||
|
func (finish *StageFinish) Exec(firstCycle bool, invalidBlockRevert bool, s *StageState, reverter Reverter, tx kv.RwTx) error { |
||||||
|
useInternalTx := tx == nil |
||||||
|
if useInternalTx { |
||||||
|
var err error |
||||||
|
tx, err = finish.configs.db.BeginRw(context.Background()) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
defer tx.Rollback() |
||||||
|
} |
||||||
|
|
||||||
|
// TODO: prepare indices (useful for RPC) and finalize
|
||||||
|
|
||||||
|
if useInternalTx { |
||||||
|
if err := tx.Commit(); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (bh *StageFinish) clearBucket(tx kv.RwTx, isBeacon bool) error { |
||||||
|
useInternalTx := tx == nil |
||||||
|
if useInternalTx { |
||||||
|
var err error |
||||||
|
tx, err = bh.configs.db.BeginRw(context.Background()) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
defer tx.Rollback() |
||||||
|
} |
||||||
|
|
||||||
|
if useInternalTx { |
||||||
|
if err := tx.Commit(); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (finish *StageFinish) Revert(firstCycle bool, u *RevertState, s *StageState, tx kv.RwTx) (err error) { |
||||||
|
useInternalTx := tx == nil |
||||||
|
if useInternalTx { |
||||||
|
tx, err = finish.configs.db.BeginRw(finish.configs.ctx) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
defer tx.Rollback() |
||||||
|
} |
||||||
|
|
||||||
|
if err = u.Done(tx); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
if useInternalTx { |
||||||
|
if err = tx.Commit(); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (finish *StageFinish) CleanUp(firstCycle bool, p *CleanUpState, tx kv.RwTx) (err error) { |
||||||
|
useInternalTx := tx == nil |
||||||
|
if useInternalTx { |
||||||
|
tx, err = finish.configs.db.BeginRw(finish.configs.ctx) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
defer tx.Rollback() |
||||||
|
} |
||||||
|
|
||||||
|
if useInternalTx { |
||||||
|
if err = tx.Commit(); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
@ -0,0 +1,157 @@ |
|||||||
|
package stagedstreamsync |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
|
||||||
|
"github.com/harmony-one/harmony/core" |
||||||
|
"github.com/harmony-one/harmony/internal/utils" |
||||||
|
"github.com/ledgerwatch/erigon-lib/kv" |
||||||
|
) |
||||||
|
|
||||||
|
type StageHeads struct { |
||||||
|
configs StageHeadsCfg |
||||||
|
} |
||||||
|
|
||||||
|
type StageHeadsCfg struct { |
||||||
|
ctx context.Context |
||||||
|
bc core.BlockChain |
||||||
|
db kv.RwDB |
||||||
|
} |
||||||
|
|
||||||
|
func NewStageHeads(cfg StageHeadsCfg) *StageHeads { |
||||||
|
return &StageHeads{ |
||||||
|
configs: cfg, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func NewStageHeadersCfg(ctx context.Context, bc core.BlockChain, db kv.RwDB) StageHeadsCfg { |
||||||
|
return StageHeadsCfg{ |
||||||
|
ctx: ctx, |
||||||
|
bc: bc, |
||||||
|
db: db, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (heads *StageHeads) SetStageContext(ctx context.Context) { |
||||||
|
heads.configs.ctx = ctx |
||||||
|
} |
||||||
|
|
||||||
|
func (heads *StageHeads) Exec(firstCycle bool, invalidBlockRevert bool, s *StageState, reverter Reverter, tx kv.RwTx) error { |
||||||
|
|
||||||
|
// no need to update target if we are redoing the stages because of bad block
|
||||||
|
if invalidBlockRevert { |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// no need for short range sync
|
||||||
|
if !s.state.initSync { |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
useInternalTx := tx == nil |
||||||
|
if useInternalTx { |
||||||
|
var err error |
||||||
|
tx, err = heads.configs.db.BeginRw(heads.configs.ctx) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
defer tx.Rollback() |
||||||
|
} |
||||||
|
|
||||||
|
maxHeight := s.state.status.targetBN |
||||||
|
maxBlocksPerSyncCycle := uint64(1024) // TODO: should be in config -> s.state.MaxBlocksPerSyncCycle
|
||||||
|
currentHeight := heads.configs.bc.CurrentBlock().NumberU64() |
||||||
|
s.state.currentCycle.TargetHeight = maxHeight |
||||||
|
targetHeight := uint64(0) |
||||||
|
if errV := CreateView(heads.configs.ctx, heads.configs.db, tx, func(etx kv.Tx) (err error) { |
||||||
|
if targetHeight, err = s.CurrentStageProgress(etx); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
return nil |
||||||
|
}); errV != nil { |
||||||
|
return errV |
||||||
|
} |
||||||
|
|
||||||
|
if currentHeight >= maxHeight { |
||||||
|
utils.Logger().Info().Uint64("current number", currentHeight).Uint64("target number", maxHeight). |
||||||
|
Msg(WrapStagedSyncMsg("early return of long range sync")) |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// if current height is ahead of target height, we need recalculate target height
|
||||||
|
if currentHeight >= targetHeight { |
||||||
|
if maxHeight <= currentHeight { |
||||||
|
return nil |
||||||
|
} |
||||||
|
utils.Logger().Info(). |
||||||
|
Uint64("max blocks per sync cycle", maxBlocksPerSyncCycle). |
||||||
|
Uint64("maxPeersHeight", maxHeight). |
||||||
|
Msgf(WrapStagedSyncMsg("current height is ahead of target height, target height is readjusted to max peers height")) |
||||||
|
targetHeight = maxHeight |
||||||
|
} |
||||||
|
|
||||||
|
if targetHeight > maxHeight { |
||||||
|
targetHeight = maxHeight |
||||||
|
} |
||||||
|
|
||||||
|
if maxBlocksPerSyncCycle > 0 && targetHeight-currentHeight > maxBlocksPerSyncCycle { |
||||||
|
targetHeight = currentHeight + maxBlocksPerSyncCycle |
||||||
|
} |
||||||
|
|
||||||
|
s.state.currentCycle.TargetHeight = targetHeight |
||||||
|
|
||||||
|
if err := s.Update(tx, targetHeight); err != nil { |
||||||
|
utils.Logger().Error(). |
||||||
|
Err(err). |
||||||
|
Msgf(WrapStagedSyncMsg("saving progress for headers stage failed")) |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
if useInternalTx { |
||||||
|
if err := tx.Commit(); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (heads *StageHeads) Revert(firstCycle bool, u *RevertState, s *StageState, tx kv.RwTx) (err error) { |
||||||
|
useInternalTx := tx == nil |
||||||
|
if useInternalTx { |
||||||
|
tx, err = heads.configs.db.BeginRw(context.Background()) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
defer tx.Rollback() |
||||||
|
} |
||||||
|
|
||||||
|
if err = u.Done(tx); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
if useInternalTx { |
||||||
|
if err := tx.Commit(); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (heads *StageHeads) CleanUp(firstCycle bool, p *CleanUpState, tx kv.RwTx) (err error) { |
||||||
|
useInternalTx := tx == nil |
||||||
|
if useInternalTx { |
||||||
|
tx, err = heads.configs.db.BeginRw(context.Background()) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
defer tx.Rollback() |
||||||
|
} |
||||||
|
|
||||||
|
if useInternalTx { |
||||||
|
if err = tx.Commit(); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
@ -0,0 +1,205 @@ |
|||||||
|
package stagedstreamsync |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
|
||||||
|
"github.com/harmony-one/harmony/core" |
||||||
|
"github.com/harmony-one/harmony/internal/utils" |
||||||
|
sttypes "github.com/harmony-one/harmony/p2p/stream/types" |
||||||
|
"github.com/ledgerwatch/erigon-lib/kv" |
||||||
|
"github.com/pkg/errors" |
||||||
|
) |
||||||
|
|
||||||
|
type StageShortRange struct { |
||||||
|
configs StageShortRangeCfg |
||||||
|
} |
||||||
|
|
||||||
|
type StageShortRangeCfg struct { |
||||||
|
ctx context.Context |
||||||
|
bc core.BlockChain |
||||||
|
db kv.RwDB |
||||||
|
} |
||||||
|
|
||||||
|
func NewStageShortRange(cfg StageShortRangeCfg) *StageShortRange { |
||||||
|
return &StageShortRange{ |
||||||
|
configs: cfg, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func NewStageShortRangeCfg(ctx context.Context, bc core.BlockChain, db kv.RwDB) StageShortRangeCfg { |
||||||
|
return StageShortRangeCfg{ |
||||||
|
ctx: ctx, |
||||||
|
bc: bc, |
||||||
|
db: db, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (sr *StageShortRange) SetStageContext(ctx context.Context) { |
||||||
|
sr.configs.ctx = ctx |
||||||
|
} |
||||||
|
|
||||||
|
func (sr *StageShortRange) Exec(firstCycle bool, invalidBlockRevert bool, s *StageState, reverter Reverter, tx kv.RwTx) error { |
||||||
|
|
||||||
|
// no need to do short range if we are redoing the stages because of bad block
|
||||||
|
if invalidBlockRevert { |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// for long range sync, skip this stage
|
||||||
|
if s.state.initSync { |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
if _, ok := sr.configs.bc.(*core.EpochChain); ok { |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
curBN := sr.configs.bc.CurrentBlock().NumberU64() |
||||||
|
if curBN >= s.state.status.targetBN { |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// do short range sync
|
||||||
|
n, err := sr.doShortRangeSync(s) |
||||||
|
s.state.inserted = n |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
useInternalTx := tx == nil |
||||||
|
if useInternalTx { |
||||||
|
var err error |
||||||
|
tx, err = sr.configs.db.BeginRw(sr.configs.ctx) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
defer tx.Rollback() |
||||||
|
} |
||||||
|
|
||||||
|
if useInternalTx { |
||||||
|
if err := tx.Commit(); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// doShortRangeSync does the short range sync.
|
||||||
|
// Compared with long range sync, short range sync is more focused on syncing to the latest block.
|
||||||
|
// It consist of 3 steps:
|
||||||
|
// 1. Obtain the block hashes and compute the longest hash chain..
|
||||||
|
// 2. Get blocks by hashes from computed hash chain.
|
||||||
|
// 3. Insert the blocks to blockchain.
|
||||||
|
func (sr *StageShortRange) doShortRangeSync(s *StageState) (int, error) { |
||||||
|
|
||||||
|
numShortRangeCounterVec.With(s.state.promLabels()).Inc() |
||||||
|
|
||||||
|
srCtx, cancel := context.WithTimeout(s.state.ctx, ShortRangeTimeout) |
||||||
|
defer cancel() |
||||||
|
|
||||||
|
sh := &srHelper{ |
||||||
|
syncProtocol: s.state.protocol, |
||||||
|
ctx: srCtx, |
||||||
|
config: s.state.config, |
||||||
|
logger: utils.Logger().With().Str("mode", "short range").Logger(), |
||||||
|
} |
||||||
|
|
||||||
|
if err := sh.checkPrerequisites(); err != nil { |
||||||
|
return 0, errors.Wrap(err, "prerequisite") |
||||||
|
} |
||||||
|
curBN := sr.configs.bc.CurrentBlock().NumberU64() |
||||||
|
blkCount := int(s.state.status.targetBN) - int(curBN) |
||||||
|
blkNums := sh.prepareBlockHashNumbers(curBN, blkCount) |
||||||
|
hashChain, whitelist, err := sh.getHashChain(blkNums) |
||||||
|
if err != nil { |
||||||
|
return 0, errors.Wrap(err, "getHashChain") |
||||||
|
} |
||||||
|
|
||||||
|
if len(hashChain) == 0 { |
||||||
|
// short circuit for no sync is needed
|
||||||
|
return 0, nil |
||||||
|
} |
||||||
|
|
||||||
|
expEndBN := curBN + uint64(len(hashChain)) |
||||||
|
utils.Logger().Info().Uint64("current number", curBN). |
||||||
|
Uint64("target number", expEndBN). |
||||||
|
Interface("hashChain", hashChain). |
||||||
|
Msg("short range start syncing") |
||||||
|
|
||||||
|
s.state.status.setTargetBN(expEndBN) |
||||||
|
|
||||||
|
s.state.status.startSyncing() |
||||||
|
defer func() { |
||||||
|
utils.Logger().Info().Msg("short range finished syncing") |
||||||
|
s.state.status.finishSyncing() |
||||||
|
}() |
||||||
|
|
||||||
|
blocks, stids, err := sh.getBlocksByHashes(hashChain, whitelist) |
||||||
|
if err != nil { |
||||||
|
utils.Logger().Warn().Err(err).Msg("getBlocksByHashes failed") |
||||||
|
if !errors.Is(err, context.Canceled) { |
||||||
|
sh.removeStreams(whitelist) // Remote nodes cannot provide blocks with target hashes
|
||||||
|
} |
||||||
|
return 0, errors.Wrap(err, "getBlocksByHashes") |
||||||
|
} |
||||||
|
|
||||||
|
utils.Logger().Info().Int("num blocks", len(blocks)).Msg("getBlockByHashes result") |
||||||
|
|
||||||
|
n, err := verifyAndInsertBlocks(sr.configs.bc, blocks) |
||||||
|
numBlocksInsertedShortRangeHistogramVec.With(s.state.promLabels()).Observe(float64(n)) |
||||||
|
if err != nil { |
||||||
|
utils.Logger().Warn().Err(err).Int("blocks inserted", n).Msg("Insert block failed") |
||||||
|
if sh.blameAllStreams(blocks, n, err) { |
||||||
|
sh.removeStreams(whitelist) // Data provided by remote nodes is corrupted
|
||||||
|
} else { |
||||||
|
// It is the last block gives a wrong commit sig. Blame the provider of the last block.
|
||||||
|
st2Blame := stids[len(stids)-1] |
||||||
|
sh.removeStreams([]sttypes.StreamID{st2Blame}) |
||||||
|
} |
||||||
|
return n, err |
||||||
|
} |
||||||
|
utils.Logger().Info().Err(err).Int("blocks inserted", n).Msg("Insert block success") |
||||||
|
|
||||||
|
return n, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (sr *StageShortRange) Revert(firstCycle bool, u *RevertState, s *StageState, tx kv.RwTx) (err error) { |
||||||
|
useInternalTx := tx == nil |
||||||
|
if useInternalTx { |
||||||
|
tx, err = sr.configs.db.BeginRw(context.Background()) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
defer tx.Rollback() |
||||||
|
} |
||||||
|
|
||||||
|
if err = u.Done(tx); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
if useInternalTx { |
||||||
|
if err := tx.Commit(); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (sr *StageShortRange) CleanUp(firstCycle bool, p *CleanUpState, tx kv.RwTx) (err error) { |
||||||
|
useInternalTx := tx == nil |
||||||
|
if useInternalTx { |
||||||
|
tx, err = sr.configs.db.BeginRw(context.Background()) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
defer tx.Rollback() |
||||||
|
} |
||||||
|
|
||||||
|
if useInternalTx { |
||||||
|
if err = tx.Commit(); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
@ -0,0 +1,295 @@ |
|||||||
|
package stagedstreamsync |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
"fmt" |
||||||
|
"time" |
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common" |
||||||
|
"github.com/ethereum/go-ethereum/rlp" |
||||||
|
"github.com/harmony-one/harmony/core" |
||||||
|
"github.com/harmony-one/harmony/core/types" |
||||||
|
"github.com/harmony-one/harmony/internal/utils" |
||||||
|
"github.com/ledgerwatch/erigon-lib/kv" |
||||||
|
"github.com/prometheus/client_golang/prometheus" |
||||||
|
"github.com/rs/zerolog" |
||||||
|
) |
||||||
|
|
||||||
|
type StageStates struct { |
||||||
|
configs StageStatesCfg |
||||||
|
} |
||||||
|
type StageStatesCfg struct { |
||||||
|
ctx context.Context |
||||||
|
bc core.BlockChain |
||||||
|
db kv.RwDB |
||||||
|
blockDBs []kv.RwDB |
||||||
|
concurrency int |
||||||
|
logger zerolog.Logger |
||||||
|
logProgress bool |
||||||
|
} |
||||||
|
|
||||||
|
func NewStageStates(cfg StageStatesCfg) *StageStates { |
||||||
|
return &StageStates{ |
||||||
|
configs: cfg, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func NewStageStatesCfg(ctx context.Context, |
||||||
|
bc core.BlockChain, |
||||||
|
db kv.RwDB, |
||||||
|
blockDBs []kv.RwDB, |
||||||
|
concurrency int, |
||||||
|
logger zerolog.Logger, |
||||||
|
logProgress bool) StageStatesCfg { |
||||||
|
|
||||||
|
return StageStatesCfg{ |
||||||
|
ctx: ctx, |
||||||
|
bc: bc, |
||||||
|
db: db, |
||||||
|
blockDBs: blockDBs, |
||||||
|
concurrency: concurrency, |
||||||
|
logger: logger, |
||||||
|
logProgress: logProgress, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (stg *StageStates) SetStageContext(ctx context.Context) { |
||||||
|
stg.configs.ctx = ctx |
||||||
|
} |
||||||
|
|
||||||
|
// Exec progresses States stage in the forward direction
|
||||||
|
func (stg *StageStates) Exec(firstCycle bool, invalidBlockRevert bool, s *StageState, reverter Reverter, tx kv.RwTx) (err error) { |
||||||
|
|
||||||
|
// for short range sync, skip this step
|
||||||
|
if !s.state.initSync { |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
maxHeight := s.state.status.targetBN |
||||||
|
currentHead := stg.configs.bc.CurrentBlock().NumberU64() |
||||||
|
if currentHead >= maxHeight { |
||||||
|
return nil |
||||||
|
} |
||||||
|
currProgress := stg.configs.bc.CurrentBlock().NumberU64() |
||||||
|
targetHeight := s.state.currentCycle.TargetHeight |
||||||
|
if currProgress >= targetHeight { |
||||||
|
return nil |
||||||
|
} |
||||||
|
useInternalTx := tx == nil |
||||||
|
if useInternalTx { |
||||||
|
var err error |
||||||
|
tx, err = stg.configs.db.BeginRw(stg.configs.ctx) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
defer tx.Rollback() |
||||||
|
} |
||||||
|
|
||||||
|
// isLastCycle := targetHeight >= maxHeight
|
||||||
|
startTime := time.Now() |
||||||
|
startBlock := currProgress |
||||||
|
pl := s.state.promLabels() |
||||||
|
gbm := s.state.gbm |
||||||
|
|
||||||
|
// prepare db transactions
|
||||||
|
txs := make([]kv.RwTx, stg.configs.concurrency) |
||||||
|
for i := 0; i < stg.configs.concurrency; i++ { |
||||||
|
txs[i], err = stg.configs.blockDBs[i].BeginRw(context.Background()) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
defer func() { |
||||||
|
for i := 0; i < stg.configs.concurrency; i++ { |
||||||
|
txs[i].Rollback() |
||||||
|
} |
||||||
|
}() |
||||||
|
|
||||||
|
if stg.configs.logProgress { |
||||||
|
fmt.Print("\033[s") // save the cursor position
|
||||||
|
} |
||||||
|
|
||||||
|
for i := currProgress + 1; i <= targetHeight; i++ { |
||||||
|
blkKey := marshalData(i) |
||||||
|
loopID, streamID := gbm.GetDownloadDetails(i) |
||||||
|
|
||||||
|
blockBytes, err := txs[loopID].GetOne(BlocksBucket, blkKey) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
sigBytes, err := txs[loopID].GetOne(BlockSignaturesBucket, blkKey) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
// if block size is invalid, we have to break the updating state loop
|
||||||
|
// we don't need to do rollback, because the latest batch haven't added to chain yet
|
||||||
|
sz := len(blockBytes) |
||||||
|
if sz <= 1 { |
||||||
|
utils.Logger().Error(). |
||||||
|
Uint64("block number", i). |
||||||
|
Msg("block size invalid") |
||||||
|
invalidBlockHash := common.Hash{} |
||||||
|
s.state.protocol.StreamFailed(streamID, "zero bytes block is received from stream") |
||||||
|
reverter.RevertTo(stg.configs.bc.CurrentBlock().NumberU64(), i, invalidBlockHash, streamID) |
||||||
|
return ErrInvalidBlockBytes |
||||||
|
} |
||||||
|
|
||||||
|
var block *types.Block |
||||||
|
if err := rlp.DecodeBytes(blockBytes, &block); err != nil { |
||||||
|
utils.Logger().Error(). |
||||||
|
Uint64("block number", i). |
||||||
|
Msg("block size invalid") |
||||||
|
s.state.protocol.StreamFailed(streamID, "invalid block is received from stream") |
||||||
|
invalidBlockHash := common.Hash{} |
||||||
|
reverter.RevertTo(stg.configs.bc.CurrentBlock().NumberU64(), i, invalidBlockHash, streamID) |
||||||
|
return ErrInvalidBlockBytes |
||||||
|
} |
||||||
|
if sigBytes != nil { |
||||||
|
block.SetCurrentCommitSig(sigBytes) |
||||||
|
} |
||||||
|
|
||||||
|
if block.NumberU64() != i { |
||||||
|
s.state.protocol.StreamFailed(streamID, "invalid block with unmatched number is received from stream") |
||||||
|
invalidBlockHash := block.Hash() |
||||||
|
reverter.RevertTo(stg.configs.bc.CurrentBlock().NumberU64(), i, invalidBlockHash, streamID) |
||||||
|
return ErrInvalidBlockNumber |
||||||
|
} |
||||||
|
|
||||||
|
if err := verifyAndInsertBlock(stg.configs.bc, block); err != nil { |
||||||
|
stg.configs.logger.Warn().Err(err).Uint64("cycle target block", targetHeight). |
||||||
|
Uint64("block number", block.NumberU64()). |
||||||
|
Msg(WrapStagedSyncMsg("insert blocks failed in long range")) |
||||||
|
s.state.protocol.StreamFailed(streamID, "unverifiable invalid block is received from stream") |
||||||
|
invalidBlockHash := block.Hash() |
||||||
|
reverter.RevertTo(stg.configs.bc.CurrentBlock().NumberU64(), block.NumberU64(), invalidBlockHash, streamID) |
||||||
|
pl["error"] = err.Error() |
||||||
|
longRangeFailInsertedBlockCounterVec.With(pl).Inc() |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
if invalidBlockRevert { |
||||||
|
if s.state.invalidBlock.Number == i { |
||||||
|
s.state.invalidBlock.resolve() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
s.state.inserted++ |
||||||
|
longRangeSyncedBlockCounterVec.With(pl).Inc() |
||||||
|
|
||||||
|
utils.Logger().Info(). |
||||||
|
Uint64("blockHeight", block.NumberU64()). |
||||||
|
Uint64("blockEpoch", block.Epoch().Uint64()). |
||||||
|
Str("blockHex", block.Hash().Hex()). |
||||||
|
Uint32("ShardID", block.ShardID()). |
||||||
|
Msg("[STAGED_STREAM_SYNC] New Block Added to Blockchain") |
||||||
|
|
||||||
|
// update cur progress
|
||||||
|
currProgress = stg.configs.bc.CurrentBlock().NumberU64() |
||||||
|
|
||||||
|
for i, tx := range block.StakingTransactions() { |
||||||
|
utils.Logger().Info(). |
||||||
|
Msgf( |
||||||
|
"StakingTxn %d: %s, %v", i, tx.StakingType().String(), tx.StakingMessage(), |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
// log the stage progress in console
|
||||||
|
if stg.configs.logProgress { |
||||||
|
//calculating block speed
|
||||||
|
dt := time.Now().Sub(startTime).Seconds() |
||||||
|
speed := float64(0) |
||||||
|
if dt > 0 { |
||||||
|
speed = float64(currProgress-startBlock) / dt |
||||||
|
} |
||||||
|
blockSpeed := fmt.Sprintf("%.2f", speed) |
||||||
|
fmt.Print("\033[u\033[K") // restore the cursor position and clear the line
|
||||||
|
fmt.Println("insert blocks progress:", currProgress, "/", targetHeight, "(", blockSpeed, "blocks/s", ")") |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
if useInternalTx { |
||||||
|
if err := tx.Commit(); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (stg *StageStates) insertChain(gbm *blockDownloadManager, |
||||||
|
protocol syncProtocol, |
||||||
|
lbls prometheus.Labels, |
||||||
|
targetBN uint64) { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
func (stg *StageStates) saveProgress(s *StageState, tx kv.RwTx) (err error) { |
||||||
|
|
||||||
|
useInternalTx := tx == nil |
||||||
|
if useInternalTx { |
||||||
|
var err error |
||||||
|
tx, err = stg.configs.db.BeginRw(context.Background()) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
defer tx.Rollback() |
||||||
|
} |
||||||
|
|
||||||
|
// save progress
|
||||||
|
if err = s.Update(tx, stg.configs.bc.CurrentBlock().NumberU64()); err != nil { |
||||||
|
utils.Logger().Error(). |
||||||
|
Err(err). |
||||||
|
Msgf("[STAGED_SYNC] saving progress for block States stage failed") |
||||||
|
return ErrSaveStateProgressFail |
||||||
|
} |
||||||
|
|
||||||
|
if useInternalTx { |
||||||
|
if err := tx.Commit(); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (stg *StageStates) Revert(firstCycle bool, u *RevertState, s *StageState, tx kv.RwTx) (err error) { |
||||||
|
useInternalTx := tx == nil |
||||||
|
if useInternalTx { |
||||||
|
tx, err = stg.configs.db.BeginRw(stg.configs.ctx) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
defer tx.Rollback() |
||||||
|
} |
||||||
|
|
||||||
|
if err = u.Done(tx); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
if useInternalTx { |
||||||
|
if err = tx.Commit(); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (stg *StageStates) CleanUp(firstCycle bool, p *CleanUpState, tx kv.RwTx) (err error) { |
||||||
|
useInternalTx := tx == nil |
||||||
|
if useInternalTx { |
||||||
|
tx, err = stg.configs.db.BeginRw(stg.configs.ctx) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
defer tx.Rollback() |
||||||
|
} |
||||||
|
|
||||||
|
if useInternalTx { |
||||||
|
if err = tx.Commit(); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
@ -0,0 +1,597 @@ |
|||||||
|
package stagedstreamsync |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
"fmt" |
||||||
|
"sync" |
||||||
|
"time" |
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common" |
||||||
|
"github.com/ethereum/go-ethereum/event" |
||||||
|
"github.com/harmony-one/harmony/core" |
||||||
|
"github.com/harmony-one/harmony/core/types" |
||||||
|
"github.com/harmony-one/harmony/internal/utils" |
||||||
|
syncproto "github.com/harmony-one/harmony/p2p/stream/protocols/sync" |
||||||
|
sttypes "github.com/harmony-one/harmony/p2p/stream/types" |
||||||
|
"github.com/ledgerwatch/erigon-lib/kv" |
||||||
|
"github.com/prometheus/client_golang/prometheus" |
||||||
|
"github.com/rs/zerolog" |
||||||
|
) |
||||||
|
|
||||||
|
type InvalidBlock struct { |
||||||
|
Active bool |
||||||
|
Number uint64 |
||||||
|
Hash common.Hash |
||||||
|
IsLogged bool |
||||||
|
StreamID []sttypes.StreamID |
||||||
|
} |
||||||
|
|
||||||
|
func (ib *InvalidBlock) set(num uint64, hash common.Hash, resetBadStreams bool) { |
||||||
|
ib.Active = true |
||||||
|
ib.IsLogged = false |
||||||
|
ib.Number = num |
||||||
|
ib.Hash = hash |
||||||
|
if resetBadStreams { |
||||||
|
ib.StreamID = make([]sttypes.StreamID, 0) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (ib *InvalidBlock) resolve() { |
||||||
|
ib.Active = false |
||||||
|
ib.IsLogged = false |
||||||
|
ib.Number = 0 |
||||||
|
ib.Hash = common.Hash{} |
||||||
|
ib.StreamID = ib.StreamID[:0] |
||||||
|
} |
||||||
|
|
||||||
|
func (ib *InvalidBlock) addBadStream(bsID sttypes.StreamID) { |
||||||
|
// only add uniques IDs
|
||||||
|
for _, stID := range ib.StreamID { |
||||||
|
if stID == bsID { |
||||||
|
return |
||||||
|
} |
||||||
|
} |
||||||
|
ib.StreamID = append(ib.StreamID, bsID) |
||||||
|
} |
||||||
|
|
||||||
|
type StagedStreamSync struct { |
||||||
|
ctx context.Context |
||||||
|
bc core.BlockChain |
||||||
|
isBeacon bool |
||||||
|
isExplorer bool |
||||||
|
db kv.RwDB |
||||||
|
protocol syncProtocol |
||||||
|
gbm *blockDownloadManager // initialized when finished get block number
|
||||||
|
inserted int |
||||||
|
config Config |
||||||
|
logger zerolog.Logger |
||||||
|
status status //TODO: merge this with currentSyncCycle
|
||||||
|
initSync bool // if sets to true, node start long range syncing
|
||||||
|
UseMemDB bool |
||||||
|
|
||||||
|
revertPoint *uint64 // used to run stages
|
||||||
|
prevRevertPoint *uint64 // used to get value from outside of staged sync after cycle (for example to notify RPCDaemon)
|
||||||
|
invalidBlock InvalidBlock |
||||||
|
currentStage uint |
||||||
|
LogProgress bool |
||||||
|
currentCycle SyncCycle // current cycle
|
||||||
|
stages []*Stage |
||||||
|
revertOrder []*Stage |
||||||
|
pruningOrder []*Stage |
||||||
|
timings []Timing |
||||||
|
logPrefixes []string |
||||||
|
|
||||||
|
evtDownloadFinished event.Feed // channel for each download task finished
|
||||||
|
evtDownloadFinishedSubscribed bool |
||||||
|
evtDownloadStarted event.Feed // channel for each download has started
|
||||||
|
evtDownloadStartedSubscribed bool |
||||||
|
} |
||||||
|
|
||||||
|
// BlockWithSig the serialization structure for request DownloaderRequest_BLOCKWITHSIG
|
||||||
|
// The block is encoded as block + commit signature
|
||||||
|
type BlockWithSig struct { |
||||||
|
Block *types.Block |
||||||
|
CommitSigAndBitmap []byte |
||||||
|
} |
||||||
|
|
||||||
|
type Timing struct { |
||||||
|
isRevert bool |
||||||
|
isCleanUp bool |
||||||
|
stage SyncStageID |
||||||
|
took time.Duration |
||||||
|
} |
||||||
|
|
||||||
|
type SyncCycle struct { |
||||||
|
Number uint64 |
||||||
|
TargetHeight uint64 |
||||||
|
lock sync.RWMutex |
||||||
|
} |
||||||
|
|
||||||
|
func (s *StagedStreamSync) Len() int { return len(s.stages) } |
||||||
|
func (s *StagedStreamSync) Context() context.Context { return s.ctx } |
||||||
|
func (s *StagedStreamSync) Blockchain() core.BlockChain { return s.bc } |
||||||
|
func (s *StagedStreamSync) DB() kv.RwDB { return s.db } |
||||||
|
func (s *StagedStreamSync) IsBeacon() bool { return s.isBeacon } |
||||||
|
func (s *StagedStreamSync) IsExplorer() bool { return s.isExplorer } |
||||||
|
func (s *StagedStreamSync) LogPrefix() string { |
||||||
|
if s == nil { |
||||||
|
return "" |
||||||
|
} |
||||||
|
return s.logPrefixes[s.currentStage] |
||||||
|
} |
||||||
|
func (s *StagedStreamSync) PrevRevertPoint() *uint64 { return s.prevRevertPoint } |
||||||
|
|
||||||
|
func (s *StagedStreamSync) NewRevertState(id SyncStageID, revertPoint uint64) *RevertState { |
||||||
|
return &RevertState{id, revertPoint, s} |
||||||
|
} |
||||||
|
|
||||||
|
func (s *StagedStreamSync) CleanUpStageState(id SyncStageID, forwardProgress uint64, tx kv.Tx, db kv.RwDB) (*CleanUpState, error) { |
||||||
|
var pruneProgress uint64 |
||||||
|
var err error |
||||||
|
|
||||||
|
if errV := CreateView(context.Background(), db, tx, func(tx kv.Tx) error { |
||||||
|
pruneProgress, err = GetStageCleanUpProgress(tx, id, s.isBeacon) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
return nil |
||||||
|
}); errV != nil { |
||||||
|
return nil, errV |
||||||
|
} |
||||||
|
|
||||||
|
return &CleanUpState{id, forwardProgress, pruneProgress, s}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *StagedStreamSync) NextStage() { |
||||||
|
if s == nil { |
||||||
|
return |
||||||
|
} |
||||||
|
s.currentStage++ |
||||||
|
} |
||||||
|
|
||||||
|
// IsBefore returns true if stage1 goes before stage2 in staged sync
|
||||||
|
func (s *StagedStreamSync) IsBefore(stage1, stage2 SyncStageID) bool { |
||||||
|
idx1 := -1 |
||||||
|
idx2 := -1 |
||||||
|
for i, stage := range s.stages { |
||||||
|
if stage.ID == stage1 { |
||||||
|
idx1 = i |
||||||
|
} |
||||||
|
|
||||||
|
if stage.ID == stage2 { |
||||||
|
idx2 = i |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return idx1 < idx2 |
||||||
|
} |
||||||
|
|
||||||
|
// IsAfter returns true if stage1 goes after stage2 in staged sync
|
||||||
|
func (s *StagedStreamSync) IsAfter(stage1, stage2 SyncStageID) bool { |
||||||
|
idx1 := -1 |
||||||
|
idx2 := -1 |
||||||
|
for i, stage := range s.stages { |
||||||
|
if stage.ID == stage1 { |
||||||
|
idx1 = i |
||||||
|
} |
||||||
|
|
||||||
|
if stage.ID == stage2 { |
||||||
|
idx2 = i |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return idx1 > idx2 |
||||||
|
} |
||||||
|
|
||||||
|
func (s *StagedStreamSync) RevertTo(revertPoint uint64, invalidBlockNumber uint64, invalidBlockHash common.Hash, invalidBlockStreamID sttypes.StreamID) { |
||||||
|
utils.Logger().Info(). |
||||||
|
Interface("invalidBlockNumber", invalidBlockNumber). |
||||||
|
Interface("invalidBlockHash", invalidBlockHash). |
||||||
|
Interface("invalidBlockStreamID", invalidBlockStreamID). |
||||||
|
Uint64("revertPoint", revertPoint). |
||||||
|
Msgf(WrapStagedSyncMsg("Reverting blocks")) |
||||||
|
s.revertPoint = &revertPoint |
||||||
|
if invalidBlockNumber > 0 || invalidBlockHash != (common.Hash{}) { |
||||||
|
resetBadStreams := !s.invalidBlock.Active |
||||||
|
s.invalidBlock.set(invalidBlockNumber, invalidBlockHash, resetBadStreams) |
||||||
|
s.invalidBlock.addBadStream(invalidBlockStreamID) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (s *StagedStreamSync) Done() { |
||||||
|
s.currentStage = uint(len(s.stages)) |
||||||
|
s.revertPoint = nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *StagedStreamSync) IsDone() bool { |
||||||
|
return s.currentStage >= uint(len(s.stages)) && s.revertPoint == nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *StagedStreamSync) SetCurrentStage(id SyncStageID) error { |
||||||
|
for i, stage := range s.stages { |
||||||
|
if stage.ID == id { |
||||||
|
s.currentStage = uint(i) |
||||||
|
return nil |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return ErrStageNotFound |
||||||
|
} |
||||||
|
|
||||||
|
func (s *StagedStreamSync) StageState(stage SyncStageID, tx kv.Tx, db kv.RwDB) (*StageState, error) { |
||||||
|
var blockNum uint64 |
||||||
|
var err error |
||||||
|
if errV := CreateView(context.Background(), db, tx, func(rtx kv.Tx) error { |
||||||
|
blockNum, err = GetStageProgress(rtx, stage, s.isBeacon) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
return nil |
||||||
|
}); errV != nil { |
||||||
|
return nil, errV |
||||||
|
} |
||||||
|
|
||||||
|
return &StageState{s, stage, blockNum}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *StagedStreamSync) cleanUp(fromStage int, db kv.RwDB, tx kv.RwTx, firstCycle bool) error { |
||||||
|
found := false |
||||||
|
for i := 0; i < len(s.pruningOrder); i++ { |
||||||
|
if s.pruningOrder[i].ID == s.stages[fromStage].ID { |
||||||
|
found = true |
||||||
|
} |
||||||
|
if !found || s.pruningOrder[i] == nil || s.pruningOrder[i].Disabled { |
||||||
|
continue |
||||||
|
} |
||||||
|
if err := s.pruneStage(firstCycle, s.pruningOrder[i], db, tx); err != nil { |
||||||
|
panic(err) |
||||||
|
} |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func New(ctx context.Context, |
||||||
|
bc core.BlockChain, |
||||||
|
db kv.RwDB, |
||||||
|
stagesList []*Stage, |
||||||
|
isBeacon bool, |
||||||
|
protocol syncProtocol, |
||||||
|
useMemDB bool, |
||||||
|
config Config, |
||||||
|
logger zerolog.Logger, |
||||||
|
) *StagedStreamSync { |
||||||
|
|
||||||
|
revertStages := make([]*Stage, len(stagesList)) |
||||||
|
for i, stageIndex := range DefaultRevertOrder { |
||||||
|
for _, s := range stagesList { |
||||||
|
if s.ID == stageIndex { |
||||||
|
revertStages[i] = s |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
pruneStages := make([]*Stage, len(stagesList)) |
||||||
|
for i, stageIndex := range DefaultCleanUpOrder { |
||||||
|
for _, s := range stagesList { |
||||||
|
if s.ID == stageIndex { |
||||||
|
pruneStages[i] = s |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
logPrefixes := make([]string, len(stagesList)) |
||||||
|
for i := range stagesList { |
||||||
|
logPrefixes[i] = fmt.Sprintf("%d/%d %s", i+1, len(stagesList), stagesList[i].ID) |
||||||
|
} |
||||||
|
|
||||||
|
status := newStatus() |
||||||
|
|
||||||
|
return &StagedStreamSync{ |
||||||
|
ctx: ctx, |
||||||
|
bc: bc, |
||||||
|
isBeacon: isBeacon, |
||||||
|
db: db, |
||||||
|
protocol: protocol, |
||||||
|
gbm: nil, |
||||||
|
status: status, |
||||||
|
inserted: 0, |
||||||
|
config: config, |
||||||
|
logger: logger, |
||||||
|
stages: stagesList, |
||||||
|
currentStage: 0, |
||||||
|
revertOrder: revertStages, |
||||||
|
pruningOrder: pruneStages, |
||||||
|
logPrefixes: logPrefixes, |
||||||
|
UseMemDB: useMemDB, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (s *StagedStreamSync) doGetCurrentNumberRequest() (uint64, sttypes.StreamID, error) { |
||||||
|
ctx, cancel := context.WithTimeout(s.ctx, 10*time.Second) |
||||||
|
defer cancel() |
||||||
|
|
||||||
|
bn, stid, err := s.protocol.GetCurrentBlockNumber(ctx, syncproto.WithHighPriority()) |
||||||
|
if err != nil { |
||||||
|
return 0, stid, err |
||||||
|
} |
||||||
|
return bn, stid, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *StagedStreamSync) promLabels() prometheus.Labels { |
||||||
|
sid := s.bc.ShardID() |
||||||
|
return prometheus.Labels{"ShardID": fmt.Sprintf("%d", sid)} |
||||||
|
} |
||||||
|
|
||||||
|
func (s *StagedStreamSync) checkHaveEnoughStreams() error { |
||||||
|
numStreams := s.protocol.NumStreams() |
||||||
|
if numStreams < s.config.MinStreams { |
||||||
|
return fmt.Errorf("number of streams smaller than minimum: %v < %v", |
||||||
|
numStreams, s.config.MinStreams) |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *StagedStreamSync) SetNewContext(ctx context.Context) error { |
||||||
|
for _, s := range s.stages { |
||||||
|
s.Handler.SetStageContext(ctx) |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *StagedStreamSync) Run(db kv.RwDB, tx kv.RwTx, firstCycle bool) error { |
||||||
|
s.prevRevertPoint = nil |
||||||
|
s.timings = s.timings[:0] |
||||||
|
|
||||||
|
for !s.IsDone() { |
||||||
|
if s.revertPoint != nil { |
||||||
|
s.prevRevertPoint = s.revertPoint |
||||||
|
s.revertPoint = nil |
||||||
|
if !s.invalidBlock.Active { |
||||||
|
for j := 0; j < len(s.revertOrder); j++ { |
||||||
|
if s.revertOrder[j] == nil || s.revertOrder[j].Disabled { |
||||||
|
continue |
||||||
|
} |
||||||
|
if err := s.revertStage(firstCycle, s.revertOrder[j], db, tx); err != nil { |
||||||
|
utils.Logger().Error(). |
||||||
|
Err(err). |
||||||
|
Interface("stage id", s.revertOrder[j].ID). |
||||||
|
Msgf(WrapStagedSyncMsg("revert stage failed")) |
||||||
|
return err |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
if err := s.SetCurrentStage(s.stages[0].ID); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
firstCycle = false |
||||||
|
} |
||||||
|
|
||||||
|
stage := s.stages[s.currentStage] |
||||||
|
|
||||||
|
if stage.Disabled { |
||||||
|
utils.Logger().Trace(). |
||||||
|
Msgf(WrapStagedSyncMsg(fmt.Sprintf("%s disabled. %s", stage.ID, stage.DisabledDescription))) |
||||||
|
|
||||||
|
s.NextStage() |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
if err := s.runStage(stage, db, tx, firstCycle, s.invalidBlock.Active); err != nil { |
||||||
|
utils.Logger().Error(). |
||||||
|
Err(err). |
||||||
|
Interface("stage id", stage.ID). |
||||||
|
Msgf(WrapStagedSyncMsg("stage failed")) |
||||||
|
return err |
||||||
|
} |
||||||
|
s.NextStage() |
||||||
|
} |
||||||
|
|
||||||
|
if err := s.cleanUp(0, db, tx, firstCycle); err != nil { |
||||||
|
utils.Logger().Error(). |
||||||
|
Err(err). |
||||||
|
Msgf(WrapStagedSyncMsg("stages cleanup failed")) |
||||||
|
return err |
||||||
|
} |
||||||
|
if err := s.SetCurrentStage(s.stages[0].ID); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
if err := printLogs(tx, s.timings); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
s.currentStage = 0 |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func CreateView(ctx context.Context, db kv.RwDB, tx kv.Tx, f func(tx kv.Tx) error) error { |
||||||
|
if tx != nil { |
||||||
|
return f(tx) |
||||||
|
} |
||||||
|
return db.View(context.Background(), func(etx kv.Tx) error { |
||||||
|
return f(etx) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
func ByteCount(b uint64) string { |
||||||
|
const unit = 1024 |
||||||
|
if b < unit { |
||||||
|
return fmt.Sprintf("%dB", b) |
||||||
|
} |
||||||
|
div, exp := uint64(unit), 0 |
||||||
|
for n := b / unit; n >= unit; n /= unit { |
||||||
|
div *= unit |
||||||
|
exp++ |
||||||
|
} |
||||||
|
return fmt.Sprintf("%.1f%cB", |
||||||
|
float64(b)/float64(div), "KMGTPE"[exp]) |
||||||
|
} |
||||||
|
|
||||||
|
func printLogs(tx kv.RwTx, timings []Timing) error { |
||||||
|
var logCtx []interface{} |
||||||
|
count := 0 |
||||||
|
for i := range timings { |
||||||
|
if timings[i].took < 50*time.Millisecond { |
||||||
|
continue |
||||||
|
} |
||||||
|
count++ |
||||||
|
if count == 50 { |
||||||
|
break |
||||||
|
} |
||||||
|
if timings[i].isRevert { |
||||||
|
logCtx = append(logCtx, "Revert "+string(timings[i].stage), timings[i].took.Truncate(time.Millisecond).String()) |
||||||
|
} else if timings[i].isCleanUp { |
||||||
|
logCtx = append(logCtx, "CleanUp "+string(timings[i].stage), timings[i].took.Truncate(time.Millisecond).String()) |
||||||
|
} else { |
||||||
|
logCtx = append(logCtx, string(timings[i].stage), timings[i].took.Truncate(time.Millisecond).String()) |
||||||
|
} |
||||||
|
} |
||||||
|
if len(logCtx) > 0 { |
||||||
|
utils.Logger().Info(). |
||||||
|
Msgf(WrapStagedSyncMsg(fmt.Sprintf("Timings (slower than 50ms) %v", logCtx...))) |
||||||
|
} |
||||||
|
|
||||||
|
if tx == nil { |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
if len(logCtx) > 0 { // also don't print this logs if everything is fast
|
||||||
|
buckets := Buckets |
||||||
|
bucketSizes := make([]interface{}, 0, 2*len(buckets)) |
||||||
|
for _, bucket := range buckets { |
||||||
|
sz, err1 := tx.BucketSize(bucket) |
||||||
|
if err1 != nil { |
||||||
|
return err1 |
||||||
|
} |
||||||
|
bucketSizes = append(bucketSizes, bucket, ByteCount(sz)) |
||||||
|
} |
||||||
|
utils.Logger().Info(). |
||||||
|
Msgf(WrapStagedSyncMsg(fmt.Sprintf("Tables %v", bucketSizes...))) |
||||||
|
} |
||||||
|
tx.CollectMetrics() |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *StagedStreamSync) runStage(stage *Stage, db kv.RwDB, tx kv.RwTx, firstCycle bool, invalidBlockRevert bool) (err error) { |
||||||
|
start := time.Now() |
||||||
|
stageState, err := s.StageState(stage.ID, tx, db) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
if err = stage.Handler.Exec(firstCycle, invalidBlockRevert, stageState, s, tx); err != nil { |
||||||
|
utils.Logger().Error(). |
||||||
|
Err(err). |
||||||
|
Interface("stage id", stage.ID). |
||||||
|
Msgf(WrapStagedSyncMsg("stage failed")) |
||||||
|
return fmt.Errorf("[%s] %w", s.LogPrefix(), err) |
||||||
|
} |
||||||
|
|
||||||
|
took := time.Since(start) |
||||||
|
if took > 60*time.Second { |
||||||
|
logPrefix := s.LogPrefix() |
||||||
|
utils.Logger().Info(). |
||||||
|
Msgf(WrapStagedSyncMsg(fmt.Sprintf("%s: DONE in %d", logPrefix, took))) |
||||||
|
|
||||||
|
} |
||||||
|
s.timings = append(s.timings, Timing{stage: stage.ID, took: took}) |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *StagedStreamSync) revertStage(firstCycle bool, stage *Stage, db kv.RwDB, tx kv.RwTx) error { |
||||||
|
start := time.Now() |
||||||
|
stageState, err := s.StageState(stage.ID, tx, db) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
revert := s.NewRevertState(stage.ID, *s.revertPoint) |
||||||
|
|
||||||
|
if stageState.BlockNumber <= revert.RevertPoint { |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
if err = s.SetCurrentStage(stage.ID); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
err = stage.Handler.Revert(firstCycle, revert, stageState, tx) |
||||||
|
if err != nil { |
||||||
|
return fmt.Errorf("[%s] %w", s.LogPrefix(), err) |
||||||
|
} |
||||||
|
|
||||||
|
took := time.Since(start) |
||||||
|
if took > 60*time.Second { |
||||||
|
logPrefix := s.LogPrefix() |
||||||
|
utils.Logger().Info(). |
||||||
|
Msgf(WrapStagedSyncMsg(fmt.Sprintf("%s: Revert done in %d", logPrefix, took))) |
||||||
|
} |
||||||
|
s.timings = append(s.timings, Timing{isRevert: true, stage: stage.ID, took: took}) |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *StagedStreamSync) pruneStage(firstCycle bool, stage *Stage, db kv.RwDB, tx kv.RwTx) error { |
||||||
|
start := time.Now() |
||||||
|
|
||||||
|
stageState, err := s.StageState(stage.ID, tx, db) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
prune, err := s.CleanUpStageState(stage.ID, stageState.BlockNumber, tx, db) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
if err = s.SetCurrentStage(stage.ID); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
err = stage.Handler.CleanUp(firstCycle, prune, tx) |
||||||
|
if err != nil { |
||||||
|
return fmt.Errorf("[%s] %w", s.LogPrefix(), err) |
||||||
|
} |
||||||
|
|
||||||
|
took := time.Since(start) |
||||||
|
if took > 60*time.Second { |
||||||
|
logPrefix := s.LogPrefix() |
||||||
|
utils.Logger().Info(). |
||||||
|
Msgf(WrapStagedSyncMsg(fmt.Sprintf("%s: CleanUp done in %d", logPrefix, took))) |
||||||
|
} |
||||||
|
s.timings = append(s.timings, Timing{isCleanUp: true, stage: stage.ID, took: took}) |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// DisableAllStages disables all stages including their reverts
|
||||||
|
func (s *StagedStreamSync) DisableAllStages() []SyncStageID { |
||||||
|
var backupEnabledIds []SyncStageID |
||||||
|
for i := range s.stages { |
||||||
|
if !s.stages[i].Disabled { |
||||||
|
backupEnabledIds = append(backupEnabledIds, s.stages[i].ID) |
||||||
|
} |
||||||
|
} |
||||||
|
for i := range s.stages { |
||||||
|
s.stages[i].Disabled = true |
||||||
|
} |
||||||
|
return backupEnabledIds |
||||||
|
} |
||||||
|
|
||||||
|
func (s *StagedStreamSync) DisableStages(ids ...SyncStageID) { |
||||||
|
for i := range s.stages { |
||||||
|
for _, id := range ids { |
||||||
|
if s.stages[i].ID != id { |
||||||
|
continue |
||||||
|
} |
||||||
|
s.stages[i].Disabled = true |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (s *StagedStreamSync) EnableStages(ids ...SyncStageID) { |
||||||
|
for i := range s.stages { |
||||||
|
for _, id := range ids { |
||||||
|
if s.stages[i].ID != id { |
||||||
|
continue |
||||||
|
} |
||||||
|
s.stages[i].Disabled = false |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,71 @@ |
|||||||
|
package stagedstreamsync |
||||||
|
|
||||||
|
import ( |
||||||
|
"github.com/ledgerwatch/erigon-lib/kv" |
||||||
|
) |
||||||
|
|
||||||
|
// SyncStageID represents the stages in the Mode.StagedSync mode
|
||||||
|
type SyncStageID string |
||||||
|
|
||||||
|
const ( |
||||||
|
Heads SyncStageID = "Heads" // Heads are downloaded
|
||||||
|
ShortRange SyncStageID = "ShortRange" // short range
|
||||||
|
SyncEpoch SyncStageID = "SyncEpoch" // epoch sync
|
||||||
|
BlockBodies SyncStageID = "BlockBodies" // Block bodies are downloaded, TxHash and UncleHash are getting verified
|
||||||
|
States SyncStageID = "States" // will construct most recent state from downloaded blocks
|
||||||
|
Finish SyncStageID = "Finish" // Nominal stage after all other stages
|
||||||
|
) |
||||||
|
|
||||||
|
func GetStageName(stage string, isBeacon bool, prune bool) string { |
||||||
|
name := stage |
||||||
|
if isBeacon { |
||||||
|
name = "beacon_" + name |
||||||
|
} |
||||||
|
if prune { |
||||||
|
name = "prune_" + name |
||||||
|
} |
||||||
|
return name |
||||||
|
} |
||||||
|
|
||||||
|
func GetStageID(stage SyncStageID, isBeacon bool, prune bool) []byte { |
||||||
|
return []byte(GetStageName(string(stage), isBeacon, prune)) |
||||||
|
} |
||||||
|
|
||||||
|
func GetBucketName(bucketName string, isBeacon bool) string { |
||||||
|
name := bucketName |
||||||
|
if isBeacon { |
||||||
|
name = "Beacon" + name |
||||||
|
} |
||||||
|
return name |
||||||
|
} |
||||||
|
|
||||||
|
// GetStageProgress retrieves saved progress of a given sync stage from the database
|
||||||
|
func GetStageProgress(db kv.Getter, stage SyncStageID, isBeacon bool) (uint64, error) { |
||||||
|
stgID := GetStageID(stage, isBeacon, false) |
||||||
|
v, err := db.GetOne(kv.SyncStageProgress, stgID) |
||||||
|
if err != nil { |
||||||
|
return 0, err |
||||||
|
} |
||||||
|
return unmarshalData(v) |
||||||
|
} |
||||||
|
|
||||||
|
// SaveStageProgress saves progress of given sync stage
|
||||||
|
func SaveStageProgress(db kv.Putter, stage SyncStageID, isBeacon bool, progress uint64) error { |
||||||
|
stgID := GetStageID(stage, isBeacon, false) |
||||||
|
return db.Put(kv.SyncStageProgress, stgID, marshalData(progress)) |
||||||
|
} |
||||||
|
|
||||||
|
// GetStageCleanUpProgress retrieves saved progress of given sync stage from the database
|
||||||
|
func GetStageCleanUpProgress(db kv.Getter, stage SyncStageID, isBeacon bool) (uint64, error) { |
||||||
|
stgID := GetStageID(stage, isBeacon, true) |
||||||
|
v, err := db.GetOne(kv.SyncStageProgress, stgID) |
||||||
|
if err != nil { |
||||||
|
return 0, err |
||||||
|
} |
||||||
|
return unmarshalData(v) |
||||||
|
} |
||||||
|
|
||||||
|
func SaveStageCleanUpProgress(db kv.Putter, stage SyncStageID, isBeacon bool, progress uint64) error { |
||||||
|
stgID := GetStageID(stage, isBeacon, true) |
||||||
|
return db.Put(kv.SyncStageProgress, stgID, marshalData(progress)) |
||||||
|
} |
@ -0,0 +1,320 @@ |
|||||||
|
package stagedstreamsync |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
"fmt" |
||||||
|
"sync" |
||||||
|
"time" |
||||||
|
|
||||||
|
"github.com/harmony-one/harmony/core" |
||||||
|
"github.com/harmony-one/harmony/internal/utils" |
||||||
|
sttypes "github.com/harmony-one/harmony/p2p/stream/types" |
||||||
|
"github.com/harmony-one/harmony/shard" |
||||||
|
"github.com/ledgerwatch/erigon-lib/kv" |
||||||
|
"github.com/ledgerwatch/erigon-lib/kv/mdbx" |
||||||
|
"github.com/ledgerwatch/erigon-lib/kv/memdb" |
||||||
|
"github.com/ledgerwatch/log/v3" |
||||||
|
"github.com/pkg/errors" |
||||||
|
"github.com/rs/zerolog" |
||||||
|
) |
||||||
|
|
||||||
|
const ( |
||||||
|
BlocksBucket = "BlockBodies" |
||||||
|
BlockSignaturesBucket = "BlockSignatures" |
||||||
|
StageProgressBucket = "StageProgress" |
||||||
|
|
||||||
|
// cache db keys
|
||||||
|
LastBlockHeight = "LastBlockHeight" |
||||||
|
LastBlockHash = "LastBlockHash" |
||||||
|
) |
||||||
|
|
||||||
|
var Buckets = []string{ |
||||||
|
BlocksBucket, |
||||||
|
BlockSignaturesBucket, |
||||||
|
StageProgressBucket, |
||||||
|
} |
||||||
|
|
||||||
|
// CreateStagedSync creates an instance of staged sync
|
||||||
|
func CreateStagedSync(ctx context.Context, |
||||||
|
bc core.BlockChain, |
||||||
|
UseMemDB bool, |
||||||
|
protocol syncProtocol, |
||||||
|
config Config, |
||||||
|
logger zerolog.Logger, |
||||||
|
logProgress bool, |
||||||
|
) (*StagedStreamSync, error) { |
||||||
|
|
||||||
|
isBeacon := bc.ShardID() == shard.BeaconChainShardID |
||||||
|
|
||||||
|
var mainDB kv.RwDB |
||||||
|
dbs := make([]kv.RwDB, config.Concurrency) |
||||||
|
if UseMemDB { |
||||||
|
mainDB = memdb.New() |
||||||
|
for i := 0; i < config.Concurrency; i++ { |
||||||
|
dbs[i] = memdb.New() |
||||||
|
} |
||||||
|
} else { |
||||||
|
mainDB = mdbx.NewMDBX(log.New()).Path(GetBlockDbPath(isBeacon, -1)).MustOpen() |
||||||
|
for i := 0; i < config.Concurrency; i++ { |
||||||
|
dbPath := GetBlockDbPath(isBeacon, i) |
||||||
|
dbs[i] = mdbx.NewMDBX(log.New()).Path(dbPath).MustOpen() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if errInitDB := initDB(ctx, mainDB, dbs, config.Concurrency); errInitDB != nil { |
||||||
|
return nil, errInitDB |
||||||
|
} |
||||||
|
|
||||||
|
stageHeadsCfg := NewStageHeadersCfg(ctx, bc, mainDB) |
||||||
|
stageShortRangeCfg := NewStageShortRangeCfg(ctx, bc, mainDB) |
||||||
|
stageSyncEpochCfg := NewStageEpochCfg(ctx, bc, mainDB) |
||||||
|
stageBodiesCfg := NewStageBodiesCfg(ctx, bc, mainDB, dbs, config.Concurrency, protocol, isBeacon, logProgress) |
||||||
|
stageStatesCfg := NewStageStatesCfg(ctx, bc, mainDB, dbs, config.Concurrency, logger, logProgress) |
||||||
|
stageFinishCfg := NewStageFinishCfg(ctx, mainDB) |
||||||
|
|
||||||
|
stages := DefaultStages(ctx, |
||||||
|
stageHeadsCfg, |
||||||
|
stageSyncEpochCfg, |
||||||
|
stageShortRangeCfg, |
||||||
|
stageBodiesCfg, |
||||||
|
stageStatesCfg, |
||||||
|
stageFinishCfg, |
||||||
|
) |
||||||
|
|
||||||
|
return New(ctx, |
||||||
|
bc, |
||||||
|
mainDB, |
||||||
|
stages, |
||||||
|
isBeacon, |
||||||
|
protocol, |
||||||
|
UseMemDB, |
||||||
|
config, |
||||||
|
logger, |
||||||
|
), nil |
||||||
|
} |
||||||
|
|
||||||
|
// initDB inits the sync loop main database and create buckets
|
||||||
|
func initDB(ctx context.Context, mainDB kv.RwDB, dbs []kv.RwDB, concurrency int) error { |
||||||
|
|
||||||
|
// create buckets for mainDB
|
||||||
|
tx, errRW := mainDB.BeginRw(ctx) |
||||||
|
if errRW != nil { |
||||||
|
return errRW |
||||||
|
} |
||||||
|
defer tx.Rollback() |
||||||
|
|
||||||
|
for _, name := range Buckets { |
||||||
|
if err := tx.CreateBucket(GetStageName(name, false, false)); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
} |
||||||
|
if err := tx.Commit(); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
// create buckets for block cache DBs
|
||||||
|
for _, db := range dbs { |
||||||
|
tx, errRW := db.BeginRw(ctx) |
||||||
|
if errRW != nil { |
||||||
|
return errRW |
||||||
|
} |
||||||
|
|
||||||
|
if err := tx.CreateBucket(BlocksBucket); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
if err := tx.CreateBucket(BlockSignaturesBucket); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
if err := tx.Commit(); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func GetBlockDbPath(beacon bool, loopID int) string { |
||||||
|
if beacon { |
||||||
|
if loopID >= 0 { |
||||||
|
return fmt.Sprintf("%s_%d", "cache/beacon_blocks_db", loopID) |
||||||
|
} else { |
||||||
|
return "cache/beacon_blocks_db_main" |
||||||
|
} |
||||||
|
} else { |
||||||
|
if loopID >= 0 { |
||||||
|
return fmt.Sprintf("%s_%d", "cache/blocks_db", loopID) |
||||||
|
} else { |
||||||
|
return "cache/blocks_db_main" |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// doSync does the long range sync.
|
||||||
|
// One LongRangeSync consists of several iterations.
|
||||||
|
// For each iteration, estimate the current block number, then fetch block & insert to blockchain
|
||||||
|
func (s *StagedStreamSync) doSync(downloaderContext context.Context, initSync bool) (int, error) { |
||||||
|
|
||||||
|
var totalInserted int |
||||||
|
|
||||||
|
s.initSync = initSync |
||||||
|
|
||||||
|
if err := s.checkPrerequisites(); err != nil { |
||||||
|
return 0, err |
||||||
|
} |
||||||
|
|
||||||
|
var estimatedHeight uint64 |
||||||
|
if initSync { |
||||||
|
if h, err := s.estimateCurrentNumber(); err != nil { |
||||||
|
return 0, err |
||||||
|
} else { |
||||||
|
estimatedHeight = h |
||||||
|
//TODO: use directly currentCycle var
|
||||||
|
s.status.setTargetBN(estimatedHeight) |
||||||
|
} |
||||||
|
if curBN := s.bc.CurrentBlock().NumberU64(); estimatedHeight <= curBN { |
||||||
|
s.logger.Info().Uint64("current number", curBN).Uint64("target number", estimatedHeight). |
||||||
|
Msg(WrapStagedSyncMsg("early return of long range sync")) |
||||||
|
return 0, nil |
||||||
|
} |
||||||
|
|
||||||
|
s.startSyncing() |
||||||
|
defer s.finishSyncing() |
||||||
|
} |
||||||
|
|
||||||
|
for { |
||||||
|
ctx, cancel := context.WithCancel(downloaderContext) |
||||||
|
s.ctx = ctx |
||||||
|
s.SetNewContext(ctx) |
||||||
|
|
||||||
|
n, err := s.doSyncCycle(ctx, initSync) |
||||||
|
if err != nil { |
||||||
|
pl := s.promLabels() |
||||||
|
pl["error"] = err.Error() |
||||||
|
numFailedDownloadCounterVec.With(pl).Inc() |
||||||
|
|
||||||
|
cancel() |
||||||
|
return totalInserted + n, err |
||||||
|
} |
||||||
|
cancel() |
||||||
|
|
||||||
|
totalInserted += n |
||||||
|
|
||||||
|
// if it's not long range sync, skip loop
|
||||||
|
if n < LastMileBlocksThreshold || !initSync { |
||||||
|
return totalInserted, nil |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
func (s *StagedStreamSync) doSyncCycle(ctx context.Context, initSync bool) (int, error) { |
||||||
|
|
||||||
|
// TODO: initSync=true means currentCycleNumber==0, so we can remove initSync
|
||||||
|
|
||||||
|
var totalInserted int |
||||||
|
|
||||||
|
s.inserted = 0 |
||||||
|
startHead := s.bc.CurrentBlock().NumberU64() |
||||||
|
canRunCycleInOneTransaction := false |
||||||
|
|
||||||
|
var tx kv.RwTx |
||||||
|
if canRunCycleInOneTransaction { |
||||||
|
var err error |
||||||
|
if tx, err = s.DB().BeginRw(context.Background()); err != nil { |
||||||
|
return totalInserted, err |
||||||
|
} |
||||||
|
defer tx.Rollback() |
||||||
|
} |
||||||
|
|
||||||
|
startTime := time.Now() |
||||||
|
|
||||||
|
// Do one cycle of staged sync
|
||||||
|
initialCycle := s.currentCycle.Number == 0 |
||||||
|
if err := s.Run(s.DB(), tx, initialCycle); err != nil { |
||||||
|
utils.Logger().Error(). |
||||||
|
Err(err). |
||||||
|
Bool("isBeacon", s.isBeacon). |
||||||
|
Uint32("shard", s.bc.ShardID()). |
||||||
|
Uint64("currentHeight", startHead). |
||||||
|
Msgf(WrapStagedSyncMsg("sync cycle failed")) |
||||||
|
return totalInserted, err |
||||||
|
} |
||||||
|
|
||||||
|
totalInserted += s.inserted |
||||||
|
|
||||||
|
s.currentCycle.lock.Lock() |
||||||
|
s.currentCycle.Number++ |
||||||
|
s.currentCycle.lock.Unlock() |
||||||
|
|
||||||
|
// calculating sync speed (blocks/second)
|
||||||
|
if s.LogProgress && s.inserted > 0 { |
||||||
|
dt := time.Now().Sub(startTime).Seconds() |
||||||
|
speed := float64(0) |
||||||
|
if dt > 0 { |
||||||
|
speed = float64(s.inserted) / dt |
||||||
|
} |
||||||
|
syncSpeed := fmt.Sprintf("%.2f", speed) |
||||||
|
fmt.Println("sync speed:", syncSpeed, "blocks/s") |
||||||
|
} |
||||||
|
|
||||||
|
return totalInserted, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *StagedStreamSync) startSyncing() { |
||||||
|
s.status.startSyncing() |
||||||
|
if s.evtDownloadStartedSubscribed { |
||||||
|
s.evtDownloadStarted.Send(struct{}{}) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (s *StagedStreamSync) finishSyncing() { |
||||||
|
s.status.finishSyncing() |
||||||
|
if s.evtDownloadFinishedSubscribed { |
||||||
|
s.evtDownloadFinished.Send(struct{}{}) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (s *StagedStreamSync) checkPrerequisites() error { |
||||||
|
return s.checkHaveEnoughStreams() |
||||||
|
} |
||||||
|
|
||||||
|
// estimateCurrentNumber roughly estimates the current block number.
|
||||||
|
// The block number does not need to be exact, but just a temporary target of the iteration
|
||||||
|
func (s *StagedStreamSync) estimateCurrentNumber() (uint64, error) { |
||||||
|
var ( |
||||||
|
cnResults = make(map[sttypes.StreamID]uint64) |
||||||
|
lock sync.Mutex |
||||||
|
wg sync.WaitGroup |
||||||
|
) |
||||||
|
wg.Add(s.config.Concurrency) |
||||||
|
for i := 0; i != s.config.Concurrency; i++ { |
||||||
|
go func() { |
||||||
|
defer wg.Done() |
||||||
|
bn, stid, err := s.doGetCurrentNumberRequest() |
||||||
|
if err != nil { |
||||||
|
s.logger.Err(err).Str("streamID", string(stid)). |
||||||
|
Msg(WrapStagedSyncMsg("getCurrentNumber request failed")) |
||||||
|
if !errors.Is(err, context.Canceled) { |
||||||
|
s.protocol.StreamFailed(stid, "getCurrentNumber request failed") |
||||||
|
} |
||||||
|
return |
||||||
|
} |
||||||
|
lock.Lock() |
||||||
|
cnResults[stid] = bn |
||||||
|
lock.Unlock() |
||||||
|
}() |
||||||
|
} |
||||||
|
wg.Wait() |
||||||
|
|
||||||
|
if len(cnResults) == 0 { |
||||||
|
select { |
||||||
|
case <-s.ctx.Done(): |
||||||
|
return 0, s.ctx.Err() |
||||||
|
default: |
||||||
|
} |
||||||
|
return 0, errors.New("zero block number response from remote nodes") |
||||||
|
} |
||||||
|
bn := computeBlockNumberByMaxVote(cnResults) |
||||||
|
return bn, nil |
||||||
|
} |
@ -0,0 +1,287 @@ |
|||||||
|
package stagedstreamsync |
||||||
|
|
||||||
|
import ( |
||||||
|
"container/heap" |
||||||
|
"sync" |
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common" |
||||||
|
"github.com/harmony-one/harmony/core/types" |
||||||
|
sttypes "github.com/harmony-one/harmony/p2p/stream/types" |
||||||
|
) |
||||||
|
|
||||||
|
var ( |
||||||
|
emptyHash common.Hash |
||||||
|
) |
||||||
|
|
||||||
|
type status struct { |
||||||
|
isSyncing bool |
||||||
|
targetBN uint64 |
||||||
|
lock sync.Mutex |
||||||
|
} |
||||||
|
|
||||||
|
func newStatus() status { |
||||||
|
return status{} |
||||||
|
} |
||||||
|
|
||||||
|
func (s *status) startSyncing() { |
||||||
|
s.lock.Lock() |
||||||
|
defer s.lock.Unlock() |
||||||
|
|
||||||
|
s.isSyncing = true |
||||||
|
} |
||||||
|
|
||||||
|
func (s *status) setTargetBN(val uint64) { |
||||||
|
s.lock.Lock() |
||||||
|
defer s.lock.Unlock() |
||||||
|
|
||||||
|
s.targetBN = val |
||||||
|
} |
||||||
|
|
||||||
|
func (s *status) finishSyncing() { |
||||||
|
s.lock.Lock() |
||||||
|
defer s.lock.Unlock() |
||||||
|
|
||||||
|
s.isSyncing = false |
||||||
|
s.targetBN = 0 |
||||||
|
} |
||||||
|
|
||||||
|
func (s *status) get() (bool, uint64) { |
||||||
|
s.lock.Lock() |
||||||
|
defer s.lock.Unlock() |
||||||
|
|
||||||
|
return s.isSyncing, s.targetBN |
||||||
|
} |
||||||
|
|
||||||
|
type getBlocksResult struct { |
||||||
|
bns []uint64 |
||||||
|
blocks []*types.Block |
||||||
|
stid sttypes.StreamID |
||||||
|
} |
||||||
|
|
||||||
|
type resultQueue struct { |
||||||
|
results *priorityQueue |
||||||
|
lock sync.Mutex |
||||||
|
} |
||||||
|
|
||||||
|
func newResultQueue() *resultQueue { |
||||||
|
pq := make(priorityQueue, 0, 200) // 200 - rough estimate
|
||||||
|
heap.Init(&pq) |
||||||
|
return &resultQueue{ |
||||||
|
results: &pq, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// addBlockResults adds the blocks to the result queue to be processed by insertChainLoop.
|
||||||
|
// If a nil block is detected in the block list, will not process further blocks.
|
||||||
|
func (rq *resultQueue) addBlockResults(blocks []*types.Block, stid sttypes.StreamID) { |
||||||
|
rq.lock.Lock() |
||||||
|
defer rq.lock.Unlock() |
||||||
|
|
||||||
|
for _, block := range blocks { |
||||||
|
if block == nil { |
||||||
|
continue |
||||||
|
} |
||||||
|
heap.Push(rq.results, &blockResult{ |
||||||
|
block: block, |
||||||
|
stid: stid, |
||||||
|
}) |
||||||
|
} |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
// popBlockResults pop a continuous list of blocks starting at expStartBN with capped size.
|
||||||
|
// Return the stale block numbers as the second return value
|
||||||
|
func (rq *resultQueue) popBlockResults(expStartBN uint64, cap int) ([]*blockResult, []uint64) { |
||||||
|
rq.lock.Lock() |
||||||
|
defer rq.lock.Unlock() |
||||||
|
|
||||||
|
var ( |
||||||
|
res = make([]*blockResult, 0, cap) |
||||||
|
stales []uint64 |
||||||
|
) |
||||||
|
|
||||||
|
for cnt := 0; rq.results.Len() > 0 && cnt < cap; cnt++ { |
||||||
|
br := heap.Pop(rq.results).(*blockResult) |
||||||
|
// stale block number
|
||||||
|
if br.block.NumberU64() < expStartBN { |
||||||
|
stales = append(stales, br.block.NumberU64()) |
||||||
|
continue |
||||||
|
} |
||||||
|
if br.block.NumberU64() != expStartBN { |
||||||
|
heap.Push(rq.results, br) |
||||||
|
return res, stales |
||||||
|
} |
||||||
|
res = append(res, br) |
||||||
|
expStartBN++ |
||||||
|
} |
||||||
|
return res, stales |
||||||
|
} |
||||||
|
|
||||||
|
// removeResultsByStreamID removes the block results of the given stream, returns the block
|
||||||
|
// number removed from the queue
|
||||||
|
func (rq *resultQueue) removeResultsByStreamID(stid sttypes.StreamID) []uint64 { |
||||||
|
rq.lock.Lock() |
||||||
|
defer rq.lock.Unlock() |
||||||
|
|
||||||
|
var removed []uint64 |
||||||
|
|
||||||
|
Loop: |
||||||
|
for { |
||||||
|
for i, res := range *rq.results { |
||||||
|
blockRes := res.(*blockResult) |
||||||
|
if blockRes.stid == stid { |
||||||
|
rq.removeByIndex(i) |
||||||
|
removed = append(removed, blockRes.block.NumberU64()) |
||||||
|
goto Loop |
||||||
|
} |
||||||
|
} |
||||||
|
break |
||||||
|
} |
||||||
|
return removed |
||||||
|
} |
||||||
|
|
||||||
|
func (rq *resultQueue) length() int { |
||||||
|
return len(*rq.results) |
||||||
|
} |
||||||
|
|
||||||
|
func (rq *resultQueue) removeByIndex(index int) { |
||||||
|
heap.Remove(rq.results, index) |
||||||
|
} |
||||||
|
|
||||||
|
// bnPrioritizedItem is the item which uses block number to determine its priority
|
||||||
|
type bnPrioritizedItem interface { |
||||||
|
getBlockNumber() uint64 |
||||||
|
} |
||||||
|
|
||||||
|
type blockResult struct { |
||||||
|
block *types.Block |
||||||
|
stid sttypes.StreamID |
||||||
|
} |
||||||
|
|
||||||
|
func (br *blockResult) getBlockNumber() uint64 { |
||||||
|
return br.block.NumberU64() |
||||||
|
} |
||||||
|
|
||||||
|
func blockResultsToBlocks(results []*blockResult) []*types.Block { |
||||||
|
blocks := make([]*types.Block, 0, len(results)) |
||||||
|
|
||||||
|
for _, result := range results { |
||||||
|
blocks = append(blocks, result.block) |
||||||
|
} |
||||||
|
return blocks |
||||||
|
} |
||||||
|
|
||||||
|
type ( |
||||||
|
prioritizedNumber uint64 |
||||||
|
|
||||||
|
prioritizedNumbers struct { |
||||||
|
q *priorityQueue |
||||||
|
} |
||||||
|
) |
||||||
|
|
||||||
|
func (b prioritizedNumber) getBlockNumber() uint64 { |
||||||
|
return uint64(b) |
||||||
|
} |
||||||
|
|
||||||
|
func newPrioritizedNumbers() *prioritizedNumbers { |
||||||
|
pqs := make(priorityQueue, 0) |
||||||
|
heap.Init(&pqs) |
||||||
|
return &prioritizedNumbers{ |
||||||
|
q: &pqs, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (pbs *prioritizedNumbers) push(bn uint64) { |
||||||
|
heap.Push(pbs.q, prioritizedNumber(bn)) |
||||||
|
} |
||||||
|
|
||||||
|
func (pbs *prioritizedNumbers) pop() uint64 { |
||||||
|
if pbs.q.Len() == 0 { |
||||||
|
return 0 |
||||||
|
} |
||||||
|
item := heap.Pop(pbs.q) |
||||||
|
return uint64(item.(prioritizedNumber)) |
||||||
|
} |
||||||
|
|
||||||
|
func (pbs *prioritizedNumbers) length() int { |
||||||
|
return len(*pbs.q) |
||||||
|
} |
||||||
|
|
||||||
|
type ( |
||||||
|
blockByNumber types.Block |
||||||
|
|
||||||
|
// blocksByNumber is the priority queue ordered by number
|
||||||
|
blocksByNumber struct { |
||||||
|
q *priorityQueue |
||||||
|
cap int |
||||||
|
} |
||||||
|
) |
||||||
|
|
||||||
|
func (b *blockByNumber) getBlockNumber() uint64 { |
||||||
|
raw := (*types.Block)(b) |
||||||
|
return raw.NumberU64() |
||||||
|
} |
||||||
|
|
||||||
|
func newBlocksByNumber(cap int) *blocksByNumber { |
||||||
|
pqs := make(priorityQueue, 0) |
||||||
|
heap.Init(&pqs) |
||||||
|
return &blocksByNumber{ |
||||||
|
q: &pqs, |
||||||
|
cap: cap, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (bs *blocksByNumber) push(b *types.Block) { |
||||||
|
heap.Push(bs.q, (*blockByNumber)(b)) |
||||||
|
for bs.q.Len() > bs.cap { |
||||||
|
heap.Pop(bs.q) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (bs *blocksByNumber) pop() *types.Block { |
||||||
|
if bs.q.Len() == 0 { |
||||||
|
return nil |
||||||
|
} |
||||||
|
item := heap.Pop(bs.q) |
||||||
|
return (*types.Block)(item.(*blockByNumber)) |
||||||
|
} |
||||||
|
|
||||||
|
func (bs *blocksByNumber) len() int { |
||||||
|
return bs.q.Len() |
||||||
|
} |
||||||
|
|
||||||
|
// priorityQueue is a priority queue with lowest block number with highest priority
|
||||||
|
type priorityQueue []bnPrioritizedItem |
||||||
|
|
||||||
|
func (q priorityQueue) Len() int { |
||||||
|
return len(q) |
||||||
|
} |
||||||
|
|
||||||
|
func (q priorityQueue) Less(i, j int) bool { |
||||||
|
bn1 := q[i].getBlockNumber() |
||||||
|
bn2 := q[j].getBlockNumber() |
||||||
|
return bn1 < bn2 // small block number has higher priority
|
||||||
|
} |
||||||
|
|
||||||
|
func (q priorityQueue) Swap(i, j int) { |
||||||
|
q[i], q[j] = q[j], q[i] |
||||||
|
} |
||||||
|
|
||||||
|
func (q *priorityQueue) Push(x interface{}) { |
||||||
|
item, ok := x.(bnPrioritizedItem) |
||||||
|
if !ok { |
||||||
|
panic("wrong type of getBlockNumber interface") |
||||||
|
} |
||||||
|
*q = append(*q, item) |
||||||
|
} |
||||||
|
|
||||||
|
func (q *priorityQueue) Pop() interface{} { |
||||||
|
prev := *q |
||||||
|
n := len(prev) |
||||||
|
if n == 0 { |
||||||
|
return nil |
||||||
|
} |
||||||
|
res := prev[n-1] |
||||||
|
*q = prev[0 : n-1] |
||||||
|
return res |
||||||
|
} |
@ -0,0 +1,266 @@ |
|||||||
|
package stagedstreamsync |
||||||
|
|
||||||
|
import ( |
||||||
|
"container/heap" |
||||||
|
"fmt" |
||||||
|
"math/big" |
||||||
|
"strings" |
||||||
|
"testing" |
||||||
|
|
||||||
|
"github.com/harmony-one/harmony/block" |
||||||
|
headerV3 "github.com/harmony-one/harmony/block/v3" |
||||||
|
"github.com/harmony-one/harmony/core/types" |
||||||
|
bls_cosi "github.com/harmony-one/harmony/crypto/bls" |
||||||
|
sttypes "github.com/harmony-one/harmony/p2p/stream/types" |
||||||
|
) |
||||||
|
|
||||||
|
func TestResultQueue_AddBlockResults(t *testing.T) { |
||||||
|
tests := []struct { |
||||||
|
initBNs []uint64 |
||||||
|
addBNs []uint64 |
||||||
|
expSize int |
||||||
|
}{ |
||||||
|
{ |
||||||
|
initBNs: []uint64{}, |
||||||
|
addBNs: []uint64{1, 2, 3, 4}, |
||||||
|
expSize: 4, |
||||||
|
}, |
||||||
|
{ |
||||||
|
initBNs: []uint64{1, 2, 3, 4}, |
||||||
|
addBNs: []uint64{5, 6, 7, 8}, |
||||||
|
expSize: 8, |
||||||
|
}, |
||||||
|
} |
||||||
|
for i, test := range tests { |
||||||
|
rq := makeTestResultQueue(test.initBNs) |
||||||
|
rq.addBlockResults(makeTestBlocks(test.addBNs), "") |
||||||
|
|
||||||
|
if rq.results.Len() != test.expSize { |
||||||
|
t.Errorf("Test %v: unexpected size: %v / %v", i, rq.results.Len(), test.expSize) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func TestResultQueue_PopBlockResults(t *testing.T) { |
||||||
|
tests := []struct { |
||||||
|
initBNs []uint64 |
||||||
|
cap int |
||||||
|
expStart uint64 |
||||||
|
expSize int |
||||||
|
staleSize int |
||||||
|
}{ |
||||||
|
{ |
||||||
|
initBNs: []uint64{1, 2, 3, 4, 5}, |
||||||
|
cap: 3, |
||||||
|
expStart: 1, |
||||||
|
expSize: 3, |
||||||
|
staleSize: 0, |
||||||
|
}, |
||||||
|
{ |
||||||
|
initBNs: []uint64{1, 2, 3, 4, 5}, |
||||||
|
cap: 10, |
||||||
|
expStart: 1, |
||||||
|
expSize: 5, |
||||||
|
staleSize: 0, |
||||||
|
}, |
||||||
|
{ |
||||||
|
initBNs: []uint64{1, 3, 4, 5}, |
||||||
|
cap: 10, |
||||||
|
expStart: 1, |
||||||
|
expSize: 1, |
||||||
|
staleSize: 0, |
||||||
|
}, |
||||||
|
{ |
||||||
|
initBNs: []uint64{1, 2, 3, 4, 5}, |
||||||
|
cap: 10, |
||||||
|
expStart: 0, |
||||||
|
expSize: 0, |
||||||
|
staleSize: 0, |
||||||
|
}, |
||||||
|
{ |
||||||
|
initBNs: []uint64{1, 1, 1, 1, 2}, |
||||||
|
cap: 10, |
||||||
|
expStart: 1, |
||||||
|
expSize: 2, |
||||||
|
staleSize: 3, |
||||||
|
}, |
||||||
|
{ |
||||||
|
initBNs: []uint64{1, 2, 3, 4, 5}, |
||||||
|
cap: 10, |
||||||
|
expStart: 2, |
||||||
|
expSize: 4, |
||||||
|
staleSize: 1, |
||||||
|
}, |
||||||
|
} |
||||||
|
for i, test := range tests { |
||||||
|
rq := makeTestResultQueue(test.initBNs) |
||||||
|
res, stales := rq.popBlockResults(test.expStart, test.cap) |
||||||
|
if len(res) != test.expSize { |
||||||
|
t.Errorf("Test %v: unexpect size %v / %v", i, len(res), test.expSize) |
||||||
|
} |
||||||
|
if len(stales) != test.staleSize { |
||||||
|
t.Errorf("Test %v: unexpect stale size %v / %v", i, len(stales), test.staleSize) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func TestResultQueue_RemoveResultsByStreamID(t *testing.T) { |
||||||
|
tests := []struct { |
||||||
|
rq *resultQueue |
||||||
|
rmStreamID sttypes.StreamID |
||||||
|
removed int |
||||||
|
expSize int |
||||||
|
}{ |
||||||
|
{ |
||||||
|
rq: makeTestResultQueue([]uint64{1, 2, 3, 4}), |
||||||
|
rmStreamID: "test stream id", |
||||||
|
removed: 4, |
||||||
|
expSize: 0, |
||||||
|
}, |
||||||
|
{ |
||||||
|
rq: func() *resultQueue { |
||||||
|
rq := makeTestResultQueue([]uint64{2, 3, 4, 5}) |
||||||
|
rq.addBlockResults([]*types.Block{ |
||||||
|
makeTestBlock(1), |
||||||
|
makeTestBlock(5), |
||||||
|
makeTestBlock(6), |
||||||
|
}, "another test stream id") |
||||||
|
return rq |
||||||
|
}(), |
||||||
|
rmStreamID: "test stream id", |
||||||
|
removed: 4, |
||||||
|
expSize: 3, |
||||||
|
}, |
||||||
|
{ |
||||||
|
rq: func() *resultQueue { |
||||||
|
rq := makeTestResultQueue([]uint64{2, 3, 4, 5}) |
||||||
|
rq.addBlockResults([]*types.Block{ |
||||||
|
makeTestBlock(1), |
||||||
|
makeTestBlock(5), |
||||||
|
makeTestBlock(6), |
||||||
|
}, "another test stream id") |
||||||
|
return rq |
||||||
|
}(), |
||||||
|
rmStreamID: "another test stream id", |
||||||
|
removed: 3, |
||||||
|
expSize: 4, |
||||||
|
}, |
||||||
|
} |
||||||
|
for i, test := range tests { |
||||||
|
res := test.rq.removeResultsByStreamID(test.rmStreamID) |
||||||
|
if len(res) != test.removed { |
||||||
|
t.Errorf("Test %v: unexpected number removed %v / %v", i, len(res), test.removed) |
||||||
|
} |
||||||
|
if gotSize := test.rq.results.Len(); gotSize != test.expSize { |
||||||
|
t.Errorf("Test %v: unexpected number after removal %v / %v", i, gotSize, test.expSize) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func makeTestResultQueue(bns []uint64) *resultQueue { |
||||||
|
rq := newResultQueue() |
||||||
|
for _, bn := range bns { |
||||||
|
heap.Push(rq.results, &blockResult{ |
||||||
|
block: makeTestBlock(bn), |
||||||
|
stid: "test stream id", |
||||||
|
}) |
||||||
|
} |
||||||
|
return rq |
||||||
|
} |
||||||
|
|
||||||
|
func TestPrioritizedBlocks(t *testing.T) { |
||||||
|
addBNs := []uint64{4, 7, 6, 9} |
||||||
|
|
||||||
|
bns := newPrioritizedNumbers() |
||||||
|
for _, bn := range addBNs { |
||||||
|
bns.push(bn) |
||||||
|
} |
||||||
|
prevBN := uint64(0) |
||||||
|
for len(*bns.q) > 0 { |
||||||
|
b := bns.pop() |
||||||
|
if b < prevBN { |
||||||
|
t.Errorf("number not incrementing") |
||||||
|
} |
||||||
|
prevBN = b |
||||||
|
} |
||||||
|
if last := bns.pop(); last != 0 { |
||||||
|
t.Errorf("last elem is not 0") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func TestBlocksByNumber(t *testing.T) { |
||||||
|
addBNs := []uint64{4, 7, 6, 9} |
||||||
|
|
||||||
|
bns := newBlocksByNumber(10) |
||||||
|
for _, bn := range addBNs { |
||||||
|
bns.push(makeTestBlock(bn)) |
||||||
|
} |
||||||
|
if bns.len() != len(addBNs) { |
||||||
|
t.Errorf("size unexpected: %v / %v", bns.len(), len(addBNs)) |
||||||
|
} |
||||||
|
prevBN := uint64(0) |
||||||
|
for len(*bns.q) > 0 { |
||||||
|
b := bns.pop() |
||||||
|
if b.NumberU64() < prevBN { |
||||||
|
t.Errorf("number not incrementing") |
||||||
|
} |
||||||
|
prevBN = b.NumberU64() |
||||||
|
} |
||||||
|
if lastBlock := bns.pop(); lastBlock != nil { |
||||||
|
t.Errorf("last block is not nil") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func TestPriorityQueue(t *testing.T) { |
||||||
|
testBNs := []uint64{1, 9, 2, 4, 5, 12} |
||||||
|
pq := make(priorityQueue, 0, 10) |
||||||
|
heap.Init(&pq) |
||||||
|
for _, bn := range testBNs { |
||||||
|
heap.Push(&pq, &blockResult{ |
||||||
|
block: makeTestBlock(bn), |
||||||
|
stid: "", |
||||||
|
}) |
||||||
|
} |
||||||
|
cmpBN := uint64(0) |
||||||
|
for pq.Len() > 0 { |
||||||
|
bn := heap.Pop(&pq).(*blockResult).block.NumberU64() |
||||||
|
if bn < cmpBN { |
||||||
|
t.Errorf("not incrementing") |
||||||
|
} |
||||||
|
cmpBN = bn |
||||||
|
} |
||||||
|
if pq.Len() != 0 { |
||||||
|
t.Errorf("after poping, size not 0") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func makeTestBlocks(bns []uint64) []*types.Block { |
||||||
|
blocks := make([]*types.Block, 0, len(bns)) |
||||||
|
for _, bn := range bns { |
||||||
|
blocks = append(blocks, makeTestBlock(bn)) |
||||||
|
} |
||||||
|
return blocks |
||||||
|
} |
||||||
|
|
||||||
|
func makeTestBlock(bn uint64) *types.Block { |
||||||
|
testHeader := &block.Header{Header: headerV3.NewHeader()} |
||||||
|
testHeader.SetNumber(big.NewInt(int64(bn))) |
||||||
|
testHeader.SetLastCommitSignature(bls_cosi.SerializedSignature{}) |
||||||
|
testHeader.SetLastCommitBitmap(make([]byte, 10)) |
||||||
|
block := types.NewBlockWithHeader(testHeader) |
||||||
|
block.SetCurrentCommitSig(make([]byte, 106)) |
||||||
|
return block |
||||||
|
} |
||||||
|
|
||||||
|
func assertError(got, expect error) error { |
||||||
|
if (got == nil) != (expect == nil) { |
||||||
|
return fmt.Errorf("unexpected error [%v] / [%v]", got, expect) |
||||||
|
} |
||||||
|
if (got == nil) || (expect == nil) { |
||||||
|
return nil |
||||||
|
} |
||||||
|
if !strings.Contains(got.Error(), expect.Error()) { |
||||||
|
return fmt.Errorf("unexpected error [%v] / [%v]", got, expect) |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
Loading…
Reference in new issue