parent
12d930fd97
commit
6f3aa67b88
@ -0,0 +1,327 @@ |
||||
package stagedstreamsync |
||||
|
||||
import ( |
||||
"context" |
||||
"fmt" |
||||
"sync" |
||||
"time" |
||||
|
||||
"github.com/ethereum/go-ethereum/common" |
||||
"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 StageReceipts struct { |
||||
configs StageReceiptsCfg |
||||
} |
||||
|
||||
type StageReceiptsCfg struct { |
||||
bc core.BlockChain |
||||
db kv.RwDB |
||||
blockDBs []kv.RwDB |
||||
concurrency int |
||||
protocol syncProtocol |
||||
isBeacon bool |
||||
logProgress bool |
||||
} |
||||
|
||||
func NewStageReceipts(cfg StageReceiptsCfg) *StageReceipts { |
||||
return &StageReceipts{ |
||||
configs: cfg, |
||||
} |
||||
} |
||||
|
||||
func NewStageReceiptsCfg(bc core.BlockChain, db kv.RwDB, blockDBs []kv.RwDB, concurrency int, protocol syncProtocol, isBeacon bool, logProgress bool) StageReceiptsCfg { |
||||
return StageReceiptsCfg{ |
||||
bc: bc, |
||||
db: db, |
||||
blockDBs: blockDBs, |
||||
concurrency: concurrency, |
||||
protocol: protocol, |
||||
isBeacon: isBeacon, |
||||
logProgress: logProgress, |
||||
} |
||||
} |
||||
|
||||
// Exec progresses Bodies stage in the forward direction
|
||||
func (b *StageReceipts) Exec(ctx context.Context, firstCycle bool, invalidBlockRevert bool, s *StageState, reverter Reverter, tx kv.RwTx) (err error) { |
||||
|
||||
useInternalTx := tx == nil |
||||
|
||||
if invalidBlockRevert { |
||||
return nil |
||||
} |
||||
|
||||
// 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 |
||||
|
||||
if errV := CreateView(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 { |
||||
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(ctx) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defer tx.Rollback() |
||||
} |
||||
|
||||
// Fetch blocks from neighbors
|
||||
s.state.rdm = newReceiptDownloadManager(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.runReceiptWorkerLoop(ctx, s.state.rdm, &wg, i, startTime) |
||||
} |
||||
|
||||
wg.Wait() |
||||
|
||||
if useInternalTx { |
||||
if err := tx.Commit(); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
|
||||
// runReceiptWorkerLoop creates a work loop for download receipts
|
||||
func (b *StageReceipts) runReceiptWorkerLoop(ctx context.Context, rdm *receiptDownloadManager, wg *sync.WaitGroup, loopID int, startTime time.Time) { |
||||
|
||||
currentBlock := int(b.configs.bc.CurrentBlock().NumberU64()) |
||||
|
||||
defer wg.Done() |
||||
|
||||
for { |
||||
select { |
||||
case <-ctx.Done(): |
||||
return |
||||
default: |
||||
} |
||||
batch := rdm.GetNextBatch() |
||||
if len(batch) == 0 { |
||||
select { |
||||
case <-ctx.Done(): |
||||
return |
||||
case <-time.After(100 * time.Millisecond): |
||||
return |
||||
} |
||||
} |
||||
var hashes []common.Hash |
||||
for _, bn := range batch { |
||||
header := b.configs.bc.GetHeaderByNumber(bn) |
||||
hashes = append(hashes, header.ReceiptHash()) |
||||
} |
||||
receipts, stid, err := b.downloadReceipts(ctx, hashes) |
||||
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") |
||||
rdm.HandleRequestError(batch, err, stid) |
||||
} else if receipts == nil || len(receipts) == 0 { |
||||
utils.Logger().Warn(). |
||||
Str("stream", string(stid)). |
||||
Interface("block numbers", batch). |
||||
Msg(WrapStagedSyncMsg("downloadRawBlocks failed, received empty reciptBytes")) |
||||
err := errors.New("downloadRawBlocks received empty reciptBytes") |
||||
rdm.HandleRequestError(batch, err, stid) |
||||
} else { |
||||
rdm.HandleRequestResult(batch, receipts, 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(rdm.rdd)) / 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(rdm.rdd), "/", int(rdm.targetBN), "(", blockSpeed, "blocks/s", ")") |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
func (b *StageReceipts) downloadReceipts(ctx context.Context, hs []common.Hash) ([]*types.Receipt, sttypes.StreamID, error) { |
||||
ctx, cancel := context.WithTimeout(ctx, 10*time.Second) |
||||
defer cancel() |
||||
|
||||
receipts, stid, err := b.configs.protocol.GetReceipts(ctx, hs) |
||||
if err != nil { |
||||
return nil, stid, err |
||||
} |
||||
if err := validateGetReceiptsResult(hs, receipts); err != nil { |
||||
return nil, stid, err |
||||
} |
||||
return receipts, stid, nil |
||||
} |
||||
|
||||
func (b *StageReceipts) downloadRawBlocks(ctx context.Context, bns []uint64) ([][]byte, [][]byte, sttypes.StreamID, error) { |
||||
ctx, cancel := context.WithTimeout(ctx, 10*time.Second) |
||||
defer cancel() |
||||
|
||||
return b.configs.protocol.GetRawBlocksByNumber(ctx, bns) |
||||
} |
||||
|
||||
func validateGetReceiptsResult(requested []common.Hash, result []*types.Receipt) error { |
||||
// TODO: validate each receipt here
|
||||
|
||||
return nil |
||||
} |
||||
|
||||
func (b *StageReceipts) saveProgress(ctx context.Context, s *StageState, progress uint64, tx kv.RwTx) (err error) { |
||||
useInternalTx := tx == nil |
||||
if useInternalTx { |
||||
var err error |
||||
tx, err = b.configs.db.BeginRw(ctx) |
||||
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 *StageReceipts) cleanBlocksDB(ctx context.Context, loopID int) (err error) { |
||||
tx, errb := b.configs.blockDBs[loopID].BeginRw(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 *StageReceipts) cleanAllBlockDBs(ctx context.Context) (err error) { |
||||
//clean all blocks DBs
|
||||
for i := 0; i < b.configs.concurrency; i++ { |
||||
if err := b.cleanBlocksDB(ctx, i); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func (b *StageReceipts) Revert(ctx context.Context, firstCycle bool, u *RevertState, s *StageState, tx kv.RwTx) (err error) { |
||||
|
||||
//clean all blocks DBs
|
||||
if err := b.cleanAllBlockDBs(ctx); err != nil { |
||||
return err |
||||
} |
||||
|
||||
useInternalTx := tx == nil |
||||
if useInternalTx { |
||||
tx, err = b.configs.db.BeginRw(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 *StageReceipts) CleanUp(ctx context.Context, firstCycle bool, p *CleanUpState, tx kv.RwTx) (err error) { |
||||
//clean all blocks DBs
|
||||
if err := b.cleanAllBlockDBs(ctx); err != nil { |
||||
return err |
||||
} |
||||
|
||||
return nil |
||||
} |
Loading…
Reference in new issue