mirror of
https://github.com/tendermint/tendermint.git
synced 2026-08-03 22:06:18 +00:00
While working on tendermint my colleague @jinmannwong fixed a few of the unit tests that we found to be flaky in our CI. We thought that you might find this useful, see below for comments.
325 lines
8.8 KiB
Go
325 lines
8.8 KiB
Go
package v0
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"sort"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
dbm "github.com/tendermint/tm-db"
|
|
|
|
abci "github.com/tendermint/tendermint/abci/types"
|
|
cfg "github.com/tendermint/tendermint/config"
|
|
"github.com/tendermint/tendermint/libs/log"
|
|
"github.com/tendermint/tendermint/mempool/mock"
|
|
"github.com/tendermint/tendermint/p2p"
|
|
"github.com/tendermint/tendermint/proxy"
|
|
sm "github.com/tendermint/tendermint/state"
|
|
"github.com/tendermint/tendermint/store"
|
|
"github.com/tendermint/tendermint/types"
|
|
tmtime "github.com/tendermint/tendermint/types/time"
|
|
)
|
|
|
|
var config *cfg.Config
|
|
|
|
func randGenesisDoc(numValidators int, randPower bool, minPower int64) (*types.GenesisDoc, []types.PrivValidator) {
|
|
validators := make([]types.GenesisValidator, numValidators)
|
|
privValidators := make([]types.PrivValidator, numValidators)
|
|
for i := 0; i < numValidators; i++ {
|
|
val, privVal := types.RandValidator(randPower, minPower)
|
|
validators[i] = types.GenesisValidator{
|
|
PubKey: val.PubKey,
|
|
Power: val.VotingPower,
|
|
}
|
|
privValidators[i] = privVal
|
|
}
|
|
sort.Sort(types.PrivValidatorsByAddress(privValidators))
|
|
|
|
return &types.GenesisDoc{
|
|
GenesisTime: tmtime.Now(),
|
|
ChainID: config.ChainID(),
|
|
Validators: validators,
|
|
}, privValidators
|
|
}
|
|
|
|
type BlockchainReactorPair struct {
|
|
reactor *BlockchainReactor
|
|
app proxy.AppConns
|
|
}
|
|
|
|
func newBlockchainReactor(
|
|
logger log.Logger,
|
|
genDoc *types.GenesisDoc,
|
|
privVals []types.PrivValidator,
|
|
maxBlockHeight int64) BlockchainReactorPair {
|
|
if len(privVals) != 1 {
|
|
panic("only support one validator")
|
|
}
|
|
|
|
app := &testApp{}
|
|
cc := proxy.NewLocalClientCreator(app)
|
|
proxyApp := proxy.NewAppConns(cc)
|
|
err := proxyApp.Start()
|
|
if err != nil {
|
|
panic(fmt.Errorf("error start app: %w", err))
|
|
}
|
|
|
|
blockDB := dbm.NewMemDB()
|
|
stateDB := dbm.NewMemDB()
|
|
blockStore := store.NewBlockStore(blockDB)
|
|
|
|
state, err := sm.LoadStateFromDBOrGenesisDoc(stateDB, genDoc)
|
|
if err != nil {
|
|
panic(fmt.Errorf("error constructing state from genesis file: %w", err))
|
|
}
|
|
|
|
// Make the BlockchainReactor itself.
|
|
// NOTE we have to create and commit the blocks first because
|
|
// pool.height is determined from the store.
|
|
fastSync := true
|
|
db := dbm.NewMemDB()
|
|
blockExec := sm.NewBlockExecutor(db, log.TestingLogger(), proxyApp.Consensus(),
|
|
mock.Mempool{}, sm.MockEvidencePool{})
|
|
sm.SaveState(db, state)
|
|
|
|
// let's add some blocks in
|
|
for blockHeight := int64(1); blockHeight <= maxBlockHeight; blockHeight++ {
|
|
lastCommit := types.NewCommit(blockHeight-1, 0, types.BlockID{}, nil)
|
|
if blockHeight > 1 {
|
|
lastBlockMeta := blockStore.LoadBlockMeta(blockHeight - 1)
|
|
lastBlock := blockStore.LoadBlock(blockHeight - 1)
|
|
|
|
vote, err := types.MakeVote(
|
|
lastBlock.Header.Height,
|
|
lastBlockMeta.BlockID,
|
|
state.Validators,
|
|
privVals[0],
|
|
lastBlock.Header.ChainID,
|
|
time.Now(),
|
|
)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
lastCommit = types.NewCommit(vote.Height, vote.Round,
|
|
lastBlockMeta.BlockID, []types.CommitSig{vote.CommitSig()})
|
|
}
|
|
|
|
thisBlock := makeBlock(blockHeight, state, lastCommit)
|
|
|
|
thisParts := thisBlock.MakePartSet(types.BlockPartSizeBytes)
|
|
blockID := types.BlockID{Hash: thisBlock.Hash(), PartSetHeader: thisParts.Header()}
|
|
|
|
state, _, err = blockExec.ApplyBlock(state, blockID, thisBlock)
|
|
if err != nil {
|
|
panic(fmt.Errorf("error apply block: %w", err))
|
|
}
|
|
|
|
blockStore.SaveBlock(thisBlock, thisParts, lastCommit)
|
|
}
|
|
|
|
bcReactor := NewBlockchainReactor(state.Copy(), blockExec, blockStore, fastSync)
|
|
bcReactor.SetLogger(logger.With("module", "blockchain"))
|
|
|
|
return BlockchainReactorPair{bcReactor, proxyApp}
|
|
}
|
|
|
|
func TestNoBlockResponse(t *testing.T) {
|
|
config = cfg.ResetTestRoot("blockchain_reactor_test")
|
|
defer os.RemoveAll(config.RootDir)
|
|
genDoc, privVals := randGenesisDoc(1, false, 30)
|
|
|
|
maxBlockHeight := int64(65)
|
|
|
|
reactorPairs := make([]BlockchainReactorPair, 2)
|
|
|
|
reactorPairs[0] = newBlockchainReactor(log.TestingLogger(), genDoc, privVals, maxBlockHeight)
|
|
reactorPairs[1] = newBlockchainReactor(log.TestingLogger(), genDoc, privVals, 0)
|
|
|
|
p2p.MakeConnectedSwitches(config.P2P, 2, func(i int, s *p2p.Switch) *p2p.Switch {
|
|
s.AddReactor("BLOCKCHAIN", reactorPairs[i].reactor)
|
|
return s
|
|
|
|
}, p2p.Connect2Switches)
|
|
|
|
defer func() {
|
|
for _, r := range reactorPairs {
|
|
err := r.reactor.Stop()
|
|
require.NoError(t, err)
|
|
err = r.app.Stop()
|
|
require.NoError(t, err)
|
|
}
|
|
}()
|
|
|
|
tests := []struct {
|
|
height int64
|
|
existent bool
|
|
}{
|
|
{maxBlockHeight + 2, false},
|
|
{10, true},
|
|
{1, true},
|
|
{100, false},
|
|
}
|
|
|
|
for {
|
|
if reactorPairs[1].reactor.pool.IsCaughtUp() {
|
|
break
|
|
}
|
|
|
|
time.Sleep(10 * time.Millisecond)
|
|
}
|
|
|
|
assert.Equal(t, maxBlockHeight, reactorPairs[0].reactor.store.Height())
|
|
|
|
for _, tt := range tests {
|
|
block := reactorPairs[1].reactor.store.LoadBlock(tt.height)
|
|
if tt.existent {
|
|
assert.True(t, block != nil)
|
|
} else {
|
|
assert.True(t, block == nil)
|
|
}
|
|
}
|
|
}
|
|
|
|
// NOTE: This is too hard to test without
|
|
// an easy way to add test peer to switch
|
|
// or without significant refactoring of the module.
|
|
// Alternatively we could actually dial a TCP conn but
|
|
// that seems extreme.
|
|
func TestBadBlockStopsPeer(t *testing.T) {
|
|
config = cfg.ResetTestRoot("blockchain_reactor_test")
|
|
defer os.RemoveAll(config.RootDir)
|
|
genDoc, privVals := randGenesisDoc(1, false, 30)
|
|
|
|
maxBlockHeight := int64(148)
|
|
|
|
// Other chain needs a different validator set
|
|
otherGenDoc, otherPrivVals := randGenesisDoc(1, false, 30)
|
|
otherChain := newBlockchainReactor(log.TestingLogger(), otherGenDoc, otherPrivVals, maxBlockHeight)
|
|
|
|
defer func() {
|
|
err := otherChain.reactor.Stop()
|
|
require.Error(t, err)
|
|
err = otherChain.app.Stop()
|
|
require.NoError(t, err)
|
|
}()
|
|
|
|
reactorPairs := make([]BlockchainReactorPair, 4)
|
|
|
|
reactorPairs[0] = newBlockchainReactor(log.TestingLogger(), genDoc, privVals, maxBlockHeight)
|
|
reactorPairs[1] = newBlockchainReactor(log.TestingLogger(), genDoc, privVals, 0)
|
|
reactorPairs[2] = newBlockchainReactor(log.TestingLogger(), genDoc, privVals, 0)
|
|
reactorPairs[3] = newBlockchainReactor(log.TestingLogger(), genDoc, privVals, 0)
|
|
|
|
switches := p2p.MakeConnectedSwitches(config.P2P, 4, func(i int, s *p2p.Switch) *p2p.Switch {
|
|
s.AddReactor("BLOCKCHAIN", reactorPairs[i].reactor)
|
|
return s
|
|
|
|
}, p2p.Connect2Switches)
|
|
|
|
defer func() {
|
|
for _, r := range reactorPairs {
|
|
err := r.reactor.Stop()
|
|
require.NoError(t, err)
|
|
|
|
err = r.app.Stop()
|
|
require.NoError(t, err)
|
|
}
|
|
}()
|
|
|
|
for {
|
|
time.Sleep(1 * time.Second)
|
|
caughtUp := true
|
|
for _, r := range reactorPairs {
|
|
if !r.reactor.pool.IsCaughtUp() {
|
|
caughtUp = false
|
|
}
|
|
}
|
|
if caughtUp {
|
|
break
|
|
}
|
|
}
|
|
|
|
//at this time, reactors[0-3] is the newest
|
|
assert.Equal(t, 3, reactorPairs[1].reactor.Switch.Peers().Size())
|
|
|
|
// Mark reactorPairs[3] as an invalid peer. Fiddling with .store without a mutex is a data
|
|
// race, but can't be easily avoided.
|
|
reactorPairs[3].reactor.store = otherChain.reactor.store
|
|
|
|
lastReactorPair := newBlockchainReactor(log.TestingLogger(), genDoc, privVals, 0)
|
|
reactorPairs = append(reactorPairs, lastReactorPair)
|
|
|
|
switches = append(switches, p2p.MakeConnectedSwitches(config.P2P, 1, func(i int, s *p2p.Switch) *p2p.Switch {
|
|
s.AddReactor("BLOCKCHAIN", reactorPairs[len(reactorPairs)-1].reactor)
|
|
return s
|
|
|
|
}, p2p.Connect2Switches)...)
|
|
|
|
for i := 0; i < len(reactorPairs)-1; i++ {
|
|
p2p.Connect2Switches(switches, i, len(reactorPairs)-1)
|
|
}
|
|
|
|
for {
|
|
if lastReactorPair.reactor.pool.IsCaughtUp() || lastReactorPair.reactor.Switch.Peers().Size() == 0 {
|
|
break
|
|
}
|
|
|
|
time.Sleep(1 * time.Second)
|
|
}
|
|
|
|
assert.True(t, lastReactorPair.reactor.Switch.Peers().Size() < len(reactorPairs)-1)
|
|
}
|
|
|
|
//----------------------------------------------
|
|
// utility funcs
|
|
|
|
func makeTxs(height int64) (txs []types.Tx) {
|
|
for i := 0; i < 10; i++ {
|
|
txs = append(txs, types.Tx([]byte{byte(height), byte(i)}))
|
|
}
|
|
return txs
|
|
}
|
|
|
|
func makeBlock(height int64, state sm.State, lastCommit *types.Commit) *types.Block {
|
|
block, _ := state.MakeBlock(height, makeTxs(height), lastCommit, nil, state.Validators.GetProposer().Address)
|
|
return block
|
|
}
|
|
|
|
type testApp struct {
|
|
abci.BaseApplication
|
|
}
|
|
|
|
var _ abci.Application = (*testApp)(nil)
|
|
|
|
func (app *testApp) Info(req abci.RequestInfo) (resInfo abci.ResponseInfo) {
|
|
return abci.ResponseInfo{}
|
|
}
|
|
|
|
func (app *testApp) BeginBlock(req abci.RequestBeginBlock) abci.ResponseBeginBlock {
|
|
return abci.ResponseBeginBlock{}
|
|
}
|
|
|
|
func (app *testApp) EndBlock(req abci.RequestEndBlock) abci.ResponseEndBlock {
|
|
return abci.ResponseEndBlock{}
|
|
}
|
|
|
|
func (app *testApp) DeliverTx(req abci.RequestDeliverTx) abci.ResponseDeliverTx {
|
|
return abci.ResponseDeliverTx{Events: []abci.Event{}}
|
|
}
|
|
|
|
func (app *testApp) CheckTx(req abci.RequestCheckTx) abci.ResponseCheckTx {
|
|
return abci.ResponseCheckTx{}
|
|
}
|
|
|
|
func (app *testApp) Commit() abci.ResponseCommit {
|
|
return abci.ResponseCommit{}
|
|
}
|
|
|
|
func (app *testApp) Query(reqQuery abci.RequestQuery) (resQuery abci.ResponseQuery) {
|
|
return
|
|
}
|