func (testR *testReactor) sendPeerError(err error, peerID p2p.ID) {
	testR.logger.Info("Reactor received sendPeerError call from FSM", "peer", peerID, "err", err)
	testR.lastPeerError.peerID = peerID
	testR.lastPeerError.err = err
}
----------------------------------------
implements the bcRNotifier
package v1
import (
	"fmt"
	"testing"
	"time"
	"github.com/stretchr/testify/assert"
	"github.com/line/ostracon/libs/log"
	tmmath "github.com/line/ostracon/libs/math"
	tmrand "github.com/line/ostracon/libs/rand"
	"github.com/line/ostracon/p2p"
	"github.com/line/ostracon/types"
)
type lastBlockRequestT struct {
	peerID p2p.ID
	height int64
}
type lastPeerErrorT struct {
	peerID p2p.ID
	err    error
}
type testReactor struct {
	logger            log.Logger
	fsm               *BcReactorFSM
	numStatusRequests int
	numBlockRequests  int
	lastBlockRequest  lastBlockRequestT
	lastPeerError     lastPeerErrorT
	stateTimerStarts  map[string]int
}
func sendEventToFSM(fsm *BcReactorFSM, ev bReactorEvent, data bReactorEventData) error {
	return fsm.Handle(&bcReactorMessage{event: ev, data: data})
}
type fsmStepTestValues struct {
	currentState string
	event        bReactorEvent
	data         bReactorEventData
	wantErr           error
	wantState         string
	wantStatusReqSent bool
	wantReqIncreased  bool
	wantNewBlocks     []int64
	wantRemovedPeers  []p2p.ID
}
func sStopFSMEv(current, expected string) fsmStepTestValues {
	return fsmStepTestValues{
		currentState: current,
		event:        stopFSMEv,
		wantState:    expected,
		wantErr:      errNoErrorFinished}
}
func sUnknownFSMEv(current string) fsmStepTestValues {
	return fsmStepTestValues{
		currentState: current,
		event:        1234,
		wantState:    current,
		wantErr:      errInvalidEvent}
}
func sStartFSMEv() fsmStepTestValues {
	return fsmStepTestValues{
		currentState:      "unknown",
		event:             startFSMEv,
		wantState:         "waitForPeer",
		wantStatusReqSent: true}
}
func sStateTimeoutEv(current, expected string, timedoutState string, wantErr error) fsmStepTestValues {
	return fsmStepTestValues{
		currentState: current,
		event:        stateTimeoutEv,
		data: bReactorEventData{
			stateName: timedoutState,
		},
		wantState: expected,
		wantErr:   wantErr,
	}
}
func sProcessedBlockEv(current, expected string, reactorError error) fsmStepTestValues {
	return fsmStepTestValues{
		currentState: current,
		event:        processedBlockEv,
		data: bReactorEventData{
			err: reactorError,
		},
		wantState: expected,
		wantErr:   reactorError,
	}
}
func sStatusEv(current, expected string, peerID p2p.ID, height int64, err error) fsmStepTestValues {
	return fsmStepTestValues{
		currentState: current,
		event:        statusResponseEv,
		data:         bReactorEventData{peerID: peerID, height: height},
		wantState:    expected,
		wantErr:      err}
}
func sMakeRequestsEv(current, expected string, maxPendingRequests int) fsmStepTestValues {
	return fsmStepTestValues{
		currentState:     current,
		event:            makeRequestsEv,
		data:             bReactorEventData{maxNumRequests: maxPendingRequests},
		wantState:        expected,
		wantReqIncreased: true,
	}
}
func sMakeRequestsEvErrored(current, expected string,
	maxPendingRequests int, err error, peersRemoved []p2p.ID) fsmStepTestValues {
	return fsmStepTestValues{
		currentState:     current,
		event:            makeRequestsEv,
		data:             bReactorEventData{maxNumRequests: maxPendingRequests},
		wantState:        expected,
		wantErr:          err,
		wantRemovedPeers: peersRemoved,
		wantReqIncreased: true,
	}
}
func sBlockRespEv(current, expected string, peerID p2p.ID, height int64, prevBlocks []int64) fsmStepTestValues {
	txs := []types.Tx{types.Tx("foo"), types.Tx("bar")}
	return fsmStepTestValues{
		currentState: current,
		event:        blockResponseEv,
		data: bReactorEventData{
			peerID: peerID,
			height: height,
			block:  types.MakeBlock(height, txs, nil, nil),
			length: 100},
		wantState:     expected,
		wantNewBlocks: append(prevBlocks, height),
	}
}
func sBlockRespEvErrored(current, expected string,
	peerID p2p.ID, height int64, prevBlocks []int64, wantErr error, peersRemoved []p2p.ID) fsmStepTestValues {
	txs := []types.Tx{types.Tx("foo"), types.Tx("bar")}
	return fsmStepTestValues{
		currentState: current,
		event:        blockResponseEv,
		data: bReactorEventData{
			peerID: peerID,
			height: height,
			block:  types.MakeBlock(height, txs, nil, nil),
			length: 100},
		wantState:        expected,
		wantErr:          wantErr,
		wantRemovedPeers: peersRemoved,
		wantNewBlocks:    prevBlocks,
	}
}
func sPeerRemoveEv(current, expected string, peerID p2p.ID, err error, peersRemoved []p2p.ID) fsmStepTestValues {
	return fsmStepTestValues{
		currentState: current,
		event:        peerRemoveEv,
		data: bReactorEventData{
			peerID: peerID,
			err:    err,
		},
		wantState:        expected,
		wantRemovedPeers: peersRemoved,
	}
}
func newTestReactor(height int64) *testReactor {
	testBcR := &testReactor{logger: log.TestingLogger(), stateTimerStarts: make(map[string]int)}
	testBcR.fsm = NewFSM(height, testBcR)
	testBcR.fsm.SetLogger(testBcR.logger)
	return testBcR
}
func fixBlockResponseEvStep(step *fsmStepTestValues, testBcR *testReactor) {
	if step.event == blockResponseEv {
		height := step.data.height
		peerID, ok := testBcR.fsm.pool.blocks[height]
		if ok {
			step.data.peerID = peerID
		}
	}
}
type testFields struct {
	name               string
	startingHeight     int64
	maxRequestsPerPeer int
	maxPendingRequests int
	steps              []fsmStepTestValues
}
func executeFSMTests(t *testing.T, tests []testFields, matchRespToReq bool) {
	for _, tt := range tests {
		tt := tt
		t.Run(tt.name, func(t *testing.T) {
			testBcR := newTestReactor(tt.startingHeight)
			if tt.maxRequestsPerPeer != 0 {
				maxRequestsPerPeer = tt.maxRequestsPerPeer
			}
			for _, step := range tt.steps {
				step := step
				assert.Equal(t, step.currentState, testBcR.fsm.state.name)
				var heightBefore int64
				if step.event == processedBlockEv && step.data.err == errBlockVerificationFailure {
					heightBefore = testBcR.fsm.pool.Height
				}
				oldNumStatusRequests := testBcR.numStatusRequests
				oldNumBlockRequests := testBcR.numBlockRequests
				if matchRespToReq {
					fixBlockResponseEvStep(&step, testBcR)
				}
				fsmErr := sendEventToFSM(testBcR.fsm, step.event, step.data)
				assert.Equal(t, step.wantErr, fsmErr)
				if step.wantStatusReqSent {
					assert.Equal(t, oldNumStatusRequests+1, testBcR.numStatusRequests)
				} else {
					assert.Equal(t, oldNumStatusRequests, testBcR.numStatusRequests)
				}
				if step.wantReqIncreased {
					assert.True(t, oldNumBlockRequests < testBcR.numBlockRequests)
				} else {
					assert.Equal(t, oldNumBlockRequests, testBcR.numBlockRequests)
				}
				for _, height := range step.wantNewBlocks {
					_, err := testBcR.fsm.pool.BlockAndPeerAtHeight(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.BlockAndPeerAtHeight(testBcR.fsm.pool.Height)
					secondAfter, err2 := testBcR.fsm.pool.BlockAndPeerAtHeight(testBcR.fsm.pool.Height + 1)
					assert.NotNil(t, err1)
					assert.NotNil(t, err2)
					assert.Nil(t, firstAfter)
					assert.Nil(t, secondAfter)
				}
				assert.Equal(t, step.wantState, testBcR.fsm.state.name)
				if step.wantState == "finished" {
					assert.True(t, testBcR.fsm.isCaughtUp())
				}
			}
		})
	}
}
func TestFSMBasic(t *testing.T) {
	tests := []testFields{
		{
			name:               "one block, one peer - TS2",
			startingHeight:     1,
			maxRequestsPerPeer: 2,
			steps: []fsmStepTestValues{
				sStartFSMEv(),
				sStatusEv("waitForPeer", "waitForBlock", "P1", 2, nil),
				sMakeRequestsEv("waitForBlock", "waitForBlock", maxNumRequests),
				sBlockRespEv("waitForBlock", "waitForBlock", "P1", 1, []int64{}),
				sBlockRespEv("waitForBlock", "waitForBlock", "P2", 2, []int64{1}),
				sProcessedBlockEv("waitForBlock", "finished", nil),
			},
		},
		{
			name:               "multi block, multi peer - TS2",
			startingHeight:     1,
			maxRequestsPerPeer: 2,
			steps: []fsmStepTestValues{
				sStartFSMEv(),
				sStatusEv("waitForPeer", "waitForBlock", "P1", 4, nil),
				sStatusEv("waitForBlock", "waitForBlock", "P2", 4, nil),
				sMakeRequestsEv("waitForBlock", "waitForBlock", maxNumRequests),
				sBlockRespEv("waitForBlock", "waitForBlock", "P1", 1, []int64{}),
				sBlockRespEv("waitForBlock", "waitForBlock", "P1", 2, []int64{1}),
				sBlockRespEv("waitForBlock", "waitForBlock", "P2", 3, []int64{1, 2}),
				sBlockRespEv("waitForBlock", "waitForBlock", "P2", 4, []int64{1, 2, 3}),
				sProcessedBlockEv("waitForBlock", "waitForBlock", nil),
				sProcessedBlockEv("waitForBlock", "waitForBlock", nil),
				sProcessedBlockEv("waitForBlock", "finished", nil),
			},
		},
	}
	executeFSMTests(t, tests, true)
}
func TestFSMBlockVerificationFailure(t *testing.T) {
	tests := []testFields{
		{
			name:               "block verification failure - TS2 variant",
			startingHeight:     1,
			maxRequestsPerPeer: 3,
			steps: []fsmStepTestValues{
				sStartFSMEv(),
				sStatusEv("waitForPeer", "waitForBlock", "P1", 3, nil),
				sMakeRequestsEv("waitForBlock", "waitForBlock", maxNumRequests),
				sBlockRespEv("waitForBlock", "waitForBlock", "P1", 1, []int64{}),
				sBlockRespEv("waitForBlock", "waitForBlock", "P1", 2, []int64{1}),
				sBlockRespEv("waitForBlock", "waitForBlock", "P1", 3, []int64{1, 2}),
				sStatusEv("waitForBlock", "waitForBlock", "P2", 3, nil),
				sProcessedBlockEv("waitForBlock", "waitForBlock", errBlockVerificationFailure),
				sMakeRequestsEv("waitForBlock", "waitForBlock", maxNumRequests),
				sBlockRespEv("waitForBlock", "waitForBlock", "P2", 1, []int64{}),
				sBlockRespEv("waitForBlock", "waitForBlock", "P2", 2, []int64{1}),
				sBlockRespEv("waitForBlock", "waitForBlock", "P2", 3, []int64{1, 2}),
				sProcessedBlockEv("waitForBlock", "waitForBlock", nil),
				sProcessedBlockEv("waitForBlock", "finished", nil),
			},
		},
	}
	executeFSMTests(t, tests, false)
}
func TestFSMBadBlockFromPeer(t *testing.T) {
	tests := []testFields{
		{
			name:               "block we haven't asked for",
			startingHeight:     1,
			maxRequestsPerPeer: 3,
			steps: []fsmStepTestValues{
				sStartFSMEv(),
				sStatusEv("waitForPeer", "waitForBlock", "P1", 300, nil),
				sMakeRequestsEv("waitForBlock", "waitForBlock", maxNumRequests),
				sBlockRespEvErrored("waitForBlock", "waitForPeer",
					"P1", 100, []int64{}, errMissingBlock, []p2p.ID{}),
			},
		},
		{
			name:               "block we already have",
			startingHeight:     1,
			maxRequestsPerPeer: 3,
			steps: []fsmStepTestValues{
				sStartFSMEv(),
				sStatusEv("waitForPeer", "waitForBlock", "P1", 100, nil),
				sMakeRequestsEv("waitForBlock", "waitForBlock", maxNumRequests),
				sBlockRespEv("waitForBlock", "waitForBlock",
					"P1", 1, []int64{}),
				sBlockRespEvErrored("waitForBlock", "waitForPeer",
					"P1", 1, []int64{}, errDuplicateBlock, []p2p.ID{"P1"}),
			},
		},
		{
			name:               "block from unknown peer",
			startingHeight:     1,
			maxRequestsPerPeer: 3,
			steps: []fsmStepTestValues{
				sStartFSMEv(),
				sStatusEv("waitForPeer", "waitForBlock", "P1", 3, nil),
				sMakeRequestsEv("waitForBlock", "waitForBlock", maxNumRequests),
				sBlockRespEvErrored("waitForBlock", "waitForBlock",
					"P2", 1, []int64{}, errBadDataFromPeer, []p2p.ID{"P2"}),
			},
		},
		{
			name:               "block from wrong peer",
			startingHeight:     1,
			maxRequestsPerPeer: 3,
			steps: []fsmStepTestValues{
				sStartFSMEv(),
				sStatusEv("waitForPeer", "waitForBlock", "P1", 3, nil),
				sMakeRequestsEv("waitForBlock", "waitForBlock", maxNumRequests),
				sStatusEv("waitForBlock", "waitForBlock", "P2", 3, nil),
				sBlockRespEvErrored("waitForBlock", "waitForBlock",
					"P2", 1, []int64{}, errBadDataFromPeer, []p2p.ID{"P2"}),
			},
		},
	}
	executeFSMTests(t, tests, false)
}
func TestFSMBlockAtCurrentHeightDoesNotArriveInTime(t *testing.T) {
	tests := []testFields{
		{
			name:               "block at current height undelivered - TS5",
			startingHeight:     1,
			maxRequestsPerPeer: 3,
			steps: []fsmStepTestValues{
				sStartFSMEv(),
				sStatusEv("waitForPeer", "waitForBlock", "P1", 3, nil),
				sMakeRequestsEv("waitForBlock", "waitForBlock", maxNumRequests),
				sBlockRespEv("waitForBlock", "waitForBlock",
					"P1", 1, []int64{}),
				sBlockRespEv("waitForBlock", "waitForBlock",
					"P1", 2, []int64{1}),
				sProcessedBlockEv("waitForBlock", "waitForBlock", nil),
				sStatusEv("waitForBlock", "waitForBlock", "P2", 3, nil),
				sStateTimeoutEv("waitForBlock", "waitForBlock", "waitForBlock", errNoPeerResponseForCurrentHeights),
				sMakeRequestsEv("waitForBlock", "waitForBlock", maxNumRequests),
				sBlockRespEv("waitForBlock", "waitForBlock", "P2", 2, []int64{}),
				sBlockRespEv("waitForBlock", "waitForBlock", "P2", 3, []int64{2}),
				sProcessedBlockEv("waitForBlock", "finished", nil),
			},
		},
		{
			name:               "block at current height undelivered, at maxPeerHeight after peer removal - TS3",
			startingHeight:     1,
			maxRequestsPerPeer: 3,
			steps: []fsmStepTestValues{
				sStartFSMEv(),
				sStatusEv("waitForPeer", "waitForBlock", "P1", 3, nil),
				sMakeRequestsEv("waitForBlock", "waitForBlock", maxNumRequests),
				sStatusEv("waitForBlock", "waitForBlock", "P2", 30, nil),
				sMakeRequestsEv("waitForBlock", "waitForBlock", maxNumRequests),
				sBlockRespEv("waitForBlock", "waitForBlock", "P1", 1, []int64{}),
				sBlockRespEv("waitForBlock", "waitForBlock", "P1", 2, []int64{1}),
				sBlockRespEv("waitForBlock", "waitForBlock", "P1", 3, []int64{1, 2}),
				sProcessedBlockEv("waitForBlock", "waitForBlock", nil),
				sProcessedBlockEv("waitForBlock", "waitForBlock", nil),
				sStateTimeoutEv("waitForBlock", "finished", "waitForBlock", nil),
			},
		},
	}
	executeFSMTests(t, tests, true)
}
func TestFSMPeerRelatedEvents(t *testing.T) {
	tests := []testFields{
		{
			name:           "peer remove event with no blocks",
			startingHeight: 1,
			steps: []fsmStepTestValues{
				sStartFSMEv(),
				sStatusEv("waitForPeer", "waitForBlock", "P1", 3, nil),
				sStatusEv("waitForBlock", "waitForBlock", "P2", 3, nil),
				sStatusEv("waitForBlock", "waitForBlock", "P3", 3, nil),
				sPeerRemoveEv("waitForBlock", "waitForBlock", "P2", errSwitchRemovesPeer, []p2p.ID{"P2"}),
			},
		},
		{
			name:           "only peer removed while in waitForBlock state",
			startingHeight: 100,
			steps: []fsmStepTestValues{
				sStartFSMEv(),
				sStatusEv("waitForPeer", "waitForBlock", "P1", 200, nil),
				sPeerRemoveEv("waitForBlock", "waitForPeer", "P1", errSwitchRemovesPeer, []p2p.ID{"P1"}),
			},
		},
		{
			name:               "highest peer removed while in waitForBlock state, node reaches maxPeerHeight - TS4 ",
			startingHeight:     100,
			maxRequestsPerPeer: 3,
			steps: []fsmStepTestValues{
				sStartFSMEv(),
				sStatusEv("waitForPeer", "waitForBlock", "P1", 101, nil),
				sMakeRequestsEv("waitForBlock", "waitForBlock", maxNumRequests),
				sStatusEv("waitForBlock", "waitForBlock", "P2", 200, nil),
				sBlockRespEv("waitForBlock", "waitForBlock", "P1", 100, []int64{}),
				sBlockRespEv("waitForBlock", "waitForBlock", "P1", 101, []int64{100}),
				sProcessedBlockEv("waitForBlock", "waitForBlock", nil),
				sPeerRemoveEv("waitForBlock", "finished", "P2", errSwitchRemovesPeer, []p2p.ID{"P2"}),
			},
		},
		{
			name:               "highest peer lowers its height in waitForBlock state, node reaches maxPeerHeight - TS4",
			startingHeight:     100,
			maxRequestsPerPeer: 3,
			steps: []fsmStepTestValues{
				sStartFSMEv(),
				sStatusEv("waitForPeer", "waitForBlock", "P1", 101, nil),
				sMakeRequestsEv("waitForBlock", "waitForBlock", maxNumRequests),
				sStatusEv("waitForBlock", "waitForBlock", "P2", 200, nil),
				sBlockRespEv("waitForBlock", "waitForBlock", "P1", 100, []int64{}),
				sBlockRespEv("waitForBlock", "waitForBlock", "P1", 101, []int64{100}),
				sProcessedBlockEv("waitForBlock", "waitForBlock", nil),
				sStatusEv("waitForBlock", "finished", "P2", 100, errPeerLowersItsHeight),
			},
		},
		{
			name:           "new short peer while in waitForPeer state",
			startingHeight: 100,
			steps: []fsmStepTestValues{
				sStartFSMEv(),
				sStatusEv("waitForPeer", "waitForPeer", "P1", 3, errPeerTooShort),
			},
		},
		{
			name:           "new short peer while in waitForBlock state",
			startingHeight: 100,
			steps: []fsmStepTestValues{
				sStartFSMEv(),
				sStatusEv("waitForPeer", "waitForBlock", "P1", 200, nil),
				sStatusEv("waitForBlock", "waitForBlock", "P2", 3, errPeerTooShort),
			},
		},
		{
			name:           "only peer updated with low height while in waitForBlock state",
			startingHeight: 100,
			steps: []fsmStepTestValues{
				sStartFSMEv(),
				sStatusEv("waitForPeer", "waitForBlock", "P1", 200, nil),
				sStatusEv("waitForBlock", "waitForPeer", "P1", 3, errPeerLowersItsHeight),
			},
		},
		{
			name:               "peer does not exist in the switch",
			startingHeight:     9999999,
			maxRequestsPerPeer: 3,
			steps: []fsmStepTestValues{
				sStartFSMEv(),
				sStatusEv("waitForPeer", "waitForBlock", "P1", 20000000, nil),
				sMakeRequestsEvErrored("waitForBlock", "waitForBlock",
					maxNumRequests, nil, []p2p.ID{"P1"}),
			},
		},
	}
	executeFSMTests(t, tests, true)
}
func TestFSMStopFSM(t *testing.T) {
	tests := []testFields{
		{
			name: "stopFSMEv in unknown",
			steps: []fsmStepTestValues{
				sStopFSMEv("unknown", "finished"),
			},
		},
		{
			name:           "stopFSMEv in waitForPeer",
			startingHeight: 1,
			steps: []fsmStepTestValues{
				sStartFSMEv(),
				sStopFSMEv("waitForPeer", "finished"),
			},
		},
		{
			name:           "stopFSMEv in waitForBlock",
			startingHeight: 1,
			steps: []fsmStepTestValues{
				sStartFSMEv(),
				sStatusEv("waitForPeer", "waitForBlock", "P1", 3, nil),
				sStopFSMEv("waitForBlock", "finished"),
			},
		},
	}
	executeFSMTests(t, tests, false)
}
func TestFSMUnknownElements(t *testing.T) {
	tests := []testFields{
		{
			name: "unknown event for state unknown",
			steps: []fsmStepTestValues{
				sUnknownFSMEv("unknown"),
			},
		},
		{
			name: "unknown event for state waitForPeer",
			steps: []fsmStepTestValues{
				sStartFSMEv(),
				sUnknownFSMEv("waitForPeer"),
			},
		},
		{
			name:           "unknown event for state waitForBlock",
			startingHeight: 1,
			steps: []fsmStepTestValues{
				sStartFSMEv(),
				sStatusEv("waitForPeer", "waitForBlock", "P1", 3, nil),
				sUnknownFSMEv("waitForBlock"),
			},
		},
	}
	executeFSMTests(t, tests, false)
}
func TestFSMPeerStateTimeoutEvent(t *testing.T) {
	tests := []testFields{
		{
			name:               "timeout event for state waitForPeer while in state waitForPeer - TS1",
			startingHeight:     1,
			maxRequestsPerPeer: 3,
			steps: []fsmStepTestValues{
				sStartFSMEv(),
				sStateTimeoutEv("waitForPeer", "finished", "waitForPeer", errNoTallerPeer),
			},
		},
		{
			name:               "timeout event for state waitForPeer while in a state != waitForPeer",
			startingHeight:     1,
			maxRequestsPerPeer: 3,
			steps: []fsmStepTestValues{
				sStartFSMEv(),
				sStateTimeoutEv("waitForPeer", "waitForPeer", "waitForBlock", errTimeoutEventWrongState),
			},
		},
		{
			name:               "timeout event for state waitForBlock while in state waitForBlock ",
			startingHeight:     1,
			maxRequestsPerPeer: 3,
			steps: []fsmStepTestValues{
				sStartFSMEv(),
				sStatusEv("waitForPeer", "waitForBlock", "P1", 3, nil),
				sMakeRequestsEv("waitForBlock", "waitForBlock", maxNumRequests),
				sStateTimeoutEv("waitForBlock", "waitForPeer", "waitForBlock", errNoPeerResponseForCurrentHeights),
			},
		},
		{
			name:               "timeout event for state waitForBlock while in a state != waitForBlock",
			startingHeight:     1,
			maxRequestsPerPeer: 3,
			steps: []fsmStepTestValues{
				sStartFSMEv(),
				sStatusEv("waitForPeer", "waitForBlock", "P1", 3, nil),
				sMakeRequestsEv("waitForBlock", "waitForBlock", maxNumRequests),
				sStateTimeoutEv("waitForBlock", "waitForBlock", "waitForPeer", errTimeoutEventWrongState),
			},
		},
		{
			name:               "timeout event for state waitForBlock with multiple peers",
			startingHeight:     1,
			maxRequestsPerPeer: 3,
			steps: []fsmStepTestValues{
				sStartFSMEv(),
				sStatusEv("waitForPeer", "waitForBlock", "P1", 3, nil),
				sMakeRequestsEv("waitForBlock", "waitForBlock", maxNumRequests),
				sStatusEv("waitForBlock", "waitForBlock", "P2", 3, nil),
				sStateTimeoutEv("waitForBlock", "waitForBlock", "waitForBlock", errNoPeerResponseForCurrentHeights),
			},
		},
	}
	executeFSMTests(t, tests, false)
}
func makeCorrectTransitionSequence(startingHeight int64, numBlocks int64, numPeers int, randomPeerHeights bool,
	maxRequestsPerPeer int, maxPendingRequests int) testFields {
	peerHeights := make([]int64, numPeers)
	for i := 0; i < numPeers; i++ {
		if i == 0 {
			peerHeights[0] = numBlocks
			continue
		}
		if randomPeerHeights {
			peerHeights[i] = int64(tmmath.MaxInt(tmrand.Intn(int(numBlocks)), int(startingHeight)+1))
		} else {
			peerHeights[i] = numBlocks
		}
	}
	testSteps := make([]fsmStepTestValues, 0, 3*numBlocks+int64(numPeers))
	testName := fmt.Sprintf("%v-blocks %v-startingHeight %v-peers %v-maxRequestsPerPeer %v-maxNumRequests",
		numBlocks, startingHeight, numPeers, maxRequestsPerPeer, maxPendingRequests)
	testSteps = append(testSteps, sStartFSMEv())
	for i := 0; i < numPeers; i++ {
		peerName := fmt.Sprintf("P%d", i)
		if i == 0 {
			testSteps = append(
				testSteps,
				sStatusEv("waitForPeer", "waitForBlock", p2p.ID(peerName), peerHeights[i], nil))
		} else {
			testSteps = append(testSteps,
				sStatusEv("waitForBlock", "waitForBlock", p2p.ID(peerName), peerHeights[i], nil))
		}
	}
	height := startingHeight
	numBlocksReceived := 0
	prevBlocks := make([]int64, 0, maxPendingRequests)
forLoop:
	for i := 0; i < int(numBlocks); i++ {
		if i%maxRequestsPerPeer == 0 {
			testSteps = append(
				testSteps,
				sMakeRequestsEv("waitForBlock", "waitForBlock", maxNumRequests),
			)
		}
		testSteps = append(
			testSteps,
			sBlockRespEv("waitForBlock", "waitForBlock",
				"P0", height, prevBlocks))
		prevBlocks = append(prevBlocks, height)
		height++
		numBlocksReceived++
		if numBlocksReceived >= maxRequestsPerPeer || height >= numBlocks {
			for j := int(height) - numBlocksReceived; j < int(height); j++ {
				if j >= int(numBlocks) {
					testSteps = append(
						testSteps,
						sProcessedBlockEv("waitForBlock", "finished", nil))
					break forLoop
				}
				testSteps = append(
					testSteps,
					sProcessedBlockEv("waitForBlock", "waitForBlock", nil))
			}
			numBlocksReceived = 0
			prevBlocks = make([]int64, 0, maxPendingRequests)
		}
	}
	return testFields{
		name:               testName,
		startingHeight:     startingHeight,
		maxRequestsPerPeer: maxRequestsPerPeer,
		maxPendingRequests: maxPendingRequests,
		steps:              testSteps,
	}
}
const (
	maxStartingHeightTest       = 100
	maxRequestsPerPeerTest      = 20
	maxTotalPendingRequestsTest = 600
	maxNumPeersTest             = 1000
	maxNumBlocksInChainTest     = 10000  
)
func makeCorrectTransitionSequenceWithRandomParameters() testFields {
	startingHeight := int64(tmrand.Intn(maxStartingHeightTest) + 1)
	maxRequestsPerPeer := tmrand.Intn(maxRequestsPerPeerTest) + 1
	maxPendingRequests := tmrand.Intn(maxTotalPendingRequestsTest-maxRequestsPerPeer) + maxRequestsPerPeer
	numBlocks := int64(tmrand.Intn(maxNumBlocksInChainTest)) + startingHeight
	numPeers := tmrand.Intn(maxNumPeersTest) + 1
	return makeCorrectTransitionSequence(startingHeight, numBlocks, numPeers, true, maxRequestsPerPeer, maxPendingRequests)
}
func shouldApplyProcessedBlockEvStep(step *fsmStepTestValues, testBcR *testReactor) bool {
	if step.event == processedBlockEv {
		_, err := testBcR.fsm.pool.BlockAndPeerAtHeight(testBcR.fsm.pool.Height)
		if err == errMissingBlock {
			return false
		}
		_, err = testBcR.fsm.pool.BlockAndPeerAtHeight(testBcR.fsm.pool.Height + 1)
		if err == errMissingBlock {
			return false
		}
	}
	return true
}
func TestFSMCorrectTransitionSequences(t *testing.T) {
	tests := []testFields{
		makeCorrectTransitionSequence(1, 100, 10, true, 10, 40),
		makeCorrectTransitionSequenceWithRandomParameters(),
	}
	for _, tt := range tests {
		tt := tt
		t.Run(tt.name, func(t *testing.T) {
			testBcR := newTestReactor(tt.startingHeight)
			if tt.maxRequestsPerPeer != 0 {
				maxRequestsPerPeer = tt.maxRequestsPerPeer
			}
			for _, step := range tt.steps {
				step := step
				assert.Equal(t, step.currentState, testBcR.fsm.state.name)
				oldNumStatusRequests := testBcR.numStatusRequests
				fixBlockResponseEvStep(&step, testBcR)
				if !shouldApplyProcessedBlockEvStep(&step, testBcR) {
					continue
				}
				fsmErr := sendEventToFSM(testBcR.fsm, step.event, step.data)
				assert.Equal(t, step.wantErr, fsmErr)
				if step.wantStatusReqSent {
					assert.Equal(t, oldNumStatusRequests+1, testBcR.numStatusRequests)
				} else {
					assert.Equal(t, oldNumStatusRequests, testBcR.numStatusRequests)
				}
				assert.Equal(t, step.wantState, testBcR.fsm.state.name)
				if step.wantState == "finished" {
					assert.True(t, testBcR.fsm.isCaughtUp())
				}
			}
		})
	}
}