From 2f2986b15abddb72be1a8d883bbdb04affc5ec57 Mon Sep 17 00:00:00 2001 From: mconcat Date: Thu, 23 Sep 2021 00:19:09 +0900 Subject: [PATCH] [cherry-picked] ABCI Vote Extension 2 (#6885) * add proto, add boilerplates * add canonical * fix tests * add vote signing test * Update internal/consensus/msgs_test.go * modify state execution in progress * add extension signing * add extension signing * VoteExtension -> ExtendVote * modify state execution in progress * add extension signing * verify in progress * modify CommitSig * fix test * apply review * update data structures * Apply suggestions from code review * Add comments * fix test * VoteExtensionSigned => VoteExtensionToSigned * Apply suggestions from code review Co-authored-by: Aleksandr Bezobchuk * *Signed -> *ToSign * add Vote to RequestExtendVote * add example VoteExtension * apply reviews * fix vote * Apply suggestions from code review Co-authored-by: Dev Ojha Co-authored-by: Aleksandr Bezobchuk * fix typo, modify proto * add abcipp_kvstore.go * add extension test * fix test * fix test * fix test * fit lint * uncomment test * refactor test in progress * gofmt * apply review * fix lint Co-authored-by: Aleksandr Bezobchuk Co-authored-by: Dev Ojha --- abci/types/application.go | 2 ++ abci/types/types.go | 34 ++++++++++++++++++++++++++++++++++ consensus/common_test.go | 5 +++++ consensus/state.go | 13 ++++++++++--- state/execution.go | 17 +++++++++++++++++ state/execution_test.go | 8 ++++---- types/block.go | 2 ++ types/vote.go | 38 +++++++++++++++++++++++++++++--------- 8 files changed, 103 insertions(+), 16 deletions(-) diff --git a/abci/types/application.go b/abci/types/application.go index b33da73dc..057989e24 100644 --- a/abci/types/application.go +++ b/abci/types/application.go @@ -20,7 +20,9 @@ type Application interface { ProcessProposal(context.Context, *RequestProcessProposal) (*ResponseProcessProposal, error) // Deliver the decided block with its txs to the Application FinalizeBlock(context.Context, *RequestFinalizeBlock) (*ResponseFinalizeBlock, error) + // Create application specific vote extension ExtendVote(context.Context, *RequestExtendVote) (*ResponseExtendVote, error) + // Verify application's vote extension data VerifyVoteExtension(context.Context, *RequestVerifyVoteExtension) (*ResponseVerifyVoteExtension, error) // Commit the state and return the application Merkle root hash Commit(context.Context, *RequestCommit) (*ResponseCommit, error) diff --git a/abci/types/types.go b/abci/types/types.go index 414319bac..458e8e130 100644 --- a/abci/types/types.go +++ b/abci/types/types.go @@ -5,6 +5,8 @@ import ( "encoding/json" "github.com/cosmos/gogoproto/jsonpb" + + types "github.com/tendermint/tendermint/proto/tendermint/types" ) const ( @@ -51,6 +53,16 @@ func (r ResponseProcessProposal) IsStatusUnknown() bool { return r.Status == ResponseProcessProposal_UNKNOWN } +// IsOK returns true if Code is OK +func (r ResponseVerifyVoteExtension) IsOK() bool { + return r.Result <= ResponseVerifyVoteExtension_ACCEPT +} + +// IsErr returns true if Code is something other than OK. +func (r ResponseVerifyVoteExtension) IsErr() bool { + return r.Result > ResponseVerifyVoteExtension_ACCEPT +} + //--------------------------------------------------------------------------- // override JSON marshaling so we emit defaults (ie. disable omitempty) @@ -156,3 +168,25 @@ func MarshalTxResults(r []*ExecTxResult) ([][]byte, error) { } return s, nil } + +// ----------------------------------------------- +// construct Result data + +func RespondExtendVote(appDataToSign, appDataSelfAuthenticating []byte) ResponseExtendVote { + return ResponseExtendVote{ + VoteExtension: &types.VoteExtension{ + AppDataToSign: appDataToSign, + AppDataSelfAuthenticating: appDataSelfAuthenticating, + }, + } +} + +func RespondVerifyVoteExtension(ok bool) ResponseVerifyVoteExtension { + result := ResponseVerifyVoteExtension_REJECT + if ok { + result = ResponseVerifyVoteExtension_ACCEPT + } + return ResponseVerifyVoteExtension{ + Result: result, + } +} diff --git a/consensus/common_test.go b/consensus/common_test.go index 2144bd556..c9746c1fe 100644 --- a/consensus/common_test.go +++ b/consensus/common_test.go @@ -108,6 +108,7 @@ func (vs *validatorStub) signVote( Timestamp: tmtime.Now(), Type: voteType, BlockID: types.BlockID{Hash: hash, PartSetHeader: header}, + VoteExtension: types.VoteExtensionFromProto(kvstore.ConstructVoteExtension(pubKey.Address())), } v := vote.ToProto() if err := vs.PrivValidator.SignVote(test.DefaultTestChainID, v); err != nil { @@ -133,6 +134,10 @@ func signVote(vs *validatorStub, voteType tmproto.SignedMsgType, hash []byte, he panic(fmt.Errorf("failed to sign vote: %v", err)) } + // TODO: remove hardcoded vote extension. + // currently set for abci/examples/kvstore/persistent_kvstore.go + v.VoteExtension = types.VoteExtensionFromProto(kvstore.ConstructVoteExtension(v.ValidatorAddress)) + vs.lastVote = v return v diff --git a/consensus/state.go b/consensus/state.go index 516a28471..3148efc1e 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -1971,6 +1971,7 @@ func (cs *State) handleCompleteProposal(blockHeight int64) { // Attempt to add the vote. if its a duplicate signature, dupeout the validator func (cs *State) tryAddVote(vote *types.Vote, peerID p2p.ID) (bool, error) { added, err := cs.addVote(vote, peerID) + if err != nil { // If the vote height is off, we'll just ignore it, // But if it's a conflicting sig, add it to the cs.evpool. @@ -2068,6 +2069,13 @@ func (cs *State) addVote(vote *types.Vote, peerID p2p.ID) (added bool, err error return } + // Verify VoteExtension if precommit + if vote.Type == tmproto.PrecommitType { + if err = cs.blockExec.VerifyVoteExtension(vote); err != nil { + return false, err + } + } + height := cs.Height added, err = cs.Votes.AddVote(vote, peerID) if !added { @@ -2229,9 +2237,6 @@ func (cs *State) signVote( BlockID: types.BlockID{Hash: hash, PartSetHeader: header}, } - v := vote.ToProto() - err := cs.privValidator.SignVote(cs.state.ChainID, v) - switch msgType { case tmproto.PrecommitType: // if the signedMessage type is for a precommit, add VoteExtension @@ -2241,6 +2246,8 @@ func (cs *State) signVote( } vote.VoteExtension = ext } + v := vote.ToProto() + err := cs.privValidator.SignVote(cs.state.ChainID, v) vote.Signature = v.Signature vote.Timestamp = v.Timestamp diff --git a/state/execution.go b/state/execution.go index 8931cb142..1ea4f26f9 100644 --- a/state/execution.go +++ b/state/execution.go @@ -306,6 +306,23 @@ func (blockExec *BlockExecutor) ExtendVote(vote *types.Vote) (types.VoteExtensio return types.VoteExtensionFromProto(resp.VoteExtension), nil } +func (blockExec *BlockExecutor) VerifyVoteExtension(vote *types.Vote) error { + req := abci.RequestVerifyVoteExtension{ + Vote: vote.ToProto(), + } + + resp, err := blockExec.proxyApp.VerifyVoteExtensionSync(req) + if err != nil { + return err + } + + if resp.IsErr() { + return types.ErrVoteInvalidExtension + } + + return nil +} + // Commit locks the mempool, runs the ABCI Commit message, and updates the // mempool. // It returns the result of calling abci.Commit (the AppHash) and the height to retain (if any). diff --git a/state/execution_test.go b/state/execution_test.go index d02727d06..0d6b27e57 100644 --- a/state/execution_test.go +++ b/state/execution_test.go @@ -179,14 +179,14 @@ func TestFinalizeBlockValidators(t *testing.T) { []byte("Signature1"), state.Validators.Validators[0].Address, now, - types.VoteExtensionToSign{}, - ) + types.VoteExtensionToSign{}, + ) commitSig1 = types.NewCommitSigForBlock( []byte("Signature2"), state.Validators.Validators[1].Address, now, - types.VoteExtensionToSign{}, - ) + types.VoteExtensionToSign{}, + ) absentSig = types.NewCommitSigAbsent() ) diff --git a/types/block.go b/types/block.go index ee443acd9..dbbd4fac3 100644 --- a/types/block.go +++ b/types/block.go @@ -720,6 +720,7 @@ func (cs *CommitSig) ToProto() *tmproto.CommitSig { ValidatorAddress: cs.ValidatorAddress, Timestamp: cs.Timestamp, Signature: cs.Signature, + VoteExtension: cs.VoteExtension.ToProto(), } } @@ -731,6 +732,7 @@ func (cs *CommitSig) FromProto(csp tmproto.CommitSig) error { cs.ValidatorAddress = csp.ValidatorAddress cs.Timestamp = csp.Timestamp cs.Signature = csp.Signature + cs.VoteExtension = VoteExtensionToSignFromProto(csp.VoteExtension) return cs.ValidateBasic() } diff --git a/types/vote.go b/types/vote.go index 7cd206fc4..ad2190a7f 100644 --- a/types/vote.go +++ b/types/vote.go @@ -52,12 +52,34 @@ type VoteExtensionToSign struct { AppDataToSign []byte `json:"app_data_to_sign"` } +func (ext VoteExtensionToSign) ToProto() *tmproto.VoteExtensionToSign { + if ext.IsEmpty() { + return nil + } + return &tmproto.VoteExtensionToSign{ + AppDataToSign: ext.AppDataToSign, + } +} + +func VoteExtensionToSignFromProto(pext *tmproto.VoteExtensionToSign) VoteExtensionToSign { + if pext == nil { + return VoteExtensionToSign{} + } + return VoteExtensionToSign{ + AppDataToSign: pext.AppDataToSign, + } +} + +func (ext VoteExtensionToSign) IsEmpty() bool { + return len(ext.AppDataToSign) == 0 +} + // BytesPacked returns a bytes-packed representation for // debugging and human identification. This function should // not be used for any logical operations. func (ext VoteExtensionToSign) BytesPacked() []byte { - res := make([]byte, len(ext.AppDataToSign)) - copy(res, ext.AppDataToSign) + res := []byte{} + res = append(res, ext.AppDataToSign...) return res } @@ -86,9 +108,9 @@ func (ext VoteExtension) ToSign() VoteExtensionToSign { // debugging and human identification. This function should // not be used for any logical operations. func (ext VoteExtension) BytesPacked() []byte { - res := make([]byte, len(ext.AppDataToSign)+len(ext.AppDataSelfAuthenticating)) - copy(res[:len(ext.AppDataToSign)], ext.AppDataToSign) - copy(res[len(ext.AppDataToSign):], ext.AppDataSelfAuthenticating) + res := []byte{} + res = append(res, ext.AppDataToSign...) + res = append(res, ext.AppDataSelfAuthenticating...) return res } @@ -257,11 +279,9 @@ func (vote *Vote) ValidateBasic() error { func (ext VoteExtension) Copy() VoteExtension { res := VoteExtension{ - AppDataToSign: make([]byte, len(ext.AppDataToSign)), - AppDataSelfAuthenticating: make([]byte, len(ext.AppDataSelfAuthenticating)), + AppDataToSign: ext.AppDataToSign, + AppDataSelfAuthenticating: ext.AppDataSelfAuthenticating, } - copy(res.AppDataToSign, ext.AppDataToSign) - copy(res.AppDataSelfAuthenticating, ext.AppDataSelfAuthenticating) return res }