mirror of
https://github.com/tendermint/tendermint.git
synced 2026-01-09 06:33:16 +00:00
* Add processor prototype
* Change processor API
+ expose a simple `handle` function which mutates internal state
* schedule event handling
* rename schedule -> scheduler
* fill in handle function
* processor tests
* fix gofmt and ohter golangci issues
* scopelint var on range scope
* add check for short block received
* small test reorg
* ci fix changes
* go.mod revert
* some cleanup and review comments
* scheduler fixes and unit tests, also small processor changes.
changed scPeerPruned to include a list of pruned peers
touchPeer to check peer state and remove the blocks from blockStates if the peer removal causes the max peer height to be lower.
remove the block at sc.initHeight
changed peersInactiveSince, peersSlowerThan, getPeersAtHeight check peer state
prunablePeers to return a sorted list of peers
lastRate in markReceived() attempted to divide by 0, temp fix.
fixed allBlocksProcessed conditions
maxHeight() and minHeight() to return sc.initHeight if no ready peers present
make selectPeer() deterministic.
added handleBlockProcessError()
added termination cond. (sc.allBlocksProcessed()) to handleTryPrunePeer() and others.
changed pcBlockVerificationFailure to include peer of H+2 block along with the one for H+1
changed the processor to call purgePeer on block verification failure.
fixed processor tests
added scheduler tests.
* typo and ci fixes
* remove height from scBlockRequest, golangci fixes
* limit on blockState map, updated tests
* remove unused
* separate test for maxHeight(), used for sched. validation
* use Math.Min
* fix golangci
* Document the semantics of blockStates in the scheduler
* better docs
* distinguish between unknown and invalid blockstate
* Standardize peer filtering methods
* feedback
* s/getPeersAtHeight/getPeersAtHeightOrAbove
* small notes
* Update blockchain/v2/scheduler.go
Co-Authored-By: Anton Kaliaev <anton.kalyaev@gmail.com>
* Update comments based on feedback
* Add enum offset
* panic on nil block in processor
* remove unused max height calculation
* format shorter line
188 lines
4.5 KiB
Go
188 lines
4.5 KiB
Go
package v2
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/tendermint/tendermint/p2p"
|
|
tdState "github.com/tendermint/tendermint/state"
|
|
"github.com/tendermint/tendermint/types"
|
|
)
|
|
|
|
type peerError struct {
|
|
priorityHigh
|
|
peerID p2p.ID
|
|
}
|
|
|
|
type pcDuplicateBlock struct {
|
|
priorityNormal
|
|
}
|
|
|
|
type pcShortBlock struct {
|
|
priorityNormal
|
|
}
|
|
|
|
type pcBlockVerificationFailure struct {
|
|
priorityNormal
|
|
height int64
|
|
firstPeerID p2p.ID
|
|
secondPeerID p2p.ID
|
|
}
|
|
|
|
type pcBlockProcessed struct {
|
|
priorityNormal
|
|
height int64
|
|
peerID p2p.ID
|
|
}
|
|
|
|
type pcProcessBlock struct {
|
|
priorityNormal
|
|
}
|
|
|
|
type pcStop struct {
|
|
priorityNormal
|
|
}
|
|
|
|
type pcFinished struct {
|
|
priorityNormal
|
|
height int64
|
|
blocksSynced int64
|
|
}
|
|
|
|
func (p pcFinished) Error() string {
|
|
return "finished"
|
|
}
|
|
|
|
type queueItem struct {
|
|
block *types.Block
|
|
peerID p2p.ID
|
|
}
|
|
|
|
type blockQueue map[int64]queueItem
|
|
|
|
type pcState struct {
|
|
height int64 // height of the last synced block
|
|
queue blockQueue // blocks waiting to be processed
|
|
chainID string
|
|
blocksSynced int64
|
|
draining bool
|
|
tdState tdState.State
|
|
context processorContext
|
|
}
|
|
|
|
func (state *pcState) String() string {
|
|
return fmt.Sprintf("height: %d queue length: %d draining: %v blocks synced: %d",
|
|
state.height, len(state.queue), state.draining, state.blocksSynced)
|
|
}
|
|
|
|
// newPcState returns a pcState initialized with the last verified block enqueued
|
|
func newPcState(initHeight int64, tdState tdState.State, chainID string, context processorContext) *pcState {
|
|
return &pcState{
|
|
height: initHeight,
|
|
queue: blockQueue{},
|
|
chainID: chainID,
|
|
draining: false,
|
|
blocksSynced: 0,
|
|
context: context,
|
|
tdState: tdState,
|
|
}
|
|
}
|
|
|
|
// nextTwo returns the next two unverified blocks
|
|
func (state *pcState) nextTwo() (queueItem, queueItem, error) {
|
|
if first, ok := state.queue[state.height+1]; ok {
|
|
if second, ok := state.queue[state.height+2]; ok {
|
|
return first, second, nil
|
|
}
|
|
}
|
|
return queueItem{}, queueItem{}, fmt.Errorf("not found")
|
|
}
|
|
|
|
// synced returns true when at most the last verified block remains in the queue
|
|
func (state *pcState) synced() bool {
|
|
return len(state.queue) <= 1
|
|
}
|
|
|
|
func (state *pcState) advance() {
|
|
state.height++
|
|
delete(state.queue, state.height)
|
|
state.blocksSynced++
|
|
}
|
|
|
|
func (state *pcState) enqueue(peerID p2p.ID, block *types.Block, height int64) error {
|
|
if _, ok := state.queue[height]; ok {
|
|
return fmt.Errorf("duplicate queue item")
|
|
}
|
|
state.queue[height] = queueItem{block: block, peerID: peerID}
|
|
return nil
|
|
}
|
|
|
|
// purgePeer moves all unprocessed blocks from the queue
|
|
func (state *pcState) purgePeer(peerID p2p.ID) {
|
|
// what if height is less than state.height?
|
|
for height, item := range state.queue {
|
|
if item.peerID == peerID {
|
|
delete(state.queue, height)
|
|
}
|
|
}
|
|
}
|
|
|
|
// handle processes FSM events
|
|
func (state *pcState) handle(event Event) (Event, error) {
|
|
switch event := event.(type) {
|
|
case *scBlockReceived:
|
|
if event.block == nil {
|
|
panic("processor received an event with a nil block")
|
|
}
|
|
if event.block.Height <= state.height {
|
|
return pcShortBlock{}, nil
|
|
}
|
|
err := state.enqueue(event.peerID, event.block, event.block.Height)
|
|
if err != nil {
|
|
return pcDuplicateBlock{}, nil
|
|
}
|
|
|
|
case pcProcessBlock:
|
|
firstItem, secondItem, err := state.nextTwo()
|
|
if err != nil {
|
|
if state.draining {
|
|
return noOp, pcFinished{height: state.height}
|
|
}
|
|
return noOp, nil
|
|
}
|
|
first, second := firstItem.block, secondItem.block
|
|
|
|
firstParts := first.MakePartSet(types.BlockPartSizeBytes)
|
|
firstPartsHeader := firstParts.Header()
|
|
firstID := types.BlockID{Hash: first.Hash(), PartsHeader: firstPartsHeader}
|
|
|
|
err = state.context.verifyCommit(state.chainID, firstID, first.Height, second.LastCommit)
|
|
if err != nil {
|
|
state.purgePeer(firstItem.peerID)
|
|
state.purgePeer(secondItem.peerID)
|
|
return pcBlockVerificationFailure{
|
|
height: first.Height, firstPeerID: firstItem.peerID, secondPeerID: secondItem.peerID},
|
|
nil
|
|
}
|
|
|
|
state.context.saveBlock(first, firstParts, second.LastCommit)
|
|
|
|
state.tdState, err = state.context.applyBlock(state.tdState, firstID, first)
|
|
if err != nil {
|
|
panic(fmt.Sprintf("failed to process committed block (%d:%X): %v", first.Height, first.Hash(), err))
|
|
}
|
|
state.advance()
|
|
return pcBlockProcessed{height: first.Height, peerID: firstItem.peerID}, nil
|
|
|
|
case *peerError:
|
|
state.purgePeer(event.peerID)
|
|
|
|
case pcStop:
|
|
if state.synced() {
|
|
return noOp, pcFinished{height: state.height, blocksSynced: state.blocksSynced}
|
|
}
|
|
state.draining = true
|
|
}
|
|
|
|
return noOp, nil
|
|
}
|