package v2
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
"github.com/tendermint/tendermint/internal/p2p"
|
|
tmState "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 int
|
|
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 (
|
|
tmState = tmState.State{LastBlockHeight: p.height}
|
|
context = newMockProcessorContext(tmState, p.verBL, p.appBL)
|
|
)
|
|
state := newPcState(context)
|
|
|
|
for _, item := range p.items {
|
|
state.enqueue(p2p.NodeID(item.pid), makePcBlock(item.height), item.height)
|
|
}
|
|
|
|
state.blocksSynced = p.blocksSynced
|
|
state.draining = p.draining
|
|
return state
|
|
}
|
|
|
|
func mBlockResponse(peerID p2p.NodeID, 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 initialize 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 TestRProcessPeerError(t *testing.T) {
|
|
tests := []testFields{
|
|
{
|
|
name: "error for existing peer",
|
|
steps: []pcFsmMakeStateValues{
|
|
{
|
|
currentState: ¶ms{items: []pcBlock{{"P1", 1}, {"P2", 2}}},
|
|
event: scPeerError{peerID: "P2"},
|
|
wantState: ¶ms{items: []pcBlock{{"P1", 1}}},
|
|
wantNextEvent: noOp,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "error for unknown peer",
|
|
steps: []pcFsmMakeStateValues{
|
|
{
|
|
currentState: ¶ms{items: []pcBlock{{"P1", 1}, {"P2", 2}}},
|
|
event: scPeerError{peerID: "P3"},
|
|
wantState: ¶ms{items: []pcBlock{{"P1", 1}, {"P2", 2}}},
|
|
wantNextEvent: noOp,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
executeProcessorTests(t, tests)
|
|
}
|
|
|
|
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,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
executeProcessorTests(t, tests)
|
|
}
|
|
|
|
func TestRProcessBlockSuccess(t *testing.T) {
|
|
tests := []testFields{
|
|
{
|
|
name: "noop - no blocks over current height",
|
|
steps: []pcFsmMakeStateValues{
|
|
{
|
|
currentState: ¶ms{}, event: rProcessBlock{},
|
|
wantState: ¶ms{}, wantNextEvent: noOp,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "noop - high new blocks",
|
|
steps: []pcFsmMakeStateValues{
|
|
{
|
|
currentState: ¶ms{height: 5, items: []pcBlock{{"P1", 30}, {"P2", 31}}}, event: rProcessBlock{},
|
|
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: rProcessBlock{},
|
|
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: scFinishedEv{},
|
|
wantState: ¶ms{items: []pcBlock{{"P1", 1}, {"P2", 2}, {"P1", 4}}, draining: true},
|
|
wantNextEvent: noOp,
|
|
},
|
|
{
|
|
event: rProcessBlock{},
|
|
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: rProcessBlock{},
|
|
wantState: ¶ms{height: 1, items: []pcBlock{{"P2", 2}, {"P1", 4}}, blocksSynced: 1, draining: true},
|
|
wantNextEvent: pcFinished{tmState: tmState.State{LastBlockHeight: 1}, blocksSynced: 1},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
executeProcessorTests(t, tests)
|
|
}
|
|
|
|
func TestRProcessBlockFailures(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: rProcessBlock{},
|
|
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: rProcessBlock{},
|
|
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{height: 0, items: []pcBlock{{"P1", 1}, {"P1", 2}, {"P2", 3}},
|
|
verBL: []int64{1}}, event: rProcessBlock{},
|
|
wantState: ¶ms{height: 0, 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: rProcessBlock{},
|
|
wantState: ¶ms{items: []pcBlock{{"P2", 3}}, appBL: []int64{1}}, wantPanic: true,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
executeProcessorTests(t, tests)
|
|
}
|
|
|
|
func TestScFinishedEv(t *testing.T) {
|
|
tests := []testFields{
|
|
{
|
|
name: "no blocks",
|
|
steps: []pcFsmMakeStateValues{
|
|
{
|
|
currentState: ¶ms{height: 100, items: []pcBlock{}, blocksSynced: 100}, event: scFinishedEv{},
|
|
wantState: ¶ms{height: 100, items: []pcBlock{}, blocksSynced: 100},
|
|
wantNextEvent: pcFinished{tmState: tmState.State{LastBlockHeight: 100}, blocksSynced: 100},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "maxHeight+1 block present",
|
|
steps: []pcFsmMakeStateValues{
|
|
{
|
|
currentState: ¶ms{height: 100, items: []pcBlock{
|
|
{"P1", 101}}, blocksSynced: 100}, event: scFinishedEv{},
|
|
wantState: ¶ms{height: 100, items: []pcBlock{{"P1", 101}}, blocksSynced: 100},
|
|
wantNextEvent: pcFinished{tmState: tmState.State{LastBlockHeight: 100}, blocksSynced: 100},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "more blocks present",
|
|
steps: []pcFsmMakeStateValues{
|
|
{
|
|
currentState: ¶ms{height: 100, items: []pcBlock{
|
|
{"P1", 101}, {"P1", 102}}, blocksSynced: 100}, event: scFinishedEv{},
|
|
wantState: ¶ms{height: 100, items: []pcBlock{
|
|
{"P1", 101}, {"P1", 102}}, blocksSynced: 100, draining: true},
|
|
wantNextEvent: noOp,
|
|
wantErr: nil,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
executeProcessorTests(t, tests)
|
|
}
|