mirror of
https://github.com/tendermint/tendermint.git
synced 2026-09-05 15:47:07 +00:00
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
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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}
|
||||
}
|
||||
|
||||
//---------------------------------------------
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
|
||||
+2858
-390
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
||||
@@ -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
@@ -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{})
|
||||
|
||||
@@ -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
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
const height = 2
|
||||
txs := factory.MakeTenTxs(height)
|
||||
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)
|
||||
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())
|
||||
|
||||
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)
|
||||
|
||||
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,
|
||||
)
|
||||
|
||||
block, err := sf.MakeBlock(state, int64(height), new(types.Commit))
|
||||
pk, err := privVal.GetPubKey(ctx)
|
||||
require.NoError(t, err)
|
||||
block.Txs = txs
|
||||
acceptBlock, err := blockExec.ProcessProposal(ctx, block)
|
||||
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) {
|
||||
|
||||
@@ -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}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user