mirror of
https://github.com/tendermint/tendermint.git
synced 2026-09-18 22:14:35 +00:00
implement vote extensions
This commit is contained in:
+374
-3
@@ -875,7 +875,7 @@ func (commit *Commit) ValidateBasic() error {
|
||||
}
|
||||
|
||||
if commit.Height >= 1 {
|
||||
if commit.BlockID.IsZero() {
|
||||
if commit.BlockID.IsNil() {
|
||||
return errors.New("commit cannot be for nil block")
|
||||
}
|
||||
|
||||
@@ -987,6 +987,360 @@ func CommitFromProto(cp *tmproto.Commit) (*Commit, error) {
|
||||
return commit, commit.ValidateBasic()
|
||||
}
|
||||
|
||||
//-------------------------------------
|
||||
|
||||
// ExtendedCommitSig contains a commit signature along with its corresponding
|
||||
// vote extension and vote extension signature.
|
||||
type ExtendedCommitSig struct {
|
||||
CommitSig // Commit signature
|
||||
Extension []byte // Vote extension
|
||||
ExtensionSignature []byte // Vote extension signature
|
||||
}
|
||||
|
||||
// NewExtendedCommitSigAbsent returns new ExtendedCommitSig with
|
||||
// BlockIDFlagAbsent. Other fields are all empty.
|
||||
func NewExtendedCommitSigAbsent() ExtendedCommitSig {
|
||||
return ExtendedCommitSig{CommitSig: NewCommitSigAbsent()}
|
||||
}
|
||||
|
||||
// String returns a string representation of an ExtendedCommitSig.
|
||||
//
|
||||
// 1. commit sig
|
||||
// 2. first 6 bytes of vote extension
|
||||
// 3. first 6 bytes of vote extension signature
|
||||
func (ecs ExtendedCommitSig) String() string {
|
||||
return fmt.Sprintf("ExtendedCommitSig{%s with %X %X}",
|
||||
ecs.CommitSig,
|
||||
tmbytes.Fingerprint(ecs.Extension),
|
||||
tmbytes.Fingerprint(ecs.ExtensionSignature),
|
||||
)
|
||||
}
|
||||
|
||||
// ValidateBasic checks whether the structure is well-formed.
|
||||
func (ecs ExtendedCommitSig) ValidateBasic() error {
|
||||
if err := ecs.CommitSig.ValidateBasic(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if ecs.BlockIDFlag == BlockIDFlagCommit {
|
||||
if len(ecs.Extension) > MaxVoteExtensionSize {
|
||||
return fmt.Errorf("vote extension is too big (max: %d)", MaxVoteExtensionSize)
|
||||
}
|
||||
if len(ecs.ExtensionSignature) > MaxSignatureSize {
|
||||
return fmt.Errorf("vote extension signature is too big (max: %d)", MaxSignatureSize)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(ecs.ExtensionSignature) == 0 && len(ecs.Extension) != 0 {
|
||||
return errors.New("vote extension signature absent on vote with extension")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// EnsureExtensions validates that a vote extensions signature is present for
|
||||
// this ExtendedCommitSig.
|
||||
func (ecs ExtendedCommitSig) EnsureExtension() error {
|
||||
if ecs.BlockIDFlag == BlockIDFlagCommit && len(ecs.ExtensionSignature) == 0 {
|
||||
return errors.New("vote extension data is missing")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToProto converts the ExtendedCommitSig to its Protobuf representation.
|
||||
func (ecs *ExtendedCommitSig) ToProto() *tmproto.ExtendedCommitSig {
|
||||
if ecs == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &tmproto.ExtendedCommitSig{
|
||||
BlockIdFlag: tmproto.BlockIDFlag(ecs.BlockIDFlag),
|
||||
ValidatorAddress: ecs.ValidatorAddress,
|
||||
Timestamp: ecs.Timestamp,
|
||||
Signature: ecs.Signature,
|
||||
Extension: ecs.Extension,
|
||||
ExtensionSignature: ecs.ExtensionSignature,
|
||||
}
|
||||
}
|
||||
|
||||
// FromProto populates the ExtendedCommitSig with values from the given
|
||||
// Protobuf representation. Returns an error if the ExtendedCommitSig is
|
||||
// invalid.
|
||||
func (ecs *ExtendedCommitSig) FromProto(ecsp tmproto.ExtendedCommitSig) error {
|
||||
ecs.BlockIDFlag = BlockIDFlag(ecsp.BlockIdFlag)
|
||||
ecs.ValidatorAddress = ecsp.ValidatorAddress
|
||||
ecs.Timestamp = ecsp.Timestamp
|
||||
ecs.Signature = ecsp.Signature
|
||||
ecs.Extension = ecsp.Extension
|
||||
ecs.ExtensionSignature = ecsp.ExtensionSignature
|
||||
|
||||
return ecs.ValidateBasic()
|
||||
}
|
||||
|
||||
//-------------------------------------
|
||||
|
||||
// ExtendedCommit is similar to Commit, except that its signatures also retain
|
||||
// their corresponding vote extensions and vote extension signatures.
|
||||
type ExtendedCommit struct {
|
||||
Height int64
|
||||
Round int32
|
||||
BlockID BlockID
|
||||
ExtendedSignatures []ExtendedCommitSig
|
||||
|
||||
bitArray *bits.BitArray
|
||||
}
|
||||
|
||||
// Clone creates a deep copy of this extended commit.
|
||||
func (ec *ExtendedCommit) Clone() *ExtendedCommit {
|
||||
sigs := make([]ExtendedCommitSig, len(ec.ExtendedSignatures))
|
||||
copy(sigs, ec.ExtendedSignatures)
|
||||
ecc := *ec
|
||||
ecc.ExtendedSignatures = sigs
|
||||
return &ecc
|
||||
}
|
||||
|
||||
// ToExtendedVoteSet constructs a VoteSet from the Commit and validator set.
|
||||
// Panics if signatures from the ExtendedCommit can't be added to the voteset.
|
||||
// Panics if any of the votes have invalid or absent vote extension data.
|
||||
// Inverse of VoteSet.MakeExtendedCommit().
|
||||
func (ec *ExtendedCommit) ToExtendedVoteSet(chainID string, vals *ValidatorSet) *VoteSet {
|
||||
voteSet := NewExtendedVoteSet(chainID, ec.Height, ec.Round, tmproto.PrecommitType, vals)
|
||||
ec.addSigsToVoteSet(voteSet)
|
||||
return voteSet
|
||||
}
|
||||
|
||||
// ToVoteSet constructs a VoteSet from the Commit and validator set.
|
||||
// Panics if signatures from the ExtendedCommit can't be added to the voteset.
|
||||
// Inverse of VoteSet.MakeExtendedCommit().
|
||||
func (ec *ExtendedCommit) ToVoteSet(chainID string, vals *ValidatorSet) *VoteSet {
|
||||
voteSet := NewVoteSet(chainID, ec.Height, ec.Round, tmproto.PrecommitType, vals)
|
||||
ec.addSigsToVoteSet(voteSet)
|
||||
return voteSet
|
||||
}
|
||||
|
||||
// addSigsToVoteSet adds all of the signature to voteSet.
|
||||
func (ec *ExtendedCommit) addSigsToVoteSet(voteSet *VoteSet) {
|
||||
for idx, ecs := range ec.ExtendedSignatures {
|
||||
if ecs.BlockIDFlag == BlockIDFlagAbsent {
|
||||
continue // OK, some precommits can be missing.
|
||||
}
|
||||
vote := ec.GetExtendedVote(int32(idx))
|
||||
if err := vote.ValidateBasic(); err != nil {
|
||||
panic(fmt.Errorf("failed to validate vote reconstructed from LastCommit: %w", err))
|
||||
}
|
||||
added, err := voteSet.AddVote(vote)
|
||||
if !added || err != nil {
|
||||
panic(fmt.Errorf("failed to reconstruct vote set from extended commit: %w", err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ToVoteSet constructs a VoteSet from the Commit and validator set.
|
||||
// Panics if signatures from the commit can't be added to the voteset.
|
||||
// Inverse of VoteSet.MakeCommit().
|
||||
func (commit *Commit) ToVoteSet(chainID string, vals *ValidatorSet) *VoteSet {
|
||||
voteSet := NewVoteSet(chainID, commit.Height, commit.Round, tmproto.PrecommitType, vals)
|
||||
for idx, cs := range commit.Signatures {
|
||||
if cs.BlockIDFlag == BlockIDFlagAbsent {
|
||||
continue // OK, some precommits can be missing.
|
||||
}
|
||||
vote := commit.GetVote(int32(idx))
|
||||
if err := vote.ValidateBasic(); err != nil {
|
||||
panic(fmt.Errorf("failed to validate vote reconstructed from commit: %w", err))
|
||||
}
|
||||
added, err := voteSet.AddVote(vote)
|
||||
if !added || err != nil {
|
||||
panic(fmt.Errorf("failed to reconstruct vote set from commit: %w", err))
|
||||
}
|
||||
}
|
||||
return voteSet
|
||||
}
|
||||
|
||||
// EnsureExtensions validates that a vote extensions signature is present for
|
||||
// every ExtendedCommitSig in the ExtendedCommit.
|
||||
func (ec *ExtendedCommit) EnsureExtensions() error {
|
||||
for _, ecs := range ec.ExtendedSignatures {
|
||||
if err := ecs.EnsureExtension(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// StripExtensions removes all VoteExtension data from an ExtendedCommit. This
|
||||
// is useful when dealing with an ExendedCommit but vote extension data is
|
||||
// expected to be absent.
|
||||
func (ec *ExtendedCommit) StripExtensions() bool {
|
||||
stripped := false
|
||||
for idx := range ec.ExtendedSignatures {
|
||||
if len(ec.ExtendedSignatures[idx].Extension) > 0 || len(ec.ExtendedSignatures[idx].ExtensionSignature) > 0 {
|
||||
stripped = true
|
||||
}
|
||||
ec.ExtendedSignatures[idx].Extension = nil
|
||||
ec.ExtendedSignatures[idx].ExtensionSignature = nil
|
||||
}
|
||||
return stripped
|
||||
}
|
||||
|
||||
// ToCommit converts an ExtendedCommit to a Commit by removing all vote
|
||||
// extension-related fields.
|
||||
func (ec *ExtendedCommit) ToCommit() *Commit {
|
||||
cs := make([]CommitSig, len(ec.ExtendedSignatures))
|
||||
for idx, ecs := range ec.ExtendedSignatures {
|
||||
cs[idx] = ecs.CommitSig
|
||||
}
|
||||
return &Commit{
|
||||
Height: ec.Height,
|
||||
Round: ec.Round,
|
||||
BlockID: ec.BlockID,
|
||||
Signatures: cs,
|
||||
}
|
||||
}
|
||||
|
||||
// GetExtendedVote converts the ExtendedCommitSig for the given validator
|
||||
// index to a Vote with a vote extensions.
|
||||
// It panics if valIndex is out of range.
|
||||
func (ec *ExtendedCommit) GetExtendedVote(valIndex int32) *Vote {
|
||||
ecs := ec.ExtendedSignatures[valIndex]
|
||||
return &Vote{
|
||||
Type: tmproto.PrecommitType,
|
||||
Height: ec.Height,
|
||||
Round: ec.Round,
|
||||
BlockID: ecs.BlockID(ec.BlockID),
|
||||
Timestamp: ecs.Timestamp,
|
||||
ValidatorAddress: ecs.ValidatorAddress,
|
||||
ValidatorIndex: valIndex,
|
||||
Signature: ecs.Signature,
|
||||
Extension: ecs.Extension,
|
||||
ExtensionSignature: ecs.ExtensionSignature,
|
||||
}
|
||||
}
|
||||
|
||||
// Type returns the vote type of the extended commit, which is always
|
||||
// VoteTypePrecommit
|
||||
// Implements VoteSetReader.
|
||||
func (ec *ExtendedCommit) Type() byte { return byte(tmproto.PrecommitType) }
|
||||
|
||||
// GetHeight returns height of the extended commit.
|
||||
// Implements VoteSetReader.
|
||||
func (ec *ExtendedCommit) GetHeight() int64 { return ec.Height }
|
||||
|
||||
// GetRound returns height of the extended commit.
|
||||
// Implements VoteSetReader.
|
||||
func (ec *ExtendedCommit) GetRound() int32 { return ec.Round }
|
||||
|
||||
// Size returns the number of signatures in the extended commit.
|
||||
// Implements VoteSetReader.
|
||||
func (ec *ExtendedCommit) Size() int {
|
||||
if ec == nil {
|
||||
return 0
|
||||
}
|
||||
return len(ec.ExtendedSignatures)
|
||||
}
|
||||
|
||||
// BitArray returns a BitArray of which validators voted for BlockID or nil in
|
||||
// this extended commit.
|
||||
// Implements VoteSetReader.
|
||||
func (ec *ExtendedCommit) BitArray() *bits.BitArray {
|
||||
if ec.bitArray == nil {
|
||||
ec.bitArray = bits.NewBitArray(len(ec.ExtendedSignatures))
|
||||
for i, extCommitSig := range ec.ExtendedSignatures {
|
||||
// TODO: need to check the BlockID otherwise we could be counting conflicts,
|
||||
// not just the one with +2/3 !
|
||||
ec.bitArray.SetIndex(i, extCommitSig.BlockIDFlag != BlockIDFlagAbsent)
|
||||
}
|
||||
}
|
||||
return ec.bitArray
|
||||
}
|
||||
|
||||
// GetByIndex returns the vote corresponding to a given validator index.
|
||||
// Panics if `index >= extCommit.Size()`.
|
||||
// Implements VoteSetReader.
|
||||
func (ec *ExtendedCommit) GetByIndex(valIdx int32) *Vote {
|
||||
return ec.GetExtendedVote(valIdx)
|
||||
}
|
||||
|
||||
// IsCommit returns true if there is at least one signature.
|
||||
// Implements VoteSetReader.
|
||||
func (ec *ExtendedCommit) IsCommit() bool {
|
||||
return len(ec.ExtendedSignatures) != 0
|
||||
}
|
||||
|
||||
// ValidateBasic checks whether the extended commit is well-formed. Does not
|
||||
// actually check the cryptographic signatures.
|
||||
func (ec *ExtendedCommit) ValidateBasic() error {
|
||||
if ec.Height < 0 {
|
||||
return errors.New("negative Height")
|
||||
}
|
||||
if ec.Round < 0 {
|
||||
return errors.New("negative Round")
|
||||
}
|
||||
|
||||
if ec.Height >= 1 {
|
||||
if ec.BlockID.IsNil() {
|
||||
return errors.New("commit cannot be for nil block")
|
||||
}
|
||||
|
||||
if len(ec.ExtendedSignatures) == 0 {
|
||||
return errors.New("no signatures in commit")
|
||||
}
|
||||
for i, extCommitSig := range ec.ExtendedSignatures {
|
||||
if err := extCommitSig.ValidateBasic(); err != nil {
|
||||
return fmt.Errorf("wrong ExtendedCommitSig #%d: %v", i, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToProto converts ExtendedCommit to protobuf
|
||||
func (ec *ExtendedCommit) ToProto() *tmproto.ExtendedCommit {
|
||||
if ec == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
c := new(tmproto.ExtendedCommit)
|
||||
sigs := make([]tmproto.ExtendedCommitSig, len(ec.ExtendedSignatures))
|
||||
for i := range ec.ExtendedSignatures {
|
||||
sigs[i] = *ec.ExtendedSignatures[i].ToProto()
|
||||
}
|
||||
c.ExtendedSignatures = sigs
|
||||
|
||||
c.Height = ec.Height
|
||||
c.Round = ec.Round
|
||||
c.BlockID = ec.BlockID.ToProto()
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
// ExtendedCommitFromProto constructs an ExtendedCommit from the given Protobuf
|
||||
// representation. It returns an error if the extended commit is invalid.
|
||||
func ExtendedCommitFromProto(ecp *tmproto.ExtendedCommit) (*ExtendedCommit, error) {
|
||||
if ecp == nil {
|
||||
return nil, errors.New("nil ExtendedCommit")
|
||||
}
|
||||
|
||||
extCommit := new(ExtendedCommit)
|
||||
|
||||
bi, err := BlockIDFromProto(&ecp.BlockID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sigs := make([]ExtendedCommitSig, len(ecp.ExtendedSignatures))
|
||||
for i := range ecp.ExtendedSignatures {
|
||||
if err := sigs[i].FromProto(ecp.ExtendedSignatures[i]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
extCommit.ExtendedSignatures = sigs
|
||||
extCommit.Height = ecp.Height
|
||||
extCommit.Round = ecp.Round
|
||||
extCommit.BlockID = *bi
|
||||
|
||||
return extCommit, extCommit.ValidateBasic()
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// Data contains the set of transactions included in the block
|
||||
@@ -1167,6 +1521,17 @@ type BlockID struct {
|
||||
PartSetHeader PartSetHeader `json:"parts"`
|
||||
}
|
||||
|
||||
func NewBlockID(hash []byte, header PartSetHeader) BlockID {
|
||||
return BlockID{
|
||||
Hash: hash,
|
||||
PartSetHeader: header,
|
||||
}
|
||||
}
|
||||
|
||||
func NilBlockID() BlockID {
|
||||
return NewBlockID(nil, PartSetHeader{})
|
||||
}
|
||||
|
||||
// Equals returns true if the BlockID matches the given BlockID
|
||||
func (blockID BlockID) Equals(other BlockID) bool {
|
||||
return bytes.Equal(blockID.Hash, other.Hash) &&
|
||||
@@ -1196,8 +1561,8 @@ func (blockID BlockID) ValidateBasic() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsZero returns true if this is the BlockID of a nil block.
|
||||
func (blockID BlockID) IsZero() bool {
|
||||
// IsNil returns true if this is the BlockID of a nil block.
|
||||
func (blockID BlockID) IsNil() bool {
|
||||
return len(blockID.Hash) == 0 &&
|
||||
blockID.PartSetHeader.IsZero()
|
||||
}
|
||||
@@ -1249,3 +1614,9 @@ func BlockIDFromProto(bID *tmproto.BlockID) (*BlockID, error) {
|
||||
|
||||
return blockID, blockID.ValidateBasic()
|
||||
}
|
||||
|
||||
// IsProtoBlockIDNil is similar to the IsNil function on BlockID, but for the
|
||||
// Protobuf representation.
|
||||
func IsProtoBlockIDNil(bID *tmproto.BlockID) bool {
|
||||
return len(bID.Hash) == 0 && IsProtoPartSetHeaderZero(&bID.PartSetHeader)
|
||||
}
|
||||
|
||||
+147
-15
@@ -38,14 +38,14 @@ func TestBlockAddEvidence(t *testing.T) {
|
||||
h := int64(3)
|
||||
|
||||
voteSet, _, vals := randVoteSet(h-1, 1, tmproto.PrecommitType, 10, 1)
|
||||
commit, err := MakeCommit(lastID, h-1, 1, voteSet, vals, time.Now())
|
||||
extCommit, err := makeExtCommit(lastID, h-1, 1, voteSet, vals, time.Now())
|
||||
require.NoError(t, err)
|
||||
|
||||
ev, err := NewMockDuplicateVoteEvidenceWithValidator(h, time.Now(), vals[0], "block-test-chain")
|
||||
require.NoError(t, err)
|
||||
evList := []Evidence{ev}
|
||||
|
||||
block := MakeBlock(h, txs, commit, evList)
|
||||
block := MakeBlock(h, txs, extCommit.ToCommit(), evList)
|
||||
require.NotNil(t, block)
|
||||
require.Equal(t, 1, len(block.Evidence.Evidence))
|
||||
require.NotNil(t, block.EvidenceHash)
|
||||
@@ -59,8 +59,9 @@ func TestBlockValidateBasic(t *testing.T) {
|
||||
h := int64(3)
|
||||
|
||||
voteSet, valSet, vals := randVoteSet(h-1, 1, tmproto.PrecommitType, 10, 1)
|
||||
commit, err := MakeCommit(lastID, h-1, 1, voteSet, vals, time.Now())
|
||||
extCommit, err := makeExtCommit(lastID, h-1, 1, voteSet, vals, time.Now())
|
||||
require.NoError(t, err)
|
||||
commit := extCommit.ToCommit()
|
||||
|
||||
ev, err := NewMockDuplicateVoteEvidenceWithValidator(h, time.Now(), vals[0], "block-test-chain")
|
||||
require.NoError(t, err)
|
||||
@@ -131,14 +132,14 @@ func TestBlockMakePartSetWithEvidence(t *testing.T) {
|
||||
h := int64(3)
|
||||
|
||||
voteSet, _, vals := randVoteSet(h-1, 1, tmproto.PrecommitType, 10, 1)
|
||||
commit, err := MakeCommit(lastID, h-1, 1, voteSet, vals, time.Now())
|
||||
extCommit, err := makeExtCommit(lastID, h-1, 1, voteSet, vals, time.Now())
|
||||
require.NoError(t, err)
|
||||
|
||||
ev, err := NewMockDuplicateVoteEvidenceWithValidator(h, time.Now(), vals[0], "block-test-chain")
|
||||
require.NoError(t, err)
|
||||
evList := []Evidence{ev}
|
||||
|
||||
partSet, err := MakeBlock(h, []Tx{Tx("Hello World")}, commit, evList).MakePartSet(512)
|
||||
partSet, err := MakeBlock(h, []Tx{Tx("Hello World")}, extCommit.ToCommit(), evList).MakePartSet(512)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.NotNil(t, partSet)
|
||||
@@ -151,14 +152,14 @@ func TestBlockHashesTo(t *testing.T) {
|
||||
lastID := makeBlockIDRandom()
|
||||
h := int64(3)
|
||||
voteSet, valSet, vals := randVoteSet(h-1, 1, tmproto.PrecommitType, 10, 1)
|
||||
commit, err := MakeCommit(lastID, h-1, 1, voteSet, vals, time.Now())
|
||||
extCommit, err := makeExtCommit(lastID, h-1, 1, voteSet, vals, time.Now())
|
||||
require.NoError(t, err)
|
||||
|
||||
ev, err := NewMockDuplicateVoteEvidenceWithValidator(h, time.Now(), vals[0], "block-test-chain")
|
||||
require.NoError(t, err)
|
||||
evList := []Evidence{ev}
|
||||
|
||||
block := MakeBlock(h, []Tx{Tx("Hello World")}, commit, evList)
|
||||
block := MakeBlock(h, []Tx{Tx("Hello World")}, extCommit.ToCommit(), evList)
|
||||
block.ValidatorsHash = valSet.Hash()
|
||||
assert.False(t, block.HashesTo([]byte{}))
|
||||
assert.False(t, block.HashesTo([]byte("something else")))
|
||||
@@ -230,7 +231,7 @@ func TestCommit(t *testing.T) {
|
||||
lastID := makeBlockIDRandom()
|
||||
h := int64(3)
|
||||
voteSet, _, vals := randVoteSet(h-1, 1, tmproto.PrecommitType, 10, 1)
|
||||
commit, err := MakeCommit(lastID, h-1, 1, voteSet, vals, time.Now())
|
||||
commit, err := makeExtCommit(lastID, h-1, 1, voteSet, vals, time.Now())
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, h-1, commit.Height)
|
||||
@@ -438,11 +439,11 @@ func randCommit(now time.Time) *Commit {
|
||||
lastID := makeBlockIDRandom()
|
||||
h := int64(3)
|
||||
voteSet, _, vals := randVoteSet(h-1, 1, tmproto.PrecommitType, 10, 1)
|
||||
commit, err := MakeCommit(lastID, h-1, 1, voteSet, vals, now)
|
||||
extCommit, err := makeExtCommit(lastID, h-1, 1, voteSet, vals, now)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return commit
|
||||
return extCommit.ToCommit()
|
||||
}
|
||||
|
||||
func hexBytesFromString(s string) bytes.HexBytes {
|
||||
@@ -514,13 +515,144 @@ func TestBlockMaxDataBytesNoEvidence(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestVoteSetToExtendedCommit tests that the extended commit produced from a
|
||||
// vote set contains the same vote information as the vote set. The test ensures
|
||||
// that the MakeExtendedCommit method behaves as expected, whether vote extensions
|
||||
// are present in the original votes or not.
|
||||
func TestVoteSetToExtendedCommit(t *testing.T) {
|
||||
for _, testCase := range []struct {
|
||||
name string
|
||||
includeExtension bool
|
||||
}{
|
||||
{
|
||||
name: "no extensions",
|
||||
includeExtension: false,
|
||||
},
|
||||
{
|
||||
name: "with extensions",
|
||||
includeExtension: true,
|
||||
},
|
||||
} {
|
||||
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
blockID := makeBlockIDRandom()
|
||||
|
||||
_, valSet, vals := randVoteSet(10, 1, tmproto.PrecommitType, 10, 1)
|
||||
var voteSet *VoteSet
|
||||
if testCase.includeExtension {
|
||||
voteSet = NewExtendedVoteSet("test_chain_id", 3, 1, tmproto.PrecommitType, valSet)
|
||||
} else {
|
||||
voteSet = NewVoteSet("test_chain_id", 3, 1, tmproto.PrecommitType, valSet)
|
||||
}
|
||||
for i := 0; i < len(vals); i++ {
|
||||
pubKey, err := vals[i].GetPubKey()
|
||||
require.NoError(t, err)
|
||||
vote := &Vote{
|
||||
ValidatorAddress: pubKey.Address(),
|
||||
ValidatorIndex: int32(i),
|
||||
Height: 3,
|
||||
Round: 1,
|
||||
Type: tmproto.PrecommitType,
|
||||
BlockID: blockID,
|
||||
Timestamp: time.Now(),
|
||||
}
|
||||
v := vote.ToProto()
|
||||
err = vals[i].SignVote(voteSet.ChainID(), v)
|
||||
require.NoError(t, err)
|
||||
vote.Signature = v.Signature
|
||||
if testCase.includeExtension {
|
||||
vote.ExtensionSignature = v.ExtensionSignature
|
||||
}
|
||||
added, err := voteSet.AddVote(vote)
|
||||
require.NoError(t, err)
|
||||
require.True(t, added)
|
||||
}
|
||||
ec := voteSet.MakeExtendedCommit()
|
||||
|
||||
for i := int32(0); int(i) < len(vals); i++ {
|
||||
vote1 := voteSet.GetByIndex(i)
|
||||
vote2 := ec.GetExtendedVote(i)
|
||||
|
||||
vote1bz, err := vote1.ToProto().Marshal()
|
||||
require.NoError(t, err)
|
||||
vote2bz, err := vote2.ToProto().Marshal()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, vote1bz, vote2bz)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestExtendedCommitToVoteSet tests that the vote set produced from an extended commit
|
||||
// contains the same vote information as the extended commit. The test ensures
|
||||
// that the ToVoteSet method behaves as expected, whether vote extensions
|
||||
// are present in the original votes or not.
|
||||
func TestExtendedCommitToVoteSet(t *testing.T) {
|
||||
for _, testCase := range []struct {
|
||||
name string
|
||||
includeExtension bool
|
||||
}{
|
||||
{
|
||||
name: "no extensions",
|
||||
includeExtension: false,
|
||||
},
|
||||
{
|
||||
name: "with extensions",
|
||||
includeExtension: true,
|
||||
},
|
||||
} {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
lastID := makeBlockIDRandom()
|
||||
h := int64(3)
|
||||
|
||||
voteSet, valSet, vals := randVoteSet(h-1, 1, tmproto.PrecommitType, 10, 1)
|
||||
extCommit, err := makeExtCommit(lastID, h-1, 1, voteSet, vals, time.Now())
|
||||
assert.NoError(t, err)
|
||||
|
||||
if !testCase.includeExtension {
|
||||
for i := 0; i < len(vals); i++ {
|
||||
v := voteSet.GetByIndex(int32(i))
|
||||
v.Extension = nil
|
||||
v.ExtensionSignature = nil
|
||||
extCommit.ExtendedSignatures[i].Extension = nil
|
||||
extCommit.ExtendedSignatures[i].ExtensionSignature = nil
|
||||
}
|
||||
}
|
||||
|
||||
chainID := voteSet.ChainID()
|
||||
var voteSet2 *VoteSet
|
||||
if testCase.includeExtension {
|
||||
voteSet2 = extCommit.ToExtendedVoteSet(chainID, valSet)
|
||||
} else {
|
||||
voteSet2 = extCommit.ToVoteSet(chainID, valSet)
|
||||
}
|
||||
|
||||
for i := int32(0); int(i) < len(vals); i++ {
|
||||
vote1 := voteSet.GetByIndex(i)
|
||||
vote2 := voteSet2.GetByIndex(i)
|
||||
vote3 := extCommit.GetExtendedVote(i)
|
||||
|
||||
vote1bz, err := vote1.ToProto().Marshal()
|
||||
require.NoError(t, err)
|
||||
vote2bz, err := vote2.ToProto().Marshal()
|
||||
require.NoError(t, err)
|
||||
vote3bz, err := vote3.ToProto().Marshal()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, vote1bz, vote2bz)
|
||||
assert.Equal(t, vote1bz, vote3bz)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommitToVoteSet(t *testing.T) {
|
||||
lastID := makeBlockIDRandom()
|
||||
h := int64(3)
|
||||
|
||||
voteSet, valSet, vals := randVoteSet(h-1, 1, tmproto.PrecommitType, 10, 1)
|
||||
commit, err := MakeCommit(lastID, h-1, 1, voteSet, vals, time.Now())
|
||||
extCommit, err := makeExtCommit(lastID, h-1, 1, voteSet, vals, time.Now())
|
||||
assert.NoError(t, err)
|
||||
commit := extCommit.ToCommit()
|
||||
|
||||
chainID := voteSet.ChainID()
|
||||
voteSet2 := CommitToVoteSet(chainID, commit, valSet)
|
||||
@@ -587,12 +719,12 @@ func TestCommitToVoteSetWithVotesForNilBlock(t *testing.T) {
|
||||
}
|
||||
|
||||
if tc.valid {
|
||||
commit := voteSet.MakeCommit() // panics without > 2/3 valid votes
|
||||
assert.NotNil(t, commit)
|
||||
err := valSet.VerifyCommit(voteSet.ChainID(), blockID, height-1, commit)
|
||||
extCommit := voteSet.MakeExtendedCommit() // panics without > 2/3 valid votes
|
||||
assert.NotNil(t, extCommit)
|
||||
err := valSet.VerifyCommit(voteSet.ChainID(), blockID, height-1, extCommit.ToCommit())
|
||||
assert.Nil(t, err)
|
||||
} else {
|
||||
assert.Panics(t, func() { voteSet.MakeCommit() })
|
||||
assert.Panics(t, func() { voteSet.MakeExtendedCommit() })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+13
-1
@@ -21,7 +21,7 @@ func CanonicalizeBlockID(bid tmproto.BlockID) *tmproto.CanonicalBlockID {
|
||||
panic(err)
|
||||
}
|
||||
var cbid *tmproto.CanonicalBlockID
|
||||
if rbid == nil || rbid.IsZero() {
|
||||
if rbid == nil || rbid.IsNil() {
|
||||
cbid = nil
|
||||
} else {
|
||||
cbid = &tmproto.CanonicalBlockID{
|
||||
@@ -64,6 +64,18 @@ func CanonicalizeVote(chainID string, vote *tmproto.Vote) tmproto.CanonicalVote
|
||||
}
|
||||
}
|
||||
|
||||
// CanonicalizeVoteExtension extracts the vote extension from the given vote
|
||||
// and constructs a CanonicalizeVoteExtension struct, whose representation in
|
||||
// bytes is what is signed in order to produce the vote extension's signature.
|
||||
func CanonicalizeVoteExtension(chainID string, vote *tmproto.Vote) tmproto.CanonicalVoteExtension {
|
||||
return tmproto.CanonicalVoteExtension{
|
||||
Extension: vote.Extension,
|
||||
Height: vote.Height,
|
||||
Round: int64(vote.Round),
|
||||
ChainId: chainID,
|
||||
}
|
||||
}
|
||||
|
||||
// CanonicalTime can be used to stringify time in a canonical way.
|
||||
func CanonicalTime(t time.Time) string {
|
||||
// Note that sending time over amino resets it to
|
||||
|
||||
@@ -99,8 +99,9 @@ func TestLightClientAttackEvidenceBasic(t *testing.T) {
|
||||
header := makeHeaderRandom()
|
||||
header.Height = height
|
||||
blockID := makeBlockID(tmhash.Sum([]byte("blockhash")), math.MaxInt32, tmhash.Sum([]byte("partshash")))
|
||||
commit, err := MakeCommit(blockID, height, 1, voteSet, privVals, defaultVoteTime)
|
||||
extCommit, err := makeExtCommit(blockID, height, 1, voteSet, privVals, defaultVoteTime)
|
||||
require.NoError(t, err)
|
||||
commit := extCommit.ToCommit()
|
||||
lcae := &LightClientAttackEvidence{
|
||||
ConflictingBlock: &LightBlock{
|
||||
SignedHeader: &SignedHeader{
|
||||
@@ -159,13 +160,13 @@ func TestLightClientAttackEvidenceValidation(t *testing.T) {
|
||||
header.Height = height
|
||||
header.ValidatorsHash = valSet.Hash()
|
||||
blockID := makeBlockID(header.Hash(), math.MaxInt32, tmhash.Sum([]byte("partshash")))
|
||||
commit, err := MakeCommit(blockID, height, 1, voteSet, privVals, time.Now())
|
||||
extCommit, err := makeExtCommit(blockID, height, 1, voteSet, privVals, time.Now())
|
||||
require.NoError(t, err)
|
||||
lcae := &LightClientAttackEvidence{
|
||||
ConflictingBlock: &LightBlock{
|
||||
SignedHeader: &SignedHeader{
|
||||
Header: header,
|
||||
Commit: commit,
|
||||
Commit: extCommit.ToCommit(),
|
||||
},
|
||||
ValidatorSet: valSet,
|
||||
},
|
||||
@@ -205,7 +206,7 @@ func TestLightClientAttackEvidenceValidation(t *testing.T) {
|
||||
ConflictingBlock: &LightBlock{
|
||||
SignedHeader: &SignedHeader{
|
||||
Header: header,
|
||||
Commit: commit,
|
||||
Commit: extCommit.ToCommit(),
|
||||
},
|
||||
ValidatorSet: valSet,
|
||||
},
|
||||
|
||||
+73
-1
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/tendermint/tendermint/crypto/ed25519"
|
||||
"github.com/tendermint/tendermint/crypto/secp256k1"
|
||||
"github.com/tendermint/tendermint/crypto/tmhash"
|
||||
tmstrings "github.com/tendermint/tendermint/libs/strings"
|
||||
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
|
||||
)
|
||||
|
||||
@@ -37,6 +38,7 @@ type ConsensusParams struct {
|
||||
Evidence EvidenceParams `json:"evidence"`
|
||||
Validator ValidatorParams `json:"validator"`
|
||||
Version VersionParams `json:"version"`
|
||||
ABCI ABCIParams `json:"abci"`
|
||||
}
|
||||
|
||||
// BlockParams define limits on the block size and gas plus minimum time
|
||||
@@ -63,6 +65,22 @@ type VersionParams struct {
|
||||
App uint64 `json:"app"`
|
||||
}
|
||||
|
||||
// ABCIParams configure ABCI functionality specific to the Application Blockchain
|
||||
// Interface.
|
||||
type ABCIParams struct {
|
||||
VoteExtensionsEnableHeight int64 `json:"vote_extensions_enable_height"`
|
||||
RecheckTx bool `json:"recheck_tx"`
|
||||
}
|
||||
|
||||
// VoteExtensionsEnabled returns true if vote extensions are enabled at height h
|
||||
// and false otherwise.
|
||||
func (a ABCIParams) VoteExtensionsEnabled(h int64) bool {
|
||||
if a.VoteExtensionsEnableHeight == 0 {
|
||||
return false
|
||||
}
|
||||
return a.VoteExtensionsEnableHeight <= h
|
||||
}
|
||||
|
||||
// DefaultConsensusParams returns a default ConsensusParams.
|
||||
func DefaultConsensusParams() *ConsensusParams {
|
||||
return &ConsensusParams{
|
||||
@@ -70,6 +88,7 @@ func DefaultConsensusParams() *ConsensusParams {
|
||||
Evidence: DefaultEvidenceParams(),
|
||||
Validator: DefaultValidatorParams(),
|
||||
Version: DefaultVersionParams(),
|
||||
ABCI: DefaultABCIParams(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,6 +123,13 @@ func DefaultVersionParams() VersionParams {
|
||||
}
|
||||
}
|
||||
|
||||
func DefaultABCIParams() ABCIParams {
|
||||
return ABCIParams{
|
||||
// When set to 0, vote extensions are not required.
|
||||
VoteExtensionsEnableHeight: 0,
|
||||
}
|
||||
}
|
||||
|
||||
func IsValidPubkeyType(params ValidatorParams, pubkeyType string) bool {
|
||||
for i := 0; i < len(params.PubKeyTypes); i++ {
|
||||
if params.PubKeyTypes[i] == pubkeyType {
|
||||
@@ -150,6 +176,10 @@ func (params ConsensusParams) ValidateBasic() error {
|
||||
params.Evidence.MaxBytes)
|
||||
}
|
||||
|
||||
if params.ABCI.VoteExtensionsEnableHeight < 0 {
|
||||
return fmt.Errorf("ABCI.VoteExtensionsEnableHeight cannot be negative. Got: %d", params.ABCI.VoteExtensionsEnableHeight)
|
||||
}
|
||||
|
||||
if len(params.Validator.PubKeyTypes) == 0 {
|
||||
return errors.New("len(Validator.PubKeyTypes) must be greater than 0")
|
||||
}
|
||||
@@ -166,6 +196,30 @@ func (params ConsensusParams) ValidateBasic() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (params ConsensusParams) ValidateUpdate(updated *tmproto.ConsensusParams, h int64) error {
|
||||
if updated.Abci == nil {
|
||||
return nil
|
||||
}
|
||||
if params.ABCI.VoteExtensionsEnableHeight == updated.Abci.VoteExtensionsEnableHeight {
|
||||
return nil
|
||||
}
|
||||
if params.ABCI.VoteExtensionsEnableHeight != 0 && updated.Abci.VoteExtensionsEnableHeight == 0 {
|
||||
return errors.New("vote extensions cannot be disabled once enabled")
|
||||
}
|
||||
if updated.Abci.VoteExtensionsEnableHeight <= h {
|
||||
return fmt.Errorf("VoteExtensionsEnableHeight cannot be updated to a past height, "+
|
||||
"initial height: %d, current height %d",
|
||||
params.ABCI.VoteExtensionsEnableHeight, h)
|
||||
}
|
||||
if params.ABCI.VoteExtensionsEnableHeight <= h {
|
||||
return fmt.Errorf("VoteExtensionsEnableHeight cannot be updated modified once"+
|
||||
"the initial height has occurred, "+
|
||||
"initial height: %d, current height %d",
|
||||
params.ABCI.VoteExtensionsEnableHeight, h)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Hash returns a hash of a subset of the parameters to store in the block header.
|
||||
// Only the Block.MaxBytes and Block.MaxGas are included in the hash.
|
||||
// This allows the ConsensusParams to evolve more without breaking the block
|
||||
@@ -190,6 +244,14 @@ func (params ConsensusParams) Hash() []byte {
|
||||
return hasher.Sum(nil)
|
||||
}
|
||||
|
||||
func (params *ConsensusParams) Equals(params2 *ConsensusParams) bool {
|
||||
return params.Block == params2.Block &&
|
||||
params.Evidence == params2.Evidence &&
|
||||
params.Version == params2.Version &&
|
||||
params.ABCI == params2.ABCI &&
|
||||
tmstrings.StringSliceEqual(params.Validator.PubKeyTypes, params2.Validator.PubKeyTypes)
|
||||
}
|
||||
|
||||
// Update returns a copy of the params with updates from the non-zero fields of p2.
|
||||
// NOTE: note: must not modify the original
|
||||
func (params ConsensusParams) Update(params2 *tmproto.ConsensusParams) ConsensusParams {
|
||||
@@ -217,6 +279,9 @@ func (params ConsensusParams) Update(params2 *tmproto.ConsensusParams) Consensus
|
||||
if params2.Version != nil {
|
||||
res.Version.App = params2.Version.App
|
||||
}
|
||||
if params2.Abci != nil {
|
||||
res.ABCI.VoteExtensionsEnableHeight = params2.Abci.GetVoteExtensionsEnableHeight()
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
@@ -237,11 +302,14 @@ func (params *ConsensusParams) ToProto() tmproto.ConsensusParams {
|
||||
Version: &tmproto.VersionParams{
|
||||
App: params.Version.App,
|
||||
},
|
||||
Abci: &tmproto.ABCIParams{
|
||||
VoteExtensionsEnableHeight: params.ABCI.VoteExtensionsEnableHeight,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func ConsensusParamsFromProto(pbParams tmproto.ConsensusParams) ConsensusParams {
|
||||
return ConsensusParams{
|
||||
c := ConsensusParams{
|
||||
Block: BlockParams{
|
||||
MaxBytes: pbParams.Block.MaxBytes,
|
||||
MaxGas: pbParams.Block.MaxGas,
|
||||
@@ -258,4 +326,8 @@ func ConsensusParamsFromProto(pbParams tmproto.ConsensusParams) ConsensusParams
|
||||
App: pbParams.Version.App,
|
||||
},
|
||||
}
|
||||
if pbParams.Abci != nil {
|
||||
c.ABCI.VoteExtensionsEnableHeight = pbParams.Abci.GetVoteExtensionsEnableHeight()
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
@@ -145,6 +145,12 @@ func PartSetHeaderFromProto(ppsh *tmproto.PartSetHeader) (*PartSetHeader, error)
|
||||
return psh, psh.ValidateBasic()
|
||||
}
|
||||
|
||||
// IsProtoPartSetHeaderZero is similar to the IsZero function for
|
||||
// PartSetHeader, but for the Protobuf representation.
|
||||
func IsProtoPartSetHeaderZero(ppsh *tmproto.PartSetHeader) bool {
|
||||
return ppsh.Total == 0 && len(ppsh.Hash) == 0
|
||||
}
|
||||
|
||||
//-------------------------------------
|
||||
|
||||
type PartSet struct {
|
||||
|
||||
@@ -82,6 +82,20 @@ func (pv MockPV) SignVote(chainID string, vote *tmproto.Vote) error {
|
||||
return err
|
||||
}
|
||||
vote.Signature = sig
|
||||
|
||||
var extSig []byte
|
||||
// We only sign vote extensions for non-nil precommits
|
||||
// We always sign extensions, even if they are empty
|
||||
if vote.Type == tmproto.PrecommitType && !IsProtoBlockIDNil(&vote.BlockID) {
|
||||
extSignBytes := VoteExtensionSignBytes(useChainID, vote)
|
||||
extSig, err = pv.PrivKey.Sign(extSignBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if len(vote.Extension) > 0 {
|
||||
return errors.New("unexpected vote extension - vote extensions are only allowed in non-nil precommits")
|
||||
}
|
||||
vote.ExtensionSignature = extSig
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -9,8 +9,8 @@ import (
|
||||
"github.com/tendermint/tendermint/version"
|
||||
)
|
||||
|
||||
func MakeCommit(blockID BlockID, height int64, round int32,
|
||||
voteSet *VoteSet, validators []PrivValidator, now time.Time) (*Commit, error) {
|
||||
func makeExtCommit(blockID BlockID, height int64, round int32,
|
||||
voteSet *VoteSet, validators []PrivValidator, now time.Time) (*ExtendedCommit, error) {
|
||||
|
||||
// all sign
|
||||
for i := 0; i < len(validators); i++ {
|
||||
@@ -34,7 +34,7 @@ func MakeCommit(blockID BlockID, height int64, round int32,
|
||||
}
|
||||
}
|
||||
|
||||
return voteSet.MakeCommit(), nil
|
||||
return voteSet.MakeExtendedCommit(), nil
|
||||
}
|
||||
|
||||
func signAddVote(privVal PrivValidator, vote *Vote, voteSet *VoteSet) (signed bool, err error) {
|
||||
|
||||
@@ -751,8 +751,9 @@ func TestValidatorSet_VerifyCommit_CheckAllSignatures(t *testing.T) {
|
||||
)
|
||||
|
||||
voteSet, valSet, vals := randVoteSet(h, 0, tmproto.PrecommitType, 4, 10)
|
||||
commit, err := MakeCommit(blockID, h, 0, voteSet, vals, time.Now())
|
||||
extCommit, err := makeExtCommit(blockID, h, 0, voteSet, vals, time.Now())
|
||||
require.NoError(t, err)
|
||||
commit := extCommit.ToCommit()
|
||||
|
||||
// malleate 4th signature
|
||||
vote := voteSet.GetByIndex(3)
|
||||
@@ -776,8 +777,9 @@ func TestValidatorSet_VerifyCommitLight_ReturnsAsSoonAsMajorityOfVotingPowerSign
|
||||
)
|
||||
|
||||
voteSet, valSet, vals := randVoteSet(h, 0, tmproto.PrecommitType, 4, 10)
|
||||
commit, err := MakeCommit(blockID, h, 0, voteSet, vals, time.Now())
|
||||
extCommit, err := makeExtCommit(blockID, h, 0, voteSet, vals, time.Now())
|
||||
require.NoError(t, err)
|
||||
commit := extCommit.ToCommit()
|
||||
|
||||
// malleate 4th signature (3 signatures are enough for 2/3+)
|
||||
vote := voteSet.GetByIndex(3)
|
||||
@@ -799,8 +801,9 @@ func TestValidatorSet_VerifyCommitLightTrusting_ReturnsAsSoonAsTrustLevelOfVotin
|
||||
)
|
||||
|
||||
voteSet, valSet, vals := randVoteSet(h, 0, tmproto.PrecommitType, 4, 10)
|
||||
commit, err := MakeCommit(blockID, h, 0, voteSet, vals, time.Now())
|
||||
extCommit, err := makeExtCommit(blockID, h, 0, voteSet, vals, time.Now())
|
||||
require.NoError(t, err)
|
||||
commit := extCommit.ToCommit()
|
||||
|
||||
// malleate 3rd signature (2 signatures are enough for 1/3+ trust level)
|
||||
vote := voteSet.GetByIndex(2)
|
||||
@@ -1521,10 +1524,11 @@ func TestValidatorSet_VerifyCommitLightTrusting(t *testing.T) {
|
||||
var (
|
||||
blockID = makeBlockIDRandom()
|
||||
voteSet, originalValset, vals = randVoteSet(1, 1, tmproto.PrecommitType, 6, 1)
|
||||
commit, err = MakeCommit(blockID, 1, 1, voteSet, vals, time.Now())
|
||||
extCommit, err = makeExtCommit(blockID, 1, 1, voteSet, vals, time.Now())
|
||||
newValSet, _ = RandValidatorSet(2, 1)
|
||||
)
|
||||
require.NoError(t, err)
|
||||
commit := extCommit.ToCommit()
|
||||
|
||||
testCases := []struct {
|
||||
valSet *ValidatorSet
|
||||
@@ -1562,9 +1566,10 @@ func TestValidatorSet_VerifyCommitLightTrustingErrorsOnOverflow(t *testing.T) {
|
||||
var (
|
||||
blockID = makeBlockIDRandom()
|
||||
voteSet, valSet, vals = randVoteSet(1, 1, tmproto.PrecommitType, 1, MaxTotalVotingPower)
|
||||
commit, err = MakeCommit(blockID, 1, 1, voteSet, vals, time.Now())
|
||||
extCommit, err = makeExtCommit(blockID, 1, 1, voteSet, vals, time.Now())
|
||||
)
|
||||
require.NoError(t, err)
|
||||
commit := extCommit.ToCommit()
|
||||
|
||||
err = valSet.VerifyCommitLightTrusting("test_chain_id", commit,
|
||||
tmmath.Fraction{Numerator: 25, Denominator: 55})
|
||||
|
||||
+196
-49
@@ -13,7 +13,11 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
nilVoteStr string = "nil-Vote"
|
||||
absentVoteStr string = "Vote{absent}"
|
||||
nilVoteStr string = "nil"
|
||||
|
||||
// The maximum supported number of bytes in a vote extension.
|
||||
MaxVoteExtensionSize int = 1024 * 1024
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -24,6 +28,7 @@ var (
|
||||
ErrVoteInvalidBlockHash = errors.New("invalid block hash")
|
||||
ErrVoteNonDeterministicSignature = errors.New("non-deterministic signature")
|
||||
ErrVoteNil = errors.New("nil vote")
|
||||
ErrVoteExtensionAbsent = errors.New("expected vote extension is absent")
|
||||
)
|
||||
|
||||
type ErrVoteConflictingVotes struct {
|
||||
@@ -48,14 +53,16 @@ type Address = crypto.Address
|
||||
// Vote represents a prevote, precommit, or commit vote from validators for
|
||||
// consensus.
|
||||
type Vote struct {
|
||||
Type tmproto.SignedMsgType `json:"type"`
|
||||
Height int64 `json:"height"`
|
||||
Round int32 `json:"round"` // assume there will not be greater than 2_147_483_647 rounds
|
||||
BlockID BlockID `json:"block_id"` // zero if vote is nil.
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
ValidatorAddress Address `json:"validator_address"`
|
||||
ValidatorIndex int32 `json:"validator_index"`
|
||||
Signature []byte `json:"signature"`
|
||||
Type tmproto.SignedMsgType `json:"type"`
|
||||
Height int64 `json:"height"`
|
||||
Round int32 `json:"round"` // assume there will not be greater than 2_147_483_647 rounds
|
||||
BlockID BlockID `json:"block_id"` // zero if vote is nil.
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
ValidatorAddress Address `json:"validator_address"`
|
||||
ValidatorIndex int32 `json:"validator_index"`
|
||||
Signature []byte `json:"signature"`
|
||||
Extension []byte `json:"extension"`
|
||||
ExtensionSignature []byte `json:"extension_signature"`
|
||||
}
|
||||
|
||||
// CommitSig converts the Vote to a CommitSig.
|
||||
@@ -68,7 +75,7 @@ func (vote *Vote) CommitSig() CommitSig {
|
||||
switch {
|
||||
case vote.BlockID.IsComplete():
|
||||
blockIDFlag = BlockIDFlagCommit
|
||||
case vote.BlockID.IsZero():
|
||||
case vote.BlockID.IsNil():
|
||||
blockIDFlag = BlockIDFlagNil
|
||||
default:
|
||||
panic(fmt.Sprintf("Invalid vote %v - expected BlockID to be either empty or complete", vote))
|
||||
@@ -82,6 +89,31 @@ func (vote *Vote) CommitSig() CommitSig {
|
||||
}
|
||||
}
|
||||
|
||||
// StripExtension removes any extension data from the vote. Useful if the
|
||||
// chain has not enabled vote extensions.
|
||||
// Returns true if extension data was present before stripping and false otherwise.
|
||||
func (vote *Vote) StripExtension() bool {
|
||||
stripped := len(vote.Extension) > 0 || len(vote.ExtensionSignature) > 0
|
||||
vote.Extension = nil
|
||||
vote.ExtensionSignature = nil
|
||||
return stripped
|
||||
}
|
||||
|
||||
// ExtendedCommitSig attempts to construct an ExtendedCommitSig from this vote.
|
||||
// Panics if either the vote extension signature is missing or if the block ID
|
||||
// is not either empty or complete.
|
||||
func (vote *Vote) ExtendedCommitSig() ExtendedCommitSig {
|
||||
if vote == nil {
|
||||
return NewExtendedCommitSigAbsent()
|
||||
}
|
||||
|
||||
return ExtendedCommitSig{
|
||||
CommitSig: vote.CommitSig(),
|
||||
Extension: vote.Extension,
|
||||
ExtensionSignature: vote.ExtensionSignature,
|
||||
}
|
||||
}
|
||||
|
||||
// VoteSignBytes returns the proto-encoding of the canonicalized Vote, for
|
||||
// signing. Panics is the marshaling fails.
|
||||
//
|
||||
@@ -100,6 +132,21 @@ func VoteSignBytes(chainID string, vote *tmproto.Vote) []byte {
|
||||
return bz
|
||||
}
|
||||
|
||||
// VoteExtensionSignBytes returns the proto-encoding of the canonicalized vote
|
||||
// extension for signing. Panics if the marshaling fails.
|
||||
//
|
||||
// Similar to VoteSignBytes, the encoded Protobuf message is varint
|
||||
// length-prefixed for backwards-compatibility with the Amino encoding.
|
||||
func VoteExtensionSignBytes(chainID string, vote *tmproto.Vote) []byte {
|
||||
pb := CanonicalizeVoteExtension(chainID, vote)
|
||||
bz, err := protoio.MarshalDelimited(&pb)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return bz
|
||||
}
|
||||
|
||||
func (vote *Vote) Copy() *Vote {
|
||||
voteCopy := *vote
|
||||
return &voteCopy
|
||||
@@ -118,38 +165,91 @@ func (vote *Vote) Copy() *Vote {
|
||||
// 9. timestamp
|
||||
func (vote *Vote) String() string {
|
||||
if vote == nil {
|
||||
return nilVoteStr
|
||||
return absentVoteStr
|
||||
}
|
||||
|
||||
var blockHashString string
|
||||
if len(vote.BlockID.Hash) > 0 {
|
||||
blockHashString = fmt.Sprintf("%X", tmbytes.Fingerprint(vote.BlockID.Hash))
|
||||
} else {
|
||||
blockHashString = nilVoteStr
|
||||
}
|
||||
|
||||
var typeString string
|
||||
switch vote.Type {
|
||||
case tmproto.PrevoteType:
|
||||
typeString = "Prevote"
|
||||
return fmt.Sprintf("Prevote{%v/%02d by %v:%X for %X <%X> @ %s}",
|
||||
vote.Height,
|
||||
vote.Round,
|
||||
vote.ValidatorIndex,
|
||||
tmbytes.Fingerprint(vote.ValidatorAddress),
|
||||
blockHashString,
|
||||
tmbytes.Fingerprint(vote.Signature),
|
||||
CanonicalTime(vote.Timestamp),
|
||||
)
|
||||
case tmproto.PrecommitType:
|
||||
typeString = "Precommit"
|
||||
return fmt.Sprintf("Precommit{%v/%02d by %v:%X for %X <%X> & %d <%X> @ %s}",
|
||||
vote.Height,
|
||||
vote.Round,
|
||||
vote.ValidatorIndex,
|
||||
tmbytes.Fingerprint(vote.ValidatorAddress),
|
||||
blockHashString,
|
||||
tmbytes.Fingerprint(vote.Signature),
|
||||
len(vote.Extension),
|
||||
tmbytes.Fingerprint(vote.Extension),
|
||||
CanonicalTime(vote.Timestamp),
|
||||
)
|
||||
default:
|
||||
panic("Unknown vote type")
|
||||
}
|
||||
|
||||
return fmt.Sprintf("Vote{%v:%X %v/%02d/%v(%v) %X %X @ %s}",
|
||||
vote.ValidatorIndex,
|
||||
tmbytes.Fingerprint(vote.ValidatorAddress),
|
||||
vote.Height,
|
||||
vote.Round,
|
||||
vote.Type,
|
||||
typeString,
|
||||
tmbytes.Fingerprint(vote.BlockID.Hash),
|
||||
tmbytes.Fingerprint(vote.Signature),
|
||||
CanonicalTime(vote.Timestamp),
|
||||
)
|
||||
}
|
||||
|
||||
func (vote *Vote) Verify(chainID string, pubKey crypto.PubKey) error {
|
||||
func (vote *Vote) verifyAndReturnProto(chainID string, pubKey crypto.PubKey) (*tmproto.Vote, error) {
|
||||
if !bytes.Equal(pubKey.Address(), vote.ValidatorAddress) {
|
||||
return ErrVoteInvalidValidatorAddress
|
||||
return nil, ErrVoteInvalidValidatorAddress
|
||||
}
|
||||
v := vote.ToProto()
|
||||
if !pubKey.VerifySignature(VoteSignBytes(chainID, v), vote.Signature) {
|
||||
return nil, ErrVoteInvalidSignature
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// Verify checks whether the signature associated with this vote corresponds to
|
||||
// the given chain ID and public key. This function does not validate vote
|
||||
// extension signatures - to do so, use VerifyWithExtension instead.
|
||||
func (vote *Vote) Verify(chainID string, pubKey crypto.PubKey) error {
|
||||
_, err := vote.verifyAndReturnProto(chainID, pubKey)
|
||||
return err
|
||||
}
|
||||
|
||||
// VerifyVoteAndExtension performs the same verification as Verify, but
|
||||
// additionally checks whether the vote extension signature corresponds to the
|
||||
// given chain ID and public key. We only verify vote extension signatures for
|
||||
// precommits.
|
||||
func (vote *Vote) VerifyVoteAndExtension(chainID string, pubKey crypto.PubKey) error {
|
||||
v, err := vote.verifyAndReturnProto(chainID, pubKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// We only verify vote extension signatures for non-nil precommits.
|
||||
if vote.Type == tmproto.PrecommitType && !IsProtoBlockIDNil(&v.BlockID) {
|
||||
extSignBytes := VoteExtensionSignBytes(chainID, v)
|
||||
if !pubKey.VerifySignature(extSignBytes, vote.ExtensionSignature) {
|
||||
return ErrVoteInvalidSignature
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// VerifyExtension checks whether the vote extension signature corresponds to the
|
||||
// given chain ID and public key.
|
||||
func (vote *Vote) VerifyExtension(chainID string, pubKey crypto.PubKey) error {
|
||||
if vote.Type != tmproto.PrecommitType || vote.BlockID.IsNil() {
|
||||
return nil
|
||||
}
|
||||
v := vote.ToProto()
|
||||
extSignBytes := VoteExtensionSignBytes(chainID, v)
|
||||
if !pubKey.VerifySignature(extSignBytes, vote.ExtensionSignature) {
|
||||
return ErrVoteInvalidSignature
|
||||
}
|
||||
return nil
|
||||
@@ -161,8 +261,8 @@ func (vote *Vote) ValidateBasic() error {
|
||||
return errors.New("invalid Type")
|
||||
}
|
||||
|
||||
if vote.Height < 0 {
|
||||
return errors.New("negative Height")
|
||||
if vote.Height <= 0 {
|
||||
return errors.New("negative or zero Height")
|
||||
}
|
||||
|
||||
if vote.Round < 0 {
|
||||
@@ -177,7 +277,7 @@ func (vote *Vote) ValidateBasic() error {
|
||||
|
||||
// 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() {
|
||||
if !vote.BlockID.IsNil() && !vote.BlockID.IsComplete() {
|
||||
return fmt.Errorf("blockID must be either empty or complete, got: %v", vote.BlockID)
|
||||
}
|
||||
|
||||
@@ -198,9 +298,52 @@ func (vote *Vote) ValidateBasic() error {
|
||||
return fmt.Errorf("signature is too big (max: %d)", MaxSignatureSize)
|
||||
}
|
||||
|
||||
// We should only ever see vote extensions in non-nil precommits, otherwise
|
||||
// this is a violation of the specification.
|
||||
// https://github.com/tendermint/tendermint/issues/8487
|
||||
if vote.Type != tmproto.PrecommitType || (vote.Type == tmproto.PrecommitType && vote.BlockID.IsNil()) {
|
||||
if len(vote.Extension) > 0 {
|
||||
return errors.New("unexpected vote extension")
|
||||
}
|
||||
if len(vote.ExtensionSignature) > 0 {
|
||||
return errors.New("unexpected vote extension signature")
|
||||
}
|
||||
} else {
|
||||
// It's possible that this vote has vote extensions but
|
||||
// they could also be disabled and thus not present thus
|
||||
// we can't do all checks
|
||||
if len(vote.ExtensionSignature) > MaxSignatureSize {
|
||||
return fmt.Errorf("vote extension signature is too big (max: %d)", MaxSignatureSize)
|
||||
}
|
||||
|
||||
// NOTE: extended votes should have a signature regardless of
|
||||
// of whether there is any data in the extension or not however
|
||||
// we don't know if extensions are enabled so we can only
|
||||
// enforce the signature when extension size is no nil
|
||||
if len(vote.ExtensionSignature) == 0 && len(vote.Extension) != 0 {
|
||||
return fmt.Errorf("vote extension signature absent on vote with extension")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// EnsureExtension checks for the presence of extensions signature data
|
||||
// on precommit vote types.
|
||||
func (vote *Vote) EnsureExtension() error {
|
||||
// We should always see vote extension signatures in non-nil precommits
|
||||
if vote.Type != tmproto.PrecommitType {
|
||||
return nil
|
||||
}
|
||||
if vote.BlockID.IsNil() {
|
||||
return nil
|
||||
}
|
||||
if len(vote.ExtensionSignature) > 0 {
|
||||
return nil
|
||||
}
|
||||
return ErrVoteExtensionAbsent
|
||||
}
|
||||
|
||||
// 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 {
|
||||
@@ -209,14 +352,16 @@ func (vote *Vote) ToProto() *tmproto.Vote {
|
||||
}
|
||||
|
||||
return &tmproto.Vote{
|
||||
Type: vote.Type,
|
||||
Height: vote.Height,
|
||||
Round: vote.Round,
|
||||
BlockID: vote.BlockID.ToProto(),
|
||||
Timestamp: vote.Timestamp,
|
||||
ValidatorAddress: vote.ValidatorAddress,
|
||||
ValidatorIndex: vote.ValidatorIndex,
|
||||
Signature: vote.Signature,
|
||||
Type: vote.Type,
|
||||
Height: vote.Height,
|
||||
Round: vote.Round,
|
||||
BlockID: vote.BlockID.ToProto(),
|
||||
Timestamp: vote.Timestamp,
|
||||
ValidatorAddress: vote.ValidatorAddress,
|
||||
ValidatorIndex: vote.ValidatorIndex,
|
||||
Signature: vote.Signature,
|
||||
Extension: vote.Extension,
|
||||
ExtensionSignature: vote.ExtensionSignature,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,15 +393,17 @@ func VoteFromProto(pv *tmproto.Vote) (*Vote, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
vote := new(Vote)
|
||||
vote.Type = pv.Type
|
||||
vote.Height = pv.Height
|
||||
vote.Round = pv.Round
|
||||
vote.BlockID = *blockID
|
||||
vote.Timestamp = pv.Timestamp
|
||||
vote.ValidatorAddress = pv.ValidatorAddress
|
||||
vote.ValidatorIndex = pv.ValidatorIndex
|
||||
vote.Signature = pv.Signature
|
||||
|
||||
vote := &Vote{
|
||||
Type: pv.Type,
|
||||
Height: pv.Height,
|
||||
Round: pv.Round,
|
||||
BlockID: *blockID,
|
||||
Timestamp: pv.Timestamp,
|
||||
ValidatorAddress: pv.ValidatorAddress,
|
||||
ValidatorIndex: pv.ValidatorIndex,
|
||||
Signature: pv.Signature,
|
||||
Extension: pv.Extension,
|
||||
ExtensionSignature: pv.ExtensionSignature,
|
||||
}
|
||||
return vote, vote.ValidateBasic()
|
||||
}
|
||||
|
||||
+33
-17
@@ -59,11 +59,12 @@ there's only a limited number of peers.
|
||||
NOTE: Assumes that the sum total of voting power does not exceed MaxUInt64.
|
||||
*/
|
||||
type VoteSet struct {
|
||||
chainID string
|
||||
height int64
|
||||
round int32
|
||||
signedMsgType tmproto.SignedMsgType
|
||||
valSet *ValidatorSet
|
||||
chainID string
|
||||
height int64
|
||||
round int32
|
||||
signedMsgType tmproto.SignedMsgType
|
||||
valSet *ValidatorSet
|
||||
extensionsEnabled bool
|
||||
|
||||
mtx tmsync.Mutex
|
||||
votesBitArray *bits.BitArray
|
||||
@@ -95,6 +96,16 @@ func NewVoteSet(chainID string, height int64, round int32,
|
||||
}
|
||||
}
|
||||
|
||||
// NewExtendedVoteSet constructs a vote set with additional vote verification logic.
|
||||
// The VoteSet constructed with NewExtendedVoteSet verifies the vote extension
|
||||
// data for every vote added to the set.
|
||||
func NewExtendedVoteSet(chainID string, height int64, round int32,
|
||||
signedMsgType tmproto.SignedMsgType, valSet *ValidatorSet) *VoteSet {
|
||||
vs := NewVoteSet(chainID, height, round, signedMsgType, valSet)
|
||||
vs.extensionsEnabled = true
|
||||
return vs
|
||||
}
|
||||
|
||||
func (voteSet *VoteSet) ChainID() string {
|
||||
return voteSet.chainID
|
||||
}
|
||||
@@ -611,36 +622,41 @@ func (voteSet *VoteSet) sumTotalFrac() (int64, int64, float64) {
|
||||
//--------------------------------------------------------------------------------
|
||||
// Commit
|
||||
|
||||
// MakeCommit constructs a Commit from the VoteSet. It only includes precommits
|
||||
// for the block, which has 2/3+ majority, and nil.
|
||||
// MakeExtendedCommit constructs a Commit from the VoteSet. It only includes
|
||||
// precommits for the block, which has 2/3+ majority, and nil.
|
||||
//
|
||||
// Panics if the vote type is not PrecommitType or if there's no +2/3 votes for
|
||||
// a single block.
|
||||
func (voteSet *VoteSet) MakeCommit() *Commit {
|
||||
func (voteSet *VoteSet) MakeExtendedCommit() *ExtendedCommit {
|
||||
if voteSet.signedMsgType != tmproto.PrecommitType {
|
||||
panic("Cannot MakeCommit() unless VoteSet.Type is PrecommitType")
|
||||
panic("Cannot MakeExtendCommit() unless VoteSet.Type is PrecommitType")
|
||||
}
|
||||
voteSet.mtx.Lock()
|
||||
defer voteSet.mtx.Unlock()
|
||||
|
||||
// Make sure we have a 2/3 majority
|
||||
if voteSet.maj23 == nil {
|
||||
panic("Cannot MakeCommit() unless a blockhash has +2/3")
|
||||
panic("Cannot MakeExtendCommit() unless a blockhash has +2/3")
|
||||
}
|
||||
|
||||
// For every validator, get the precommit
|
||||
commitSigs := make([]CommitSig, len(voteSet.votes))
|
||||
// For every validator, get the precommit with extensions
|
||||
sigs := make([]ExtendedCommitSig, len(voteSet.votes))
|
||||
for i, v := range voteSet.votes {
|
||||
commitSig := v.CommitSig()
|
||||
sig := v.ExtendedCommitSig()
|
||||
// if block ID exists but doesn't match, exclude sig
|
||||
if commitSig.ForBlock() && !v.BlockID.Equals(*voteSet.maj23) {
|
||||
commitSig = NewCommitSigAbsent()
|
||||
if sig.BlockIDFlag == BlockIDFlagCommit && !v.BlockID.Equals(*voteSet.maj23) {
|
||||
sig = NewExtendedCommitSigAbsent()
|
||||
}
|
||||
|
||||
commitSigs[i] = commitSig
|
||||
sigs[i] = sig
|
||||
}
|
||||
|
||||
return NewCommit(voteSet.GetHeight(), voteSet.GetRound(), *voteSet.maj23, commitSigs)
|
||||
return &ExtendedCommit{
|
||||
Height: voteSet.GetHeight(),
|
||||
Round: voteSet.GetRound(),
|
||||
BlockID: *voteSet.maj23,
|
||||
ExtendedSignatures: sigs,
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
|
||||
+14
-14
@@ -25,7 +25,7 @@ func TestVoteSet_AddVote_Good(t *testing.T) {
|
||||
assert.Nil(t, voteSet.GetByAddress(val0Addr))
|
||||
assert.False(t, voteSet.BitArray().GetIndex(0))
|
||||
blockID, ok := voteSet.TwoThirdsMajority()
|
||||
assert.False(t, ok || !blockID.IsZero(), "there should be no 2/3 majority")
|
||||
assert.False(t, ok || !blockID.IsNil(), "there should be no 2/3 majority")
|
||||
|
||||
vote := &Vote{
|
||||
ValidatorAddress: val0Addr,
|
||||
@@ -42,7 +42,7 @@ func TestVoteSet_AddVote_Good(t *testing.T) {
|
||||
assert.NotNil(t, voteSet.GetByAddress(val0Addr))
|
||||
assert.True(t, voteSet.BitArray().GetIndex(0))
|
||||
blockID, ok = voteSet.TwoThirdsMajority()
|
||||
assert.False(t, ok || !blockID.IsZero(), "there should be no 2/3 majority")
|
||||
assert.False(t, ok || !blockID.IsNil(), "there should be no 2/3 majority")
|
||||
}
|
||||
|
||||
func TestVoteSet_AddVote_Bad(t *testing.T) {
|
||||
@@ -143,7 +143,7 @@ func TestVoteSet_2_3Majority(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
blockID, ok := voteSet.TwoThirdsMajority()
|
||||
assert.False(t, ok || !blockID.IsZero(), "there should be no 2/3 majority")
|
||||
assert.False(t, ok || !blockID.IsNil(), "there should be no 2/3 majority")
|
||||
|
||||
// 7th validator voted for some blockhash
|
||||
{
|
||||
@@ -154,7 +154,7 @@ func TestVoteSet_2_3Majority(t *testing.T) {
|
||||
_, err = signAddVote(privValidators[6], withBlockHash(vote, tmrand.Bytes(32)), voteSet)
|
||||
require.NoError(t, err)
|
||||
blockID, ok = voteSet.TwoThirdsMajority()
|
||||
assert.False(t, ok || !blockID.IsZero(), "there should be no 2/3 majority")
|
||||
assert.False(t, ok || !blockID.IsNil(), "there should be no 2/3 majority")
|
||||
}
|
||||
|
||||
// 8th validator voted for nil.
|
||||
@@ -166,7 +166,7 @@ func TestVoteSet_2_3Majority(t *testing.T) {
|
||||
_, err = signAddVote(privValidators[7], vote, voteSet)
|
||||
require.NoError(t, err)
|
||||
blockID, ok = voteSet.TwoThirdsMajority()
|
||||
assert.True(t, ok || blockID.IsZero(), "there should be 2/3 majority for nil")
|
||||
assert.True(t, ok || blockID.IsNil(), "there should be 2/3 majority for nil")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,7 +198,7 @@ func TestVoteSet_2_3MajorityRedux(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
blockID, ok := voteSet.TwoThirdsMajority()
|
||||
assert.False(t, ok || !blockID.IsZero(),
|
||||
assert.False(t, ok || !blockID.IsNil(),
|
||||
"there should be no 2/3 majority")
|
||||
|
||||
// 67th validator voted for nil
|
||||
@@ -210,7 +210,7 @@ func TestVoteSet_2_3MajorityRedux(t *testing.T) {
|
||||
_, err = signAddVote(privValidators[66], withBlockHash(vote, nil), voteSet)
|
||||
require.NoError(t, err)
|
||||
blockID, ok = voteSet.TwoThirdsMajority()
|
||||
assert.False(t, ok || !blockID.IsZero(),
|
||||
assert.False(t, ok || !blockID.IsNil(),
|
||||
"there should be no 2/3 majority: last vote added was nil")
|
||||
}
|
||||
|
||||
@@ -224,7 +224,7 @@ func TestVoteSet_2_3MajorityRedux(t *testing.T) {
|
||||
_, err = signAddVote(privValidators[67], withBlockPartSetHeader(vote, blockPartsHeader), voteSet)
|
||||
require.NoError(t, err)
|
||||
blockID, ok = voteSet.TwoThirdsMajority()
|
||||
assert.False(t, ok || !blockID.IsZero(),
|
||||
assert.False(t, ok || !blockID.IsNil(),
|
||||
"there should be no 2/3 majority: last vote added had different PartSetHeader Hash")
|
||||
}
|
||||
|
||||
@@ -238,7 +238,7 @@ func TestVoteSet_2_3MajorityRedux(t *testing.T) {
|
||||
_, err = signAddVote(privValidators[68], withBlockPartSetHeader(vote, blockPartsHeader), voteSet)
|
||||
require.NoError(t, err)
|
||||
blockID, ok = voteSet.TwoThirdsMajority()
|
||||
assert.False(t, ok || !blockID.IsZero(),
|
||||
assert.False(t, ok || !blockID.IsNil(),
|
||||
"there should be no 2/3 majority: last vote added had different PartSetHeader Total")
|
||||
}
|
||||
|
||||
@@ -251,7 +251,7 @@ func TestVoteSet_2_3MajorityRedux(t *testing.T) {
|
||||
_, err = signAddVote(privValidators[69], withBlockHash(vote, tmrand.Bytes(32)), voteSet)
|
||||
require.NoError(t, err)
|
||||
blockID, ok = voteSet.TwoThirdsMajority()
|
||||
assert.False(t, ok || !blockID.IsZero(),
|
||||
assert.False(t, ok || !blockID.IsNil(),
|
||||
"there should be no 2/3 majority: last vote added had different BlockHash")
|
||||
}
|
||||
|
||||
@@ -426,7 +426,7 @@ func TestVoteSet_MakeCommit(t *testing.T) {
|
||||
}
|
||||
|
||||
// MakeCommit should fail.
|
||||
assert.Panics(t, func() { voteSet.MakeCommit() }, "Doesn't have +2/3 majority")
|
||||
assert.Panics(t, func() { voteSet.MakeExtendedCommit() }, "Doesn't have +2/3 majority")
|
||||
|
||||
// 7th voted for some other block.
|
||||
{
|
||||
@@ -463,13 +463,13 @@ func TestVoteSet_MakeCommit(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
commit := voteSet.MakeCommit()
|
||||
extCommit := voteSet.MakeExtendedCommit()
|
||||
|
||||
// Commit should have 10 elements
|
||||
assert.Equal(t, 10, len(commit.Signatures))
|
||||
assert.Equal(t, 10, len(extCommit.ExtendedSignatures))
|
||||
|
||||
// Ensure that Commit is good.
|
||||
if err := commit.ValidateBasic(); err != nil {
|
||||
if err := extCommit.ValidateBasic(); err != nil {
|
||||
t.Errorf("error in Commit.ValidateBasic(): %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
+7
-3
@@ -219,13 +219,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 := `Precommit{12345/02 by 56789:6AF1F4111082 for 384230313032333338364333 <000000000000> & 0 <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 = `Prevote{12345/02 by 56789:6AF1F4111082 for 384230313032333338364333 <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)
|
||||
}
|
||||
@@ -259,7 +259,11 @@ func TestVoteValidateBasic(t *testing.T) {
|
||||
vote.Signature = v.Signature
|
||||
require.NoError(t, err)
|
||||
tc.malleateVote(vote)
|
||||
assert.Equal(t, tc.expectErr, vote.ValidateBasic() != nil, "Validate Basic had an unexpected result")
|
||||
if tc.expectErr {
|
||||
require.Error(t, vote.ValidateBasic())
|
||||
} else {
|
||||
require.NoError(t, vote.ValidateBasic())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user