mirror of
https://github.com/tendermint/tendermint.git
synced 2026-02-07 04:20:44 +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
357 lines
10 KiB
Go
357 lines
10 KiB
Go
package v2
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/tendermint/tendermint/p2p"
|
|
tdState "github.com/tendermint/tendermint/state"
|
|
"github.com/tendermint/tendermint/types"
|
|
)
|
|
|
|
// pcBlock is a test helper structure with simple types. Its purpose is to help with test readability.
|
|
type pcBlock struct {
|
|
pid string
|
|
height int64
|
|
}
|
|
|
|
// params is a test structure used to create processor state.
|
|
type params struct {
|
|
height int64
|
|
items []pcBlock
|
|
blocksSynced int64
|
|
verBL []int64
|
|
appBL []int64
|
|
draining bool
|
|
}
|
|
|
|
// makePcBlock makes an empty block.
|
|
func makePcBlock(height int64) *types.Block {
|
|
return &types.Block{Header: types.Header{Height: height}}
|
|
}
|
|
|
|
// makeState takes test parameters and creates a specific processor state.
|
|
func makeState(p *params) *pcState {
|
|
var (
|
|
tdState = tdState.State{}
|
|
context = newMockProcessorContext(p.verBL, p.appBL)
|
|
)
|
|
state := newPcState(p.height, tdState, "test", context)
|
|
|
|
for _, item := range p.items {
|
|
_ = state.enqueue(p2p.ID(item.pid), makePcBlock(item.height), item.height)
|
|
}
|
|
|
|
state.blocksSynced = p.blocksSynced
|
|
state.draining = p.draining
|
|
return state
|
|
}
|
|
|
|
func mBlockResponse(peerID p2p.ID, height int64) *scBlockReceived {
|
|
return &scBlockReceived{
|
|
peerID: peerID,
|
|
block: makePcBlock(height),
|
|
}
|
|
}
|
|
|
|
type pcFsmMakeStateValues struct {
|
|
currentState *params
|
|
event Event
|
|
wantState *params
|
|
wantNextEvent Event
|
|
wantErr error
|
|
wantPanic bool
|
|
}
|
|
|
|
type testFields struct {
|
|
name string
|
|
steps []pcFsmMakeStateValues
|
|
}
|
|
|
|
func executeProcessorTests(t *testing.T, tests []testFields) {
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
var state *pcState
|
|
for _, step := range tt.steps {
|
|
defer func() {
|
|
r := recover()
|
|
if (r != nil) != step.wantPanic {
|
|
t.Errorf("recover = %v, wantPanic = %v", r, step.wantPanic)
|
|
}
|
|
}()
|
|
|
|
// First step must always initialise the currentState as state.
|
|
if step.currentState != nil {
|
|
state = makeState(step.currentState)
|
|
}
|
|
if state == nil {
|
|
panic("Bad (initial?) step")
|
|
}
|
|
|
|
nextEvent, err := state.handle(step.event)
|
|
t.Log(state)
|
|
assert.Equal(t, step.wantErr, err)
|
|
assert.Equal(t, makeState(step.wantState), state)
|
|
assert.Equal(t, step.wantNextEvent, nextEvent)
|
|
// Next step may use the wantedState as their currentState.
|
|
state = makeState(step.wantState)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPcBlockResponse(t *testing.T) {
|
|
tests := []testFields{
|
|
|
|
{
|
|
name: "add one block",
|
|
steps: []pcFsmMakeStateValues{
|
|
{
|
|
currentState: ¶ms{}, event: mBlockResponse("P1", 1),
|
|
wantState: ¶ms{items: []pcBlock{{"P1", 1}}}, wantNextEvent: noOp,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "add two blocks",
|
|
steps: []pcFsmMakeStateValues{
|
|
{
|
|
currentState: ¶ms{}, event: mBlockResponse("P1", 3),
|
|
wantState: ¶ms{items: []pcBlock{{"P1", 3}}}, wantNextEvent: noOp,
|
|
},
|
|
{ // use previous wantState as currentState,
|
|
event: mBlockResponse("P1", 4),
|
|
wantState: ¶ms{items: []pcBlock{{"P1", 3}, {"P1", 4}}}, wantNextEvent: noOp,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "add duplicate block from same peer",
|
|
steps: []pcFsmMakeStateValues{
|
|
{
|
|
currentState: ¶ms{}, event: mBlockResponse("P1", 3),
|
|
wantState: ¶ms{items: []pcBlock{{"P1", 3}}}, wantNextEvent: noOp,
|
|
},
|
|
{ // use previous wantState as currentState,
|
|
event: mBlockResponse("P1", 3),
|
|
wantState: ¶ms{items: []pcBlock{{"P1", 3}}}, wantNextEvent: pcDuplicateBlock{},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "add duplicate block from different peer",
|
|
steps: []pcFsmMakeStateValues{
|
|
{
|
|
currentState: ¶ms{}, event: mBlockResponse("P1", 3),
|
|
wantState: ¶ms{items: []pcBlock{{"P1", 3}}}, wantNextEvent: noOp,
|
|
},
|
|
{ // use previous wantState as currentState,
|
|
event: mBlockResponse("P2", 3),
|
|
wantState: ¶ms{items: []pcBlock{{"P1", 3}}}, wantNextEvent: pcDuplicateBlock{},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "attempt to add block with height equal to state.height",
|
|
steps: []pcFsmMakeStateValues{
|
|
{
|
|
currentState: ¶ms{height: 2, items: []pcBlock{{"P1", 3}}}, event: mBlockResponse("P1", 2),
|
|
wantState: ¶ms{height: 2, items: []pcBlock{{"P1", 3}}}, wantNextEvent: pcShortBlock{},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "attempt to add block with height smaller than state.height",
|
|
steps: []pcFsmMakeStateValues{
|
|
{
|
|
currentState: ¶ms{height: 2, items: []pcBlock{{"P1", 3}}}, event: mBlockResponse("P1", 1),
|
|
wantState: ¶ms{height: 2, items: []pcBlock{{"P1", 3}}}, wantNextEvent: pcShortBlock{},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
executeProcessorTests(t, tests)
|
|
}
|
|
|
|
func TestPcProcessBlockSuccess(t *testing.T) {
|
|
tests := []testFields{
|
|
{
|
|
name: "noop - no blocks over current height",
|
|
steps: []pcFsmMakeStateValues{
|
|
{
|
|
currentState: ¶ms{}, event: pcProcessBlock{},
|
|
wantState: ¶ms{}, wantNextEvent: noOp,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "noop - high new blocks",
|
|
steps: []pcFsmMakeStateValues{
|
|
{
|
|
currentState: ¶ms{height: 5, items: []pcBlock{{"P1", 30}, {"P2", 31}}}, event: pcProcessBlock{},
|
|
wantState: ¶ms{height: 5, items: []pcBlock{{"P1", 30}, {"P2", 31}}}, wantNextEvent: noOp,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "blocks H+1 and H+2 present",
|
|
steps: []pcFsmMakeStateValues{
|
|
{
|
|
currentState: ¶ms{items: []pcBlock{{"P1", 1}, {"P2", 2}}}, event: pcProcessBlock{},
|
|
wantState: ¶ms{height: 1, items: []pcBlock{{"P2", 2}}, blocksSynced: 1},
|
|
wantNextEvent: pcBlockProcessed{height: 1, peerID: "P1"},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "blocks H+1 and H+2 present after draining",
|
|
steps: []pcFsmMakeStateValues{
|
|
{ // some contiguous blocks - on stop check draining is set
|
|
currentState: ¶ms{items: []pcBlock{{"P1", 1}, {"P2", 2}, {"P1", 4}}}, event: pcStop{},
|
|
wantState: ¶ms{items: []pcBlock{{"P1", 1}, {"P2", 2}, {"P1", 4}}, draining: true},
|
|
wantNextEvent: noOp,
|
|
},
|
|
{
|
|
event: pcProcessBlock{},
|
|
wantState: ¶ms{height: 1, items: []pcBlock{{"P2", 2}, {"P1", 4}}, blocksSynced: 1, draining: true},
|
|
wantNextEvent: pcBlockProcessed{height: 1, peerID: "P1"},
|
|
},
|
|
{ // finish when H+1 or/and H+2 are missing
|
|
event: pcProcessBlock{},
|
|
wantState: ¶ms{height: 1, items: []pcBlock{{"P2", 2}, {"P1", 4}}, blocksSynced: 1, draining: true},
|
|
wantNextEvent: noOp,
|
|
wantErr: pcFinished{height: 1},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
executeProcessorTests(t, tests)
|
|
}
|
|
|
|
func TestPcProcessBlockFailures(t *testing.T) {
|
|
tests := []testFields{
|
|
{
|
|
name: "blocks H+1 and H+2 present from different peers - H+1 verification fails ",
|
|
steps: []pcFsmMakeStateValues{
|
|
{
|
|
currentState: ¶ms{items: []pcBlock{{"P1", 1}, {"P2", 2}}, verBL: []int64{1}}, event: pcProcessBlock{},
|
|
wantState: ¶ms{items: []pcBlock{}, verBL: []int64{1}},
|
|
wantNextEvent: pcBlockVerificationFailure{height: 1, firstPeerID: "P1", secondPeerID: "P2"},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "blocks H+1 and H+2 present from same peer - H+1 applyBlock fails ",
|
|
steps: []pcFsmMakeStateValues{
|
|
{
|
|
currentState: ¶ms{items: []pcBlock{{"P1", 1}, {"P2", 2}}, appBL: []int64{1}}, event: pcProcessBlock{},
|
|
wantState: ¶ms{items: []pcBlock{}, appBL: []int64{1}}, wantPanic: true,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "blocks H+1 and H+2 present from same peers - H+1 verification fails ",
|
|
steps: []pcFsmMakeStateValues{
|
|
{
|
|
currentState: ¶ms{items: []pcBlock{{"P1", 1}, {"P1", 2}, {"P2", 3}}, verBL: []int64{1}},
|
|
event: pcProcessBlock{},
|
|
wantState: ¶ms{items: []pcBlock{{"P2", 3}}, verBL: []int64{1}},
|
|
wantNextEvent: pcBlockVerificationFailure{height: 1, firstPeerID: "P1", secondPeerID: "P1"},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "blocks H+1 and H+2 present from different peers - H+1 applyBlock fails ",
|
|
steps: []pcFsmMakeStateValues{
|
|
{
|
|
currentState: ¶ms{items: []pcBlock{{"P1", 1}, {"P2", 2}, {"P2", 3}}, appBL: []int64{1}},
|
|
event: pcProcessBlock{},
|
|
wantState: ¶ms{items: []pcBlock{{"P2", 3}}, appBL: []int64{1}}, wantPanic: true,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
executeProcessorTests(t, tests)
|
|
}
|
|
|
|
func TestPcPeerError(t *testing.T) {
|
|
tests := []testFields{
|
|
{
|
|
name: "peer not present",
|
|
steps: []pcFsmMakeStateValues{
|
|
{
|
|
currentState: ¶ms{items: []pcBlock{{"P1", 1}, {"P2", 2}}}, event: &peerError{peerID: "P3"},
|
|
wantState: ¶ms{items: []pcBlock{{"P1", 1}, {"P2", 2}}},
|
|
wantNextEvent: noOp,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "some blocks are from errored peer",
|
|
steps: []pcFsmMakeStateValues{
|
|
{
|
|
currentState: ¶ms{items: []pcBlock{{"P1", 100}, {"P1", 99}, {"P2", 101}}}, event: &peerError{peerID: "P1"},
|
|
wantState: ¶ms{items: []pcBlock{{"P2", 101}}},
|
|
wantNextEvent: noOp,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "all blocks are from errored peer",
|
|
steps: []pcFsmMakeStateValues{
|
|
{
|
|
currentState: ¶ms{items: []pcBlock{{"P1", 100}, {"P1", 99}}}, event: &peerError{peerID: "P1"},
|
|
wantState: ¶ms{},
|
|
wantNextEvent: noOp,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
executeProcessorTests(t, tests)
|
|
}
|
|
|
|
func TestStop(t *testing.T) {
|
|
tests := []testFields{
|
|
{
|
|
name: "no blocks",
|
|
steps: []pcFsmMakeStateValues{
|
|
{
|
|
currentState: ¶ms{height: 100, items: []pcBlock{}, blocksSynced: 100}, event: pcStop{},
|
|
wantState: ¶ms{height: 100, items: []pcBlock{}, blocksSynced: 100},
|
|
wantNextEvent: noOp,
|
|
wantErr: pcFinished{height: 100, blocksSynced: 100},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "maxHeight+1 block present",
|
|
steps: []pcFsmMakeStateValues{
|
|
{
|
|
currentState: ¶ms{height: 100, items: []pcBlock{{"P1", 101}}, blocksSynced: 100}, event: pcStop{},
|
|
wantState: ¶ms{height: 100, items: []pcBlock{{"P1", 101}}, blocksSynced: 100},
|
|
wantNextEvent: noOp,
|
|
wantErr: pcFinished{height: 100, blocksSynced: 100},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "more blocks present",
|
|
steps: []pcFsmMakeStateValues{
|
|
{
|
|
currentState: ¶ms{height: 100, items: []pcBlock{{"P1", 101}, {"P1", 102}}, blocksSynced: 100}, event: pcStop{},
|
|
wantState: ¶ms{height: 100, items: []pcBlock{{"P1", 101}, {"P1", 102}}, blocksSynced: 100, draining: true},
|
|
wantNextEvent: noOp,
|
|
wantErr: nil,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
executeProcessorTests(t, tests)
|
|
}
|