package state_test import ( "bytes" "context" "fmt" "time" dbm "git.cw.tr/mukan-network/mukan-consensus-db" abci "git.cw.tr/mukan-network/mukan-consensus/abci/types" "git.cw.tr/mukan-network/mukan-consensus/crypto" "git.cw.tr/mukan-network/mukan-consensus/crypto/ed25519" "git.cw.tr/mukan-network/mukan-consensus/internal/test" cmtproto "git.cw.tr/mukan-network/mukan-consensus/proto/tendermint/types" "git.cw.tr/mukan-network/mukan-consensus/proxy" sm "git.cw.tr/mukan-network/mukan-consensus/state" "git.cw.tr/mukan-network/mukan-consensus/types" cmttime "git.cw.tr/mukan-network/mukan-consensus/types/time" ) type paramsChangeTestCase struct { height int64 params types.ConsensusParams } func newTestApp() proxy.AppConns { app := &testApp{} cc := proxy.NewLocalClientCreator(app) return proxy.NewAppConns(cc, proxy.NopMetrics()) } func makeAndCommitGoodBlock( state sm.State, height int64, lastCommit *types.Commit, proposerAddr []byte, blockExec *sm.BlockExecutor, privVals map[string]types.PrivValidator, evidence []types.Evidence, ) (sm.State, types.BlockID, *types.ExtendedCommit, error) { // A good block passes state, blockID, err := makeAndApplyGoodBlock(state, height, lastCommit, proposerAddr, blockExec, evidence) if err != nil { return state, types.BlockID{}, nil, err } // Simulate a lastCommit for this block from all validators for the next height commit, _, err := makeValidCommit(height, blockID, state.Validators, privVals) if err != nil { return state, types.BlockID{}, nil, err } return state, blockID, commit, nil } func makeAndApplyGoodBlock(state sm.State, height int64, lastCommit *types.Commit, proposerAddr []byte, blockExec *sm.BlockExecutor, evidence []types.Evidence, ) (sm.State, types.BlockID, error) { block, err := state.MakeBlock(height, test.MakeNTxs(height, 10), lastCommit, evidence, proposerAddr) if err != nil { return state, types.BlockID{}, nil } partSet, err := block.MakePartSet(types.BlockPartSizeBytes) if err != nil { return state, types.BlockID{}, err } if err := blockExec.ValidateBlock(state, block); err != nil { return state, types.BlockID{}, err } blockID := types.BlockID{ Hash: block.Hash(), PartSetHeader: partSet.Header(), } state, err = blockExec.ApplyBlock(state, blockID, block) if err != nil { return state, types.BlockID{}, err } return state, blockID, nil } func makeBlock(state sm.State, height int64, c *types.Commit) (*types.Block, error) { return state.MakeBlock( height, test.MakeNTxs(state.LastBlockHeight, 10), c, nil, state.Validators.GetProposer().Address, ) } func makeValidCommit( height int64, blockID types.BlockID, vals *types.ValidatorSet, privVals map[string]types.PrivValidator, ) (*types.ExtendedCommit, []*types.Vote, error) { sigs := make([]types.ExtendedCommitSig, vals.Size()) votes := make([]*types.Vote, vals.Size()) for i := 0; i < vals.Size(); i++ { _, val := vals.GetByIndex(int32(i)) vote, err := types.MakeVote( privVals[val.Address.String()], chainID, int32(i), height, 0, cmtproto.PrecommitType, blockID, time.Now(), ) if err != nil { return nil, nil, err } sigs[i] = vote.ExtendedCommitSig() votes[i] = vote } return &types.ExtendedCommit{ Height: height, BlockID: blockID, ExtendedSignatures: sigs, }, votes, nil } func makeState(nVals, height int) (sm.State, dbm.DB, map[string]types.PrivValidator) { vals := make([]types.GenesisValidator, nVals) privVals := make(map[string]types.PrivValidator, nVals) for i := 0; i < nVals; i++ { secret := []byte(fmt.Sprintf("test%d", i)) pk := ed25519.GenPrivKeyFromSecret(secret) valAddr := pk.PubKey().Address() vals[i] = types.GenesisValidator{ Address: valAddr, PubKey: pk.PubKey(), Power: 1000, Name: fmt.Sprintf("test%d", i), } privVals[valAddr.String()] = types.NewMockPVWithParams(pk, false, false) } s, _ := sm.MakeGenesisState(&types.GenesisDoc{ ChainID: chainID, Validators: vals, AppHash: nil, }) stateDB := dbm.NewMemDB() stateStore := sm.NewStore(stateDB, sm.StoreOptions{ DiscardABCIResponses: false, }) if err := stateStore.Save(s); err != nil { panic(err) } for i := 1; i < height; i++ { s.LastBlockHeight++ s.LastValidators = s.Validators.Copy() if err := stateStore.Save(s); err != nil { panic(err) } } return s, stateDB, privVals } func genValSet(size int) *types.ValidatorSet { vals := make([]*types.Validator, size) for i := 0; i < size; i++ { vals[i] = types.NewValidator(ed25519.GenPrivKey().PubKey(), 10) } return types.NewValidatorSet(vals) } func makeHeaderPartsResponsesValPubKeyChange( state sm.State, pubkey crypto.PubKey, ) (types.Header, types.BlockID, *abci.ResponseFinalizeBlock) { block, err := makeBlock(state, state.LastBlockHeight+1, new(types.Commit)) if err != nil { return types.Header{}, types.BlockID{}, nil } abciResponses := &abci.ResponseFinalizeBlock{} // If the pubkey is new, remove the old and add the new. _, val := state.NextValidators.GetByIndex(0) if !bytes.Equal(pubkey.Bytes(), val.PubKey.Bytes()) { abciResponses.ValidatorUpdates = []abci.ValidatorUpdate{ types.TM2PB.NewValidatorUpdate(val.PubKey, 0), types.TM2PB.NewValidatorUpdate(pubkey, 10), } } return block.Header, types.BlockID{Hash: block.Hash(), PartSetHeader: types.PartSetHeader{}}, abciResponses } func makeHeaderPartsResponsesValPowerChange( state sm.State, power int64, ) (types.Header, types.BlockID, *abci.ResponseFinalizeBlock) { block, err := makeBlock(state, state.LastBlockHeight+1, new(types.Commit)) if err != nil { return types.Header{}, types.BlockID{}, nil } abciResponses := &abci.ResponseFinalizeBlock{} // If the pubkey is new, remove the old and add the new. _, val := state.NextValidators.GetByIndex(0) if val.VotingPower != power { abciResponses.ValidatorUpdates = []abci.ValidatorUpdate{ types.TM2PB.NewValidatorUpdate(val.PubKey, power), } } return block.Header, types.BlockID{Hash: block.Hash(), PartSetHeader: types.PartSetHeader{}}, abciResponses } func makeHeaderPartsResponsesParams( state sm.State, params cmtproto.ConsensusParams, ) (types.Header, types.BlockID, *abci.ResponseFinalizeBlock) { block, err := makeBlock(state, state.LastBlockHeight+1, new(types.Commit)) if err != nil { return types.Header{}, types.BlockID{}, nil } abciResponses := &abci.ResponseFinalizeBlock{ ConsensusParamUpdates: ¶ms, } return block.Header, types.BlockID{Hash: block.Hash(), PartSetHeader: types.PartSetHeader{}}, abciResponses } func randomGenesisDoc() *types.GenesisDoc { pubkey := ed25519.GenPrivKey().PubKey() return &types.GenesisDoc{ GenesisTime: cmttime.Now(), ChainID: "abc", Validators: []types.GenesisValidator{ { Address: pubkey.Address(), PubKey: pubkey, Power: 10, Name: "myval", }, }, ConsensusParams: types.DefaultConsensusParams(), } } //---------------------------------------------------------------------------- type testApp struct { abci.BaseApplication CommitVotes []abci.VoteInfo Misbehavior []abci.Misbehavior LastTime time.Time ValidatorUpdates []abci.ValidatorUpdate AppHash []byte } var _ abci.Application = (*testApp)(nil) func (app *testApp) FinalizeBlock(_ context.Context, req *abci.RequestFinalizeBlock) (*abci.ResponseFinalizeBlock, error) { app.CommitVotes = req.DecidedLastCommit.Votes app.Misbehavior = req.Misbehavior app.LastTime = req.Time txResults := make([]*abci.ExecTxResult, len(req.Txs)) for idx := range req.Txs { txResults[idx] = &abci.ExecTxResult{ Code: abci.CodeTypeOK, } } return &abci.ResponseFinalizeBlock{ ValidatorUpdates: app.ValidatorUpdates, ConsensusParamUpdates: &cmtproto.ConsensusParams{ Version: &cmtproto.VersionParams{ App: 1, }, }, TxResults: txResults, AppHash: app.AppHash, }, nil } func (app *testApp) Commit(_ context.Context, _ *abci.RequestCommit) (*abci.ResponseCommit, error) { return &abci.ResponseCommit{RetainHeight: 1}, nil } func (app *testApp) PrepareProposal( _ context.Context, req *abci.RequestPrepareProposal, ) (*abci.ResponsePrepareProposal, error) { txs := make([][]byte, 0, len(req.Txs)) var totalBytes int64 for _, tx := range req.Txs { if len(tx) == 0 { continue } totalBytes += int64(len(tx)) if totalBytes > req.MaxTxBytes { break } txs = append(txs, tx) } return &abci.ResponsePrepareProposal{Txs: txs}, nil } func (app *testApp) ProcessProposal( _ context.Context, req *abci.RequestProcessProposal, ) (*abci.ResponseProcessProposal, error) { for _, tx := range req.Txs { if len(tx) == 0 { return &abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT}, nil } } return &abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_ACCEPT}, nil }