Browse Source

state: synchronize the ProcessProposal implementation with the latest version of the spec (#7961)

This change implements the spec for `ProcessProposal`. It first calls the Tendermint block validation logic to check that all of the proposed block fields are well formed and do not violate any of the rules for Tendermint to consider the block valid and then passes the validated block the `ProcessProposal`.

This change also adds additional fixtures to test the change. It adds the `baseMock` types that holds a mock as well as a reference to `BaseApplication`. If the function was not setup by the test on the contained mock Application, the type delegates to the `BaseApplication` and returns what `BaseApplication` returns. 

The change also switches the `makeState` helper to take an arg struct so that an ABCI application can be plumbed through when needed.

closes: #7656
pull/8015/head
William Banfield 3 years ago
committed by GitHub
parent
commit
c80734e5af
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 3728 additions and 760 deletions
  1. +2
    -0
      abci/client/socket_client.go
  2. +2
    -2
      abci/example/kvstore/kvstore.go
  3. +1
    -0
      abci/types/application.go
  4. +209
    -0
      abci/types/mocks/application.go
  5. +189
    -0
      abci/types/mocks/base.go
  6. +0
    -5
      abci/types/result.go
  7. +3034
    -566
      abci/types/types.pb.go
  8. +29
    -6
      internal/consensus/common_test.go
  9. +5
    -0
      internal/consensus/mempool_test.go
  10. +23
    -24
      internal/consensus/state.go
  11. +102
    -79
      internal/consensus/state_test.go
  12. +41
    -43
      internal/state/execution.go
  13. +67
    -33
      internal/state/execution_test.go
  14. +2
    -2
      internal/state/helpers_test.go
  15. +12
    -0
      test/e2e/app/app.go
  16. +10
    -0
      types/evidence.go

+ 2
- 0
abci/client/socket_client.go View File

@ -387,6 +387,8 @@ func resMatchesReq(req *types.Request, res *types.Response) (ok bool) {
_, ok = res.Value.(*types.Response_Query)
case *types.Request_InitChain:
_, ok = res.Value.(*types.Response_InitChain)
case *types.Request_ProcessProposal:
_, ok = res.Value.(*types.Response_ProcessProposal)
case *types.Request_PrepareProposal:
_, ok = res.Value.(*types.Response_PrepareProposal)
case *types.Request_ExtendVote:


+ 2
- 2
abci/example/kvstore/kvstore.go View File

@ -290,10 +290,10 @@ func (app *Application) PrepareProposal(req types.RequestPrepareProposal) types.
func (*Application) ProcessProposal(req types.RequestProcessProposal) types.ResponseProcessProposal {
for _, tx := range req.Txs {
if len(tx) == 0 {
return types.ResponseProcessProposal{Result: types.ResponseProcessProposal_REJECT}
return types.ResponseProcessProposal{Accept: false}
}
}
return types.ResponseProcessProposal{Result: types.ResponseProcessProposal_ACCEPT}
return types.ResponseProcessProposal{Accept: true}
}
//---------------------------------------------


+ 1
- 0
abci/types/application.go View File

@ -4,6 +4,7 @@ import (
"context"
)
//go:generate ../../scripts/mockery_generate.sh Application
// Application is an interface that enables any finite, deterministic state machine
// to be driven by a blockchain-based replication engine via the ABCI.
// All methods take a RequestXxx argument and return a ResponseXxx argument,


+ 209
- 0
abci/types/mocks/application.go View File

@ -0,0 +1,209 @@
// Code generated by mockery. DO NOT EDIT.
package mocks
import (
mock "github.com/stretchr/testify/mock"
types "github.com/tendermint/tendermint/abci/types"
)
// Application is an autogenerated mock type for the Application type
type Application struct {
mock.Mock
}
// ApplySnapshotChunk provides a mock function with given fields: _a0
func (_m *Application) ApplySnapshotChunk(_a0 types.RequestApplySnapshotChunk) types.ResponseApplySnapshotChunk {
ret := _m.Called(_a0)
var r0 types.ResponseApplySnapshotChunk
if rf, ok := ret.Get(0).(func(types.RequestApplySnapshotChunk) types.ResponseApplySnapshotChunk); ok {
r0 = rf(_a0)
} else {
r0 = ret.Get(0).(types.ResponseApplySnapshotChunk)
}
return r0
}
// CheckTx provides a mock function with given fields: _a0
func (_m *Application) CheckTx(_a0 types.RequestCheckTx) types.ResponseCheckTx {
ret := _m.Called(_a0)
var r0 types.ResponseCheckTx
if rf, ok := ret.Get(0).(func(types.RequestCheckTx) types.ResponseCheckTx); ok {
r0 = rf(_a0)
} else {
r0 = ret.Get(0).(types.ResponseCheckTx)
}
return r0
}
// Commit provides a mock function with given fields:
func (_m *Application) Commit() types.ResponseCommit {
ret := _m.Called()
var r0 types.ResponseCommit
if rf, ok := ret.Get(0).(func() types.ResponseCommit); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(types.ResponseCommit)
}
return r0
}
// ExtendVote provides a mock function with given fields: _a0
func (_m *Application) ExtendVote(_a0 types.RequestExtendVote) types.ResponseExtendVote {
ret := _m.Called(_a0)
var r0 types.ResponseExtendVote
if rf, ok := ret.Get(0).(func(types.RequestExtendVote) types.ResponseExtendVote); ok {
r0 = rf(_a0)
} else {
r0 = ret.Get(0).(types.ResponseExtendVote)
}
return r0
}
// FinalizeBlock provides a mock function with given fields: _a0
func (_m *Application) FinalizeBlock(_a0 types.RequestFinalizeBlock) types.ResponseFinalizeBlock {
ret := _m.Called(_a0)
var r0 types.ResponseFinalizeBlock
if rf, ok := ret.Get(0).(func(types.RequestFinalizeBlock) types.ResponseFinalizeBlock); ok {
r0 = rf(_a0)
} else {
r0 = ret.Get(0).(types.ResponseFinalizeBlock)
}
return r0
}
// Info provides a mock function with given fields: _a0
func (_m *Application) Info(_a0 types.RequestInfo) types.ResponseInfo {
ret := _m.Called(_a0)
var r0 types.ResponseInfo
if rf, ok := ret.Get(0).(func(types.RequestInfo) types.ResponseInfo); ok {
r0 = rf(_a0)
} else {
r0 = ret.Get(0).(types.ResponseInfo)
}
return r0
}
// InitChain provides a mock function with given fields: _a0
func (_m *Application) InitChain(_a0 types.RequestInitChain) types.ResponseInitChain {
ret := _m.Called(_a0)
var r0 types.ResponseInitChain
if rf, ok := ret.Get(0).(func(types.RequestInitChain) types.ResponseInitChain); ok {
r0 = rf(_a0)
} else {
r0 = ret.Get(0).(types.ResponseInitChain)
}
return r0
}
// ListSnapshots provides a mock function with given fields: _a0
func (_m *Application) ListSnapshots(_a0 types.RequestListSnapshots) types.ResponseListSnapshots {
ret := _m.Called(_a0)
var r0 types.ResponseListSnapshots
if rf, ok := ret.Get(0).(func(types.RequestListSnapshots) types.ResponseListSnapshots); ok {
r0 = rf(_a0)
} else {
r0 = ret.Get(0).(types.ResponseListSnapshots)
}
return r0
}
// LoadSnapshotChunk provides a mock function with given fields: _a0
func (_m *Application) LoadSnapshotChunk(_a0 types.RequestLoadSnapshotChunk) types.ResponseLoadSnapshotChunk {
ret := _m.Called(_a0)
var r0 types.ResponseLoadSnapshotChunk
if rf, ok := ret.Get(0).(func(types.RequestLoadSnapshotChunk) types.ResponseLoadSnapshotChunk); ok {
r0 = rf(_a0)
} else {
r0 = ret.Get(0).(types.ResponseLoadSnapshotChunk)
}
return r0
}
// OfferSnapshot provides a mock function with given fields: _a0
func (_m *Application) OfferSnapshot(_a0 types.RequestOfferSnapshot) types.ResponseOfferSnapshot {
ret := _m.Called(_a0)
var r0 types.ResponseOfferSnapshot
if rf, ok := ret.Get(0).(func(types.RequestOfferSnapshot) types.ResponseOfferSnapshot); ok {
r0 = rf(_a0)
} else {
r0 = ret.Get(0).(types.ResponseOfferSnapshot)
}
return r0
}
// PrepareProposal provides a mock function with given fields: _a0
func (_m *Application) PrepareProposal(_a0 types.RequestPrepareProposal) types.ResponsePrepareProposal {
ret := _m.Called(_a0)
var r0 types.ResponsePrepareProposal
if rf, ok := ret.Get(0).(func(types.RequestPrepareProposal) types.ResponsePrepareProposal); ok {
r0 = rf(_a0)
} else {
r0 = ret.Get(0).(types.ResponsePrepareProposal)
}
return r0
}
// ProcessProposal provides a mock function with given fields: _a0
func (_m *Application) ProcessProposal(_a0 types.RequestProcessProposal) types.ResponseProcessProposal {
ret := _m.Called(_a0)
var r0 types.ResponseProcessProposal
if rf, ok := ret.Get(0).(func(types.RequestProcessProposal) types.ResponseProcessProposal); ok {
r0 = rf(_a0)
} else {
r0 = ret.Get(0).(types.ResponseProcessProposal)
}
return r0
}
// Query provides a mock function with given fields: _a0
func (_m *Application) Query(_a0 types.RequestQuery) types.ResponseQuery {
ret := _m.Called(_a0)
var r0 types.ResponseQuery
if rf, ok := ret.Get(0).(func(types.RequestQuery) types.ResponseQuery); ok {
r0 = rf(_a0)
} else {
r0 = ret.Get(0).(types.ResponseQuery)
}
return r0
}
// VerifyVoteExtension provides a mock function with given fields: _a0
func (_m *Application) VerifyVoteExtension(_a0 types.RequestVerifyVoteExtension) types.ResponseVerifyVoteExtension {
ret := _m.Called(_a0)
var r0 types.ResponseVerifyVoteExtension
if rf, ok := ret.Get(0).(func(types.RequestVerifyVoteExtension) types.ResponseVerifyVoteExtension); ok {
r0 = rf(_a0)
} else {
r0 = ret.Get(0).(types.ResponseVerifyVoteExtension)
}
return r0
}

+ 189
- 0
abci/types/mocks/base.go View File

@ -0,0 +1,189 @@
package mocks
import (
types "github.com/tendermint/tendermint/abci/types"
)
// BaseMock provides a wrapper around the generated Application mock and a BaseApplication.
// BaseMock first tries to use the mock's implementation of the method.
// If no functionality was provided for the mock by the user, BaseMock dispatches
// to the BaseApplication and uses its functionality.
// BaseMock allows users to provide mocked functionality for only the methods that matter
// for their test while avoiding a panic if the code calls Application methods that are
// not relevant to the test.
type BaseMock struct {
base *types.BaseApplication
*Application
}
func NewBaseMock() BaseMock {
return BaseMock{
base: types.NewBaseApplication(),
Application: new(Application),
}
}
// Info/Query Connection
// Return application info
func (m BaseMock) Info(input types.RequestInfo) types.ResponseInfo {
var ret types.ResponseInfo
defer func() {
if r := recover(); r != nil {
ret = m.base.Info(input)
}
}()
ret = m.Application.Info(input)
return ret
}
func (m BaseMock) Query(input types.RequestQuery) types.ResponseQuery {
var ret types.ResponseQuery
defer func() {
if r := recover(); r != nil {
ret = m.base.Query(input)
}
}()
ret = m.Application.Query(input)
return ret
}
// Mempool Connection
// Validate a tx for the mempool
func (m BaseMock) CheckTx(input types.RequestCheckTx) types.ResponseCheckTx {
var ret types.ResponseCheckTx
defer func() {
if r := recover(); r != nil {
ret = m.base.CheckTx(input)
}
}()
ret = m.Application.CheckTx(input)
return ret
}
// Consensus Connection
// Initialize blockchain w validators/other info from TendermintCore
func (m BaseMock) InitChain(input types.RequestInitChain) types.ResponseInitChain {
var ret types.ResponseInitChain
defer func() {
if r := recover(); r != nil {
ret = m.base.InitChain(input)
}
}()
ret = m.Application.InitChain(input)
return ret
}
func (m BaseMock) PrepareProposal(input types.RequestPrepareProposal) types.ResponsePrepareProposal {
var ret types.ResponsePrepareProposal
defer func() {
if r := recover(); r != nil {
ret = m.base.PrepareProposal(input)
}
}()
ret = m.Application.PrepareProposal(input)
return ret
}
func (m BaseMock) ProcessProposal(input types.RequestProcessProposal) types.ResponseProcessProposal {
var ret types.ResponseProcessProposal
defer func() {
if r := recover(); r != nil {
ret = m.base.ProcessProposal(input)
}
}()
ret = m.Application.ProcessProposal(input)
return ret
}
// Commit the state and return the application Merkle root hash
func (m BaseMock) Commit() types.ResponseCommit {
var ret types.ResponseCommit
defer func() {
if r := recover(); r != nil {
ret = m.base.Commit()
}
}()
ret = m.Application.Commit()
return ret
}
// Create application specific vote extension
func (m BaseMock) ExtendVote(input types.RequestExtendVote) types.ResponseExtendVote {
var ret types.ResponseExtendVote
defer func() {
if r := recover(); r != nil {
ret = m.base.ExtendVote(input)
}
}()
ret = m.Application.ExtendVote(input)
return ret
}
// Verify application's vote extension data
func (m BaseMock) VerifyVoteExtension(input types.RequestVerifyVoteExtension) types.ResponseVerifyVoteExtension {
var ret types.ResponseVerifyVoteExtension
defer func() {
if r := recover(); r != nil {
ret = m.base.VerifyVoteExtension(input)
}
}()
ret = m.Application.VerifyVoteExtension(input)
return ret
}
// State Sync Connection
// List available snapshots
func (m BaseMock) ListSnapshots(input types.RequestListSnapshots) types.ResponseListSnapshots {
var ret types.ResponseListSnapshots
defer func() {
if r := recover(); r != nil {
ret = m.base.ListSnapshots(input)
}
}()
ret = m.Application.ListSnapshots(input)
return ret
}
func (m BaseMock) OfferSnapshot(input types.RequestOfferSnapshot) types.ResponseOfferSnapshot {
var ret types.ResponseOfferSnapshot
defer func() {
if r := recover(); r != nil {
ret = m.base.OfferSnapshot(input)
}
}()
ret = m.Application.OfferSnapshot(input)
return ret
}
func (m BaseMock) LoadSnapshotChunk(input types.RequestLoadSnapshotChunk) types.ResponseLoadSnapshotChunk {
var ret types.ResponseLoadSnapshotChunk
defer func() {
if r := recover(); r != nil {
ret = m.base.LoadSnapshotChunk(input)
}
}()
ret = m.Application.LoadSnapshotChunk(input)
return ret
}
func (m BaseMock) ApplySnapshotChunk(input types.RequestApplySnapshotChunk) types.ResponseApplySnapshotChunk {
var ret types.ResponseApplySnapshotChunk
defer func() {
if r := recover(); r != nil {
ret = m.base.ApplySnapshotChunk(input)
}
}()
ret = m.Application.ApplySnapshotChunk(input)
return ret
}
func (m BaseMock) FinalizeBlock(input types.RequestFinalizeBlock) types.ResponseFinalizeBlock {
var ret types.ResponseFinalizeBlock
defer func() {
if r := recover(); r != nil {
ret = m.base.FinalizeBlock(input)
}
}()
ret = m.Application.FinalizeBlock(input)
return ret
}

+ 0
- 5
abci/types/result.go View File

@ -58,11 +58,6 @@ func (r ResponseVerifyVoteExtension) IsErr() bool {
return r.Result != ResponseVerifyVoteExtension_ACCEPT
}
// IsOK returns true if Code is OK
func (r ResponseProcessProposal) IsOK() bool {
return r.Result == ResponseProcessProposal_ACCEPT
}
//---------------------------------------------------------------------------
// override JSON marshaling so we emit defaults (ie. disable omitempty)


+ 3034
- 566
abci/types/types.pb.go
File diff suppressed because it is too large
View File


+ 29
- 6
internal/consensus/common_test.go View File

@ -506,18 +506,41 @@ func loadPrivValidator(t *testing.T, cfg *config.Config) *privval.FilePV {
return privValidator
}
func makeState(ctx context.Context, t *testing.T, cfg *config.Config, logger log.Logger, nValidators int) (*State, []*validatorStub) {
type makeStateArgs struct {
config *config.Config
logger log.Logger
validators int
application abci.Application
}
func makeState(ctx context.Context, t *testing.T, args makeStateArgs) (*State, []*validatorStub) {
t.Helper()
// Get State
state, privVals := makeGenesisState(ctx, t, cfg, genesisStateArgs{
Validators: nValidators,
validators := 4
if args.validators != 0 {
validators = args.validators
}
var app abci.Application
app = kvstore.NewApplication()
if args.application != nil {
app = args.application
}
if args.config == nil {
args.config = configSetup(t)
}
if args.logger == nil {
args.logger = log.NewNopLogger()
}
state, privVals := makeGenesisState(ctx, t, args.config, genesisStateArgs{
Validators: validators,
})
vss := make([]*validatorStub, nValidators)
vss := make([]*validatorStub, validators)
cs := newState(ctx, t, logger, state, privVals[0], kvstore.NewApplication())
cs := newState(ctx, t, args.logger, state, privVals[0], app)
for i := 0; i < nValidators; i++ {
for i := 0; i < validators; i++ {
vss[i] = newValidatorStub(privVals[i], int32(i))
}
// since cs1 starts at 1


+ 5
- 0
internal/consensus/mempool_test.go View File

@ -312,3 +312,8 @@ func (app *CounterApplication) PrepareProposal(
req abci.RequestPrepareProposal) abci.ResponsePrepareProposal {
return abci.ResponsePrepareProposal{BlockData: req.BlockData}
}
func (app *CounterApplication) ProcessProposal(
req abci.RequestProcessProposal) abci.ResponseProcessProposal {
return abci.ResponseProcessProposal{Accept: true}
}

+ 23
- 24
internal/consensus/state.go View File

@ -1452,6 +1452,29 @@ func (cs *State) defaultDoPrevote(ctx context.Context, height int64, round int32
return
}
/*
The block has now passed Tendermint's validation rules.
Before prevoting the block received from the proposer for the current round and height,
we request the Application, via the ProcessProposal, ABCI call to confirm that the block is
valid. If the Application does not accept the block, Tendermint prevotes nil.
WARNING: misuse of block rejection by the Application can seriously compromise Tendermint's
liveness properties. Please see PrepareProposal-ProcessProposal coherence and determinism
properties in the ABCI++ specification.
*/
isAppValid, err := cs.blockExec.ProcessProposal(ctx, cs.ProposalBlock, cs.state)
if err != nil {
panic(fmt.Sprintf("ProcessProposal: %v", err))
}
// Vote nil if the Application rejected the block
if !isAppValid {
logger.Error("prevote step: state machine rejected a proposed block; this should not happen:"+
"the proposer may be misbehaving; prevoting nil", "err", err)
cs.signAddVote(ctx, tmproto.PrevoteType, nil, types.PartSetHeader{})
return
}
/*
22: upon <PROPOSAL, h_p, round_p, v, 1> from proposer(h_p, round_p) while step_p = propose do
23: if valid(v) && (lockedRound_p = 1 || lockedValue_p = v) then
@ -1510,30 +1533,6 @@ func (cs *State) defaultDoPrevote(ctx context.Context, height int64, round int32
}
}
/*
Before prevoting on the block received from the proposer for the current round and height,
we request the Application, via `ProcessProposal` ABCI call, to confirm that the block is
valid. If the Application does not accept the block, Tendermint prevotes `nil`.
WARNING: misuse of block rejection by the Application can seriously compromise Tendermint's
liveness properties. Please see `PrepareProosal`-`ProcessProposal` coherence and determinism
properties in the ABCI++ specification.
*/
stateMachineValidBlock, err := cs.blockExec.ProcessProposal(ctx, cs.ProposalBlock)
if err != nil {
panic(fmt.Sprintf(
"state machine returned an error (%v) when calling ProcessProposal", err,
))
}
// Vote nil if the Application rejected the block
if !stateMachineValidBlock {
logger.Error("prevote step: state machine rejected a proposed block; this should not happen:"+
"the proposer may be misbehaving; prevoting nil", "err", err)
cs.signAddVote(ctx, tmproto.PrevoteType, nil, types.PartSetHeader{})
return
}
logger.Debug("prevote step: ProposalBlock is valid but was not our locked block or " +
"did not receive a more recent majority; prevoting nil")
cs.signAddVote(ctx, tmproto.PrevoteType, nil, types.PartSetHeader{})


+ 102
- 79
internal/consensus/state_test.go View File

@ -8,14 +8,18 @@ import (
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"github.com/tendermint/tendermint/abci/example/kvstore"
abcitypes "github.com/tendermint/tendermint/abci/types"
abcimocks "github.com/tendermint/tendermint/abci/types/mocks"
"github.com/tendermint/tendermint/crypto/tmhash"
cstypes "github.com/tendermint/tendermint/internal/consensus/types"
"github.com/tendermint/tendermint/internal/eventbus"
tmpubsub "github.com/tendermint/tendermint/internal/pubsub"
tmquery "github.com/tendermint/tendermint/internal/pubsub/query"
tmbytes "github.com/tendermint/tendermint/libs/bytes"
"github.com/tendermint/tendermint/libs/log"
tmrand "github.com/tendermint/tendermint/libs/rand"
tmtime "github.com/tendermint/tendermint/libs/time"
@ -68,9 +72,8 @@ func TestStateProposerSelection0(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
config := configSetup(t)
logger := log.NewNopLogger()
cs1, vss := makeState(ctx, t, config, logger, 4)
cs1, vss := makeState(ctx, t, makeStateArgs{config: config})
height, round := cs1.Height, cs1.Round
newRoundCh := subscribe(ctx, t, cs1.eventBus, types.EventQueryNewRound)
@ -110,11 +113,10 @@ func TestStateProposerSelection0(t *testing.T) {
// Now let's do it all again, but starting from round 2 instead of 0
func TestStateProposerSelection2(t *testing.T) {
config := configSetup(t)
logger := log.NewNopLogger()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
cs1, vss := makeState(ctx, t, config, logger, 4) // test needs more work for more than 3 validators
cs1, vss := makeState(ctx, t, makeStateArgs{config: config}) // test needs more work for more than 3 validators
height := cs1.Height
newRoundCh := subscribe(ctx, t, cs1.eventBus, types.EventQueryNewRound)
@ -149,11 +151,10 @@ func TestStateProposerSelection2(t *testing.T) {
// a non-validator should timeout into the prevote round
func TestStateEnterProposeNoPrivValidator(t *testing.T) {
config := configSetup(t)
logger := log.NewNopLogger()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
cs, _ := makeState(ctx, t, config, logger, 1)
cs, _ := makeState(ctx, t, makeStateArgs{config: config, validators: 1})
cs.SetPrivValidator(ctx, nil)
height, round := cs.Height, cs.Round
@ -173,11 +174,10 @@ func TestStateEnterProposeNoPrivValidator(t *testing.T) {
// a validator should not timeout of the prevote round (TODO: unless the block is really big!)
func TestStateEnterProposeYesPrivValidator(t *testing.T) {
config := configSetup(t)
logger := log.NewNopLogger()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
cs, _ := makeState(ctx, t, config, logger, 1)
cs, _ := makeState(ctx, t, makeStateArgs{config: config, validators: 1})
height, round := cs.Height, cs.Round
// Listen for propose timeout event
@ -208,11 +208,10 @@ func TestStateEnterProposeYesPrivValidator(t *testing.T) {
func TestStateBadProposal(t *testing.T) {
config := configSetup(t)
logger := log.NewNopLogger()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
cs1, vss := makeState(ctx, t, config, logger, 2)
cs1, vss := makeState(ctx, t, makeStateArgs{config: config, validators: 2})
height, round := cs1.Height, cs1.Round
vs2 := vss[1]
@ -270,11 +269,10 @@ func TestStateBadProposal(t *testing.T) {
func TestStateOversizedBlock(t *testing.T) {
config := configSetup(t)
logger := log.NewNopLogger()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
cs1, vss := makeState(ctx, t, config, logger, 2)
cs1, vss := makeState(ctx, t, makeStateArgs{config: config, validators: 2})
cs1.state.ConsensusParams.Block.MaxBytes = 2000
height, round := cs1.Height, cs1.Round
vs2 := vss[1]
@ -336,11 +334,10 @@ func TestStateOversizedBlock(t *testing.T) {
// propose, prevote, and precommit a block
func TestStateFullRound1(t *testing.T) {
config := configSetup(t)
logger := log.NewNopLogger()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
cs, vss := makeState(ctx, t, config, logger, 1)
cs, vss := makeState(ctx, t, makeStateArgs{config: config, validators: 1})
height, round := cs.Height, cs.Round
voteCh := subscribe(ctx, t, cs.eventBus, types.EventQueryVote)
@ -367,11 +364,10 @@ func TestStateFullRound1(t *testing.T) {
// nil is proposed, so prevote and precommit nil
func TestStateFullRoundNil(t *testing.T) {
config := configSetup(t)
logger := log.NewNopLogger()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
cs, _ := makeState(ctx, t, config, logger, 1)
cs, _ := makeState(ctx, t, makeStateArgs{config: config, validators: 1})
height, round := cs.Height, cs.Round
voteCh := subscribe(ctx, t, cs.eventBus, types.EventQueryVote)
@ -387,11 +383,10 @@ func TestStateFullRoundNil(t *testing.T) {
// where the first validator has to wait for votes from the second
func TestStateFullRound2(t *testing.T) {
config := configSetup(t)
logger := log.NewNopLogger()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
cs1, vss := makeState(ctx, t, config, logger, 2)
cs1, vss := makeState(ctx, t, makeStateArgs{config: config, validators: 2})
vs2 := vss[1]
height, round := cs1.Height, cs1.Round
@ -432,11 +427,10 @@ func TestStateFullRound2(t *testing.T) {
// two vals take turns proposing. val1 locks on first one, precommits nil on everything else
func TestStateLock_NoPOL(t *testing.T) {
config := configSetup(t)
logger := log.NewNopLogger()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
cs1, vss := makeState(ctx, t, config, logger, 2)
cs1, vss := makeState(ctx, t, makeStateArgs{config: config, validators: 2})
vs2 := vss[1]
height, round := cs1.Height, cs1.Round
@ -580,7 +574,9 @@ func TestStateLock_NoPOL(t *testing.T) {
ensureNewTimeout(t, timeoutWaitCh, height, round, cs1.config.Precommit(round).Nanoseconds())
cs2, _ := makeState(ctx, t, config, logger, 2) // needed so generated block is different than locked block
// cs1 is locked on a block at this point, so we must generate a new consensus
// state to force a new proposal block to be generated.
cs2, _ := makeState(ctx, t, makeStateArgs{config: config, validators: 2})
// before we time out into new round, set next proposal block
prop, propBlock := decideProposal(ctx, t, cs2, vs2, vs2.Height, vs2.Round+1)
require.NotNil(t, propBlock, "Failed to create proposal block with vs2")
@ -643,7 +639,7 @@ func TestStateLock_POLUpdateLock(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
cs1, vss := makeState(ctx, t, config, logger, 4)
cs1, vss := makeState(ctx, t, makeStateArgs{config: config, logger: logger})
vs2, vs3, vs4 := vss[1], vss[2], vss[3]
height, round := cs1.Height, cs1.Round
@ -751,9 +747,8 @@ func TestStateLock_POLRelock(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
config := configSetup(t)
logger := log.NewNopLogger()
cs1, vss := makeState(ctx, t, config, logger, 4)
cs1, vss := makeState(ctx, t, makeStateArgs{config: config})
vs2, vs3, vs4 := vss[1], vss[2], vss[3]
height, round := cs1.Height, cs1.Round
@ -851,9 +846,8 @@ func TestStateLock_PrevoteNilWhenLockedAndMissProposal(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
config := configSetup(t)
logger := log.NewNopLogger()
cs1, vss := makeState(ctx, t, config, logger, 4)
cs1, vss := makeState(ctx, t, makeStateArgs{config: config})
vs2, vs3, vs4 := vss[1], vss[2], vss[3]
height, round := cs1.Height, cs1.Round
@ -942,7 +936,7 @@ func TestStateLock_PrevoteNilWhenLockedAndDifferentProposal(t *testing.T) {
state.
*/
cs1, vss := makeState(ctx, t, config, logger, 4)
cs1, vss := makeState(ctx, t, makeStateArgs{config: config, logger: logger})
vs2, vs3, vs4 := vss[1], vss[2], vss[3]
height, round := cs1.Height, cs1.Round
@ -1044,7 +1038,7 @@ func TestStateLock_POLDoesNotUnlock(t *testing.T) {
state.
*/
cs1, vss := makeState(ctx, t, config, logger, 4)
cs1, vss := makeState(ctx, t, makeStateArgs{config: config, logger: logger})
vs2, vs3, vs4 := vss[1], vss[2], vss[3]
height, round := cs1.Height, cs1.Round
@ -1178,7 +1172,7 @@ func TestStateLock_MissingProposalWhenPOLSeenDoesNotUpdateLock(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
cs1, vss := makeState(ctx, t, config, logger, 4)
cs1, vss := makeState(ctx, t, makeStateArgs{config: config, logger: logger})
vs2, vs3, vs4 := vss[1], vss[2], vss[3]
height, round := cs1.Height, cs1.Round
@ -1267,9 +1261,8 @@ func TestStateLock_DoesNotLockOnOldProposal(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
config := configSetup(t)
logger := log.NewNopLogger()
cs1, vss := makeState(ctx, t, config, logger, 4)
cs1, vss := makeState(ctx, t, makeStateArgs{config: config})
vs2, vs3, vs4 := vss[1], vss[2], vss[3]
height, round := cs1.Height, cs1.Round
@ -1347,7 +1340,7 @@ func TestStateLock_POLSafety1(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
cs1, vss := makeState(ctx, t, config, logger, 4)
cs1, vss := makeState(ctx, t, makeStateArgs{config: config, logger: logger})
vs2, vs3, vs4 := vss[1], vss[2], vss[3]
height, round := cs1.Height, cs1.Round
@ -1464,11 +1457,10 @@ func TestStateLock_POLSafety1(t *testing.T) {
// dont see P0, lock on P1 at R1, dont unlock using P0 at R2
func TestStateLock_POLSafety2(t *testing.T) {
config := configSetup(t)
logger := log.NewNopLogger()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
cs1, vss := makeState(ctx, t, config, logger, 4)
cs1, vss := makeState(ctx, t, makeStateArgs{config: config})
vs2, vs3, vs4 := vss[1], vss[2], vss[3]
height, round := cs1.Height, cs1.Round
@ -1564,7 +1556,7 @@ func TestState_PrevotePOLFromPreviousRound(t *testing.T) {
config := configSetup(t)
logger := log.NewNopLogger()
cs1, vss := makeState(ctx, t, config, logger, 4)
cs1, vss := makeState(ctx, t, makeStateArgs{config: config, logger: logger})
vs2, vs3, vs4 := vss[1], vss[2], vss[3]
height, round := cs1.Height, cs1.Round
@ -1702,12 +1694,11 @@ func TestState_PrevotePOLFromPreviousRound(t *testing.T) {
// What we want:
// P0 proposes B0 at R3.
func TestProposeValidBlock(t *testing.T) {
cfg := configSetup(t)
logger := log.NewNopLogger()
config := configSetup(t)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
cs1, vss := makeState(ctx, t, cfg, logger, 4)
cs1, vss := makeState(ctx, t, makeStateArgs{config: config})
vs2, vs3, vs4 := vss[1], vss[2], vss[3]
height, round := cs1.Height, cs1.Round
@ -1739,14 +1730,14 @@ func TestProposeValidBlock(t *testing.T) {
ensurePrevoteMatch(t, voteCh, height, round, blockID.Hash)
// the others sign a polka
signAddVotes(ctx, t, cs1, tmproto.PrevoteType, cfg.ChainID(), blockID, vs2, vs3, vs4)
signAddVotes(ctx, t, cs1, tmproto.PrevoteType, config.ChainID(), blockID, vs2, vs3, vs4)
ensurePrecommit(t, voteCh, height, round)
// we should have precommitted the proposed block in this round.
validatePrecommit(ctx, t, cs1, round, round, vss[0], blockID.Hash, blockID.Hash)
signAddVotes(ctx, t, cs1, tmproto.PrecommitType, cfg.ChainID(), types.BlockID{}, vs2, vs3, vs4)
signAddVotes(ctx, t, cs1, tmproto.PrecommitType, config.ChainID(), types.BlockID{}, vs2, vs3, vs4)
ensureNewTimeout(t, timeoutWaitCh, height, round, cs1.config.Precommit(round).Nanoseconds())
@ -1762,7 +1753,7 @@ func TestProposeValidBlock(t *testing.T) {
// We did not see a valid proposal within this round, so prevote nil.
ensurePrevoteMatch(t, voteCh, height, round, nil)
signAddVotes(ctx, t, cs1, tmproto.PrecommitType, cfg.ChainID(), types.BlockID{}, vs2, vs3, vs4)
signAddVotes(ctx, t, cs1, tmproto.PrecommitType, config.ChainID(), types.BlockID{}, vs2, vs3, vs4)
ensurePrecommit(t, voteCh, height, round)
// we should have precommitted nil during this round because we received
@ -1772,7 +1763,7 @@ func TestProposeValidBlock(t *testing.T) {
incrementRound(vs2, vs3, vs4)
incrementRound(vs2, vs3, vs4)
signAddVotes(ctx, t, cs1, tmproto.PrecommitType, cfg.ChainID(), types.BlockID{}, vs2, vs3, vs4)
signAddVotes(ctx, t, cs1, tmproto.PrecommitType, config.ChainID(), types.BlockID{}, vs2, vs3, vs4)
round += 2 // increment by multiple rounds
@ -1798,11 +1789,10 @@ func TestProposeValidBlock(t *testing.T) {
// P0 miss to lock B but set valid block to B after receiving delayed prevote.
func TestSetValidBlockOnDelayedPrevote(t *testing.T) {
config := configSetup(t)
logger := log.NewNopLogger()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
cs1, vss := makeState(ctx, t, config, logger, 4)
cs1, vss := makeState(ctx, t, makeStateArgs{config: config})
vs2, vs3, vs4 := vss[1], vss[2], vss[3]
height, round := cs1.Height, cs1.Round
@ -1868,11 +1858,10 @@ func TestSetValidBlockOnDelayedPrevote(t *testing.T) {
// receiving delayed Block Proposal.
func TestSetValidBlockOnDelayedProposal(t *testing.T) {
config := configSetup(t)
logger := log.NewNopLogger()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
cs1, vss := makeState(ctx, t, config, logger, 4)
cs1, vss := makeState(ctx, t, makeStateArgs{config: config})
vs2, vs3, vs4 := vss[1], vss[2], vss[3]
height, round := cs1.Height, cs1.Round
@ -1928,6 +1917,54 @@ func TestSetValidBlockOnDelayedProposal(t *testing.T) {
assert.True(t, rs.ValidRound == round)
}
func TestProcessProposalAccept(t *testing.T) {
for _, testCase := range []struct {
name string
accept bool
expectedNilPrevote bool
}{
{
name: "accepted block is prevoted",
accept: true,
expectedNilPrevote: false,
},
{
name: "rejected block is not prevoted",
accept: false,
expectedNilPrevote: true,
},
} {
t.Run(testCase.name, func(t *testing.T) {
config := configSetup(t)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
m := abcimocks.NewBaseMock()
m.On("ProcessProposal", mock.Anything).Return(abcitypes.ResponseProcessProposal{Accept: testCase.accept})
cs1, _ := makeState(ctx, t, makeStateArgs{config: config, application: m})
height, round := cs1.Height, cs1.Round
proposalCh := subscribe(ctx, t, cs1.eventBus, types.EventQueryCompleteProposal)
newRoundCh := subscribe(ctx, t, cs1.eventBus, types.EventQueryNewRound)
pv1, err := cs1.privValidator.GetPubKey(ctx)
require.NoError(t, err)
addr := pv1.Address()
voteCh := subscribeToVoter(ctx, t, cs1, addr)
startTestRound(ctx, cs1, cs1.Height, round)
ensureNewRound(t, newRoundCh, height, round)
ensureNewProposal(t, proposalCh, height, round)
rs := cs1.GetRoundState()
var prevoteHash tmbytes.HexBytes
if !testCase.expectedNilPrevote {
prevoteHash = rs.ProposalBlock.Hash()
}
ensurePrevoteMatch(t, voteCh, height, round, prevoteHash)
})
}
}
// 4 vals, 3 Nil Precommits at P0
// What we want:
// P0 waits for timeoutPrecommit before starting next round
@ -1935,9 +1972,8 @@ func TestWaitingTimeoutOnNilPolka(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
config := configSetup(t)
logger := log.NewNopLogger()
cs1, vss := makeState(ctx, t, config, logger, 4)
cs1, vss := makeState(ctx, t, makeStateArgs{config: config})
vs2, vs3, vs4 := vss[1], vss[2], vss[3]
height, round := cs1.Height, cs1.Round
@ -1959,11 +1995,10 @@ func TestWaitingTimeoutOnNilPolka(t *testing.T) {
// P0 waits for timeoutPropose in the next round before entering prevote
func TestWaitingTimeoutProposeOnNewRound(t *testing.T) {
config := configSetup(t)
logger := log.NewNopLogger()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
cs1, vss := makeState(ctx, t, config, logger, 4)
cs1, vss := makeState(ctx, t, makeStateArgs{config: config})
vs2, vs3, vs4 := vss[1], vss[2], vss[3]
height, round := cs1.Height, cs1.Round
@ -1999,11 +2034,10 @@ func TestWaitingTimeoutProposeOnNewRound(t *testing.T) {
// P0 jump to higher round, precommit and start precommit wait
func TestRoundSkipOnNilPolkaFromHigherRound(t *testing.T) {
config := configSetup(t)
logger := log.NewNopLogger()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
cs1, vss := makeState(ctx, t, config, logger, 4)
cs1, vss := makeState(ctx, t, makeStateArgs{config: config})
vs2, vs3, vs4 := vss[1], vss[2], vss[3]
height, round := cs1.Height, cs1.Round
@ -2040,11 +2074,10 @@ func TestRoundSkipOnNilPolkaFromHigherRound(t *testing.T) {
// P0 wait for timeoutPropose to expire before sending prevote.
func TestWaitTimeoutProposeOnNilPolkaForTheCurrentRound(t *testing.T) {
config := configSetup(t)
logger := log.NewNopLogger()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
cs1, vss := makeState(ctx, t, config, logger, 4)
cs1, vss := makeState(ctx, t, makeStateArgs{config: config})
vs2, vs3, vs4 := vss[1], vss[2], vss[3]
height, round := cs1.Height, int32(1)
@ -2071,11 +2104,10 @@ func TestWaitTimeoutProposeOnNilPolkaForTheCurrentRound(t *testing.T) {
// P0 emit NewValidBlock event upon receiving 2/3+ Precommit for B but hasn't received block B yet
func TestEmitNewValidBlockEventOnCommitWithoutBlock(t *testing.T) {
config := configSetup(t)
logger := log.NewNopLogger()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
cs1, vss := makeState(ctx, t, config, logger, 4)
cs1, vss := makeState(ctx, t, makeStateArgs{config: config})
vs2, vs3, vs4 := vss[1], vss[2], vss[3]
height, round := cs1.Height, int32(1)
@ -2114,11 +2146,10 @@ func TestEmitNewValidBlockEventOnCommitWithoutBlock(t *testing.T) {
// After receiving block, it executes block and moves to the next height.
func TestCommitFromPreviousRound(t *testing.T) {
config := configSetup(t)
logger := log.NewNopLogger()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
cs1, vss := makeState(ctx, t, config, logger, 4)
cs1, vss := makeState(ctx, t, makeStateArgs{config: config})
vs2, vs3, vs4 := vss[1], vss[2], vss[3]
height, round := cs1.Height, int32(1)
@ -2176,12 +2207,11 @@ func (n *fakeTxNotifier) Notify() {
// start of the next round
func TestStartNextHeightCorrectlyAfterTimeout(t *testing.T) {
config := configSetup(t)
logger := log.NewNopLogger()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
config.Consensus.SkipTimeoutCommit = false
cs1, vss := makeState(ctx, t, config, logger, 4)
cs1, vss := makeState(ctx, t, makeStateArgs{config: config})
cs1.txNotifier = &fakeTxNotifier{ch: make(chan struct{})}
vs2, vs3, vs4 := vss[1], vss[2], vss[3]
@ -2243,12 +2273,11 @@ func TestStartNextHeightCorrectlyAfterTimeout(t *testing.T) {
func TestResetTimeoutPrecommitUponNewHeight(t *testing.T) {
config := configSetup(t)
logger := log.NewNopLogger()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
config.Consensus.SkipTimeoutCommit = false
cs1, vss := makeState(ctx, t, config, logger, 4)
cs1, vss := makeState(ctx, t, makeStateArgs{config: config})
vs2, vs3, vs4 := vss[1], vss[2], vss[3]
height, round := cs1.Height, cs1.Round
@ -2313,12 +2342,11 @@ func TestResetTimeoutPrecommitUponNewHeight(t *testing.T) {
// 4 vals.
// we receive a final precommit after going into next round, but others might have gone to commit already!
func TestStateHalt1(t *testing.T) {
cfg := configSetup(t)
logger := log.NewNopLogger()
config := configSetup(t)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
cs1, vss := makeState(ctx, t, cfg, logger, 4)
cs1, vss := makeState(ctx, t, makeStateArgs{config: config})
vs2, vs3, vs4 := vss[1], vss[2], vss[3]
height, round := cs1.Height, cs1.Round
partSize := types.BlockPartSizeBytes
@ -2348,17 +2376,17 @@ func TestStateHalt1(t *testing.T) {
ensurePrevote(t, voteCh, height, round)
signAddVotes(ctx, t, cs1, tmproto.PrevoteType, cfg.ChainID(), blockID, vs2, vs3, vs4)
signAddVotes(ctx, t, cs1, tmproto.PrevoteType, config.ChainID(), blockID, vs2, vs3, vs4)
ensurePrecommit(t, voteCh, height, round)
// the proposed block should now be locked and our precommit added
validatePrecommit(ctx, t, cs1, round, round, vss[0], propBlock.Hash(), propBlock.Hash())
// add precommits from the rest
signAddVotes(ctx, t, cs1, tmproto.PrecommitType, cfg.ChainID(), types.BlockID{}, vs2) // didnt receive proposal
signAddVotes(ctx, t, cs1, tmproto.PrecommitType, cfg.ChainID(), blockID, vs3)
signAddVotes(ctx, t, cs1, tmproto.PrecommitType, config.ChainID(), types.BlockID{}, vs2) // didnt receive proposal
signAddVotes(ctx, t, cs1, tmproto.PrecommitType, config.ChainID(), blockID, vs3)
// we receive this later, but vs3 might receive it earlier and with ours will go to commit!
precommit4 := signVote(ctx, t, vs4, tmproto.PrecommitType, cfg.ChainID(), blockID)
precommit4 := signVote(ctx, t, vs4, tmproto.PrecommitType, config.ChainID(), blockID)
incrementRound(vs2, vs3, vs4)
@ -2389,12 +2417,11 @@ func TestStateHalt1(t *testing.T) {
func TestStateOutputsBlockPartsStats(t *testing.T) {
config := configSetup(t)
logger := log.NewNopLogger()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// create dummy peer
cs, _ := makeState(ctx, t, config, logger, 1)
cs, _ := makeState(ctx, t, makeStateArgs{config: config, validators: 1})
peerID, err := types.NewNodeID("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")
require.NoError(t, err)
@ -2438,11 +2465,10 @@ func TestStateOutputsBlockPartsStats(t *testing.T) {
func TestStateOutputVoteStats(t *testing.T) {
config := configSetup(t)
logger := log.NewNopLogger()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
cs, vss := makeState(ctx, t, config, logger, 2)
cs, vss := makeState(ctx, t, makeStateArgs{config: config, validators: 2})
// create dummy peer
peerID, err := types.NewNodeID("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")
require.NoError(t, err)
@ -2480,11 +2506,10 @@ func TestStateOutputVoteStats(t *testing.T) {
func TestSignSameVoteTwice(t *testing.T) {
config := configSetup(t)
logger := log.NewNopLogger()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
_, vss := makeState(ctx, t, config, logger, 2)
_, vss := makeState(ctx, t, makeStateArgs{config: config, validators: 2})
randBytes := tmrand.Bytes(tmhash.Size)
@ -2521,11 +2546,10 @@ func TestSignSameVoteTwice(t *testing.T) {
// corresponding proposal message.
func TestStateTimestamp_ProposalNotMatch(t *testing.T) {
config := configSetup(t)
logger := log.NewNopLogger()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
cs1, vss := makeState(ctx, t, config, logger, 4)
cs1, vss := makeState(ctx, t, makeStateArgs{config: config})
height, round := cs1.Height, cs1.Round
vs2, vs3, vs4 := vss[1], vss[2], vss[3]
@ -2570,11 +2594,10 @@ func TestStateTimestamp_ProposalNotMatch(t *testing.T) {
// corresponding proposal message.
func TestStateTimestamp_ProposalMatch(t *testing.T) {
config := configSetup(t)
logger := log.NewNopLogger()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
cs1, vss := makeState(ctx, t, config, logger, 4)
cs1, vss := makeState(ctx, t, makeStateArgs{config: config})
height, round := cs1.Height, cs1.Round
vs2, vs3, vs4 := vss[1], vss[2], vss[3]


+ 41
- 43
internal/state/execution.go View File

@ -156,10 +156,14 @@ func (blockExec *BlockExecutor) CreateProposalBlock(
func (blockExec *BlockExecutor) ProcessProposal(
ctx context.Context,
block *types.Block,
state State,
) (bool, error) {
req := abci.RequestProcessProposal{
Txs: block.Data.Txs.ToSliceOfBytes(),
Header: *block.Header.ToProto(),
Hash: block.Header.Hash(),
Header: *block.Header.ToProto(),
Txs: block.Data.Txs.ToSliceOfBytes(),
LastCommitInfo: buildLastCommitInfo(block, blockExec.store, state.InitialHeight),
ByzantineValidators: block.Evidence.ToABCI(),
}
resp, err := blockExec.proxyApp.ProcessProposal(ctx, req)
@ -167,7 +171,7 @@ func (blockExec *BlockExecutor) ProcessProposal(
return false, ErrInvalidBlock(err)
}
return resp.IsOK(), nil
return resp.Accept, nil
}
// ValidateBlock validates the given block against the given state.
@ -381,13 +385,6 @@ func execBlockOnProxyApp(
dtxs := make([]*abci.ResponseDeliverTx, len(block.Txs))
abciResponses.FinalizeBlock.Txs = dtxs
commitInfo := getBeginBlockValidatorInfo(block, store, initialHeight)
byzVals := make([]abci.Evidence, 0)
for _, evidence := range block.Evidence {
byzVals = append(byzVals, evidence.ABCI()...)
}
// Begin block
var err error
pbh := block.Header.ToProto()
@ -398,12 +395,12 @@ func execBlockOnProxyApp(
abciResponses.FinalizeBlock, err = proxyAppConn.FinalizeBlock(
ctx,
abci.RequestFinalizeBlock{
Txs: block.Txs.ToSliceOfBytes(),
Hash: block.Hash(),
Header: *pbh,
Height: block.Height,
LastCommitInfo: commitInfo,
ByzantineValidators: byzVals,
Txs: block.Txs.ToSliceOfBytes(),
LastCommitInfo: buildLastCommitInfo(block, store, initialHeight),
ByzantineValidators: block.Evidence.ToABCI(),
},
)
if err != nil {
@ -414,43 +411,44 @@ func execBlockOnProxyApp(
return abciResponses, nil
}
func getBeginBlockValidatorInfo(block *types.Block, store Store,
initialHeight int64) abci.LastCommitInfo {
voteInfos := make([]abci.VoteInfo, block.LastCommit.Size())
// Initial block -> LastCommitInfo.Votes are empty.
// Remember that the first LastCommit is intentionally empty, so it makes
// sense for LastCommitInfo.Votes to also be empty.
if block.Height > initialHeight {
lastValSet, err := store.LoadValidators(block.Height - 1)
if err != nil {
panic(err)
}
func buildLastCommitInfo(block *types.Block, store Store, initialHeight int64) abci.LastCommitInfo {
if block.Height == initialHeight {
// there is no last commmit for the initial height.
// return an empty value.
return abci.LastCommitInfo{}
}
// Sanity check that commit size matches validator set size - only applies
// after first block.
var (
commitSize = block.LastCommit.Size()
valSetLen = len(lastValSet.Validators)
)
if commitSize != valSetLen {
panic(fmt.Sprintf(
"commit size (%d) doesn't match valset length (%d) at height %d\n\n%v\n\n%v",
commitSize, valSetLen, block.Height, block.LastCommit.Signatures, lastValSet.Validators,
))
}
lastValSet, err := store.LoadValidators(block.Height - 1)
if err != nil {
panic(err)
}
for i, val := range lastValSet.Validators {
commitSig := block.LastCommit.Signatures[i]
voteInfos[i] = abci.VoteInfo{
Validator: types.TM2PB.Validator(val),
SignedLastBlock: !commitSig.Absent(),
}
var (
commitSize = block.LastCommit.Size()
valSetLen = len(lastValSet.Validators)
)
// ensure that the size of the validator set in the last commit matches
// the size of the validator set in the state store.
if commitSize != valSetLen {
panic(fmt.Sprintf(
"commit size (%d) doesn't match validator set length (%d) at height %d\n\n%v\n\n%v",
commitSize, valSetLen, block.Height, block.LastCommit.Signatures, lastValSet.Validators,
))
}
votes := make([]abci.VoteInfo, block.LastCommit.Size())
for i, val := range lastValSet.Validators {
commitSig := block.LastCommit.Signatures[i]
votes[i] = abci.VoteInfo{
Validator: types.TM2PB.Validator(val),
SignedLastBlock: !commitSig.Absent(),
}
}
return abci.LastCommitInfo{
Round: block.LastCommit.Round,
Votes: voteInfos,
Votes: votes,
}
}


+ 67
- 33
internal/state/execution_test.go View File

@ -12,6 +12,7 @@ import (
abciclient "github.com/tendermint/tendermint/abci/client"
abci "github.com/tendermint/tendermint/abci/types"
abcimocks "github.com/tendermint/tendermint/abci/types/mocks"
"github.com/tendermint/tendermint/crypto"
"github.com/tendermint/tendermint/crypto/ed25519"
"github.com/tendermint/tendermint/crypto/encoding"
@ -241,44 +242,77 @@ func TestBeginBlockByzantineValidators(t *testing.T) {
}
func TestProcessProposal(t *testing.T) {
height := 1
runTest := func(txs types.Txs, expectAccept bool) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
app := &testApp{}
cc := abciclient.NewLocalCreator(app)
logger := log.TestingLogger()
proxyApp := proxy.NewAppConns(cc, logger, proxy.NopMetrics())
err := proxyApp.Start(ctx)
require.NoError(t, err)
const height = 2
txs := factory.MakeTenTxs(height)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
state, stateDB, _ := makeState(t, 1, height)
stateStore := sm.NewStore(stateDB)
blockStore := store.NewBlockStore(dbm.NewMemDB())
blockExec := sm.NewBlockExecutor(
stateStore,
logger,
proxyApp.Consensus(),
mmock.Mempool{},
sm.EmptyEvidencePool{},
blockStore,
)
app := abcimocks.NewBaseMock()
cc := abciclient.NewLocalCreator(app)
logger := log.TestingLogger()
proxyApp := proxy.NewAppConns(cc, logger, proxy.NopMetrics())
err := proxyApp.Start(ctx)
require.NoError(t, err)
state, stateDB, privVals := makeState(t, 1, height)
stateStore := sm.NewStore(stateDB)
blockStore := store.NewBlockStore(dbm.NewMemDB())
block, err := sf.MakeBlock(state, int64(height), new(types.Commit))
blockExec := sm.NewBlockExecutor(
stateStore,
logger,
proxyApp.Consensus(),
mmock.Mempool{},
sm.EmptyEvidencePool{},
blockStore,
)
block0, err := sf.MakeBlock(state, height-1, new(types.Commit))
require.NoError(t, err)
lastCommitSig := []types.CommitSig{}
partSet, err := block0.MakePartSet(types.BlockPartSizeBytes)
require.NoError(t, err)
blockID := types.BlockID{Hash: block0.Hash(), PartSetHeader: partSet.Header()}
voteInfos := []abci.VoteInfo{}
for _, privVal := range privVals {
vote, err := factory.MakeVote(ctx, privVal, block0.Header.ChainID, 0, 0, 0, 2, blockID, time.Now())
require.NoError(t, err)
block.Txs = txs
acceptBlock, err := blockExec.ProcessProposal(ctx, block)
pk, err := privVal.GetPubKey(ctx)
require.NoError(t, err)
require.Equal(t, expectAccept, acceptBlock)
addr := pk.Address()
voteInfos = append(voteInfos,
abci.VoteInfo{
SignedLastBlock: true,
Validator: abci.Validator{
Address: addr,
Power: 1000,
},
})
lastCommitSig = append(lastCommitSig, vote.CommitSig())
}
goodTxs := factory.MakeTenTxs(int64(height))
runTest(goodTxs, true)
// testApp has process proposal fail if any tx is 0-len
badTxs := factory.MakeTenTxs(int64(height))
badTxs[0] = types.Tx{}
runTest(badTxs, false)
lastCommit := types.NewCommit(height-1, 0, types.BlockID{}, lastCommitSig)
block1, err := sf.MakeBlock(state, height, lastCommit)
require.NoError(t, err)
block1.Txs = txs
expectedRpp := abci.RequestProcessProposal{
Hash: block1.Hash(),
Header: *block1.Header.ToProto(),
Txs: block1.Txs.ToSliceOfBytes(),
ByzantineValidators: block1.Evidence.ToABCI(),
LastCommitInfo: abci.LastCommitInfo{
Round: 0,
Votes: voteInfos,
},
}
app.On("ProcessProposal", mock.Anything).Return(abci.ResponseProcessProposal{Accept: true})
acceptBlock, err := blockExec.ProcessProposal(ctx, block1, state)
require.NoError(t, err)
require.True(t, acceptBlock)
app.AssertExpectations(t)
app.AssertCalled(t, "ProcessProposal", expectedRpp)
}
func TestValidateValidatorUpdates(t *testing.T) {


+ 2
- 2
internal/state/helpers_test.go View File

@ -335,8 +335,8 @@ func (app *testApp) Query(reqQuery abci.RequestQuery) (resQuery abci.ResponseQue
func (app *testApp) ProcessProposal(req abci.RequestProcessProposal) abci.ResponseProcessProposal {
for _, tx := range req.Txs {
if len(tx) == 0 {
return abci.ResponseProcessProposal{Result: abci.ResponseProcessProposal_REJECT}
return abci.ResponseProcessProposal{Accept: false}
}
}
return abci.ResponseProcessProposal{Result: abci.ResponseProcessProposal_ACCEPT}
return abci.ResponseProcessProposal{Accept: true}
}

+ 12
- 0
test/e2e/app/app.go View File

@ -303,6 +303,18 @@ func (app *Application) PrepareProposal(req abci.RequestPrepareProposal) abci.Re
return abci.ResponsePrepareProposal{BlockData: req.BlockData}
}
// ProcessProposal implements part of the Application interface.
// It accepts any proposal that does not contain a malformed transaction.
func (app *Application) ProcessProposal(req abci.RequestProcessProposal) abci.ResponseProcessProposal {
for _, tx := range req.Txs {
_, _, err := parseTx(tx)
if err != nil {
return abci.ResponseProcessProposal{Accept: false}
}
}
return abci.ResponseProcessProposal{Accept: true}
}
func (app *Application) Rollback() error {
app.mu.Lock()
defer app.mu.Unlock()


+ 10
- 0
types/evidence.go View File

@ -681,6 +681,16 @@ func (evl EvidenceList) Has(evidence Evidence) bool {
return false
}
// ToABCI converts the evidence list to a slice of the ABCI protobuf messages
// for use when communicating the evidence to an application.
func (evl EvidenceList) ToABCI() []abci.Evidence {
var el []abci.Evidence
for _, e := range evl {
el = append(el, e.ABCI()...)
}
return el
}
//------------------------------------------ PROTO --------------------------------------
// EvidenceToProto is a generalized function for encoding evidence that conforms to the


Loading…
Cancel
Save