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()
|
|
stateStore := sm.NewStore(stateDB)
|
|
blockStore := store.NewBlockStore(blockDB)
|
|
|
|
state, err := stateStore.LoadFromDBOrGenesisDoc(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()
|
|
stateStore = sm.NewStore(db)
|
|
blockExec := sm.NewBlockExecutor(stateStore, log.TestingLogger(), proxyApp.Consensus(),
|
|
mock.Mempool{}, sm.EmptyEvidencePool{})
|
|
if err = stateStore.Save(state); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
// 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
|
|
}
|