mirror of
https://github.com/tendermint/tendermint.git
synced 2026-01-07 13:55:17 +00:00
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:
@@ -602,19 +602,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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -647,12 +649,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))
|
||||
}
|
||||
|
||||
@@ -801,6 +805,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,9 +302,19 @@ func (vote *Vote) ToProto() *tmproto.Vote {
|
||||
ValidatorAddress: vote.ValidatorAddress,
|
||||
ValidatorIndex: vote.ValidatorIndex,
|
||||
Signature: vote.Signature,
|
||||
VoteExtension: vote.VoteExtension.ToProto(),
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -241,6 +336,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()
|
||||
}
|
||||
|
||||
@@ -128,6 +128,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()
|
||||
@@ -226,13 +253,13 @@ func TestVoteVerify(t *testing.T) {
|
||||
|
||||
func TestVoteString(t *testing.T) {
|
||||
str := examplePrecommit(t).String()
|
||||
expected := `Vote{56789:6AF1F4111082 12345/02/SIGNED_MSG_TYPE_PRECOMMIT(Precommit) 8B01023386C3 000000000000 @ 2017-12-25T03:00:01.234Z}`
|
||||
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(t).String()
|
||||
expected = `Vote{56789:6AF1F4111082 12345/02/SIGNED_MSG_TYPE_PREVOTE(Prevote) 8B01023386C3 000000000000 @ 2017-12-25T03:00:01.234Z}`
|
||||
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