2019-04-13 09:23:43 -04:00
|
|
|
package blockchain
|
2019-03-26 09:58:30 +01:00
|
|
|
|
|
|
|
import (
|
2019-04-12 22:02:30 -04:00
|
|
|
"fmt"
|
2019-04-14 22:53:24 -04:00
|
|
|
"sync"
|
2019-04-04 00:13:32 +02:00
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
2019-03-26 09:58:30 +01:00
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
cmn "github.com/tendermint/tendermint/libs/common"
|
|
|
|
"github.com/tendermint/tendermint/libs/log"
|
|
|
|
"github.com/tendermint/tendermint/p2p"
|
2019-04-12 22:02:30 -04:00
|
|
|
"github.com/tendermint/tendermint/types"
|
2019-03-26 09:58:30 +01:00
|
|
|
)
|
|
|
|
|
2019-04-12 22:02:30 -04:00
|
|
|
// reactor for FSM testing
|
|
|
|
type testReactor struct {
|
|
|
|
logger log.Logger
|
|
|
|
fsm *bReactorFSM
|
|
|
|
}
|
|
|
|
|
|
|
|
func sendEventToFSM(fsm *bReactorFSM, ev bReactorEvent, data bReactorEventData) error {
|
2019-05-07 21:06:43 -04:00
|
|
|
return fsm.handle(&bcReactorMessage{event: ev, data: data})
|
2019-04-12 22:02:30 -04:00
|
|
|
}
|
|
|
|
|
2019-03-26 09:58:30 +01:00
|
|
|
var (
|
2019-04-17 22:41:28 -04:00
|
|
|
testMutex sync.Mutex
|
|
|
|
numStatusRequests int
|
|
|
|
numBlockRequests int32
|
2019-03-26 09:58:30 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
type lastBlockRequestT struct {
|
|
|
|
peerID p2p.ID
|
|
|
|
height int64
|
|
|
|
}
|
|
|
|
|
|
|
|
var lastBlockRequest lastBlockRequestT
|
|
|
|
|
|
|
|
type lastPeerErrorT struct {
|
|
|
|
peerID p2p.ID
|
|
|
|
err error
|
|
|
|
}
|
|
|
|
|
|
|
|
var lastPeerError lastPeerErrorT
|
|
|
|
|
|
|
|
var stateTimerStarts map[string]int
|
|
|
|
|
|
|
|
func resetTestValues() {
|
|
|
|
stateTimerStarts = make(map[string]int)
|
|
|
|
numStatusRequests = 0
|
|
|
|
numBlockRequests = 0
|
|
|
|
lastBlockRequest.peerID = ""
|
|
|
|
lastBlockRequest.height = 0
|
|
|
|
lastPeerError.peerID = ""
|
|
|
|
lastPeerError.err = nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type fsmStepTestValues struct {
|
2019-04-17 22:41:28 -04:00
|
|
|
currentState string
|
|
|
|
event bReactorEvent
|
|
|
|
data bReactorEventData
|
|
|
|
errWanted error
|
|
|
|
expectedState string
|
2019-03-26 09:58:30 +01:00
|
|
|
|
|
|
|
shouldSendStatusReq bool
|
2019-04-15 23:26:07 -04:00
|
|
|
blockReqIncreased bool
|
|
|
|
blocksAdded []int64
|
|
|
|
peersNotInPool []p2p.ID
|
2019-04-17 22:41:28 -04:00
|
|
|
}
|
2019-03-26 09:58:30 +01:00
|
|
|
|
2019-04-17 22:41:28 -04:00
|
|
|
// --------------------------------------------
|
|
|
|
// helper function to make tests easier to read
|
|
|
|
func makeStepStopFSMEv(current, expected string) fsmStepTestValues {
|
|
|
|
return fsmStepTestValues{
|
|
|
|
currentState: current,
|
|
|
|
expectedState: expected,
|
|
|
|
event: stopFSMEv,
|
|
|
|
errWanted: errNoErrorFinished}
|
2019-03-26 09:58:30 +01:00
|
|
|
}
|
|
|
|
|
2019-04-17 22:41:28 -04:00
|
|
|
func makeStepUnknownFSMEv(current string) fsmStepTestValues {
|
|
|
|
return fsmStepTestValues{
|
|
|
|
currentState: current,
|
|
|
|
event: 1234,
|
|
|
|
expectedState: current,
|
|
|
|
errWanted: errInvalidEvent}
|
|
|
|
}
|
|
|
|
|
|
|
|
func makeStepStartFSMEv() fsmStepTestValues {
|
|
|
|
return fsmStepTestValues{
|
|
|
|
currentState: "unknown",
|
|
|
|
event: startFSMEv,
|
2019-04-12 22:02:30 -04:00
|
|
|
expectedState: "waitForPeer",
|
|
|
|
shouldSendStatusReq: true}
|
2019-04-17 22:41:28 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
func makeStepStateTimeoutEv(current, expected string, timedoutState string, errWanted error) fsmStepTestValues {
|
|
|
|
return fsmStepTestValues{
|
|
|
|
currentState: current,
|
|
|
|
event: stateTimeoutEv,
|
|
|
|
data: bReactorEventData{
|
|
|
|
stateName: timedoutState,
|
|
|
|
},
|
|
|
|
expectedState: expected,
|
|
|
|
errWanted: errWanted,
|
2019-04-12 22:02:30 -04:00
|
|
|
}
|
2019-04-17 22:41:28 -04:00
|
|
|
}
|
2019-04-12 22:02:30 -04:00
|
|
|
|
2019-04-17 22:41:28 -04:00
|
|
|
func makeStepProcessedBlockEv(current, expected string, reactorError error) fsmStepTestValues {
|
2019-04-12 22:02:30 -04:00
|
|
|
return fsmStepTestValues{
|
2019-04-17 22:41:28 -04:00
|
|
|
currentState: current,
|
|
|
|
event: processedBlockEv,
|
|
|
|
expectedState: expected,
|
|
|
|
data: bReactorEventData{
|
|
|
|
err: reactorError,
|
|
|
|
},
|
|
|
|
errWanted: reactorError,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func makeStepStatusEv(current, expected string, peerID p2p.ID, height int64, err error) fsmStepTestValues {
|
|
|
|
return fsmStepTestValues{
|
|
|
|
currentState: current,
|
|
|
|
event: statusResponseEv,
|
|
|
|
data: bReactorEventData{peerId: peerID, height: height},
|
|
|
|
expectedState: expected,
|
|
|
|
errWanted: err}
|
|
|
|
}
|
|
|
|
|
|
|
|
func makeStepMakeRequestsEv(current, expected string, maxPendingRequests int32) fsmStepTestValues {
|
|
|
|
return fsmStepTestValues{
|
|
|
|
currentState: current,
|
|
|
|
event: makeRequestsEv,
|
2019-04-12 22:02:30 -04:00
|
|
|
data: bReactorEventData{maxNumRequests: maxPendingRequests},
|
2019-04-17 22:41:28 -04:00
|
|
|
expectedState: expected,
|
2019-04-12 22:02:30 -04:00
|
|
|
blockReqIncreased: true,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-17 22:41:28 -04:00
|
|
|
func makeStepMakeRequestsEvErrored(current, expected string,
|
|
|
|
maxPendingRequests int32, err error, peersRemoved []p2p.ID) fsmStepTestValues {
|
2019-04-12 22:02:30 -04:00
|
|
|
return fsmStepTestValues{
|
2019-04-17 22:41:28 -04:00
|
|
|
currentState: current,
|
|
|
|
event: makeRequestsEv,
|
|
|
|
data: bReactorEventData{maxNumRequests: maxPendingRequests},
|
|
|
|
expectedState: expected,
|
|
|
|
errWanted: err,
|
|
|
|
peersNotInPool: peersRemoved,
|
|
|
|
}
|
2019-04-12 22:02:30 -04:00
|
|
|
}
|
|
|
|
|
2019-04-17 22:41:28 -04:00
|
|
|
func makeStepBlockRespEv(current, expected string, peerID p2p.ID, height int64, prevBlocks []int64) fsmStepTestValues {
|
|
|
|
txs := []types.Tx{types.Tx("foo"), types.Tx("bar")}
|
2019-04-12 22:02:30 -04:00
|
|
|
return fsmStepTestValues{
|
2019-04-17 22:41:28 -04:00
|
|
|
currentState: current,
|
|
|
|
event: blockResponseEv,
|
|
|
|
data: bReactorEventData{
|
|
|
|
peerId: peerID,
|
|
|
|
height: height,
|
|
|
|
block: types.MakeBlock(int64(height), txs, nil, nil),
|
|
|
|
length: 100},
|
|
|
|
expectedState: expected,
|
|
|
|
blocksAdded: append(prevBlocks, height),
|
|
|
|
}
|
2019-04-12 22:02:30 -04:00
|
|
|
}
|
|
|
|
|
2019-04-17 22:41:28 -04:00
|
|
|
func makeStepBlockRespEvErrored(current, expected string,
|
|
|
|
peerID p2p.ID, height int64, prevBlocks []int64, errWanted error, peersRemoved []p2p.ID) fsmStepTestValues {
|
2019-04-12 22:02:30 -04:00
|
|
|
txs := []types.Tx{types.Tx("foo"), types.Tx("bar")}
|
|
|
|
|
|
|
|
return fsmStepTestValues{
|
2019-04-17 22:41:28 -04:00
|
|
|
currentState: current,
|
|
|
|
event: blockResponseEv,
|
2019-04-12 22:02:30 -04:00
|
|
|
data: bReactorEventData{
|
|
|
|
peerId: peerID,
|
|
|
|
height: height,
|
2019-04-17 22:41:28 -04:00
|
|
|
block: types.MakeBlock(int64(height), txs, nil, nil),
|
|
|
|
length: 100},
|
|
|
|
expectedState: expected,
|
|
|
|
errWanted: errWanted,
|
|
|
|
peersNotInPool: peersRemoved,
|
|
|
|
blocksAdded: prevBlocks,
|
2019-04-12 22:02:30 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-17 22:41:28 -04:00
|
|
|
func makeStepPeerRemoveEv(current, expected string, peerID p2p.ID, err error, peersRemoved []p2p.ID) fsmStepTestValues {
|
2019-04-12 23:32:21 -04:00
|
|
|
return fsmStepTestValues{
|
2019-04-17 22:41:28 -04:00
|
|
|
currentState: current,
|
|
|
|
event: peerRemoveEv,
|
2019-04-12 23:32:21 -04:00
|
|
|
data: bReactorEventData{
|
|
|
|
peerId: peerID,
|
|
|
|
err: err,
|
|
|
|
},
|
2019-04-17 22:41:28 -04:00
|
|
|
expectedState: expected,
|
|
|
|
peersNotInPool: peersRemoved,
|
2019-04-12 23:32:21 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-17 22:41:28 -04:00
|
|
|
// --------------------------------------------
|
|
|
|
|
2019-04-12 22:02:30 -04:00
|
|
|
func newTestReactor(height int64) *testReactor {
|
2019-03-26 22:43:50 +01:00
|
|
|
testBcR := &testReactor{logger: log.TestingLogger()}
|
2019-04-12 22:02:30 -04:00
|
|
|
testBcR.fsm = NewFSM(height, testBcR)
|
2019-03-26 22:43:50 +01:00
|
|
|
testBcR.fsm.setLogger(testBcR.logger)
|
2019-03-26 14:51:37 +01:00
|
|
|
return testBcR
|
|
|
|
}
|
|
|
|
|
2019-04-12 22:02:30 -04:00
|
|
|
func fixBlockResponseEvStep(step *fsmStepTestValues, testBcR *testReactor) {
|
|
|
|
// There is no good way to know to which peer a block request was sent.
|
|
|
|
// So before we simulate a block response we cheat and look where it is expected from.
|
|
|
|
if step.event == blockResponseEv {
|
|
|
|
height := step.data.height
|
|
|
|
peerID, ok := testBcR.fsm.pool.blocks[height]
|
|
|
|
if ok {
|
|
|
|
step.data.peerId = peerID
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-15 23:26:07 -04:00
|
|
|
func shouldApplyProcessedBlockEvStep(step *fsmStepTestValues, testBcR *testReactor) bool {
|
|
|
|
if step.event == processedBlockEv {
|
|
|
|
_, err := testBcR.fsm.pool.getBlockAndPeerAtHeight(testBcR.fsm.pool.height)
|
|
|
|
if err == errMissingBlocks {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
_, err = testBcR.fsm.pool.getBlockAndPeerAtHeight(testBcR.fsm.pool.height + 1)
|
|
|
|
if err == errMissingBlocks {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2019-04-12 22:02:30 -04:00
|
|
|
func TestFSMBasic(t *testing.T) {
|
|
|
|
tests := []struct {
|
|
|
|
name string
|
|
|
|
startingHeight int64
|
|
|
|
maxRequestsPerPeer int32
|
|
|
|
steps []fsmStepTestValues
|
|
|
|
}{
|
2019-03-26 09:58:30 +01:00
|
|
|
{
|
2019-04-12 22:02:30 -04:00
|
|
|
name: "one block, one peer",
|
|
|
|
startingHeight: 1,
|
|
|
|
maxRequestsPerPeer: 2,
|
|
|
|
steps: []fsmStepTestValues{
|
|
|
|
// startFSMEv
|
2019-04-17 22:41:28 -04:00
|
|
|
makeStepStartFSMEv(),
|
2019-04-12 22:02:30 -04:00
|
|
|
// statusResponseEv
|
2019-04-17 22:41:28 -04:00
|
|
|
makeStepStatusEv("waitForPeer", "waitForBlock", "P1", 2, nil),
|
2019-04-12 22:02:30 -04:00
|
|
|
// makeRequestEv
|
2019-04-17 22:41:28 -04:00
|
|
|
makeStepMakeRequestsEv("waitForBlock", "waitForBlock", maxNumPendingRequests),
|
2019-04-12 22:02:30 -04:00
|
|
|
// blockResponseEv for height 1
|
2019-04-17 22:41:28 -04:00
|
|
|
makeStepBlockRespEv("waitForBlock", "waitForBlock",
|
|
|
|
"P1", 1, []int64{}),
|
2019-04-12 22:02:30 -04:00
|
|
|
// blockResponseEv for height 2
|
2019-04-17 22:41:28 -04:00
|
|
|
makeStepBlockRespEv("waitForBlock", "waitForBlock",
|
|
|
|
"P2", 2, []int64{1}),
|
2019-04-12 22:02:30 -04:00
|
|
|
// processedBlockEv
|
2019-04-17 22:41:28 -04:00
|
|
|
makeStepProcessedBlockEv("waitForBlock", "finished", nil),
|
2019-04-12 22:02:30 -04:00
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "multi block, multi peer",
|
|
|
|
startingHeight: 1,
|
|
|
|
maxRequestsPerPeer: 2,
|
|
|
|
steps: []fsmStepTestValues{
|
|
|
|
// startFSMEv
|
2019-04-17 22:41:28 -04:00
|
|
|
makeStepStartFSMEv(),
|
2019-04-12 22:02:30 -04:00
|
|
|
// statusResponseEv from P1
|
2019-04-17 22:41:28 -04:00
|
|
|
makeStepStatusEv("waitForPeer", "waitForBlock", "P1", 4, nil),
|
2019-04-12 22:02:30 -04:00
|
|
|
// statusResponseEv from P2
|
2019-04-17 22:41:28 -04:00
|
|
|
makeStepStatusEv("waitForBlock", "waitForBlock", "P2", 4, nil),
|
2019-04-12 22:02:30 -04:00
|
|
|
// makeRequestEv
|
2019-04-17 22:41:28 -04:00
|
|
|
makeStepMakeRequestsEv("waitForBlock", "waitForBlock", maxNumPendingRequests),
|
|
|
|
|
2019-04-12 22:02:30 -04:00
|
|
|
// blockResponseEv for height 1
|
2019-04-28 22:03:26 -04:00
|
|
|
makeStepBlockRespEv("waitForBlock", "waitForBlock", "P1", 1, []int64{}),
|
2019-04-12 22:02:30 -04:00
|
|
|
// blockResponseEv for height 2
|
2019-04-28 22:03:26 -04:00
|
|
|
makeStepBlockRespEv("waitForBlock", "waitForBlock", "P1", 2, []int64{1}),
|
2019-04-12 22:02:30 -04:00
|
|
|
// blockResponseEv for height 3
|
2019-04-28 22:03:26 -04:00
|
|
|
makeStepBlockRespEv("waitForBlock", "waitForBlock", "P2", 3, []int64{1, 2}),
|
2019-04-12 22:02:30 -04:00
|
|
|
// blockResponseEv for height 4
|
2019-04-28 22:03:26 -04:00
|
|
|
makeStepBlockRespEv("waitForBlock", "waitForBlock", "P2", 4, []int64{1, 2, 3}),
|
2019-04-12 22:02:30 -04:00
|
|
|
// processedBlockEv
|
2019-04-17 22:41:28 -04:00
|
|
|
makeStepProcessedBlockEv("waitForBlock", "waitForBlock", nil),
|
|
|
|
makeStepProcessedBlockEv("waitForBlock", "waitForBlock", nil),
|
|
|
|
makeStepProcessedBlockEv("waitForBlock", "finished", nil),
|
2019-04-12 22:02:30 -04:00
|
|
|
},
|
2019-03-26 09:58:30 +01:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2019-04-12 22:02:30 -04:00
|
|
|
for _, tt := range tests {
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
// Create test reactor
|
|
|
|
testBcR := newTestReactor(tt.startingHeight)
|
|
|
|
resetTestValues()
|
|
|
|
|
|
|
|
if tt.maxRequestsPerPeer != 0 {
|
|
|
|
maxRequestsPerPeer = tt.maxRequestsPerPeer
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, step := range tt.steps {
|
|
|
|
assert.Equal(t, step.currentState, testBcR.fsm.state.name)
|
|
|
|
|
|
|
|
oldNumStatusRequests := numStatusRequests
|
|
|
|
oldNumBlockRequests := numBlockRequests
|
|
|
|
|
|
|
|
fixBlockResponseEvStep(&step, testBcR)
|
|
|
|
fsmErr := sendEventToFSM(testBcR.fsm, step.event, step.data)
|
|
|
|
assert.Equal(t, step.errWanted, fsmErr)
|
|
|
|
|
|
|
|
if step.shouldSendStatusReq {
|
|
|
|
assert.Equal(t, oldNumStatusRequests+1, numStatusRequests)
|
|
|
|
} else {
|
|
|
|
assert.Equal(t, oldNumStatusRequests, numStatusRequests)
|
|
|
|
}
|
|
|
|
|
|
|
|
if step.blockReqIncreased {
|
|
|
|
assert.True(t, oldNumBlockRequests < numBlockRequests)
|
|
|
|
} else {
|
|
|
|
assert.Equal(t, oldNumBlockRequests, numBlockRequests)
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, height := range step.blocksAdded {
|
|
|
|
_, err := testBcR.fsm.pool.getBlockAndPeerAtHeight(height)
|
|
|
|
assert.Nil(t, err)
|
|
|
|
}
|
|
|
|
assert.Equal(t, step.expectedState, testBcR.fsm.state.name)
|
2019-04-17 22:41:28 -04:00
|
|
|
if step.expectedState == "finished" {
|
|
|
|
assert.True(t, testBcR.fsm.isCaughtUp())
|
|
|
|
}
|
2019-04-12 22:02:30 -04:00
|
|
|
}
|
2019-03-26 09:58:30 +01:00
|
|
|
|
2019-04-12 22:02:30 -04:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
2019-03-26 09:58:30 +01:00
|
|
|
|
2019-04-12 22:02:30 -04:00
|
|
|
func TestFSMBlockVerificationFailure(t *testing.T) {
|
|
|
|
tests := []struct {
|
|
|
|
name string
|
|
|
|
startingHeight int64
|
|
|
|
maxRequestsPerPeer int32
|
|
|
|
steps []fsmStepTestValues
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "block verification failure",
|
|
|
|
startingHeight: 1,
|
2019-04-12 23:32:21 -04:00
|
|
|
maxRequestsPerPeer: 3,
|
2019-04-12 22:02:30 -04:00
|
|
|
steps: []fsmStepTestValues{
|
|
|
|
// startFSMEv
|
2019-04-17 22:41:28 -04:00
|
|
|
makeStepStartFSMEv(),
|
2019-04-12 22:02:30 -04:00
|
|
|
// statusResponseEv from P1
|
2019-04-17 22:41:28 -04:00
|
|
|
makeStepStatusEv("waitForPeer", "waitForBlock", "P1", 3, nil),
|
2019-04-12 23:32:21 -04:00
|
|
|
|
2019-04-12 22:02:30 -04:00
|
|
|
// makeRequestEv
|
2019-04-17 22:41:28 -04:00
|
|
|
makeStepMakeRequestsEv("waitForBlock", "waitForBlock", maxNumPendingRequests),
|
2019-04-12 22:02:30 -04:00
|
|
|
// blockResponseEv for height 1
|
2019-04-17 22:41:28 -04:00
|
|
|
makeStepBlockRespEv("waitForBlock", "waitForBlock",
|
|
|
|
"P1", 1, []int64{}),
|
2019-04-12 22:02:30 -04:00
|
|
|
// blockResponseEv for height 2
|
2019-04-17 22:41:28 -04:00
|
|
|
makeStepBlockRespEv("waitForBlock", "waitForBlock",
|
|
|
|
"P1", 2, []int64{1}),
|
2019-04-12 22:02:30 -04:00
|
|
|
// blockResponseEv for height 3
|
2019-04-17 22:41:28 -04:00
|
|
|
makeStepBlockRespEv("waitForBlock", "waitForBlock",
|
|
|
|
"P1", 3, []int64{1, 2}),
|
2019-04-12 23:32:21 -04:00
|
|
|
|
|
|
|
// statusResponseEv from P2
|
2019-04-17 22:41:28 -04:00
|
|
|
makeStepStatusEv("waitForBlock", "waitForBlock", "P2", 3, nil),
|
2019-04-12 22:02:30 -04:00
|
|
|
|
|
|
|
// processedBlockEv with Error
|
2019-04-17 22:41:28 -04:00
|
|
|
makeStepProcessedBlockEv("waitForBlock", "waitForBlock", errBlockVerificationFailure),
|
2019-04-12 22:02:30 -04:00
|
|
|
|
|
|
|
// makeRequestEv
|
2019-04-17 22:41:28 -04:00
|
|
|
makeStepMakeRequestsEv("waitForBlock", "waitForBlock", maxNumPendingRequests),
|
|
|
|
makeStepBlockRespEv("waitForBlock", "waitForBlock",
|
|
|
|
"P2", 1, []int64{}),
|
2019-04-12 22:02:30 -04:00
|
|
|
// blockResponseEv for height 2
|
2019-04-28 22:03:26 -04:00
|
|
|
makeStepBlockRespEv("waitForBlock", "waitForBlock", "P2", 2, []int64{1}),
|
2019-04-17 22:41:28 -04:00
|
|
|
// blockResponseEv for height 3
|
2019-04-28 22:03:26 -04:00
|
|
|
makeStepBlockRespEv("waitForBlock", "waitForBlock", "P2", 3, []int64{1, 2}),
|
2019-04-12 22:02:30 -04:00
|
|
|
|
2019-04-17 22:41:28 -04:00
|
|
|
makeStepProcessedBlockEv("waitForBlock", "waitForBlock", nil),
|
|
|
|
makeStepProcessedBlockEv("waitForBlock", "finished", nil),
|
2019-04-12 22:02:30 -04:00
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
2019-03-26 09:58:30 +01:00
|
|
|
|
2019-04-12 22:02:30 -04:00
|
|
|
for _, tt := range tests {
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
// Create test reactor
|
|
|
|
testBcR := newTestReactor(tt.startingHeight)
|
|
|
|
resetTestValues()
|
2019-04-04 00:13:32 +02:00
|
|
|
|
2019-04-12 22:02:30 -04:00
|
|
|
if tt.maxRequestsPerPeer != 0 {
|
|
|
|
maxRequestsPerPeer = tt.maxRequestsPerPeer
|
2019-03-26 09:58:30 +01:00
|
|
|
}
|
|
|
|
|
2019-04-12 22:02:30 -04:00
|
|
|
for _, step := range tt.steps {
|
|
|
|
assert.Equal(t, step.currentState, testBcR.fsm.state.name)
|
|
|
|
|
|
|
|
oldNumStatusRequests := numStatusRequests
|
|
|
|
oldNumBlockRequests := numBlockRequests
|
|
|
|
|
|
|
|
var heightBefore int64
|
|
|
|
if step.event == processedBlockEv && step.data.err == errBlockVerificationFailure {
|
|
|
|
heightBefore = testBcR.fsm.pool.height
|
|
|
|
}
|
|
|
|
|
|
|
|
fsmErr := sendEventToFSM(testBcR.fsm, step.event, step.data)
|
|
|
|
assert.Equal(t, step.errWanted, fsmErr)
|
|
|
|
|
|
|
|
if step.shouldSendStatusReq {
|
|
|
|
assert.Equal(t, oldNumStatusRequests+1, numStatusRequests)
|
|
|
|
} else {
|
|
|
|
assert.Equal(t, oldNumStatusRequests, numStatusRequests)
|
|
|
|
}
|
|
|
|
|
|
|
|
if step.blockReqIncreased {
|
|
|
|
assert.True(t, oldNumBlockRequests < numBlockRequests)
|
|
|
|
} else {
|
|
|
|
assert.Equal(t, oldNumBlockRequests, numBlockRequests)
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, height := range step.blocksAdded {
|
|
|
|
_, err := testBcR.fsm.pool.getBlockAndPeerAtHeight(height)
|
|
|
|
assert.Nil(t, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if step.event == processedBlockEv && step.data.err == errBlockVerificationFailure {
|
|
|
|
heightAfter := testBcR.fsm.pool.height
|
|
|
|
assert.Equal(t, heightBefore, heightAfter)
|
|
|
|
firstAfter, err1 := testBcR.fsm.pool.getBlockAndPeerAtHeight(testBcR.fsm.pool.height)
|
|
|
|
secondAfter, err2 := testBcR.fsm.pool.getBlockAndPeerAtHeight(testBcR.fsm.pool.height + 1)
|
|
|
|
assert.NotNil(t, err1)
|
|
|
|
assert.NotNil(t, err2)
|
2019-04-28 22:03:26 -04:00
|
|
|
assert.Nil(t, firstAfter)
|
|
|
|
assert.Nil(t, secondAfter)
|
2019-04-12 22:02:30 -04:00
|
|
|
}
|
|
|
|
assert.Equal(t, step.expectedState, testBcR.fsm.state.name)
|
2019-04-17 22:41:28 -04:00
|
|
|
if step.expectedState == "finished" {
|
|
|
|
assert.True(t, testBcR.fsm.isCaughtUp())
|
|
|
|
}
|
2019-03-26 09:58:30 +01:00
|
|
|
}
|
2019-04-12 22:02:30 -04:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
2019-03-26 09:58:30 +01:00
|
|
|
|
2019-04-17 22:41:28 -04:00
|
|
|
func TestFSMBadBlockFromPeer(t *testing.T) {
|
2019-04-12 23:32:21 -04:00
|
|
|
tests := []struct {
|
|
|
|
name string
|
|
|
|
startingHeight int64
|
|
|
|
maxRequestsPerPeer int32
|
|
|
|
steps []fsmStepTestValues
|
|
|
|
}{
|
|
|
|
{
|
2019-04-17 22:41:28 -04:00
|
|
|
name: "block we haven't asked for",
|
|
|
|
startingHeight: 1,
|
|
|
|
maxRequestsPerPeer: 3,
|
|
|
|
steps: []fsmStepTestValues{
|
|
|
|
// startFSMEv
|
|
|
|
makeStepStartFSMEv(),
|
|
|
|
// statusResponseEv from P1
|
|
|
|
makeStepStatusEv("waitForPeer", "waitForBlock", "P1", 300, nil),
|
|
|
|
// makeRequestEv
|
|
|
|
makeStepMakeRequestsEv("waitForBlock", "waitForBlock", maxNumPendingRequests),
|
|
|
|
// blockResponseEv for height 100
|
|
|
|
makeStepBlockRespEvErrored("waitForBlock", "waitForBlock",
|
|
|
|
"P1", 100, []int64{}, errBadDataFromPeer, []p2p.ID{}),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "block we already have",
|
2019-04-12 23:32:21 -04:00
|
|
|
startingHeight: 1,
|
|
|
|
maxRequestsPerPeer: 3,
|
|
|
|
steps: []fsmStepTestValues{
|
|
|
|
// startFSMEv
|
2019-04-17 22:41:28 -04:00
|
|
|
makeStepStartFSMEv(),
|
|
|
|
// statusResponseEv from P1
|
|
|
|
makeStepStatusEv("waitForPeer", "waitForBlock", "P1", 100, nil),
|
|
|
|
|
|
|
|
// makeRequestEv
|
|
|
|
makeStepMakeRequestsEv("waitForBlock", "waitForBlock", maxNumPendingRequests),
|
|
|
|
makeStepBlockRespEv("waitForBlock", "waitForBlock",
|
|
|
|
"P1", 1, []int64{}),
|
|
|
|
|
|
|
|
// simulate error in this step. Since peer is removed together with block 1,
|
|
|
|
// the blocks present in the pool will be {}
|
|
|
|
makeStepBlockRespEvErrored("waitForBlock", "waitForBlock",
|
|
|
|
"P1", 1, []int64{}, errBadDataFromPeer, []p2p.ID{"P1"}),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "block from unknown peer",
|
|
|
|
startingHeight: 1,
|
|
|
|
maxRequestsPerPeer: 3,
|
|
|
|
steps: []fsmStepTestValues{
|
|
|
|
// startFSMEv
|
|
|
|
makeStepStartFSMEv(),
|
|
|
|
// statusResponseEv from P1
|
|
|
|
makeStepStatusEv("waitForPeer", "waitForBlock", "P1", 3, nil),
|
|
|
|
|
|
|
|
// makeRequestEv
|
|
|
|
makeStepMakeRequestsEv("waitForBlock", "waitForBlock", maxNumPendingRequests),
|
|
|
|
makeStepBlockRespEvErrored("waitForBlock", "waitForBlock",
|
|
|
|
"P2", 1, []int64{}, errBadDataFromPeer, []p2p.ID{"P2"}),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "block from wrong peer",
|
|
|
|
startingHeight: 1,
|
|
|
|
maxRequestsPerPeer: 3,
|
|
|
|
steps: []fsmStepTestValues{
|
|
|
|
// startFSMEv
|
|
|
|
makeStepStartFSMEv(),
|
|
|
|
// statusResponseEv from P1
|
|
|
|
makeStepStatusEv("waitForPeer", "waitForBlock", "P1", 3, nil),
|
|
|
|
// makeRequestEv
|
|
|
|
makeStepMakeRequestsEv("waitForBlock", "waitForBlock", maxNumPendingRequests),
|
|
|
|
|
|
|
|
// add another peer
|
|
|
|
makeStepStatusEv("waitForBlock", "waitForBlock", "P2", 3, nil),
|
|
|
|
|
|
|
|
makeStepBlockRespEvErrored("waitForBlock", "waitForBlock",
|
|
|
|
"P2", 1, []int64{}, errBadDataFromPeer, []p2p.ID{"P2"}),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2019-04-28 22:03:26 -04:00
|
|
|
for _, tt := range tests {
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
// Create test reactor
|
|
|
|
testBcR := newTestReactor(tt.startingHeight)
|
|
|
|
resetTestValues()
|
|
|
|
|
|
|
|
if tt.maxRequestsPerPeer != 0 {
|
|
|
|
maxRequestsPerPeer = tt.maxRequestsPerPeer
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, step := range tt.steps {
|
|
|
|
assert.Equal(t, step.currentState, testBcR.fsm.state.name)
|
|
|
|
|
|
|
|
oldNumStatusRequests := numStatusRequests
|
|
|
|
oldNumBlockRequests := numBlockRequests
|
|
|
|
|
|
|
|
fsmErr := sendEventToFSM(testBcR.fsm, step.event, step.data)
|
|
|
|
assert.Equal(t, step.errWanted, fsmErr)
|
|
|
|
|
|
|
|
if step.shouldSendStatusReq {
|
|
|
|
assert.Equal(t, oldNumStatusRequests+1, numStatusRequests)
|
|
|
|
} else {
|
|
|
|
assert.Equal(t, oldNumStatusRequests, numStatusRequests)
|
|
|
|
}
|
|
|
|
|
|
|
|
if step.blockReqIncreased {
|
|
|
|
assert.True(t, oldNumBlockRequests < numBlockRequests)
|
|
|
|
} else {
|
|
|
|
assert.Equal(t, oldNumBlockRequests, numBlockRequests)
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, height := range step.blocksAdded {
|
|
|
|
_, err := testBcR.fsm.pool.getBlockAndPeerAtHeight(height)
|
|
|
|
assert.Nil(t, err)
|
|
|
|
}
|
|
|
|
assert.Equal(t, step.expectedState, testBcR.fsm.state.name)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-07 21:06:43 -04:00
|
|
|
func TestFSMBlockAtCurrentHeightDoesNotArriveInTime(t *testing.T) {
|
2019-04-28 22:03:26 -04:00
|
|
|
tests := []struct {
|
|
|
|
name string
|
|
|
|
startingHeight int64
|
|
|
|
maxRequestsPerPeer int32
|
|
|
|
steps []fsmStepTestValues
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "block at current height undelivered",
|
|
|
|
startingHeight: 1,
|
|
|
|
maxRequestsPerPeer: 3,
|
|
|
|
steps: []fsmStepTestValues{
|
|
|
|
// startFSMEv
|
|
|
|
makeStepStartFSMEv(),
|
|
|
|
// statusResponseEv from P1
|
|
|
|
makeStepStatusEv("waitForPeer", "waitForBlock", "P1", 3, nil),
|
|
|
|
// make some requests
|
|
|
|
makeStepMakeRequestsEv("waitForBlock", "waitForBlock", maxNumPendingRequests),
|
|
|
|
// block received for height 1
|
|
|
|
makeStepBlockRespEv("waitForBlock", "waitForBlock",
|
|
|
|
"P1", 1, []int64{}),
|
|
|
|
// block recevied for height 2
|
|
|
|
makeStepBlockRespEv("waitForBlock", "waitForBlock",
|
|
|
|
"P1", 2, []int64{1}),
|
|
|
|
// processed block at height 1
|
|
|
|
makeStepProcessedBlockEv("waitForBlock", "waitForBlock", nil),
|
|
|
|
|
|
|
|
// add peer P2
|
|
|
|
makeStepStatusEv("waitForBlock", "waitForBlock", "P2", 3, nil),
|
|
|
|
// timeout on block at height 3
|
|
|
|
makeStepStateTimeoutEv("waitForBlock", "waitForBlock", "waitForBlock", errNoPeerResponse),
|
|
|
|
|
|
|
|
// make some requests (includes redo-s for blocks 2 and 3)
|
|
|
|
makeStepMakeRequestsEv("waitForBlock", "waitForBlock", maxNumPendingRequests),
|
|
|
|
// block received for height 2 from P2
|
|
|
|
makeStepBlockRespEv("waitForBlock", "waitForBlock", "P2", 2, []int64{}),
|
|
|
|
// block received for height 3 from P2
|
|
|
|
makeStepBlockRespEv("waitForBlock", "waitForBlock", "P2", 3, []int64{2}),
|
|
|
|
makeStepProcessedBlockEv("waitForBlock", "finished", nil),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
2019-04-17 22:41:28 -04:00
|
|
|
for _, tt := range tests {
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
// Create test reactor
|
|
|
|
testBcR := newTestReactor(tt.startingHeight)
|
|
|
|
resetTestValues()
|
|
|
|
|
|
|
|
if tt.maxRequestsPerPeer != 0 {
|
|
|
|
maxRequestsPerPeer = tt.maxRequestsPerPeer
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, step := range tt.steps {
|
|
|
|
assert.Equal(t, step.currentState, testBcR.fsm.state.name)
|
|
|
|
|
|
|
|
oldNumStatusRequests := numStatusRequests
|
|
|
|
oldNumBlockRequests := numBlockRequests
|
|
|
|
|
|
|
|
fsmErr := sendEventToFSM(testBcR.fsm, step.event, step.data)
|
|
|
|
assert.Equal(t, step.errWanted, fsmErr)
|
|
|
|
|
|
|
|
if step.shouldSendStatusReq {
|
|
|
|
assert.Equal(t, oldNumStatusRequests+1, numStatusRequests)
|
|
|
|
} else {
|
|
|
|
assert.Equal(t, oldNumStatusRequests, numStatusRequests)
|
|
|
|
}
|
|
|
|
|
|
|
|
if step.blockReqIncreased {
|
|
|
|
assert.True(t, oldNumBlockRequests < numBlockRequests)
|
|
|
|
} else {
|
|
|
|
assert.Equal(t, oldNumBlockRequests, numBlockRequests)
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, height := range step.blocksAdded {
|
|
|
|
_, err := testBcR.fsm.pool.getBlockAndPeerAtHeight(height)
|
|
|
|
assert.Nil(t, err)
|
|
|
|
}
|
|
|
|
assert.Equal(t, step.expectedState, testBcR.fsm.state.name)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-17 22:48:49 -04:00
|
|
|
func TestFSMPeerRelatedErrors(t *testing.T) {
|
2019-04-17 22:41:28 -04:00
|
|
|
tests := []struct {
|
|
|
|
name string
|
|
|
|
startingHeight int64
|
|
|
|
maxRequestsPerPeer int32
|
|
|
|
steps []fsmStepTestValues
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "peer remove event with no blocks",
|
|
|
|
startingHeight: 1,
|
|
|
|
steps: []fsmStepTestValues{
|
|
|
|
// startFSMEv
|
|
|
|
makeStepStartFSMEv(),
|
2019-04-12 23:32:21 -04:00
|
|
|
// statusResponseEv from P1
|
2019-04-17 22:41:28 -04:00
|
|
|
makeStepStatusEv("waitForPeer", "waitForBlock", "P1", 3, nil),
|
2019-04-12 23:32:21 -04:00
|
|
|
// statusResponseEv from P2
|
2019-04-17 22:41:28 -04:00
|
|
|
makeStepStatusEv("waitForBlock", "waitForBlock", "P2", 3, nil),
|
2019-04-12 23:32:21 -04:00
|
|
|
// statusResponseEv from P3
|
2019-04-17 22:41:28 -04:00
|
|
|
makeStepStatusEv("waitForBlock", "waitForBlock", "P3", 3, nil),
|
|
|
|
makeStepPeerRemoveEv("waitForBlock", "waitForBlock", "P2", errSwitchRemovesPeer, []p2p.ID{"P2"}),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "new peer with low height while in waitForPeer state",
|
|
|
|
startingHeight: 100,
|
|
|
|
steps: []fsmStepTestValues{
|
|
|
|
// startFSMEv
|
|
|
|
makeStepStartFSMEv(),
|
|
|
|
// statusResponseEv from P1
|
|
|
|
makeStepStatusEv("waitForPeer", "waitForPeer", "P1", 3, errPeerTooShort),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "new peer added with low height while in waitForBlock state",
|
|
|
|
startingHeight: 100,
|
|
|
|
steps: []fsmStepTestValues{
|
|
|
|
// startFSMEv
|
|
|
|
makeStepStartFSMEv(),
|
|
|
|
// statusResponseEv from P1
|
|
|
|
makeStepStatusEv("waitForPeer", "waitForBlock", "P1", 200, nil),
|
|
|
|
// statusResponseEv from P2
|
|
|
|
makeStepStatusEv("waitForBlock", "waitForBlock", "P2", 3, errPeerTooShort),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "only peer updated with low height while in waitForBlock state",
|
|
|
|
startingHeight: 100,
|
|
|
|
steps: []fsmStepTestValues{
|
|
|
|
// startFSMEv
|
|
|
|
makeStepStartFSMEv(),
|
|
|
|
// statusResponseEv from P1
|
|
|
|
makeStepStatusEv("waitForPeer", "waitForBlock", "P1", 200, nil),
|
|
|
|
// statusResponseEv from P1
|
|
|
|
makeStepStatusEv("waitForBlock", "waitForPeer", "P1", 3, errPeerTooShort),
|
2019-04-12 23:32:21 -04:00
|
|
|
},
|
|
|
|
},
|
2019-04-17 22:41:28 -04:00
|
|
|
{
|
|
|
|
name: "peer remove event with no blocks",
|
|
|
|
startingHeight: 9999999,
|
|
|
|
maxRequestsPerPeer: 3,
|
|
|
|
steps: []fsmStepTestValues{
|
|
|
|
// startFSMEv
|
|
|
|
makeStepStartFSMEv(),
|
|
|
|
// statusResponseEv from P1
|
|
|
|
makeStepStatusEv("waitForPeer", "waitForBlock", "P1", 20000000, nil),
|
|
|
|
makeStepMakeRequestsEvErrored("waitForBlock", "waitForBlock",
|
|
|
|
maxNumPendingRequests, nil, []p2p.ID{"P1"}),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tt := range tests {
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
// Create test reactor
|
|
|
|
testBcR := newTestReactor(tt.startingHeight)
|
|
|
|
resetTestValues()
|
|
|
|
|
|
|
|
if tt.maxRequestsPerPeer != 0 {
|
|
|
|
maxRequestsPerPeer = tt.maxRequestsPerPeer
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, step := range tt.steps {
|
|
|
|
assert.Equal(t, step.currentState, testBcR.fsm.state.name)
|
|
|
|
|
|
|
|
oldNumStatusRequests := numStatusRequests
|
|
|
|
|
|
|
|
fsmErr := sendEventToFSM(testBcR.fsm, step.event, step.data)
|
|
|
|
assert.Equal(t, step.errWanted, fsmErr)
|
|
|
|
|
|
|
|
if step.shouldSendStatusReq {
|
|
|
|
assert.Equal(t, oldNumStatusRequests+1, numStatusRequests)
|
|
|
|
} else {
|
|
|
|
assert.Equal(t, oldNumStatusRequests, numStatusRequests)
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, peerID := range step.peersNotInPool {
|
|
|
|
_, ok := testBcR.fsm.pool.peers[peerID]
|
|
|
|
assert.False(t, ok)
|
|
|
|
}
|
|
|
|
assert.Equal(t, step.expectedState, testBcR.fsm.state.name)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestFSMStopFSM(t *testing.T) {
|
|
|
|
tests := []struct {
|
|
|
|
name string
|
|
|
|
startingHeight int64
|
|
|
|
steps []fsmStepTestValues
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "stopFSMEv in unknown",
|
|
|
|
steps: []fsmStepTestValues{
|
|
|
|
// startFSMEv
|
|
|
|
makeStepStopFSMEv("unknown", "finished"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "stopFSMEv in waitForPeer",
|
|
|
|
startingHeight: 1,
|
|
|
|
steps: []fsmStepTestValues{
|
|
|
|
// startFSMEv
|
|
|
|
makeStepStartFSMEv(),
|
|
|
|
// stopFSMEv
|
|
|
|
makeStepStopFSMEv("waitForPeer", "finished"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "stopFSMEv in waitForBlock",
|
|
|
|
startingHeight: 1,
|
|
|
|
steps: []fsmStepTestValues{
|
|
|
|
// startFSMEv
|
|
|
|
makeStepStartFSMEv(),
|
|
|
|
// statusResponseEv from P1
|
|
|
|
makeStepStatusEv("waitForPeer", "waitForBlock", "P1", 3, nil),
|
|
|
|
makeStepStopFSMEv("waitForBlock", "finished"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tt := range tests {
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
// Create test reactor
|
|
|
|
testBcR := newTestReactor(tt.startingHeight)
|
|
|
|
resetTestValues()
|
|
|
|
|
|
|
|
for _, step := range tt.steps {
|
|
|
|
assert.Equal(t, step.currentState, testBcR.fsm.state.name)
|
|
|
|
fsmErr := sendEventToFSM(testBcR.fsm, step.event, step.data)
|
|
|
|
assert.Equal(t, step.errWanted, fsmErr)
|
|
|
|
assert.Equal(t, step.expectedState, testBcR.fsm.state.name)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestFSMUnknownElements(t *testing.T) {
|
|
|
|
tests := []struct {
|
|
|
|
name string
|
|
|
|
startingHeight int64
|
|
|
|
steps []fsmStepTestValues
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "unknown event for state unknown",
|
|
|
|
steps: []fsmStepTestValues{
|
|
|
|
makeStepUnknownFSMEv("unknown"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "unknown event for state waitForPeer",
|
|
|
|
steps: []fsmStepTestValues{
|
|
|
|
makeStepStartFSMEv(),
|
|
|
|
makeStepUnknownFSMEv("waitForPeer"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "unknown event for state waitForBlock",
|
|
|
|
startingHeight: 1,
|
|
|
|
steps: []fsmStepTestValues{
|
|
|
|
makeStepStartFSMEv(),
|
|
|
|
makeStepStatusEv("waitForPeer", "waitForBlock", "P1", 3, nil),
|
|
|
|
makeStepUnknownFSMEv("waitForBlock"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tt := range tests {
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
// Create test reactor
|
|
|
|
testBcR := newTestReactor(tt.startingHeight)
|
|
|
|
resetTestValues()
|
|
|
|
|
|
|
|
for _, step := range tt.steps {
|
|
|
|
assert.Equal(t, step.currentState, testBcR.fsm.state.name)
|
|
|
|
fsmErr := sendEventToFSM(testBcR.fsm, step.event, step.data)
|
|
|
|
assert.Equal(t, step.errWanted, fsmErr)
|
|
|
|
assert.Equal(t, step.expectedState, testBcR.fsm.state.name)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestFSMPeerStateTimeoutEvent(t *testing.T) {
|
|
|
|
tests := []struct {
|
|
|
|
name string
|
|
|
|
startingHeight int64
|
|
|
|
maxRequestsPerPeer int32
|
|
|
|
steps []fsmStepTestValues
|
|
|
|
}{
|
|
|
|
{
|
2019-05-07 21:06:43 -04:00
|
|
|
name: "timeout event for state waitForPeer while in state waitForPeer",
|
2019-04-17 22:41:28 -04:00
|
|
|
startingHeight: 1,
|
|
|
|
maxRequestsPerPeer: 3,
|
|
|
|
steps: []fsmStepTestValues{
|
|
|
|
makeStepStartFSMEv(),
|
|
|
|
makeStepStateTimeoutEv("waitForPeer", "finished", "waitForPeer", errNoPeerResponse),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
2019-05-07 21:06:43 -04:00
|
|
|
name: "timeout event for state waitForPeer while in a state != waitForPeer",
|
2019-04-17 22:41:28 -04:00
|
|
|
startingHeight: 1,
|
|
|
|
maxRequestsPerPeer: 3,
|
|
|
|
steps: []fsmStepTestValues{
|
|
|
|
makeStepStartFSMEv(),
|
|
|
|
makeStepStateTimeoutEv("waitForPeer", "waitForPeer", "waitForBlock", errTimeoutEventWrongState),
|
|
|
|
},
|
|
|
|
},
|
2019-05-07 21:06:43 -04:00
|
|
|
{
|
|
|
|
name: "timeout event for state waitForBlock while in state waitForBlock ",
|
|
|
|
startingHeight: 1,
|
|
|
|
maxRequestsPerPeer: 3,
|
|
|
|
steps: []fsmStepTestValues{
|
|
|
|
makeStepStartFSMEv(),
|
|
|
|
makeStepStatusEv("waitForPeer", "waitForBlock", "P1", 3, nil),
|
|
|
|
makeStepMakeRequestsEv("waitForBlock", "waitForBlock", maxNumPendingRequests),
|
|
|
|
makeStepStateTimeoutEv("waitForBlock", "waitForPeer", "waitForBlock", errNoPeerResponse),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "timeout event for state waitForBlock while in a state != waitForBlock",
|
|
|
|
startingHeight: 1,
|
|
|
|
maxRequestsPerPeer: 3,
|
|
|
|
steps: []fsmStepTestValues{
|
|
|
|
makeStepStartFSMEv(),
|
|
|
|
makeStepStatusEv("waitForPeer", "waitForBlock", "P1", 3, nil),
|
|
|
|
makeStepMakeRequestsEv("waitForBlock", "waitForBlock", maxNumPendingRequests),
|
|
|
|
makeStepStateTimeoutEv("waitForBlock", "waitForBlock", "waitForPeer", errTimeoutEventWrongState),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "timeout event for state waitForBlock with multiple peers",
|
|
|
|
startingHeight: 1,
|
|
|
|
maxRequestsPerPeer: 3,
|
|
|
|
steps: []fsmStepTestValues{
|
|
|
|
makeStepStartFSMEv(),
|
|
|
|
makeStepStatusEv("waitForPeer", "waitForBlock", "P1", 3, nil),
|
|
|
|
makeStepMakeRequestsEv("waitForBlock", "waitForBlock", maxNumPendingRequests),
|
|
|
|
makeStepStatusEv("waitForBlock", "waitForBlock", "P2", 3, nil),
|
|
|
|
makeStepStateTimeoutEv("waitForBlock", "waitForBlock", "waitForBlock", errNoPeerResponse),
|
|
|
|
},
|
|
|
|
},
|
2019-04-17 22:41:28 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, tt := range tests {
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
// Create test reactor
|
|
|
|
testBcR := newTestReactor(tt.startingHeight)
|
|
|
|
resetTestValues()
|
|
|
|
|
|
|
|
for _, step := range tt.steps {
|
|
|
|
assert.Equal(t, step.currentState, testBcR.fsm.state.name)
|
|
|
|
fsmErr := sendEventToFSM(testBcR.fsm, step.event, step.data)
|
|
|
|
assert.Equal(t, step.errWanted, fsmErr)
|
|
|
|
assert.Equal(t, step.expectedState, testBcR.fsm.state.name)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
2019-04-12 22:02:30 -04:00
|
|
|
|
|
|
|
type testFields struct {
|
|
|
|
name string
|
|
|
|
startingHeight int64
|
|
|
|
maxRequestsPerPeer int32
|
|
|
|
maxPendingRequests int32
|
|
|
|
steps []fsmStepTestValues
|
|
|
|
}
|
|
|
|
|
|
|
|
func makeCorrectTransitionSequence(startingHeight int64, numBlocks int64, numPeers int, randomPeerHeights bool,
|
|
|
|
maxRequestsPerPeer int32, maxPendingRequests int32) testFields {
|
|
|
|
|
2019-04-15 23:26:07 -04:00
|
|
|
// Generate numPeers peers with random or numBlocks heights according to the randomPeerHeights flag.
|
2019-04-12 22:02:30 -04:00
|
|
|
peerHeights := make([]int64, numPeers)
|
|
|
|
for i := 0; i < numPeers; i++ {
|
|
|
|
if i == 0 {
|
|
|
|
peerHeights[0] = numBlocks
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if randomPeerHeights {
|
|
|
|
peerHeights[i] = int64(cmn.MaxInt(cmn.RandIntn(int(numBlocks)), int(startingHeight)+1))
|
|
|
|
} else {
|
|
|
|
peerHeights[i] = numBlocks
|
2019-03-26 09:58:30 +01:00
|
|
|
}
|
|
|
|
}
|
2019-04-12 22:02:30 -04:00
|
|
|
|
|
|
|
// Approximate the slice capacity to save time for appends.
|
|
|
|
testSteps := make([]fsmStepTestValues, 0, 3*numBlocks+int64(numPeers))
|
|
|
|
|
|
|
|
testName := fmt.Sprintf("%v-blocks %v-startingHeight %v-peers %v-maxRequestsPerPeer %v-maxNumPendingRequests",
|
|
|
|
numBlocks, startingHeight, numPeers, maxRequestsPerPeer, maxPendingRequests)
|
|
|
|
|
|
|
|
// Add startFSMEv step.
|
2019-04-17 22:41:28 -04:00
|
|
|
testSteps = append(testSteps, makeStepStartFSMEv())
|
2019-04-12 22:02:30 -04:00
|
|
|
|
|
|
|
// For each peer, add statusResponseEv step.
|
|
|
|
for i := 0; i < numPeers; i++ {
|
|
|
|
peerName := fmt.Sprintf("P%d", i)
|
|
|
|
if i == 0 {
|
2019-04-17 22:41:28 -04:00
|
|
|
testSteps = append(
|
|
|
|
testSteps,
|
|
|
|
makeStepStatusEv("waitForPeer", "waitForBlock", p2p.ID(peerName), peerHeights[i], nil))
|
2019-04-12 22:02:30 -04:00
|
|
|
} else {
|
2019-04-17 22:41:28 -04:00
|
|
|
testSteps = append(testSteps,
|
|
|
|
makeStepStatusEv("waitForBlock", "waitForBlock", p2p.ID(peerName), peerHeights[i], nil))
|
2019-04-12 22:02:30 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
height := startingHeight
|
|
|
|
numBlocksReceived := 0
|
|
|
|
prevBlocks := make([]int64, 0, maxPendingRequests)
|
|
|
|
|
|
|
|
forLoop:
|
|
|
|
for i := 0; i < int(numBlocks); i++ {
|
|
|
|
|
2019-04-15 23:26:07 -04:00
|
|
|
// Add the makeRequestEv step periodically.
|
2019-04-12 22:02:30 -04:00
|
|
|
if i%int(maxRequestsPerPeer) == 0 {
|
2019-04-17 22:41:28 -04:00
|
|
|
testSteps = append(
|
|
|
|
testSteps,
|
|
|
|
makeStepMakeRequestsEv("waitForBlock", "waitForBlock", maxNumPendingRequests),
|
|
|
|
)
|
2019-04-12 22:02:30 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// Add the blockRespEv step
|
2019-04-17 22:41:28 -04:00
|
|
|
testSteps = append(
|
|
|
|
testSteps,
|
|
|
|
makeStepBlockRespEv("waitForBlock", "waitForBlock",
|
|
|
|
"P0", height, prevBlocks))
|
2019-04-12 22:02:30 -04:00
|
|
|
prevBlocks = append(prevBlocks, height)
|
|
|
|
height++
|
|
|
|
numBlocksReceived++
|
|
|
|
|
2019-04-15 23:26:07 -04:00
|
|
|
// Add the processedBlockEv step periodically.
|
2019-04-12 22:02:30 -04:00
|
|
|
if numBlocksReceived >= int(maxRequestsPerPeer) || height >= numBlocks {
|
|
|
|
for j := int(height) - numBlocksReceived; j < int(height); j++ {
|
2019-04-15 23:26:07 -04:00
|
|
|
if j >= int(numBlocks) {
|
2019-04-12 22:02:30 -04:00
|
|
|
// This is the last block that is processed, we should be in "finished" state.
|
2019-04-17 22:41:28 -04:00
|
|
|
testSteps = append(
|
|
|
|
testSteps,
|
|
|
|
makeStepProcessedBlockEv("waitForBlock", "finished", nil))
|
2019-04-12 22:02:30 -04:00
|
|
|
break forLoop
|
|
|
|
}
|
2019-04-17 22:41:28 -04:00
|
|
|
testSteps = append(
|
|
|
|
testSteps,
|
|
|
|
makeStepProcessedBlockEv("waitForBlock", "waitForBlock", nil))
|
2019-04-12 22:02:30 -04:00
|
|
|
}
|
|
|
|
numBlocksReceived = 0
|
|
|
|
prevBlocks = make([]int64, 0, maxPendingRequests)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return testFields{
|
|
|
|
name: testName,
|
|
|
|
startingHeight: startingHeight,
|
|
|
|
maxRequestsPerPeer: maxRequestsPerPeer,
|
2019-04-15 23:26:07 -04:00
|
|
|
maxPendingRequests: maxPendingRequests,
|
2019-04-12 22:02:30 -04:00
|
|
|
steps: testSteps,
|
|
|
|
}
|
2019-03-26 09:58:30 +01:00
|
|
|
}
|
|
|
|
|
2019-04-17 22:41:28 -04:00
|
|
|
const (
|
|
|
|
maxStartingHeightTest = 100
|
|
|
|
maxRequestsPerPeerTest = 20
|
|
|
|
maxTotalPendingRequestsTest = 600
|
|
|
|
maxNumPeersTest = 1000
|
|
|
|
maxNumBlocksInChainTest = 10000 //should be smaller than 9999999
|
|
|
|
)
|
|
|
|
|
2019-04-12 22:02:30 -04:00
|
|
|
func makeCorrectTransitionSequenceWithRandomParameters() testFields {
|
2019-04-15 23:26:07 -04:00
|
|
|
// Generate a starting height for fast sync.
|
2019-04-12 22:02:30 -04:00
|
|
|
startingHeight := int64(cmn.RandIntn(maxStartingHeightTest) + 1)
|
2019-03-26 09:58:30 +01:00
|
|
|
|
2019-04-15 23:26:07 -04:00
|
|
|
// Generate the number of requests per peer.
|
2019-04-12 22:02:30 -04:00
|
|
|
maxRequestsPerPeer := int32(cmn.RandIntn(maxRequestsPerPeerTest) + 1)
|
|
|
|
|
2019-04-15 23:26:07 -04:00
|
|
|
// Generate the maximum number of total pending requests, >= maxRequestsPerPeer.
|
2019-04-14 22:53:24 -04:00
|
|
|
maxPendingRequests := int32(cmn.RandIntn(maxTotalPendingRequestsTest-int(maxRequestsPerPeer))) + maxRequestsPerPeer
|
2019-04-12 22:02:30 -04:00
|
|
|
|
2019-04-15 23:26:07 -04:00
|
|
|
// Generate the number of blocks to be synced.
|
2019-04-12 22:02:30 -04:00
|
|
|
numBlocks := int64(cmn.RandIntn(maxNumBlocksInChainTest)) + startingHeight
|
2019-03-26 09:58:30 +01:00
|
|
|
|
2019-04-15 23:26:07 -04:00
|
|
|
// Generate a number of peers.
|
2019-04-12 22:02:30 -04:00
|
|
|
numPeers := cmn.RandIntn(maxNumPeersTest) + 1
|
|
|
|
|
|
|
|
return makeCorrectTransitionSequence(startingHeight, numBlocks, numPeers, true, maxRequestsPerPeer, maxPendingRequests)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestFSMCorrectTransitionSequences(t *testing.T) {
|
|
|
|
|
|
|
|
tests := []testFields{
|
|
|
|
makeCorrectTransitionSequence(1, 100, 10, true, 10, 40),
|
|
|
|
makeCorrectTransitionSequenceWithRandomParameters(),
|
2019-03-26 09:58:30 +01:00
|
|
|
}
|
|
|
|
|
2019-04-12 22:02:30 -04:00
|
|
|
for _, tt := range tests {
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
// Create test reactor
|
|
|
|
testBcR := newTestReactor(tt.startingHeight)
|
|
|
|
resetTestValues()
|
2019-03-26 09:58:30 +01:00
|
|
|
|
2019-04-12 22:02:30 -04:00
|
|
|
if tt.maxRequestsPerPeer != 0 {
|
|
|
|
maxRequestsPerPeer = tt.maxRequestsPerPeer
|
|
|
|
}
|
2019-03-26 09:58:30 +01:00
|
|
|
|
2019-04-12 22:02:30 -04:00
|
|
|
for _, step := range tt.steps {
|
|
|
|
assert.Equal(t, step.currentState, testBcR.fsm.state.name)
|
|
|
|
|
|
|
|
oldNumStatusRequests := numStatusRequests
|
|
|
|
fixBlockResponseEvStep(&step, testBcR)
|
2019-04-15 23:26:07 -04:00
|
|
|
if !shouldApplyProcessedBlockEvStep(&step, testBcR) {
|
|
|
|
continue
|
|
|
|
}
|
2019-04-12 22:02:30 -04:00
|
|
|
|
|
|
|
fsmErr := sendEventToFSM(testBcR.fsm, step.event, step.data)
|
|
|
|
assert.Equal(t, step.errWanted, fsmErr)
|
|
|
|
|
|
|
|
if step.shouldSendStatusReq {
|
|
|
|
assert.Equal(t, oldNumStatusRequests+1, numStatusRequests)
|
|
|
|
} else {
|
|
|
|
assert.Equal(t, oldNumStatusRequests, numStatusRequests)
|
|
|
|
}
|
|
|
|
|
|
|
|
assert.Equal(t, step.expectedState, testBcR.fsm.state.name)
|
2019-04-17 22:41:28 -04:00
|
|
|
if step.expectedState == "finished" {
|
|
|
|
assert.True(t, testBcR.fsm.isCaughtUp())
|
|
|
|
}
|
2019-04-12 22:02:30 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
})
|
2019-04-04 00:13:32 +02:00
|
|
|
}
|
2019-03-26 09:58:30 +01:00
|
|
|
}
|
|
|
|
|
2019-04-14 22:53:24 -04:00
|
|
|
func TestFSMPeerTimeout(t *testing.T) {
|
2019-04-04 00:13:32 +02:00
|
|
|
maxRequestsPerPeer = 2
|
2019-03-26 09:58:30 +01:00
|
|
|
resetTestValues()
|
|
|
|
peerTimeout = 20 * time.Millisecond
|
|
|
|
// Create and start the FSM
|
2019-04-12 22:02:30 -04:00
|
|
|
testBcR := newTestReactor(1)
|
2019-03-26 14:51:37 +01:00
|
|
|
fsm := testBcR.fsm
|
2019-03-26 09:58:30 +01:00
|
|
|
fsm.start()
|
|
|
|
|
|
|
|
// Check that FSM sends a status request message
|
|
|
|
time.Sleep(time.Millisecond)
|
|
|
|
assert.Equal(t, 1, numStatusRequests)
|
|
|
|
|
|
|
|
// Send a status response message to FSM
|
2019-04-12 23:32:21 -04:00
|
|
|
peerID := p2p.ID("P1")
|
2019-03-26 09:58:30 +01:00
|
|
|
sendStatusResponse(fsm, peerID, 10)
|
|
|
|
time.Sleep(5 * time.Millisecond)
|
|
|
|
|
2019-05-07 21:06:43 -04:00
|
|
|
if err := fsm.handle(&bcReactorMessage{
|
2019-04-04 00:13:32 +02:00
|
|
|
event: makeRequestsEv,
|
|
|
|
data: bReactorEventData{maxNumRequests: maxNumPendingRequests}}); err != nil {
|
|
|
|
}
|
2019-03-26 09:58:30 +01:00
|
|
|
// Check that FSM sends a block request message and...
|
2019-04-04 00:13:32 +02:00
|
|
|
assert.Equal(t, maxRequestsPerPeer, numBlockRequests)
|
2019-03-26 09:58:30 +01:00
|
|
|
// ... the block request has the expected height and peer
|
2019-04-04 00:13:32 +02:00
|
|
|
assert.Equal(t, maxRequestsPerPeer, int32(len(fsm.pool.peers[peerID].blocks)))
|
2019-03-26 09:58:30 +01:00
|
|
|
assert.Equal(t, peerID, lastBlockRequest.peerID)
|
|
|
|
|
|
|
|
// let FSM timeout on the block response message
|
|
|
|
time.Sleep(100 * time.Millisecond)
|
2019-04-17 22:41:28 -04:00
|
|
|
testMutex.Lock()
|
|
|
|
defer testMutex.Unlock()
|
2019-04-12 23:32:21 -04:00
|
|
|
assert.Equal(t, peerID, lastPeerError.peerID)
|
|
|
|
assert.Equal(t, errNoPeerResponse, lastPeerError.err)
|
|
|
|
|
|
|
|
peerTimeout = 15 * time.Second
|
2019-04-15 23:26:07 -04:00
|
|
|
maxRequestsPerPeer = 40
|
2019-03-26 09:58:30 +01:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// ----------------------------------------
|
|
|
|
// implementation for the test reactor APIs
|
|
|
|
|
|
|
|
func (testR *testReactor) sendPeerError(err error, peerID p2p.ID) {
|
2019-04-17 22:41:28 -04:00
|
|
|
testMutex.Lock()
|
|
|
|
defer testMutex.Unlock()
|
2019-03-26 09:58:30 +01:00
|
|
|
testR.logger.Info("Reactor received sendPeerError call from FSM", "peer", peerID, "err", err)
|
|
|
|
lastPeerError.peerID = peerID
|
|
|
|
lastPeerError.err = err
|
|
|
|
}
|
|
|
|
|
2019-04-04 00:13:32 +02:00
|
|
|
func (testR *testReactor) sendStatusRequest() {
|
2019-03-26 09:58:30 +01:00
|
|
|
testR.logger.Info("Reactor received sendStatusRequest call from FSM")
|
|
|
|
numStatusRequests++
|
|
|
|
}
|
|
|
|
|
|
|
|
func (testR *testReactor) sendBlockRequest(peerID p2p.ID, height int64) error {
|
|
|
|
testR.logger.Info("Reactor received sendBlockRequest call from FSM", "peer", peerID, "height", height)
|
|
|
|
numBlockRequests++
|
|
|
|
lastBlockRequest.peerID = peerID
|
|
|
|
lastBlockRequest.height = height
|
2019-04-17 22:41:28 -04:00
|
|
|
if height == 9999999 {
|
|
|
|
// simulate switch does not have peer
|
|
|
|
return errNilPeerForBlockRequest
|
|
|
|
}
|
2019-03-26 09:58:30 +01:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-04-14 22:53:24 -04:00
|
|
|
func (testR *testReactor) resetStateTimer(name string, timer **time.Timer, timeout time.Duration) {
|
2019-03-26 09:58:30 +01:00
|
|
|
testR.logger.Info("Reactor received resetStateTimer call from FSM", "state", name, "timeout", timeout)
|
|
|
|
if _, ok := stateTimerStarts[name]; !ok {
|
|
|
|
stateTimerStarts[name] = 1
|
|
|
|
} else {
|
|
|
|
stateTimerStarts[name]++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ----------------------------------------
|
|
|
|
|
|
|
|
// -------------------------------------------------------
|
|
|
|
// helper functions for tests to simulate different events
|
|
|
|
func sendStatusResponse(fsm *bReactorFSM, peerID p2p.ID, height int64) {
|
|
|
|
msgBytes := makeStatusResponseMessage(height)
|
2019-04-30 23:21:43 -04:00
|
|
|
|
2019-05-07 21:06:43 -04:00
|
|
|
_ = fsm.handle(&bcReactorMessage{
|
2019-03-26 09:58:30 +01:00
|
|
|
event: statusResponseEv,
|
|
|
|
data: bReactorEventData{
|
|
|
|
peerId: peerID,
|
|
|
|
height: height,
|
|
|
|
length: len(msgBytes),
|
|
|
|
},
|
2019-04-30 23:21:43 -04:00
|
|
|
})
|
2019-03-26 09:58:30 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// -------------------------------------------------------
|
|
|
|
|
|
|
|
// ----------------------------------------------------
|
|
|
|
// helper functions to make blockchain reactor messages
|
|
|
|
func makeStatusResponseMessage(height int64) []byte {
|
|
|
|
msgBytes := cdc.MustMarshalBinaryBare(&bcStatusResponseMessage{height})
|
|
|
|
return msgBytes
|
|
|
|
}
|
|
|
|
|
|
|
|
// ----------------------------------------------------
|