diff --git a/abci/client/client.go b/abci/client/client.go index a46d83aa0..0c63d0ce5 100644 --- a/abci/client/client.go +++ b/abci/client/client.go @@ -43,6 +43,7 @@ type Client interface { OfferSnapshotAsync(types.RequestOfferSnapshot) *ReqRes LoadSnapshotChunkAsync(types.RequestLoadSnapshotChunk) *ReqRes ApplySnapshotChunkAsync(types.RequestApplySnapshotChunk) *ReqRes + ProcessProposalAsync(types.RequestProcessProposal) *ReqRes FlushSync() error EchoSync(msg string) (*types.ResponseEcho, error) @@ -60,6 +61,7 @@ type Client interface { OfferSnapshotSync(types.RequestOfferSnapshot) (*types.ResponseOfferSnapshot, error) LoadSnapshotChunkSync(types.RequestLoadSnapshotChunk) (*types.ResponseLoadSnapshotChunk, error) ApplySnapshotChunkSync(types.RequestApplySnapshotChunk) (*types.ResponseApplySnapshotChunk, error) + ProcessProposalSync(types.RequestProcessProposal) (*types.ResponseProcessProposal, error) } //---------------------------------------- diff --git a/abci/client/grpc_client.go b/abci/client/grpc_client.go index 38a7973d5..2034445b2 100644 --- a/abci/client/grpc_client.go +++ b/abci/client/grpc_client.go @@ -309,6 +309,28 @@ func (cli *grpcClient) PrepareProposalAsync(params types.RequestPrepareProposal) return cli.finishAsyncCall(req, &types.Response{Value: &types.Response_PrepareProposal{PrepareProposal: res}}) } +func (cli *grpcClient) ProcessProposalAsync( + ctx context.Context, + params types.RequestProcessProposal, +) (*ReqRes, error) { + + req := types.ToRequestProcessProposal(params) + res, err := cli.client.ProcessProposal(ctx, req.GetProcessProposal(), grpc.WaitForReady(true)) + if err != nil { + return nil, err + } + + return cli.finishAsyncCall( + ctx, + req, + &types.Response{ + Value: &types.Response_ProcessProposal{ + ProcessProposal: res, + }, + }, + ) +} + // finishAsyncCall creates a ReqRes for an async call, and immediately populates it // with the response. We don't complete it until it's been ordered via the channel. func (cli *grpcClient) finishAsyncCall(req *types.Request, res *types.Response) *ReqRes { @@ -432,3 +454,15 @@ func (cli *grpcClient) PrepareProposalSync( reqres := cli.PrepareProposalAsync(params) return cli.finishSyncCall(reqres).GetPrepareProposal(), cli.Error() } + +func (cli *grpcClient) ProcessProposalSync( + ctx context.Context, + params types.RequestProcessProposal, +) (*types.ResponseProcessProposal, error) { + + reqres, err := cli.ProcessProposalAsync(ctx, params) + if err != nil { + return nil, err + } + return cli.finishSyncCall(reqres).GetProcessProposal(), cli.Error() +} diff --git a/abci/client/local_client.go b/abci/client/local_client.go index e33e68ca2..e2773aa32 100644 --- a/abci/client/local_client.go +++ b/abci/client/local_client.go @@ -218,6 +218,20 @@ func (app *localClient) PrepareProposalAsync(req types.RequestPrepareProposal) * ) } +func (app *localClient) ProcessProposalAsync( + ctx context.Context, + req types.RequestProcessProposal, +) (*ReqRes, error) { + app.mtx.Lock() + defer app.mtx.Unlock() + + res := app.Application.ProcessProposal(req) + return app.callback( + types.ToRequestProcessProposal(req), + types.ToResponseProcessProposal(res), + ), nil +} + //------------------------------------------------------- func (app *localClient) FlushSync() error { @@ -342,6 +356,17 @@ func (app *localClient) PrepareProposalSync(req types.RequestPrepareProposal) (* return &res, nil } +func (app *localClient) ProcessProposalSync( + ctx context.Context, + req types.RequestProcessProposal, +) (*types.ResponseProcessProposal, error) { + app.mtx.Lock() + defer app.mtx.Unlock() + + res := app.Application.ProcessProposal(req) + return &res, nil +} + //------------------------------------------------------- func (app *localClient) callback(req *types.Request, res *types.Response) *ReqRes { diff --git a/abci/client/socket_client.go b/abci/client/socket_client.go index dd1c556c2..cea929c8c 100644 --- a/abci/client/socket_client.go +++ b/abci/client/socket_client.go @@ -283,6 +283,13 @@ func (cli *socketClient) PrepareProposalAsync(req types.RequestPrepareProposal) return cli.queueRequest(types.ToRequestPrepareProposal(req)) } +func (cli *socketClient) ProcessProposalAsync( + ctx context.Context, + req types.RequestProcessProposal, +) (*ReqRes, error) { + return cli.queueRequestAsync(ctx, types.ToRequestProcessProposal(req)) +} + //---------------------------------------- func (cli *socketClient) FlushSync() error { @@ -430,6 +437,18 @@ func (cli *socketClient) PrepareProposalSync(req types.RequestPrepareProposal) ( return reqres.Response.GetPrepareProposal(), cli.Error() } +func (cli *socketClient) ProcessProposalSync( + ctx context.Context, + req types.RequestProcessProposal, +) (*types.ResponseProcessProposal, error) { + + reqres, err := cli.queueRequestAndFlushSync(ctx, types.ToRequestProcessProposal(req)) + if err != nil { + return nil, err + } + return reqres.Response.GetProcessProposal(), nil +} + //---------------------------------------- func (cli *socketClient) queueRequest(req *types.Request) *ReqRes { diff --git a/abci/example/kvstore/persistent_kvstore.go b/abci/example/kvstore/persistent_kvstore.go index 320f25502..14a6c93e9 100644 --- a/abci/example/kvstore/persistent_kvstore.go +++ b/abci/example/kvstore/persistent_kvstore.go @@ -179,6 +179,16 @@ func (app *PersistentKVStoreApplication) PrepareProposal( return types.ResponsePrepareProposal{BlockData: req.BlockData} } +func (app *PersistentKVStoreApplication) 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{Result: types.ResponseProcessProposal_ACCEPT} +} + //--------------------------------------------- // update validators diff --git a/abci/types/application.go b/abci/types/application.go index fe69b6f05..576480ace 100644 --- a/abci/types/application.go +++ b/abci/types/application.go @@ -24,6 +24,7 @@ type Application interface { DeliverTx(RequestDeliverTx) ResponseDeliverTx // Deliver a tx for full processing EndBlock(RequestEndBlock) ResponseEndBlock // Signals the end of a block, returns changes to the validator set Commit() ResponseCommit // Commit the state and return the application Merkle root hash + ProcessProposal(RequestProcessProposal) ResponseProcessProposal // State Sync Connection ListSnapshots(RequestListSnapshots) ResponseListSnapshots // List available snapshots @@ -102,6 +103,10 @@ func (BaseApplication) PrepareProposal(req RequestPrepareProposal) ResponsePrepa } } +func (BaseApplication) ProcessProposal(req RequestProcessProposal) ResponseProcessProposal { + return ResponseProcessProposal{} +} + //------------------------------------------------------- // GRPCApplication is a GRPC wrapper for Application @@ -195,3 +200,9 @@ func (app *GRPCApplication) PrepareProposal( res := app.app.PrepareProposal(*req) return &res, nil } + +func (app *GRPCApplication) ProcessProposal( + ctx context.Context, req *RequestProcessProposal) (*ResponseProcessProposal, error) { + res := app.app.ProcessProposal(*req) + return &res, nil +} diff --git a/abci/types/messages.go b/abci/types/messages.go index 599e8c05f..4314b5bfc 100644 --- a/abci/types/messages.go +++ b/abci/types/messages.go @@ -165,6 +165,12 @@ func ToRequestPrepareProposal(req RequestPrepareProposal) *Request { } } +func ToRequestProcessProposal(req RequestProcessProposal) *Request { + return &Request{ + Value: &Request_ProcessProposal{&req}, + } +} + //---------------------------------------- func ToResponseException(errStr string) *Response { @@ -268,3 +274,9 @@ func ToResponsePrepareProposal(res ResponsePrepareProposal) *Response { Value: &Response_PrepareProposal{&res}, } } + +func ToResponseProcessProposal(res ResponseProcessProposal) *Response { + return &Response{ + Value: &Response_ProcessProposal{&res}, + } +} diff --git a/abci/types/result.go b/abci/types/result.go index 53acdb906..d29910a83 100644 --- a/abci/types/result.go +++ b/abci/types/result.go @@ -41,6 +41,21 @@ func (r ResponseQuery) IsErr() bool { return r.Code != CodeTypeOK } +// IsUnknown returns true if Code is Unknown +func (r ResponseProcessProposal) IsUnknown() bool { + return r.Result == ResponseProcessProposal_UNKNOWN +} + +// IsOK returns true if Code is OK +func (r ResponseProcessProposal) IsOK() bool { + return r.Result == ResponseProcessProposal_ACCEPT +} + +// IsErr returns true if Code is something other than OK. +func (r ResponseProcessProposal) IsErr() bool { + return r.Result != ResponseProcessProposal_ACCEPT +} + //--------------------------------------------------------------------------- // override JSON marshaling so we emit defaults (ie. disable omitempty) diff --git a/consensus/byzantine_test.go b/consensus/byzantine_test.go index dd5febcbd..a207c172e 100644 --- a/consensus/byzantine_test.go +++ b/consensus/byzantine_test.go @@ -223,7 +223,7 @@ func TestByzantinePrevoteEquivocation(t *testing.T) { // Make proposal propBlockID := types.BlockID{Hash: block.Hash(), PartSetHeader: blockParts.Header()} - proposal := types.NewProposal(height, round, lazyProposer.ValidRound, propBlockID) + proposal := types.NewProposal(height, round, lazyProposer.TwoThirdPrevoteRound, propBlockID) p := proposal.ToProto() if err := lazyProposer.privValidator.SignProposal(lazyProposer.state.ChainID, p); err == nil { proposal.Signature = p.Signature @@ -462,7 +462,7 @@ func byzantineDecideProposalFunc(t *testing.T, height int64, round int32, cs *St // Create a new proposal block from state/txs from the mempool. block1, blockParts1 := cs.createProposalBlock() - polRound, propBlockID := cs.ValidRound, types.BlockID{Hash: block1.Hash(), PartSetHeader: blockParts1.Header()} + polRound, propBlockID := cs.TwoThirdPrevoteRound, types.BlockID{Hash: block1.Hash(), PartSetHeader: blockParts1.Header()} proposal1 := types.NewProposal(height, round, polRound, propBlockID) p1 := proposal1.ToProto() if err := cs.privValidator.SignProposal(cs.state.ChainID, p1); err != nil { @@ -476,7 +476,7 @@ func byzantineDecideProposalFunc(t *testing.T, height int64, round int32, cs *St // Create a new proposal block from state/txs from the mempool. block2, blockParts2 := cs.createProposalBlock() - polRound, propBlockID = cs.ValidRound, types.BlockID{Hash: block2.Hash(), PartSetHeader: blockParts2.Header()} + polRound, propBlockID = cs.TwoThirdPrevoteRound, types.BlockID{Hash: block2.Hash(), PartSetHeader: blockParts2.Header()} proposal2 := types.NewProposal(height, round, polRound, propBlockID) p2 := proposal2.ToProto() if err := cs.privValidator.SignProposal(cs.state.ChainID, p2); err != nil { diff --git a/consensus/common_test.go b/consensus/common_test.go index 09512f49d..4722e1600 100644 --- a/consensus/common_test.go +++ b/consensus/common_test.go @@ -208,7 +208,7 @@ func decideProposal( ) (proposal *types.Proposal, block *types.Block) { cs1.mtx.Lock() block, blockParts := cs1.createProposalBlock() - validRound := cs1.ValidRound + validRound := cs1.TwoThirdPrevoteRound chainID := cs1.state.ChainID cs1.mtx.Unlock() if block == nil { diff --git a/consensus/state.go b/consensus/state.go index 9e6d980a3..17a02bcd1 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -667,9 +667,9 @@ func (cs *State) updateToState(state sm.State) { cs.LockedRound = -1 cs.LockedBlock = nil cs.LockedBlockParts = nil - cs.ValidRound = -1 - cs.ValidBlock = nil - cs.ValidBlockParts = nil + cs.TwoThirdPrevoteRound = -1 + cs.TwoThirdPrevoteBlock = nil + cs.TwoThirdPrevoteBlockParts = nil cs.Votes = cstypes.NewHeightVoteSet(state.ChainID, height, validators) cs.CommitRound = -1 cs.LastValidators = state.LastValidators @@ -1126,9 +1126,9 @@ func (cs *State) defaultDecideProposal(height int64, round int32) { var blockParts *types.PartSet // Decide on block - if cs.ValidBlock != nil { + if cs.TwoThirdPrevoteBlock != nil { // If there is valid block, choose that. - block, blockParts = cs.ValidBlock, cs.ValidBlockParts + block, blockParts = cs.TwoThirdPrevoteBlock, cs.TwoThirdPrevoteBlockParts } else { // Create a new proposal block from state/txs from the mempool. block, blockParts = cs.createProposalBlock() @@ -1145,7 +1145,7 @@ func (cs *State) defaultDecideProposal(height int64, round int32) { // Make proposal propBlockID := types.BlockID{Hash: block.Hash(), PartSetHeader: blockParts.Header()} - proposal := types.NewProposal(height, round, cs.ValidRound, propBlockID) + proposal := types.NewProposal(height, round, cs.TwoThirdPrevoteRound, propBlockID) p := proposal.ToProto() if err := cs.privValidator.SignProposal(cs.state.ChainID, p); err == nil { proposal.Signature = p.Signature @@ -1276,6 +1276,19 @@ func (cs *State) defaultDoPrevote(height int64, round int32) { return } + stateMachineValidBlock, err := cs.blockExec.ProcessProposal(cs.ProposalBlock) + if err != nil { + cs.Logger.Error("state machine returned an error when trying to process proposal block", "err", err) + } + + // Vote nil if application invalidated the block + if !stateMachineValidBlock { + // Consensus says we must vote nil + logger.Error("prevote step: consensus deems this block to be mustVoteNil", "err", err) + cs.signAddVote(tmproto.PrevoteType, nil, types.PartSetHeader{}) + return + } + // Prevote cs.ProposalBlock // NOTE: the proposal signature is validated when it is received, // and the proposal block parts are validated as they are received (against the merkle hash in the proposal) @@ -1913,7 +1926,7 @@ func (cs *State) handleCompleteProposal(blockHeight int64) { // Update Valid* if we can. prevotes := cs.Votes.Prevotes(cs.Round) blockID, hasTwoThirds := prevotes.TwoThirdsMajority() - if hasTwoThirds && !blockID.IsZero() && (cs.ValidRound < cs.Round) { + if hasTwoThirds && !blockID.IsZero() && (cs.TwoThirdPrevoteRound < cs.Round) { if cs.ProposalBlock.HashesTo(blockID.Hash) { cs.Logger.Debug( "updating valid block to new proposal block", @@ -1921,9 +1934,9 @@ func (cs *State) handleCompleteProposal(blockHeight int64) { "valid_block_hash", log.NewLazyBlockHash(cs.ProposalBlock), ) - cs.ValidRound = cs.Round - cs.ValidBlock = cs.ProposalBlock - cs.ValidBlockParts = cs.ProposalBlockParts + cs.TwoThirdPrevoteRound = cs.Round + cs.TwoThirdPrevoteBlock = cs.ProposalBlock + cs.TwoThirdPrevoteBlockParts = cs.ProposalBlockParts } // TODO: In case there is +2/3 majority in Prevotes set for some // block and cs.ProposalBlock contains different block, either @@ -2083,12 +2096,12 @@ func (cs *State) addVote(vote *types.Vote, peerID p2p.ID) (added bool, err error // Update Valid* if we can. // NOTE: our proposal block may be nil or not what received a polka.. - if len(blockID.Hash) != 0 && (cs.ValidRound < vote.Round) && (vote.Round == cs.Round) { + if len(blockID.Hash) != 0 && (cs.TwoThirdPrevoteRound < vote.Round) && (vote.Round == cs.Round) { if cs.ProposalBlock.HashesTo(blockID.Hash) { - cs.Logger.Debug("updating valid block because of POL", "valid_round", cs.ValidRound, "pol_round", vote.Round) - cs.ValidRound = vote.Round - cs.ValidBlock = cs.ProposalBlock - cs.ValidBlockParts = cs.ProposalBlockParts + cs.Logger.Debug("updating valid block because of POL", "valid_round", cs.TwoThirdPrevoteRound, "pol_round", vote.Round) + cs.TwoThirdPrevoteRound = vote.Round + cs.TwoThirdPrevoteBlock = cs.ProposalBlock + cs.TwoThirdPrevoteBlockParts = cs.ProposalBlockParts } else { cs.Logger.Debug( "valid block we do not know about; set ProposalBlock=nil", diff --git a/consensus/state_test.go b/consensus/state_test.go index 1e3887fd9..8403f4950 100644 --- a/consensus/state_test.go +++ b/consensus/state_test.go @@ -1199,9 +1199,9 @@ func TestProposeValidBlock(t *testing.T) { rs = cs1.GetRoundState() assert.True(t, bytes.Equal(rs.ProposalBlock.Hash(), propBlockHash)) - assert.True(t, bytes.Equal(rs.ProposalBlock.Hash(), rs.ValidBlock.Hash())) - assert.True(t, rs.Proposal.POLRound == rs.ValidRound) - assert.True(t, bytes.Equal(rs.Proposal.BlockID.Hash, rs.ValidBlock.Hash())) + assert.True(t, bytes.Equal(rs.ProposalBlock.Hash(), rs.TwoThirdPrevoteBlock.Hash())) + assert.True(t, rs.Proposal.POLRound == rs.TwoThirdPrevoteRound) + assert.True(t, bytes.Equal(rs.Proposal.BlockID.Hash, rs.TwoThirdPrevoteBlock.Hash())) } // What we want: @@ -1249,9 +1249,9 @@ func TestSetValidBlockOnDelayedPrevote(t *testing.T) { rs = cs1.GetRoundState() - assert.True(t, rs.ValidBlock == nil) - assert.True(t, rs.ValidBlockParts == nil) - assert.True(t, rs.ValidRound == -1) + assert.True(t, rs.TwoThirdPrevoteBlock == nil) + assert.True(t, rs.TwoThirdPrevoteBlockParts == nil) + assert.True(t, rs.TwoThirdPrevoteRound == -1) // vs2 send (delayed) prevote for propBlock signAddVotes(cs1, tmproto.PrevoteType, propBlockHash, propBlockParts.Header(), vs4) @@ -1260,9 +1260,9 @@ func TestSetValidBlockOnDelayedPrevote(t *testing.T) { rs = cs1.GetRoundState() - assert.True(t, bytes.Equal(rs.ValidBlock.Hash(), propBlockHash)) - assert.True(t, rs.ValidBlockParts.Header().Equals(propBlockParts.Header())) - assert.True(t, rs.ValidRound == round) + assert.True(t, bytes.Equal(rs.TwoThirdPrevoteBlock.Hash(), propBlockHash)) + assert.True(t, rs.TwoThirdPrevoteBlockParts.Header().Equals(propBlockParts.Header())) + assert.True(t, rs.TwoThirdPrevoteRound == round) } // What we want: @@ -1316,9 +1316,9 @@ func TestSetValidBlockOnDelayedProposal(t *testing.T) { ensureNewProposal(proposalCh, height, round) rs := cs1.GetRoundState() - assert.True(t, bytes.Equal(rs.ValidBlock.Hash(), propBlockHash)) - assert.True(t, rs.ValidBlockParts.Header().Equals(propBlockParts.Header())) - assert.True(t, rs.ValidRound == round) + assert.True(t, bytes.Equal(rs.TwoThirdPrevoteBlock.Hash(), propBlockHash)) + assert.True(t, rs.TwoThirdPrevoteBlockParts.Header().Equals(propBlockParts.Header())) + assert.True(t, rs.TwoThirdPrevoteRound == round) } // 4 vals, 3 Nil Precommits at P0 diff --git a/consensus/types/round_state.go b/consensus/types/round_state.go index 9e67b76c0..3b11c2e17 100644 --- a/consensus/types/round_state.go +++ b/consensus/types/round_state.go @@ -81,11 +81,11 @@ type RoundState struct { LockedBlockParts *types.PartSet `json:"locked_block_parts"` // Last known round with POL for non-nil valid block. - ValidRound int32 `json:"valid_round"` - ValidBlock *types.Block `json:"valid_block"` // Last known block of POL mentioned above. + TwoThirdPrevoteRound int32 `json:"valid_round"` + TwoThirdPrevoteBlock *types.Block `json:"valid_block"` // Last known block of POL mentioned above. // Last known block parts of POL mentioned above. - ValidBlockParts *types.PartSet `json:"valid_block_parts"` + TwoThirdPrevoteBlockParts *types.PartSet `json:"valid_block_parts"` Votes *HeightVoteSet `json:"votes"` CommitRound int32 `json:"commit_round"` // LastCommit *types.VoteSet `json:"last_commit"` // Last precommits at Height-1 @@ -119,7 +119,7 @@ func (rs *RoundState) RoundStateSimple() RoundStateSimple { StartTime: rs.StartTime, ProposalBlockHash: rs.ProposalBlock.Hash(), LockedBlockHash: rs.LockedBlock.Hash(), - ValidBlockHash: rs.ValidBlock.Hash(), + ValidBlockHash: rs.TwoThirdPrevoteBlock.Hash(), Votes: votesJSON, Proposer: types.ValidatorInfo{ Address: addr, @@ -186,8 +186,8 @@ func (rs *RoundState) StringIndented(indent string) string { %s ProposalBlock: %v %v %s LockedRound: %v %s LockedBlock: %v %v -%s ValidRound: %v -%s ValidBlock: %v %v +%s TwoThirdPrevoteRound: %v +%s TwoThirdPrevoteBlock: %v %v %s Votes: %v %s LastCommit: %v %s LastValidators:%v @@ -200,8 +200,8 @@ func (rs *RoundState) StringIndented(indent string) string { indent, rs.ProposalBlockParts.StringShort(), rs.ProposalBlock.StringShort(), indent, rs.LockedRound, indent, rs.LockedBlockParts.StringShort(), rs.LockedBlock.StringShort(), - indent, rs.ValidRound, - indent, rs.ValidBlockParts.StringShort(), rs.ValidBlock.StringShort(), + indent, rs.TwoThirdPrevoteRound, + indent, rs.TwoThirdPrevoteBlockParts.StringShort(), rs.TwoThirdPrevoteBlock.StringShort(), indent, rs.Votes.StringIndented(indent+" "), indent, rs.LastCommit.StringShort(), indent, rs.LastValidators.StringIndented(indent+" "), diff --git a/proto/tendermint/abci/types.proto b/proto/tendermint/abci/types.proto index fc3228280..9415a2804 100644 --- a/proto/tendermint/abci/types.proto +++ b/proto/tendermint/abci/types.proto @@ -37,6 +37,7 @@ message Request { RequestLoadSnapshotChunk load_snapshot_chunk = 14; RequestApplySnapshotChunk apply_snapshot_chunk = 15; RequestPrepareProposal prepare_proposal = 16; + RequestProcessProposal process_proposal = 17; } } @@ -134,6 +135,11 @@ message RequestPrepareProposal { int64 block_data_size = 2; } +message RequestProcessProposal { + tendermint.types.Header header = 1 [(gogoproto.nullable) = false]; + repeated bytes txs = 2; +} + //---------------------------------------- // Response types @@ -156,7 +162,7 @@ message Response { ResponseLoadSnapshotChunk load_snapshot_chunk = 15; ResponseApplySnapshotChunk apply_snapshot_chunk = 16; ResponsePrepareProposal prepare_proposal = 17; - + ResponseProcessProposal process_proposal = 18; } } @@ -298,6 +304,17 @@ message ResponsePrepareProposal { repeated bytes block_data = 1; } +message ResponseProcessProposal { + Result result = 1; + repeated bytes evidence = 2; + + enum Result { + UNKNOWN = 0; // Unknown result, invalidate + ACCEPT = 1; // proposal verified, vote on the proposal + REJECT = 2; // proposal invalidated + } +} + //---------------------------------------- // Misc. @@ -427,4 +444,5 @@ service ABCIApplication { rpc ApplySnapshotChunk(RequestApplySnapshotChunk) returns (ResponseApplySnapshotChunk); rpc PrepareProposal(RequestPrepareProposal) returns (ResponsePrepareProposal); + rpc ProcessProposal(RequestProcessProposal) returns (ResponseProcessProposal); } diff --git a/proxy/app_conn.go b/proxy/app_conn.go index 5a08f2d1a..9614767cb 100644 --- a/proxy/app_conn.go +++ b/proxy/app_conn.go @@ -15,8 +15,8 @@ type AppConnConsensus interface { Error() error InitChainSync(types.RequestInitChain) (*types.ResponseInitChain, error) - PrepareProposalSync(types.RequestPrepareProposal) (*types.ResponsePrepareProposal, error) + ProcessProposalSync(types.RequestProcessProposal) (*types.ResponseProcessProposal, error) BeginBlockSync(types.RequestBeginBlock) (*types.ResponseBeginBlock, error) DeliverTxAsync(types.RequestDeliverTx) *abcicli.ReqRes EndBlockSync(types.RequestEndBlock) (*types.ResponseEndBlock, error) @@ -85,6 +85,10 @@ func (app *appConnConsensus) PrepareProposalSync( return app.appConn.PrepareProposalSync(req) } +func (app *appConnConsensus) ProcessProposalSync(req types.RequestProcessProposal) (*types.ResponseProcessProposal, error) { + return app.appConn.ProcessProposalSync(req) +} + func (app *appConnConsensus) BeginBlockSync(req types.RequestBeginBlock) (*types.ResponseBeginBlock, error) { return app.appConn.BeginBlockSync(req) } diff --git a/state/execution.go b/state/execution.go index a6451146d..7f292c3af 100644 --- a/state/execution.go +++ b/state/execution.go @@ -138,6 +138,23 @@ func (blockExec *BlockExecutor) CreateProposalBlock( return state.MakeBlock(height, modifiedTxs, commit, evidence, proposerAddr) } +func (blockExec *BlockExecutor) ProcessProposal( + block *types.Block, +) (bool, error) { + ctx := context.Background() + req := abci.RequestProcessProposal{ + Txs: block.Data.Txs.ToSliceOfBytes(), + Header: *block.Header.ToProto(), + } + + resp, err := blockExec.proxyApp.ProcessProposalSync(ctx, req) + if err != nil { + return false, ErrInvalidBlock(err) + } + + return resp.IsOK(), nil +} + // ValidateBlock validates the given block against the given state. // If the block is invalid, it returns an error. // Validation does not mutate state, but does require historical information from the stateDB, diff --git a/state/execution_test.go b/state/execution_test.go index cfde97f92..9527407fb 100644 --- a/state/execution_test.go +++ b/state/execution_test.go @@ -14,6 +14,7 @@ import ( "github.com/tendermint/tendermint/crypto/ed25519" cryptoenc "github.com/tendermint/tendermint/crypto/encoding" "github.com/tendermint/tendermint/crypto/tmhash" + sf "github.com/tendermint/tendermint/internal/test/factory" "github.com/tendermint/tendermint/libs/log" mmock "github.com/tendermint/tendermint/mempool/mock" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" @@ -22,9 +23,11 @@ import ( sm "github.com/tendermint/tendermint/state" "github.com/tendermint/tendermint/state/mocks" sf "github.com/tendermint/tendermint/state/test/factory" + "github.com/tendermint/tendermint/store" "github.com/tendermint/tendermint/types" tmtime "github.com/tendermint/tendermint/types/time" "github.com/tendermint/tendermint/version" + dbm "github.com/tendermint/tm-db" ) var ( @@ -214,6 +217,38 @@ func TestBeginBlockByzantineValidators(t *testing.T) { assert.Equal(t, abciEv, app.ByzantineValidators) } +func TestProcessProposal(t *testing.T) { + height := 1 + runTest := func(txs types.Txs, expectAccept bool) { + app := &testApp{} + cc := proxy.NewLocalClientCreator(app) + proxyApp := proxy.NewAppConns(cc) + err := proxyApp.Start() + require.Nil(t, err) + defer proxyApp.Stop() //nolint:errcheck // ignore for tests + + state, stateDB, _ := makeState(1, height) + stateStore := sm.NewStore(stateDB) + + blockStore := store.NewBlockStore(dbm.NewMemDB()) + + blockExec := sm.NewBlockExecutor(stateStore, log.TestingLogger(), proxyApp.Consensus(), + mmock.Mempool{}, sm.EmptyEvidencePool{}, blockStore) + + block := sf.MakeBlock(state, int64(height), new(types.Commit)) + block.Txs = txs + acceptBlock, err := blockExec.ProcessProposal(block) + require.Nil(t, err) + require.Equal(t, expectAccept, acceptBlock) + } + goodTxs := sf.MakeTenTxs(int64(height)) + runTest(goodTxs, true) + // testApp has process proposal fail if any tx is 0-len + badTxs := sf.MakeTenTxs(int64(height)) + badTxs[0] = types.Tx{} + runTest(badTxs, false) +} + func TestValidateValidatorUpdates(t *testing.T) { pubkey1 := ed25519.GenPrivKey().PubKey() pubkey2 := ed25519.GenPrivKey().PubKey() diff --git a/state/helpers_test.go b/state/helpers_test.go index 8945ed506..471533769 100644 --- a/state/helpers_test.go +++ b/state/helpers_test.go @@ -256,3 +256,12 @@ func (app *testApp) Commit() abci.ResponseCommit { func (app *testApp) Query(reqQuery abci.RequestQuery) (resQuery abci.ResponseQuery) { return } + +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{Result: abci.ResponseProcessProposal_ACCEPT} +}