mirror of
https://github.com/fluencelabs/tendermint
synced 2025-06-14 22:01:20 +00:00
fix order of BlockID and Timestamp in Vote and Proposal (#3078)
* Consistent order fields of Timestamp/BlockID fields in CanonicalVote and CanonicalProposal * update spec too * Introduce and use IsZero & IsComplete: - update IsZero method according to spec and introduce IsComplete - use methods in validate basic to validate: proposals come with a "complete" blockId and votes are either complete or empty - update spec: BlockID.IsNil() -> BlockID.IsZero() and fix typo * BlockID comes first * fix tests
This commit is contained in:
committed by
Ethan Buchman
parent
1ccc0918f5
commit
1f68318875
@ -7,6 +7,7 @@ Special thanks to external contributors on this release:
|
|||||||
### BREAKING CHANGES:
|
### BREAKING CHANGES:
|
||||||
|
|
||||||
* CLI/RPC/Config
|
* CLI/RPC/Config
|
||||||
|
- [types] consistent field order of `CanonicalVote` and `CanonicalProposal`
|
||||||
|
|
||||||
* Apps
|
* Apps
|
||||||
|
|
||||||
|
@ -109,10 +109,6 @@ Tendermint uses the
|
|||||||
[Google.Protobuf.WellKnownTypes.Timestamp](https://developers.google.com/protocol-buffers/docs/reference/csharp/class/google/protobuf/well-known-types/timestamp)
|
[Google.Protobuf.WellKnownTypes.Timestamp](https://developers.google.com/protocol-buffers/docs/reference/csharp/class/google/protobuf/well-known-types/timestamp)
|
||||||
format, which uses two integers, one for Seconds and for Nanoseconds.
|
format, which uses two integers, one for Seconds and for Nanoseconds.
|
||||||
|
|
||||||
NOTE: there is currently a small divergence between Tendermint and the
|
|
||||||
Google.Protobuf.WellKnownTypes.Timestamp that should be resolved. See [this
|
|
||||||
issue](https://github.com/tendermint/go-amino/issues/223) for details.
|
|
||||||
|
|
||||||
## Data
|
## Data
|
||||||
|
|
||||||
Data is just a wrapper for a list of transactions, where transactions are
|
Data is just a wrapper for a list of transactions, where transactions are
|
||||||
|
@ -301,8 +301,8 @@ type CanonicalVote struct {
|
|||||||
Type byte
|
Type byte
|
||||||
Height int64 `binary:"fixed64"`
|
Height int64 `binary:"fixed64"`
|
||||||
Round int64 `binary:"fixed64"`
|
Round int64 `binary:"fixed64"`
|
||||||
Timestamp time.Time
|
|
||||||
BlockID CanonicalBlockID
|
BlockID CanonicalBlockID
|
||||||
|
Timestamp time.Time
|
||||||
ChainID string
|
ChainID string
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
@ -59,9 +59,9 @@ type PartSetHeader struct {
|
|||||||
```
|
```
|
||||||
|
|
||||||
To be included in a valid vote or proposal, BlockID must either represent a `nil` block, or a complete one.
|
To be included in a valid vote or proposal, BlockID must either represent a `nil` block, or a complete one.
|
||||||
We introduce two methods, `BlockID.IsNil()` and `BlockID.IsComplete()` for these cases, respectively.
|
We introduce two methods, `BlockID.IsZero()` and `BlockID.IsComplete()` for these cases, respectively.
|
||||||
|
|
||||||
`BlockID.IsNil()` returns true for BlockID `b` if each of the following
|
`BlockID.IsZero()` returns true for BlockID `b` if each of the following
|
||||||
are true:
|
are true:
|
||||||
|
|
||||||
```
|
```
|
||||||
@ -81,7 +81,7 @@ len(b.PartsHeader.Hash) == 32
|
|||||||
|
|
||||||
## Proposals
|
## Proposals
|
||||||
|
|
||||||
The structure of a propsal for signing looks like:
|
The structure of a proposal for signing looks like:
|
||||||
|
|
||||||
```
|
```
|
||||||
type CanonicalProposal struct {
|
type CanonicalProposal struct {
|
||||||
@ -118,8 +118,8 @@ type CanonicalVote struct {
|
|||||||
Type SignedMsgType // type alias for byte
|
Type SignedMsgType // type alias for byte
|
||||||
Height int64 `binary:"fixed64"`
|
Height int64 `binary:"fixed64"`
|
||||||
Round int64 `binary:"fixed64"`
|
Round int64 `binary:"fixed64"`
|
||||||
Timestamp time.Time
|
|
||||||
BlockID BlockID
|
BlockID BlockID
|
||||||
|
Timestamp time.Time
|
||||||
ChainID string
|
ChainID string
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@ -130,7 +130,7 @@ A vote is valid if each of the following lines evaluates to true for vote `v`:
|
|||||||
v.Type == 0x1 || v.Type == 0x2
|
v.Type == 0x1 || v.Type == 0x2
|
||||||
v.Height > 0
|
v.Height > 0
|
||||||
v.Round >= 0
|
v.Round >= 0
|
||||||
v.BlockID.IsNil() || v.BlockID.IsValid()
|
v.BlockID.IsZero() || v.BlockID.IsComplete()
|
||||||
```
|
```
|
||||||
|
|
||||||
In other words, a vote is valid for signing if it contains the type of a Prevote
|
In other words, a vote is valid for signing if it contains the type of a Prevote
|
||||||
|
@ -11,6 +11,7 @@ import (
|
|||||||
|
|
||||||
"github.com/tendermint/tendermint/crypto"
|
"github.com/tendermint/tendermint/crypto"
|
||||||
"github.com/tendermint/tendermint/crypto/merkle"
|
"github.com/tendermint/tendermint/crypto/merkle"
|
||||||
|
"github.com/tendermint/tendermint/crypto/tmhash"
|
||||||
cmn "github.com/tendermint/tendermint/libs/common"
|
cmn "github.com/tendermint/tendermint/libs/common"
|
||||||
"github.com/tendermint/tendermint/version"
|
"github.com/tendermint/tendermint/version"
|
||||||
)
|
)
|
||||||
@ -788,11 +789,6 @@ type BlockID struct {
|
|||||||
PartsHeader PartSetHeader `json:"parts"`
|
PartsHeader PartSetHeader `json:"parts"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsZero returns true if this is the BlockID for a nil-block
|
|
||||||
func (blockID BlockID) IsZero() bool {
|
|
||||||
return len(blockID.Hash) == 0 && blockID.PartsHeader.IsZero()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Equals returns true if the BlockID matches the given BlockID
|
// Equals returns true if the BlockID matches the given BlockID
|
||||||
func (blockID BlockID) Equals(other BlockID) bool {
|
func (blockID BlockID) Equals(other BlockID) bool {
|
||||||
return bytes.Equal(blockID.Hash, other.Hash) &&
|
return bytes.Equal(blockID.Hash, other.Hash) &&
|
||||||
@ -820,6 +816,19 @@ func (blockID BlockID) ValidateBasic() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsZero returns true if this is the BlockID of a nil block.
|
||||||
|
func (blockID BlockID) IsZero() bool {
|
||||||
|
return len(blockID.Hash) == 0 &&
|
||||||
|
blockID.PartsHeader.IsZero()
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsComplete returns true if this is a valid BlockID of a non-nil block.
|
||||||
|
func (blockID BlockID) IsComplete() bool {
|
||||||
|
return len(blockID.Hash) == tmhash.Size &&
|
||||||
|
blockID.PartsHeader.Total > 0 &&
|
||||||
|
len(blockID.PartsHeader.Hash) == tmhash.Size
|
||||||
|
}
|
||||||
|
|
||||||
// String returns a human readable string representation of the BlockID
|
// String returns a human readable string representation of the BlockID
|
||||||
func (blockID BlockID) String() string {
|
func (blockID BlockID) String() string {
|
||||||
return fmt.Sprintf(`%v:%v`, blockID.Hash, blockID.PartsHeader)
|
return fmt.Sprintf(`%v:%v`, blockID.Hash, blockID.PartsHeader)
|
||||||
|
@ -36,8 +36,8 @@ type CanonicalVote struct {
|
|||||||
Type SignedMsgType // type alias for byte
|
Type SignedMsgType // type alias for byte
|
||||||
Height int64 `binary:"fixed64"`
|
Height int64 `binary:"fixed64"`
|
||||||
Round int64 `binary:"fixed64"`
|
Round int64 `binary:"fixed64"`
|
||||||
Timestamp time.Time
|
|
||||||
BlockID CanonicalBlockID
|
BlockID CanonicalBlockID
|
||||||
|
Timestamp time.Time
|
||||||
ChainID string
|
ChainID string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,8 +75,8 @@ func CanonicalizeVote(chainID string, vote *Vote) CanonicalVote {
|
|||||||
Type: vote.Type,
|
Type: vote.Type,
|
||||||
Height: vote.Height,
|
Height: vote.Height,
|
||||||
Round: int64(vote.Round), // cast int->int64 to make amino encode it fixed64 (does not work for int)
|
Round: int64(vote.Round), // cast int->int64 to make amino encode it fixed64 (does not work for int)
|
||||||
Timestamp: vote.Timestamp,
|
|
||||||
BlockID: CanonicalizeBlockID(vote.BlockID),
|
BlockID: CanonicalizeBlockID(vote.BlockID),
|
||||||
|
Timestamp: vote.Timestamp,
|
||||||
ChainID: chainID,
|
ChainID: chainID,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -75,7 +75,7 @@ func (psh PartSetHeader) String() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (psh PartSetHeader) IsZero() bool {
|
func (psh PartSetHeader) IsZero() bool {
|
||||||
return psh.Total == 0
|
return psh.Total == 0 && len(psh.Hash) == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (psh PartSetHeader) Equals(other PartSetHeader) bool {
|
func (psh PartSetHeader) Equals(other PartSetHeader) bool {
|
||||||
|
@ -60,6 +60,10 @@ func (p *Proposal) ValidateBasic() error {
|
|||||||
if err := p.BlockID.ValidateBasic(); err != nil {
|
if err := p.BlockID.ValidateBasic(); err != nil {
|
||||||
return fmt.Errorf("Wrong BlockID: %v", err)
|
return fmt.Errorf("Wrong BlockID: %v", err)
|
||||||
}
|
}
|
||||||
|
// ValidateBasic above would pass even if the BlockID was empty:
|
||||||
|
if !p.BlockID.IsComplete() {
|
||||||
|
return fmt.Errorf("Expected a complete, non-empty BlockID, got: %v", p.BlockID)
|
||||||
|
}
|
||||||
|
|
||||||
// NOTE: Timestamp validation is subtle and handled elsewhere.
|
// NOTE: Timestamp validation is subtle and handled elsewhere.
|
||||||
|
|
||||||
|
@ -52,8 +52,8 @@ type Vote struct {
|
|||||||
Type SignedMsgType `json:"type"`
|
Type SignedMsgType `json:"type"`
|
||||||
Height int64 `json:"height"`
|
Height int64 `json:"height"`
|
||||||
Round int `json:"round"`
|
Round int `json:"round"`
|
||||||
Timestamp time.Time `json:"timestamp"`
|
|
||||||
BlockID BlockID `json:"block_id"` // zero if vote is nil.
|
BlockID BlockID `json:"block_id"` // zero if vote is nil.
|
||||||
|
Timestamp time.Time `json:"timestamp"`
|
||||||
ValidatorAddress Address `json:"validator_address"`
|
ValidatorAddress Address `json:"validator_address"`
|
||||||
ValidatorIndex int `json:"validator_index"`
|
ValidatorIndex int `json:"validator_index"`
|
||||||
Signature []byte `json:"signature"`
|
Signature []byte `json:"signature"`
|
||||||
@ -127,6 +127,11 @@ func (vote *Vote) ValidateBasic() error {
|
|||||||
if err := vote.BlockID.ValidateBasic(); err != nil {
|
if err := vote.BlockID.ValidateBasic(); err != nil {
|
||||||
return fmt.Errorf("Wrong BlockID: %v", err)
|
return fmt.Errorf("Wrong BlockID: %v", err)
|
||||||
}
|
}
|
||||||
|
// BlockID.ValidateBasic would not err if we for instance have an empty hash but a
|
||||||
|
// non-empty PartsSetHeader:
|
||||||
|
if !vote.BlockID.IsZero() && !vote.BlockID.IsComplete() {
|
||||||
|
return fmt.Errorf("BlockID must be either empty or complete, got: %v", vote.BlockID)
|
||||||
|
}
|
||||||
if len(vote.ValidatorAddress) != crypto.AddressSize {
|
if len(vote.ValidatorAddress) != crypto.AddressSize {
|
||||||
return fmt.Errorf("Expected ValidatorAddress size to be %d bytes, got %d bytes",
|
return fmt.Errorf("Expected ValidatorAddress size to be %d bytes, got %d bytes",
|
||||||
crypto.AddressSize,
|
crypto.AddressSize,
|
||||||
|
@ -63,8 +63,8 @@ func TestVoteSignableTestVectors(t *testing.T) {
|
|||||||
{
|
{
|
||||||
CanonicalizeVote("", &Vote{}),
|
CanonicalizeVote("", &Vote{}),
|
||||||
// NOTE: Height and Round are skipped here. This case needs to be considered while parsing.
|
// NOTE: Height and Round are skipped here. This case needs to be considered while parsing.
|
||||||
// []byte{0x22, 0x9, 0x9, 0x0, 0x9, 0x6e, 0x88, 0xf1, 0xff, 0xff, 0xff},
|
// []byte{0x2a, 0x9, 0x9, 0x0, 0x9, 0x6e, 0x88, 0xf1, 0xff, 0xff, 0xff},
|
||||||
[]byte{0x22, 0xb, 0x8, 0x80, 0x92, 0xb8, 0xc3, 0x98, 0xfe, 0xff, 0xff, 0xff, 0x1},
|
[]byte{0x2a, 0xb, 0x8, 0x80, 0x92, 0xb8, 0xc3, 0x98, 0xfe, 0xff, 0xff, 0xff, 0x1},
|
||||||
},
|
},
|
||||||
// with proper (fixed size) height and round (PreCommit):
|
// with proper (fixed size) height and round (PreCommit):
|
||||||
{
|
{
|
||||||
@ -76,7 +76,7 @@ func TestVoteSignableTestVectors(t *testing.T) {
|
|||||||
0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // height
|
0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // height
|
||||||
0x19, // (field_number << 3) | wire_type
|
0x19, // (field_number << 3) | wire_type
|
||||||
0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // round
|
0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // round
|
||||||
0x22, // (field_number << 3) | wire_type
|
0x2a, // (field_number << 3) | wire_type
|
||||||
// remaining fields (timestamp):
|
// remaining fields (timestamp):
|
||||||
0xb, 0x8, 0x80, 0x92, 0xb8, 0xc3, 0x98, 0xfe, 0xff, 0xff, 0xff, 0x1},
|
0xb, 0x8, 0x80, 0x92, 0xb8, 0xc3, 0x98, 0xfe, 0xff, 0xff, 0xff, 0x1},
|
||||||
},
|
},
|
||||||
@ -90,7 +90,7 @@ func TestVoteSignableTestVectors(t *testing.T) {
|
|||||||
0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // height
|
0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // height
|
||||||
0x19, // (field_number << 3) | wire_type
|
0x19, // (field_number << 3) | wire_type
|
||||||
0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // round
|
0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // round
|
||||||
0x22, // (field_number << 3) | wire_type
|
0x2a, // (field_number << 3) | wire_type
|
||||||
// remaining fields (timestamp):
|
// remaining fields (timestamp):
|
||||||
0xb, 0x8, 0x80, 0x92, 0xb8, 0xc3, 0x98, 0xfe, 0xff, 0xff, 0xff, 0x1},
|
0xb, 0x8, 0x80, 0x92, 0xb8, 0xc3, 0x98, 0xfe, 0xff, 0xff, 0xff, 0x1},
|
||||||
},
|
},
|
||||||
@ -102,7 +102,7 @@ func TestVoteSignableTestVectors(t *testing.T) {
|
|||||||
0x19, // (field_number << 3) | wire_type
|
0x19, // (field_number << 3) | wire_type
|
||||||
0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // round
|
0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // round
|
||||||
// remaining fields (timestamp):
|
// remaining fields (timestamp):
|
||||||
0x22,
|
0x2a,
|
||||||
0xb, 0x8, 0x80, 0x92, 0xb8, 0xc3, 0x98, 0xfe, 0xff, 0xff, 0xff, 0x1},
|
0xb, 0x8, 0x80, 0x92, 0xb8, 0xc3, 0x98, 0xfe, 0xff, 0xff, 0xff, 0x1},
|
||||||
},
|
},
|
||||||
// containing non-empty chain_id:
|
// containing non-empty chain_id:
|
||||||
@ -114,7 +114,7 @@ func TestVoteSignableTestVectors(t *testing.T) {
|
|||||||
0x19, // (field_number << 3) | wire_type
|
0x19, // (field_number << 3) | wire_type
|
||||||
0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // round
|
0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // round
|
||||||
// remaining fields:
|
// remaining fields:
|
||||||
0x22, // (field_number << 3) | wire_type
|
0x2a, // (field_number << 3) | wire_type
|
||||||
0xb, 0x8, 0x80, 0x92, 0xb8, 0xc3, 0x98, 0xfe, 0xff, 0xff, 0xff, 0x1, // timestamp
|
0xb, 0x8, 0x80, 0x92, 0xb8, 0xc3, 0x98, 0xfe, 0xff, 0xff, 0xff, 0x1, // timestamp
|
||||||
0x32, // (field_number << 3) | wire_type
|
0x32, // (field_number << 3) | wire_type
|
||||||
0xd, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64}, // chainID
|
0xd, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64}, // chainID
|
||||||
|
Reference in New Issue
Block a user