[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:
mconcat
2021-08-24 04:24:25 +09:00
committed by Sergio Mena
parent 70813a80cf
commit 10588ec66b
20 changed files with 383 additions and 54 deletions

View File

@@ -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))
}

View File

@@ -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()

View File

@@ -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:

View File

@@ -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).

View File

@@ -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)
}

View File

@@ -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 {

View File

@@ -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},

View File

@@ -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"},

View File

@@ -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
}

View File

@@ -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"},

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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 {

View File

@@ -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)

View File

@@ -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,

View File

@@ -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()
)

View File

@@ -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(),
}
}

View File

@@ -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),
}
}

View File

@@ -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()
}

View File

@@ -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)
}