tendermint/blockchain_new/reactor_fsm_test.go

288 lines
7.7 KiB
Go
Raw Normal View History

2019-03-26 09:58:30 +01:00
package blockchain_new
import (
"github.com/stretchr/testify/assert"
cmn "github.com/tendermint/tendermint/libs/common"
dbm "github.com/tendermint/tendermint/libs/db"
"github.com/tendermint/tendermint/libs/log"
"github.com/tendermint/tendermint/p2p"
"github.com/tendermint/tendermint/types"
"testing"
"time"
)
var (
failSendStatusRequest bool
failSendBlockRequest bool
numStatusRequests int
numBlockRequests int
)
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)
failSendBlockRequest = false
failSendStatusRequest = false
numStatusRequests = 0
numBlockRequests = 0
lastBlockRequest.peerID = ""
lastBlockRequest.height = 0
lastPeerError.peerID = ""
lastPeerError.err = nil
}
type fsmStepTestValues struct {
currentState string
event bReactorEvent
expectedState string
// input
failStatusReq bool
shouldSendStatusReq bool
failBlockReq bool
blockReqIncreased bool
data bReactorEventData
expectedLastBlockReq *lastBlockRequestT
}
// WIP
func TestFSMTransitionSequences(t *testing.T) {
maxRequestBatchSize = 2
fsmTransitionSequenceTests := [][]fsmStepTestValues{
{
{currentState: "unknown", event: startFSMEv, shouldSendStatusReq: true,
expectedState: "waitForPeer"},
{currentState: "waitForPeer", event: statusResponseEv,
data: bReactorEventData{peerId: "P1", height: 10},
blockReqIncreased: true,
expectedState: "waitForBlock"},
},
}
for _, tt := range fsmTransitionSequenceTests {
// Create and start the FSM
testBcR := &testReactor{logger: log.TestingLogger()}
blockDB := dbm.NewMemDB()
store := NewBlockStore(blockDB)
fsm := NewFSM(store, testBcR)
fsm.setLogger(log.TestingLogger())
resetTestValues()
// always start from unknown
fsm.resetStateTimer(unknown)
assert.Equal(t, 1, stateTimerStarts[unknown.name])
for _, step := range tt {
assert.Equal(t, step.currentState, fsm.state.name)
failSendStatusRequest = step.failStatusReq
failSendBlockRequest = step.failBlockReq
oldNumStatusRequests := numStatusRequests
oldNumBlockRequests := numBlockRequests
_ = sendEventToFSM(fsm, step.event, step.data)
if step.shouldSendStatusReq {
assert.Equal(t, oldNumStatusRequests+1, numStatusRequests)
} else {
assert.Equal(t, oldNumStatusRequests, numStatusRequests)
}
if step.blockReqIncreased {
assert.Equal(t, oldNumBlockRequests+maxRequestBatchSize, numBlockRequests)
} else {
assert.Equal(t, oldNumBlockRequests, numBlockRequests)
}
assert.Equal(t, step.expectedState, fsm.state.name)
}
}
}
func TestReactorFSMBasic(t *testing.T) {
maxRequestBatchSize = 2
resetTestValues()
// Create and start the FSM
testBcR := &testReactor{logger: log.TestingLogger()}
blockDB := dbm.NewMemDB()
store := NewBlockStore(blockDB)
fsm := NewFSM(store, testBcR)
fsm.setLogger(log.TestingLogger())
if err := fsm.handle(&bReactorMessageData{event: startFSMEv}); err != nil {
}
// Check that FSM sends a status request message
assert.Equal(t, 1, numStatusRequests)
assert.Equal(t, waitForPeer.name, fsm.state.name)
// Send a status response message to FSM
peerID := p2p.ID(cmn.RandStr(12))
sendStatusResponse2(fsm, peerID, 10)
// Check that FSM sends a block request message and...
assert.Equal(t, maxRequestBatchSize, numBlockRequests)
// ... the block request has the expected height
assert.Equal(t, int64(maxRequestBatchSize), lastBlockRequest.height)
assert.Equal(t, waitForBlock.name, fsm.state.name)
}
func TestReactorFSMPeerTimeout(t *testing.T) {
maxRequestBatchSize = 2
resetTestValues()
peerTimeout = 20 * time.Millisecond
// Create and start the FSM
testBcR := &testReactor{logger: log.TestingLogger()}
blockDB := dbm.NewMemDB()
store := NewBlockStore(blockDB)
fsm := NewFSM(store, testBcR)
fsm.setLogger(log.TestingLogger())
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
peerID := p2p.ID(cmn.RandStr(12))
sendStatusResponse(fsm, peerID, 10)
time.Sleep(5 * time.Millisecond)
// Check that FSM sends a block request message and...
assert.Equal(t, maxRequestBatchSize, numBlockRequests)
// ... the block request has the expected height and peer
assert.Equal(t, int64(maxRequestBatchSize), lastBlockRequest.height)
assert.Equal(t, peerID, lastBlockRequest.peerID)
// let FSM timeout on the block response message
time.Sleep(100 * time.Millisecond)
}
// reactor for FSM testing
type testReactor struct {
logger log.Logger
fsm *bReactorFSM
testCh chan bReactorMessageData
}
func sendEventToFSM(fsm *bReactorFSM, ev bReactorEvent, data bReactorEventData) error {
return fsm.handle(&bReactorMessageData{event: ev, data: data})
}
// ----------------------------------------
// implementation for the test reactor APIs
func (testR *testReactor) sendPeerError(err error, peerID p2p.ID) {
testR.logger.Info("Reactor received sendPeerError call from FSM", "peer", peerID, "err", err)
lastPeerError.peerID = peerID
lastPeerError.err = err
}
func (testR *testReactor) sendStatusRequest() error {
testR.logger.Info("Reactor received sendStatusRequest call from FSM")
numStatusRequests++
if failSendStatusRequest {
return errSendQueueFull
}
return nil
}
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
return nil
}
func (testR *testReactor) resetStateTimer(name string, timer *time.Timer, timeout time.Duration, f func()) {
testR.logger.Info("Reactor received resetStateTimer call from FSM", "state", name, "timeout", timeout)
if _, ok := stateTimerStarts[name]; !ok {
stateTimerStarts[name] = 1
} else {
stateTimerStarts[name]++
}
}
func (testR *testReactor) processBlocks(first *types.Block, second *types.Block) error {
testR.logger.Info("Reactor received processBlocks call from FSM", "first", first.Height, "second", second.Height)
return nil
}
func (testR *testReactor) switchToConsensus() {
testR.logger.Info("Reactor received switchToConsensus call from FSM")
}
// ----------------------------------------
// -------------------------------------------------------
// helper functions for tests to simulate different events
func sendStatusResponse(fsm *bReactorFSM, peerID p2p.ID, height int64) {
msgBytes := makeStatusResponseMessage(height)
msgData := bReactorMessageData{
event: statusResponseEv,
data: bReactorEventData{
peerId: peerID,
height: height,
length: len(msgBytes),
},
}
sendMessageToFSM(fsm, msgData)
}
func sendStatusResponse2(fsm *bReactorFSM, peerID p2p.ID, height int64) {
msgBytes := makeStatusResponseMessage(height)
msgData := &bReactorMessageData{
event: statusResponseEv,
data: bReactorEventData{
peerId: peerID,
height: height,
length: len(msgBytes),
},
}
_ = fsm.handle(msgData)
}
func sendStateTimeout(fsm *bReactorFSM, name string) {
msgData := &bReactorMessageData{
event: stateTimeoutEv,
data: bReactorEventData{
stateName: name,
},
}
_ = fsm.handle(msgData)
}
// -------------------------------------------------------
// ----------------------------------------------------
// helper functions to make blockchain reactor messages
func makeStatusResponseMessage(height int64) []byte {
msgBytes := cdc.MustMarshalBinaryBare(&bcStatusResponseMessage{height})
return msgBytes
}
// ----------------------------------------------------