|
|
@ -0,0 +1,336 @@ |
|
|
|
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) *bcBlockResponse { |
|
|
|
return &bcBlockResponse{ |
|
|
|
peerID: peerID, |
|
|
|
block: makePcBlock(height), |
|
|
|
height: 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 - H+1 verification fails ", |
|
|
|
steps: []pcFsmMakeStateValues{ |
|
|
|
{ |
|
|
|
currentState: ¶ms{items: []pcBlock{{"P1", 1}, {"P2", 2}}, verBL: []int64{1}}, event: pcProcessBlock{}, |
|
|
|
wantState: ¶ms{items: []pcBlock{{"P1", 1}, {"P2", 2}}, verBL: []int64{1}}, |
|
|
|
wantNextEvent: pcBlockVerificationFailure{peerID: "P1", height: 1}, |
|
|
|
}, |
|
|
|
}, |
|
|
|
}, |
|
|
|
{ |
|
|
|
name: "blocks H+1 and H+2 present - H+1 applyBlock fails ", |
|
|
|
steps: []pcFsmMakeStateValues{ |
|
|
|
{ |
|
|
|
currentState: ¶ms{items: []pcBlock{{"P1", 1}, {"P2", 2}}, appBL: []int64{1}}, event: pcProcessBlock{}, |
|
|
|
wantState: ¶ms{items: []pcBlock{{"P1", 1}, {"P2", 2}}, 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) |
|
|
|
} |