mirror of
https://github.com/tendermint/tendermint.git
synced 2026-04-28 19:36:57 +00:00
[cherry-picked] abci: Vote Extension 1 (#6646)
* 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 * VoteExtension -> ExtendVote * apply review * update data structures * Add comments * Apply suggestions from code review Co-authored-by: Aleksandr Bezobchuk <alexanderbez@users.noreply.github.com> * *Signed -> *ToSign * add Vote to RequestExtendVote * apply reviews * Apply suggestions from code review Co-authored-by: Dev Ojha <ValarDragon@users.noreply.github.com> Co-authored-by: Aleksandr Bezobchuk <alexanderbez@users.noreply.github.com> * fix typo, modify proto Co-authored-by: Aleksandr Bezobchuk <alexanderbez@users.noreply.github.com> Co-authored-by: Dev Ojha <ValarDragon@users.noreply.github.com>
This commit is contained in:
@@ -234,6 +234,14 @@ func (cli *grpcClient) ProcessProposal(ctx context.Context, req *types.RequestPr
|
||||
return cli.client.ProcessProposal(ctx, types.ToRequestProcessProposal(req).GetProcessProposal(), grpc.WaitForReady(true))
|
||||
}
|
||||
|
||||
func (cli *grpcClient) ExtendVote(ctx context.Context, req *types.RequestExtendVote) (*types.ResponseExtendVote, error) {
|
||||
return cli.client.ExtendVote(ctx, types.ToRequestExtendVote(req).GetExtendVote(), grpc.WaitForReady(true))
|
||||
}
|
||||
|
||||
func (cli *grpcClient) VerifyVoteExtension(ctx context.Context, req *types.RequestVerifyVoteExtension) (*types.ResponseVerifyVoteExtension, error) {
|
||||
return cli.client.VerifyVoteExtension(ctx, types.ToRequestVerifyVoteExtension(req).GetVerifyVoteExtension(), grpc.WaitForReady(true))
|
||||
}
|
||||
|
||||
func (cli *grpcClient) FinalizeBlock(ctx context.Context, req *types.RequestFinalizeBlock) (*types.ResponseFinalizeBlock, error) {
|
||||
return cli.client.FinalizeBlock(ctx, types.ToRequestFinalizeBlock(req).GetFinalizeBlock(), grpc.WaitForReady(true))
|
||||
}
|
||||
|
||||
@@ -164,6 +164,20 @@ func (app *localClient) ProcessProposal(ctx context.Context, req *types.RequestP
|
||||
return app.Application.ProcessProposal(ctx, req)
|
||||
}
|
||||
|
||||
func (app *localClient) ExtendVote(ctx context.Context, req *types.RequestExtendVote) (*types.ResponseExtendVote, error) {
|
||||
app.mtx.Lock()
|
||||
defer app.mtx.Unlock()
|
||||
|
||||
return app.Application.ExtendVote(ctx, req)
|
||||
}
|
||||
|
||||
func (app *localClient) VerifyVoteExtension(ctx context.Context, req *types.RequestVerifyVoteExtension) (*types.ResponseVerifyVoteExtension, error) {
|
||||
app.mtx.Lock()
|
||||
defer app.mtx.Unlock()
|
||||
|
||||
return app.Application.VerifyVoteExtension(ctx, req)
|
||||
}
|
||||
|
||||
func (app *localClient) FinalizeBlock(ctx context.Context, req *types.RequestFinalizeBlock) (*types.ResponseFinalizeBlock, error) {
|
||||
app.mtx.Lock()
|
||||
defer app.mtx.Unlock()
|
||||
|
||||
@@ -379,6 +379,28 @@ func (cli *socketClient) ProcessProposal(ctx context.Context, req *types.Request
|
||||
return reqRes.Response.GetProcessProposal(), cli.Error()
|
||||
}
|
||||
|
||||
func (cli *socketClient) ExtendVote(ctx context.Context, req *types.RequestExtendVote) (*types.ResponseExtendVote, error) {
|
||||
reqRes, err := cli.queueRequest(ctx, types.ToRequestExtendVote(req))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := cli.Flush(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return reqRes.Response.GetExtendVote(), cli.Error()
|
||||
}
|
||||
|
||||
func (cli *socketClient) VerifyVoteExtension(ctx context.Context, req *types.RequestVerifyVoteExtension) (*types.ResponseVerifyVoteExtension, error) {
|
||||
reqRes, err := cli.queueRequest(ctx, types.ToRequestVerifyVoteExtension(req))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := cli.Flush(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return reqRes.Response.GetVerifyVoteExtension(), cli.Error()
|
||||
}
|
||||
|
||||
func (cli *socketClient) FinalizeBlock(ctx context.Context, req *types.RequestFinalizeBlock) (*types.ResponseFinalizeBlock, error) {
|
||||
reqRes, err := cli.queueRequest(ctx, types.ToRequestFinalizeBlock(req))
|
||||
if err != nil {
|
||||
@@ -461,6 +483,10 @@ func resMatchesReq(req *types.Request, res *types.Response) (ok bool) {
|
||||
_, ok = res.Value.(*types.Response_ListSnapshots)
|
||||
case *types.Request_OfferSnapshot:
|
||||
_, ok = res.Value.(*types.Response_OfferSnapshot)
|
||||
case *types.Request_ExtendVote:
|
||||
_, ok = res.Value.(*types.Response_ExtendVote)
|
||||
case *types.Request_VerifyVoteExtension:
|
||||
_, ok = res.Value.(*types.Response_VerifyVoteExtension)
|
||||
case *types.Request_PrepareProposal:
|
||||
_, ok = res.Value.(*types.Response_PrepareProposal)
|
||||
case *types.Request_ProcessProposal:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# KVStore
|
||||
|
||||
The KVStoreApplication is a simple merkle key-value store.
|
||||
The KVStoreApplication is a simple merkle key-value store.
|
||||
Transactions of the form `key=value` are stored as key-value pairs in the tree.
|
||||
Transactions without an `=` sign set the value to the key.
|
||||
The app has no replay protection (other than what the mempool provides).
|
||||
|
||||
@@ -288,6 +288,18 @@ func (s *SocketServer) handleRequest(ctx context.Context, req *types.Request) (*
|
||||
return nil, err
|
||||
}
|
||||
return types.ToResponseApplySnapshotChunk(res), nil
|
||||
case *types.Request_ExtendVote:
|
||||
res, err := s.app.ExtendVote(ctx, r.ExtendVote)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return types.ToResponseExtendVote(res), nil
|
||||
case *types.Request_VerifyVoteExtension:
|
||||
res, err := s.app.VerifyVoteExtension(ctx, r.VerifyVoteExtension)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return types.ToResponseVerifyVoteExtension(res), nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown request from client: %T", req)
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@ 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)
|
||||
ExtendVote(context.Context, *RequestExtendVote) (*ResponseExtendVote, error)
|
||||
VerifyVoteExtension(context.Context, *RequestVerifyVoteExtension) (*ResponseVerifyVoteExtension, error)
|
||||
// Commit the state and return the application Merkle root hash
|
||||
Commit(context.Context, *RequestCommit) (*ResponseCommit, error)
|
||||
|
||||
@@ -94,6 +96,14 @@ func (BaseApplication) ProcessProposal(_ context.Context, req *RequestProcessPro
|
||||
return &ResponseProcessProposal{Status: ResponseProcessProposal_ACCEPT}, nil
|
||||
}
|
||||
|
||||
func (BaseApplication) ExtendVote(_ context.Context, req *RequestExtendVote) (*ResponseExtendVote, error) {
|
||||
return &ResponseExtendVote{}, nil
|
||||
}
|
||||
|
||||
func (BaseApplication) VerifyVoteExtension(_ context.Context, req *RequestVerifyVoteExtension) (*ResponseVerifyVoteExtension, error) {
|
||||
return &ResponseVerifyVoteExtension{Result: OK}, nil
|
||||
}
|
||||
|
||||
func (BaseApplication) FinalizeBlock(_ context.Context, req *RequestFinalizeBlock) (*ResponseFinalizeBlock, error) {
|
||||
txs := make([]*ExecTxResult, len(req.Txs))
|
||||
for i := range req.Txs {
|
||||
|
||||
@@ -105,6 +105,18 @@ func ToRequestProcessProposal(req *RequestProcessProposal) *Request {
|
||||
}
|
||||
}
|
||||
|
||||
func ToRequestExtendVote(req *RequestExtendVote) *Request {
|
||||
return &Request{
|
||||
Value: &Request_ExtendVote{req},
|
||||
}
|
||||
}
|
||||
|
||||
func ToRequestVerifyVoteExtension(req *RequestVerifyVoteExtension) *Request {
|
||||
return &Request{
|
||||
Value: &Request_VerifyVoteExtension{req},
|
||||
}
|
||||
}
|
||||
|
||||
func ToRequestFinalizeBlock(req *RequestFinalizeBlock) *Request {
|
||||
return &Request{
|
||||
Value: &Request_FinalizeBlock{req},
|
||||
@@ -197,6 +209,18 @@ func ToResponseProcessProposal(res *ResponseProcessProposal) *Response {
|
||||
}
|
||||
}
|
||||
|
||||
func ToResponseExtendVote(res *ResponseExtendVote) *Response {
|
||||
return &Response{
|
||||
Value: &Response_ExtendVote{res},
|
||||
}
|
||||
}
|
||||
|
||||
func ToResponseVerifyVoteExtension(res *ResponseVerifyVoteExtension) *Response {
|
||||
return &Response{
|
||||
Value: &Response_VerifyVoteExtension{res},
|
||||
}
|
||||
}
|
||||
|
||||
func ToResponseFinalizeBlock(res *ResponseFinalizeBlock) *Response {
|
||||
return &Response{
|
||||
Value: &Response_FinalizeBlock{res},
|
||||
|
||||
@@ -339,6 +339,11 @@ func TestConsMsgsVectors(t *testing.T) {
|
||||
}
|
||||
pbProposal := proposal.ToProto()
|
||||
|
||||
ext := types.VoteExtension{
|
||||
AppDataToSign: []byte("signed"),
|
||||
AppDataSelfAuthenticating: []byte("auth"),
|
||||
}
|
||||
|
||||
v := &types.Vote{
|
||||
ValidatorAddress: []byte("add_more_exclamation"),
|
||||
ValidatorIndex: 1,
|
||||
@@ -347,6 +352,7 @@ func TestConsMsgsVectors(t *testing.T) {
|
||||
Timestamp: date,
|
||||
Type: tmproto.PrecommitType,
|
||||
BlockID: bi,
|
||||
VoteExtension: ext,
|
||||
}
|
||||
vpb := v.ToProto()
|
||||
|
||||
@@ -383,7 +389,7 @@ func TestConsMsgsVectors(t *testing.T) {
|
||||
"2a36080110011a3008011204746573741a26080110011a206164645f6d6f72655f6578636c616d6174696f6e5f6d61726b735f636f64652d"},
|
||||
{"Vote", &tmcons.Message{Sum: &tmcons.Message_Vote{
|
||||
Vote: &tmcons.Vote{Vote: vpb}}},
|
||||
"32700a6e0802100122480a206164645f6d6f72655f6578636c616d6174696f6e5f6d61726b735f636f64652d1224080112206164645f6d6f72655f6578636c616d6174696f6e5f6d61726b735f636f64652d2a0608c0b89fdc0532146164645f6d6f72655f6578636c616d6174696f6e3801"},
|
||||
"3280010a7e0802100122480a206164645f6d6f72655f6578636c616d6174696f6e5f6d61726b735f636f64652d1224080112206164645f6d6f72655f6578636c616d6174696f6e5f6d61726b735f636f64652d2a0608c0b89fdc0532146164645f6d6f72655f6578636c616d6174696f6e38014a0e0a067369676e6564120461757468"},
|
||||
{"HasVote", &tmcons.Message{Sum: &tmcons.Message_HasVote{
|
||||
HasVote: &tmcons.HasVote{Height: 1, Round: 1, Type: tmproto.PrevoteType, Index: 1}}},
|
||||
"3a080801100118012001"},
|
||||
|
||||
@@ -2231,6 +2231,16 @@ func (cs *State) signVote(
|
||||
|
||||
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
|
||||
ext, err := cs.blockExec.ExtendVote(vote)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vote.VoteExtension = ext
|
||||
}
|
||||
vote.Signature = v.Signature
|
||||
vote.Timestamp = v.Timestamp
|
||||
|
||||
@@ -2259,7 +2269,11 @@ func (cs *State) voteTime() time.Time {
|
||||
}
|
||||
|
||||
// sign the vote and publish on internalMsgQueue
|
||||
func (cs *State) signAddVote(msgType tmproto.SignedMsgType, hash []byte, header types.PartSetHeader) *types.Vote {
|
||||
func (cs *State) signAddVote(
|
||||
msgType tmproto.SignedMsgType,
|
||||
hash []byte,
|
||||
header types.PartSetHeader,
|
||||
) *types.Vote {
|
||||
if cs.privValidator == nil { // the node does not have a key
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -35,6 +35,10 @@ func exampleVote() *types.Vote {
|
||||
},
|
||||
ValidatorAddress: crypto.AddressHash([]byte("validator_address")),
|
||||
ValidatorIndex: 56789,
|
||||
VoteExtension: types.VoteExtension {
|
||||
AppDataToSign: []byte("app_data_to_sign"),
|
||||
AppDataSelfAuthenticating: []byte("app_data_self_authenticating"),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,8 +88,8 @@ func TestPrivvalVectors(t *testing.T) {
|
||||
{"pubKey request", &privproto.PubKeyRequest{}, "0a00"},
|
||||
{"pubKey response", &privproto.PubKeyResponse{PubKey: ppk, Error: nil}, "12240a220a20556a436f1218d30942efe798420f51dc9b6a311b929c578257457d05c5fcf230"},
|
||||
{"pubKey response with error", &privproto.PubKeyResponse{PubKey: cryptoproto.PublicKey{}, Error: remoteError}, "12140a0012100801120c697427732061206572726f72"},
|
||||
{"Vote Request", &privproto.SignVoteRequest{Vote: votepb}, "1a760a74080110031802224a0a208b01023386c371778ecb6368573e539afc3cc860ec3a2f614e54fe5652f4fc80122608c0843d122072db3d959635dff1bb567bedaa70573392c5159666a3f8caf11e413aac52207a2a0608f49a8ded0532146af1f4111082efb388211bc72c55bcd61e9ac3d538d5bb03"},
|
||||
{"Vote Response", &privproto.SignedVoteResponse{Vote: *votepb, Error: nil}, "22760a74080110031802224a0a208b01023386c371778ecb6368573e539afc3cc860ec3a2f614e54fe5652f4fc80122608c0843d122072db3d959635dff1bb567bedaa70573392c5159666a3f8caf11e413aac52207a2a0608f49a8ded0532146af1f4111082efb388211bc72c55bcd61e9ac3d538d5bb03"},
|
||||
{"Vote Request", &privproto.SignVoteRequest{Vote: votepb}, "1aa8010aa501080110031802224a0a208b01023386c371778ecb6368573e539afc3cc860ec3a2f614e54fe5652f4fc80122608c0843d122072db3d959635dff1bb567bedaa70573392c5159666a3f8caf11e413aac52207a2a0608f49a8ded0532146af1f4111082efb388211bc72c55bcd61e9ac3d538d5bb034a2f0a0f6170705f646174615f7369676e6564121c6170705f646174615f73656c665f61757468656e7469636174696e67"},
|
||||
{"Vote Response", &privproto.SignedVoteResponse{Vote: *votepb, Error: nil}, "22a8010aa501080110031802224a0a208b01023386c371778ecb6368573e539afc3cc860ec3a2f614e54fe5652f4fc80122608c0843d122072db3d959635dff1bb567bedaa70573392c5159666a3f8caf11e413aac52207a2a0608f49a8ded0532146af1f4111082efb388211bc72c55bcd61e9ac3d538d5bb034a2f0a0f6170705f646174615f7369676e6564121c6170705f646174615f73656c665f61757468656e7469636174696e67"},
|
||||
{"Vote Response with error", &privproto.SignedVoteResponse{Vote: tmproto.Vote{}, Error: remoteError}, "22250a11220212002a0b088092b8c398feffffff0112100801120c697427732061206572726f72"},
|
||||
{"Proposal Request", &privproto.SignProposalRequest{Proposal: proposalpb}, "2a700a6e08011003180220022a4a0a208b01023386c371778ecb6368573e539afc3cc860ec3a2f614e54fe5652f4fc80122608c0843d122072db3d959635dff1bb567bedaa70573392c5159666a3f8caf11e413aac52207a320608f49a8ded053a10697427732061207369676e6174757265"},
|
||||
{"Proposal Response", &privproto.SignedProposalResponse{Proposal: *proposalpb, Error: nil}, "32700a6e08011003180220022a4a0a208b01023386c371778ecb6368573e539afc3cc860ec3a2f614e54fe5652f4fc80122608c0843d122072db3d959635dff1bb567bedaa70573392c5159666a3f8caf11e413aac52207a320608f49a8ded053a10697427732061207369676e6174757265"},
|
||||
|
||||
@@ -30,6 +30,8 @@ service ABCI {
|
||||
returns (ResponseApplySnapshotChunk);
|
||||
rpc PrepareProposal(RequestPrepareProposal) returns (ResponsePrepareProposal);
|
||||
rpc ProcessProposal(RequestProcessProposal) returns (ResponseProcessProposal);
|
||||
rpc ExtendVote(RequestExtendVote) returns (ResponseExtendVote);
|
||||
rpc VerifyVoteExtension(RequestVerifyVoteExtension) returns (ResponseVerifyVoteExtension);
|
||||
rpc FinalizeBlock(RequestFinalizeBlock) returns (ResponseFinalizeBlock);
|
||||
}
|
||||
|
||||
@@ -38,20 +40,22 @@ service ABCI {
|
||||
|
||||
message Request {
|
||||
oneof value {
|
||||
RequestEcho echo = 1;
|
||||
RequestFlush flush = 2;
|
||||
RequestInfo info = 3;
|
||||
RequestInitChain init_chain = 5;
|
||||
RequestQuery query = 6;
|
||||
RequestCheckTx check_tx = 8;
|
||||
RequestCommit commit = 11;
|
||||
RequestListSnapshots list_snapshots = 12;
|
||||
RequestOfferSnapshot offer_snapshot = 13;
|
||||
RequestLoadSnapshotChunk load_snapshot_chunk = 14;
|
||||
RequestApplySnapshotChunk apply_snapshot_chunk = 15;
|
||||
RequestPrepareProposal prepare_proposal = 16;
|
||||
RequestProcessProposal process_proposal = 17;
|
||||
RequestFinalizeBlock finalize_block = 20;
|
||||
RequestEcho echo = 1;
|
||||
RequestFlush flush = 2;
|
||||
RequestInfo info = 3;
|
||||
RequestInitChain init_chain = 5;
|
||||
RequestQuery query = 6;
|
||||
RequestCheckTx check_tx = 8;
|
||||
RequestCommit commit = 11;
|
||||
RequestListSnapshots list_snapshots = 12;
|
||||
RequestOfferSnapshot offer_snapshot = 13;
|
||||
RequestLoadSnapshotChunk load_snapshot_chunk = 14;
|
||||
RequestApplySnapshotChunk apply_snapshot_chunk = 15;
|
||||
RequestPrepareProposal prepare_proposal = 16;
|
||||
RequestProcessProposal process_proposal = 17;
|
||||
RequestExtendVote extend_vote = 18;
|
||||
RequestVerifyVoteExtension verify_vote_extension = 19;
|
||||
RequestFinalizeBlock finalize_block = 20;
|
||||
}
|
||||
reserved 4, 7, 9, 10; // SetOption, BeginBlock, DeliverTx, EndBlock
|
||||
}
|
||||
@@ -121,6 +125,16 @@ message RequestApplySnapshotChunk {
|
||||
string sender = 3;
|
||||
}
|
||||
|
||||
// Extends a vote with application-side injection
|
||||
message RequestExtendVote {
|
||||
types.Vote vote = 1;
|
||||
}
|
||||
|
||||
// Verify the vote extension
|
||||
message RequestVerifyVoteExtension {
|
||||
types.Vote vote = 1;
|
||||
}
|
||||
|
||||
message RequestPrepareProposal {
|
||||
// the modified transactions cannot exceed this size.
|
||||
int64 max_tx_bytes = 1;
|
||||
@@ -167,21 +181,23 @@ message RequestFinalizeBlock {
|
||||
|
||||
message Response {
|
||||
oneof value {
|
||||
ResponseException exception = 1;
|
||||
ResponseEcho echo = 2;
|
||||
ResponseFlush flush = 3;
|
||||
ResponseInfo info = 4;
|
||||
ResponseInitChain init_chain = 6;
|
||||
ResponseQuery query = 7;
|
||||
ResponseCheckTx check_tx = 9;
|
||||
ResponseCommit commit = 12;
|
||||
ResponseListSnapshots list_snapshots = 13;
|
||||
ResponseOfferSnapshot offer_snapshot = 14;
|
||||
ResponseLoadSnapshotChunk load_snapshot_chunk = 15;
|
||||
ResponseApplySnapshotChunk apply_snapshot_chunk = 16;
|
||||
ResponsePrepareProposal prepare_proposal = 17;
|
||||
ResponseProcessProposal process_proposal = 18;
|
||||
ResponseFinalizeBlock finalize_block = 21;
|
||||
ResponseException exception = 1;
|
||||
ResponseEcho echo = 2;
|
||||
ResponseFlush flush = 3;
|
||||
ResponseInfo info = 4;
|
||||
ResponseInitChain init_chain = 6;
|
||||
ResponseQuery query = 7;
|
||||
ResponseCheckTx check_tx = 9;
|
||||
ResponseCommit commit = 12;
|
||||
ResponseListSnapshots list_snapshots = 13;
|
||||
ResponseOfferSnapshot offer_snapshot = 14;
|
||||
ResponseLoadSnapshotChunk load_snapshot_chunk = 15;
|
||||
ResponseApplySnapshotChunk apply_snapshot_chunk = 16;
|
||||
ResponsePrepareProposal prepare_proposal = 17;
|
||||
ResponseProcessProposal process_proposal = 18;
|
||||
ResponseExtendVote extend_vote = 19;
|
||||
ResponseVerifyVoteExtension verify_vote_extension = 20;
|
||||
ResponseFinalizeBlock finalize_block = 21;
|
||||
}
|
||||
reserved 5, 8, 10, 11; // SetOption, BeginBlock, DeliverTx, EndBlock
|
||||
}
|
||||
@@ -285,6 +301,21 @@ message ResponseApplySnapshotChunk {
|
||||
}
|
||||
}
|
||||
|
||||
message ResponseExtendVote {
|
||||
tendermint.types.VoteExtension vote_extension = 1;
|
||||
}
|
||||
|
||||
message ResponseVerifyVoteExtension {
|
||||
Result result = 1;
|
||||
|
||||
enum Result {
|
||||
UNKNOWN = 0; // Unknown result, treat as ACCEPT by default
|
||||
ACCEPT = 1; // Vote extension verified, include the vote
|
||||
SLASH = 2; // Vote extension verification aborted, continue but slash validator
|
||||
REJECT = 3; // Vote extension invalidated
|
||||
}
|
||||
}
|
||||
|
||||
message ResponsePrepareProposal {
|
||||
repeated bytes txs = 1;
|
||||
}
|
||||
|
||||
@@ -34,4 +34,5 @@ message CanonicalVote {
|
||||
CanonicalBlockID block_id = 4 [(gogoproto.customname) = "BlockID"];
|
||||
google.protobuf.Timestamp timestamp = 5 [(gogoproto.nullable) = false, (gogoproto.stdtime) = true];
|
||||
string chain_id = 6 [(gogoproto.customname) = "ChainID"];
|
||||
VoteExtensionToSign vote_extension = 7;
|
||||
}
|
||||
|
||||
@@ -102,6 +102,19 @@ message Vote {
|
||||
bytes validator_address = 6;
|
||||
int32 validator_index = 7;
|
||||
bytes signature = 8;
|
||||
VoteExtension vote_extension = 9;
|
||||
}
|
||||
|
||||
// VoteExtension is app-defined additional information to the validator votes.
|
||||
message VoteExtension {
|
||||
bytes app_data_to_sign = 1;
|
||||
bytes app_data_self_authenticating = 2;
|
||||
}
|
||||
|
||||
// VoteExtensionToSign is a subset of VoteExtension that is signed by the validators private key.
|
||||
// VoteExtensionToSign is extracted from an existing VoteExtension.
|
||||
message VoteExtensionToSign {
|
||||
bytes app_data_to_sign = 1;
|
||||
}
|
||||
|
||||
// Commit contains the evidence that a block was committed by a set of validators.
|
||||
@@ -119,6 +132,7 @@ message CommitSig {
|
||||
google.protobuf.Timestamp timestamp = 3
|
||||
[(gogoproto.nullable) = false, (gogoproto.stdtime) = true];
|
||||
bytes signature = 4;
|
||||
VoteExtensionToSign vote_extension = 5;
|
||||
}
|
||||
|
||||
message Proposal {
|
||||
|
||||
@@ -20,6 +20,8 @@ type AppConnConsensus interface {
|
||||
InitChain(context.Context, *types.RequestInitChain) (*types.ResponseInitChain, error)
|
||||
PrepareProposal(context.Context, *types.RequestPrepareProposal) (*types.ResponsePrepareProposal, error)
|
||||
ProcessProposal(context.Context, *types.RequestProcessProposal) (*types.ResponseProcessProposal, error)
|
||||
ExtendVote(context.Context, *types.RequestExtendVote) (*types.ResponseExtendVote, error)
|
||||
VerifyVoteExtension(context.Context, *types.RequestVerifyVoteExtension) (*types.ResponseVerifyVoteExtension, error)
|
||||
FinalizeBlock(context.Context, *types.RequestFinalizeBlock) (*types.ResponseFinalizeBlock, error)
|
||||
Commit(context.Context) (*types.ResponseCommit, error)
|
||||
}
|
||||
@@ -97,6 +99,14 @@ func (app *appConnConsensus) Commit(ctx context.Context) (*types.ResponseCommit,
|
||||
return app.appConn.Commit(ctx, &types.RequestCommit{})
|
||||
}
|
||||
|
||||
func (app *appConnConsensus) ExtendVoteSync(req types.RequestExtendVote) (*types.ResponseExtendVote, error) {
|
||||
return app.appConn.ExtendVoteSync(req)
|
||||
}
|
||||
|
||||
func (app *appConnConsensus) VerifyVoteExtensionSync(req types.RequestVerifyVoteExtension) (*types.ResponseVerifyVoteExtension, error) {
|
||||
return app.appConn.VerifyVoteExtensionSync(req)
|
||||
}
|
||||
|
||||
//------------------------------------------------
|
||||
// Implements AppConnMempool (subset of abcicli.Client)
|
||||
|
||||
|
||||
@@ -293,6 +293,19 @@ func (blockExec *BlockExecutor) ApplyBlock(
|
||||
return state, nil
|
||||
}
|
||||
|
||||
func (blockExec *BlockExecutor) ExtendVote(vote *types.Vote) (types.VoteExtension, error) {
|
||||
req := abci.RequestExtendVote{
|
||||
Vote: vote.ToProto(),
|
||||
}
|
||||
|
||||
resp, err := blockExec.proxyApp.ExtendVoteSync(req)
|
||||
if err != nil {
|
||||
return types.VoteExtension{}, err
|
||||
}
|
||||
|
||||
return types.VoteExtensionFromProto(resp.VoteExtension), 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).
|
||||
@@ -473,7 +486,7 @@ func updateState(
|
||||
|
||||
nextVersion := state.Version
|
||||
|
||||
// NOTE: the AppHash has not been populated.
|
||||
// NOTE: the AppHash and the VoteExtension has not been populated.
|
||||
// It will be filled on state.Save.
|
||||
return State{
|
||||
Version: nextVersion,
|
||||
|
||||
@@ -178,11 +178,15 @@ func TestFinalizeBlockValidators(t *testing.T) {
|
||||
commitSig0 = types.NewCommitSigForBlock(
|
||||
[]byte("Signature1"),
|
||||
state.Validators.Validators[0].Address,
|
||||
now)
|
||||
now,
|
||||
types.VoteExtensionToSign{},
|
||||
)
|
||||
commitSig1 = types.NewCommitSigForBlock(
|
||||
[]byte("Signature2"),
|
||||
state.Validators.Validators[1].Address,
|
||||
now)
|
||||
now,
|
||||
types.VoteExtensionToSign{},
|
||||
)
|
||||
absentSig = types.NewCommitSigAbsent()
|
||||
)
|
||||
|
||||
|
||||
@@ -594,19 +594,21 @@ const (
|
||||
|
||||
// CommitSig is a part of the Vote included in a Commit.
|
||||
type CommitSig struct {
|
||||
BlockIDFlag BlockIDFlag `json:"block_id_flag"`
|
||||
ValidatorAddress Address `json:"validator_address"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
Signature []byte `json:"signature"`
|
||||
BlockIDFlag BlockIDFlag `json:"block_id_flag"`
|
||||
ValidatorAddress Address `json:"validator_address"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
Signature []byte `json:"signature"`
|
||||
VoteExtension VoteExtensionToSign `json:"vote_extension"`
|
||||
}
|
||||
|
||||
// NewCommitSigForBlock returns new CommitSig with BlockIDFlagCommit.
|
||||
func NewCommitSigForBlock(signature []byte, valAddr Address, ts time.Time) CommitSig {
|
||||
func NewCommitSigForBlock(signature []byte, valAddr Address, ts time.Time, ext VoteExtensionToSign) CommitSig {
|
||||
return CommitSig{
|
||||
BlockIDFlag: BlockIDFlagCommit,
|
||||
ValidatorAddress: valAddr,
|
||||
Timestamp: ts,
|
||||
Signature: signature,
|
||||
VoteExtension: ext,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -639,12 +641,14 @@ func (cs CommitSig) Absent() bool {
|
||||
// 1. first 6 bytes of signature
|
||||
// 2. first 6 bytes of validator address
|
||||
// 3. block ID flag
|
||||
// 4. timestamp
|
||||
// 4. first 6 bytes of the vote extension
|
||||
// 5. timestamp
|
||||
func (cs CommitSig) String() string {
|
||||
return fmt.Sprintf("CommitSig{%X by %X on %v @ %s}",
|
||||
return fmt.Sprintf("CommitSig{%X by %X on %v with %X @ %s}",
|
||||
tmbytes.Fingerprint(cs.Signature),
|
||||
tmbytes.Fingerprint(cs.ValidatorAddress),
|
||||
cs.BlockIDFlag,
|
||||
tmbytes.Fingerprint(cs.VoteExtension.BytesPacked()),
|
||||
CanonicalTime(cs.Timestamp))
|
||||
}
|
||||
|
||||
@@ -793,6 +797,7 @@ func (commit *Commit) GetVote(valIdx int32) *Vote {
|
||||
ValidatorAddress: commitSig.ValidatorAddress,
|
||||
ValidatorIndex: valIdx,
|
||||
Signature: commitSig.Signature,
|
||||
VoteExtension: commitSig.VoteExtension.ToVoteExtension(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -51,16 +51,26 @@ func CanonicalizeProposal(chainID string, proposal *tmproto.Proposal) tmproto.Ca
|
||||
}
|
||||
}
|
||||
|
||||
func GetVoteExtensionToSign(ext *tmproto.VoteExtension) *tmproto.VoteExtensionToSign {
|
||||
if ext == nil {
|
||||
return nil
|
||||
}
|
||||
return &tmproto.VoteExtensionToSign{
|
||||
AppDataToSign: ext.AppDataToSign,
|
||||
}
|
||||
}
|
||||
|
||||
// CanonicalizeVote transforms the given Vote to a CanonicalVote, which does
|
||||
// not contain ValidatorIndex and ValidatorAddress fields.
|
||||
func CanonicalizeVote(chainID string, vote *tmproto.Vote) tmproto.CanonicalVote {
|
||||
return tmproto.CanonicalVote{
|
||||
Type: vote.Type,
|
||||
Height: vote.Height, // encoded as sfixed64
|
||||
Round: int64(vote.Round), // encoded as sfixed64
|
||||
BlockID: CanonicalizeBlockID(vote.BlockID),
|
||||
Timestamp: vote.Timestamp,
|
||||
ChainID: chainID,
|
||||
Type: vote.Type,
|
||||
Height: vote.Height, // encoded as sfixed64
|
||||
Round: int64(vote.Round), // encoded as sfixed64
|
||||
BlockID: CanonicalizeBlockID(vote.BlockID),
|
||||
Timestamp: vote.Timestamp,
|
||||
ChainID: chainID,
|
||||
VoteExtension: GetVoteExtensionToSign(vote.VoteExtension),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
100
types/vote.go
100
types/vote.go
@@ -24,6 +24,7 @@ var (
|
||||
ErrVoteInvalidBlockHash = errors.New("invalid block hash")
|
||||
ErrVoteNonDeterministicSignature = errors.New("non-deterministic signature")
|
||||
ErrVoteNil = errors.New("nil vote")
|
||||
ErrVoteInvalidExtension = errors.New("invalid vote extension")
|
||||
)
|
||||
|
||||
type ErrVoteConflictingVotes struct {
|
||||
@@ -45,6 +46,52 @@ func NewConflictingVoteError(vote1, vote2 *Vote) *ErrVoteConflictingVotes {
|
||||
// Address is hex bytes.
|
||||
type Address = crypto.Address
|
||||
|
||||
// VoteExtensionToSign is a subset of VoteExtension
|
||||
// that is signed by the validators private key
|
||||
type VoteExtensionToSign struct {
|
||||
AppDataToSign []byte `json:"app_data_to_sign"`
|
||||
}
|
||||
|
||||
// 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)
|
||||
return res
|
||||
}
|
||||
|
||||
// ToVoteExtension constructs a VoteExtension from a VoteExtensionToSign
|
||||
func (ext VoteExtensionToSign) ToVoteExtension() VoteExtension {
|
||||
return VoteExtension{
|
||||
AppDataToSign: ext.AppDataToSign,
|
||||
}
|
||||
}
|
||||
|
||||
// VoteExtension is a set of data provided by the application
|
||||
// that is additionally included in the vote
|
||||
type VoteExtension struct {
|
||||
AppDataToSign []byte `json:"app_data_to_sign"`
|
||||
AppDataSelfAuthenticating []byte `json:"app_data_self_authenticating"`
|
||||
}
|
||||
|
||||
// ToSign constructs a VoteExtensionToSign from a VoteExtenstion
|
||||
func (ext VoteExtension) ToSign() VoteExtensionToSign {
|
||||
return VoteExtensionToSign{
|
||||
AppDataToSign: ext.AppDataToSign,
|
||||
}
|
||||
}
|
||||
|
||||
// BytesPacked returns a bytes-packed representation for
|
||||
// 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)
|
||||
return res
|
||||
}
|
||||
|
||||
// Vote represents a prevote, precommit, or commit vote from validators for
|
||||
// consensus.
|
||||
type Vote struct {
|
||||
@@ -56,6 +103,7 @@ type Vote struct {
|
||||
ValidatorAddress Address `json:"validator_address"`
|
||||
ValidatorIndex int32 `json:"validator_index"`
|
||||
Signature []byte `json:"signature"`
|
||||
VoteExtension VoteExtension `json:"vote_extension"`
|
||||
}
|
||||
|
||||
// CommitSig converts the Vote to a CommitSig.
|
||||
@@ -79,6 +127,7 @@ func (vote *Vote) CommitSig() CommitSig {
|
||||
ValidatorAddress: vote.ValidatorAddress,
|
||||
Timestamp: vote.Timestamp,
|
||||
Signature: vote.Signature,
|
||||
VoteExtension: vote.VoteExtension.ToSign(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,6 +151,7 @@ func VoteSignBytes(chainID string, vote *tmproto.Vote) []byte {
|
||||
|
||||
func (vote *Vote) Copy() *Vote {
|
||||
voteCopy := *vote
|
||||
voteCopy.VoteExtension = vote.VoteExtension.Copy()
|
||||
return &voteCopy
|
||||
}
|
||||
|
||||
@@ -115,7 +165,8 @@ func (vote *Vote) Copy() *Vote {
|
||||
// 6. type string
|
||||
// 7. first 6 bytes of block hash
|
||||
// 8. first 6 bytes of signature
|
||||
// 9. timestamp
|
||||
// 9. first 6 bytes of vote extension
|
||||
// 10. timestamp
|
||||
func (vote *Vote) String() string {
|
||||
if vote == nil {
|
||||
return nilVoteStr
|
||||
@@ -131,7 +182,7 @@ func (vote *Vote) String() string {
|
||||
panic("Unknown vote type")
|
||||
}
|
||||
|
||||
return fmt.Sprintf("Vote{%v:%X %v/%02d/%v(%v) %X %X @ %s}",
|
||||
return fmt.Sprintf("Vote{%v:%X %v/%02d/%v(%v) %X %X %X @ %s}",
|
||||
vote.ValidatorIndex,
|
||||
tmbytes.Fingerprint(vote.ValidatorAddress),
|
||||
vote.Height,
|
||||
@@ -140,6 +191,7 @@ func (vote *Vote) String() string {
|
||||
typeString,
|
||||
tmbytes.Fingerprint(vote.BlockID.Hash),
|
||||
tmbytes.Fingerprint(vote.Signature),
|
||||
tmbytes.Fingerprint(vote.VoteExtension.BytesPacked()),
|
||||
CanonicalTime(vote.Timestamp),
|
||||
)
|
||||
}
|
||||
@@ -198,9 +250,42 @@ func (vote *Vote) ValidateBasic() error {
|
||||
return fmt.Errorf("signature is too big (max: %d)", MaxSignatureSize)
|
||||
}
|
||||
|
||||
// XXX: add length verification for vote extension?
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ext VoteExtension) Copy() VoteExtension {
|
||||
res := VoteExtension{
|
||||
AppDataToSign: make([]byte, len(ext.AppDataToSign)),
|
||||
AppDataSelfAuthenticating: make([]byte, len(ext.AppDataSelfAuthenticating)),
|
||||
}
|
||||
copy(res.AppDataToSign, ext.AppDataToSign)
|
||||
copy(res.AppDataSelfAuthenticating, ext.AppDataSelfAuthenticating)
|
||||
return res
|
||||
}
|
||||
|
||||
func (ext VoteExtension) IsEmpty() bool {
|
||||
if len(ext.AppDataToSign) != 0 {
|
||||
return false
|
||||
}
|
||||
if len(ext.AppDataSelfAuthenticating) != 0 {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (ext VoteExtension) ToProto() *tmproto.VoteExtension {
|
||||
if ext.IsEmpty() {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &tmproto.VoteExtension{
|
||||
AppDataToSign: ext.AppDataToSign,
|
||||
AppDataSelfAuthenticating: ext.AppDataSelfAuthenticating,
|
||||
}
|
||||
}
|
||||
|
||||
// ToProto converts the handwritten type to proto generated type
|
||||
// return type, nil if everything converts safely, otherwise nil, error
|
||||
func (vote *Vote) ToProto() *tmproto.Vote {
|
||||
@@ -217,6 +302,7 @@ func (vote *Vote) ToProto() *tmproto.Vote {
|
||||
ValidatorAddress: vote.ValidatorAddress,
|
||||
ValidatorIndex: vote.ValidatorIndex,
|
||||
Signature: vote.Signature,
|
||||
VoteExtension: vote.VoteExtension.ToProto(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,6 +322,15 @@ func VotesToProto(votes []*Vote) []*tmproto.Vote {
|
||||
return res
|
||||
}
|
||||
|
||||
func VoteExtensionFromProto(pext *tmproto.VoteExtension) VoteExtension {
|
||||
ext := VoteExtension{}
|
||||
if pext != nil {
|
||||
ext.AppDataToSign = pext.AppDataToSign
|
||||
ext.AppDataSelfAuthenticating = pext.AppDataSelfAuthenticating
|
||||
}
|
||||
return ext
|
||||
}
|
||||
|
||||
// FromProto converts a proto generetad type to a handwritten type
|
||||
// return type, nil if everything converts safely, otherwise nil, error
|
||||
func VoteFromProto(pv *tmproto.Vote) (*Vote, error) {
|
||||
@@ -257,6 +352,7 @@ func VoteFromProto(pv *tmproto.Vote) (*Vote, error) {
|
||||
vote.ValidatorAddress = pv.ValidatorAddress
|
||||
vote.ValidatorIndex = pv.ValidatorIndex
|
||||
vote.Signature = pv.Signature
|
||||
vote.VoteExtension = VoteExtensionFromProto(pv.VoteExtension)
|
||||
|
||||
return vote, vote.ValidateBasic()
|
||||
}
|
||||
|
||||
@@ -127,6 +127,33 @@ func TestVoteSignBytesTestVectors(t *testing.T) {
|
||||
0x32,
|
||||
0xd, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64}, // chainID
|
||||
},
|
||||
// containing vote extension
|
||||
5: {
|
||||
"test_chain_id", &Vote{Height: 1, Round: 1, VoteExtension: VoteExtension{
|
||||
AppDataToSign: []byte("signed"),
|
||||
AppDataSelfAuthenticating: []byte("auth"),
|
||||
}},
|
||||
[]byte{
|
||||
0x38, // length
|
||||
0x11, // (field_number << 3) | wire_type
|
||||
0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // height
|
||||
0x19, // (field_number << 3) | wire_type
|
||||
0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // round
|
||||
// remaning fields:
|
||||
0x2a, // (field_number << 3) | wire_type
|
||||
0xb, 0x8, 0x80, 0x92, 0xb8, 0xc3, 0x98, 0xfe, 0xff, 0xff, 0xff, 0x1, // timestamp
|
||||
// (field_number << 3) | wire_type
|
||||
0x32,
|
||||
0xd, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, // chainID
|
||||
// (field_number << 3) | wire_type
|
||||
0x3a,
|
||||
0x8, // length
|
||||
0xa, // (field_number << 3) | wire_type
|
||||
0x6, // length
|
||||
0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, // AppDataSigned
|
||||
// SelfAuthenticating data is excluded on signing
|
||||
}, // chainID
|
||||
},
|
||||
}
|
||||
for i, tc := range tests {
|
||||
v := tc.vote.ToProto()
|
||||
@@ -219,13 +246,13 @@ func TestVoteVerify(t *testing.T) {
|
||||
|
||||
func TestVoteString(t *testing.T) {
|
||||
str := examplePrecommit().String()
|
||||
expected := `Vote{56789:6AF1F4111082 12345/02/SIGNED_MSG_TYPE_PRECOMMIT(Precommit) 8B01023386C3 000000000000 @ 2017-12-25T03:00:01.234Z}` //nolint:lll //ignore line length for tests
|
||||
expected := `Vote{56789:6AF1F4111082 12345/02/SIGNED_MSG_TYPE_PRECOMMIT(Precommit) 8B01023386C3 000000000000 000000000000 @ 2017-12-25T03:00:01.234Z}` //nolint:lll //ignore line length for tests
|
||||
if str != expected {
|
||||
t.Errorf("got unexpected string for Vote. Expected:\n%v\nGot:\n%v", expected, str)
|
||||
}
|
||||
|
||||
str2 := examplePrevote().String()
|
||||
expected = `Vote{56789:6AF1F4111082 12345/02/SIGNED_MSG_TYPE_PREVOTE(Prevote) 8B01023386C3 000000000000 @ 2017-12-25T03:00:01.234Z}` //nolint:lll //ignore line length for tests
|
||||
expected = `Vote{56789:6AF1F4111082 12345/02/SIGNED_MSG_TYPE_PREVOTE(Prevote) 8B01023386C3 000000000000 000000000000 @ 2017-12-25T03:00:01.234Z}` //nolint:lll //ignore line length for tests
|
||||
if str2 != expected {
|
||||
t.Errorf("got unexpected string for Vote. Expected:\n%v\nGot:\n%v", expected, str2)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user