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 ff498ff333
commit 29f7573762
25 changed files with 2586 additions and 440 deletions

View File

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

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

View File

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