mirror of
https://github.com/tendermint/tendermint.git
synced 2026-09-19 06:31:57 +00:00
Fix conflicts with upsteam changes
This commit is contained in:
+119
-32
@@ -198,7 +198,7 @@ func (b *Block) Hash() cmn.HexBytes {
|
||||
b.mtx.Lock()
|
||||
defer b.mtx.Unlock()
|
||||
|
||||
if b == nil || b.LastCommit == nil {
|
||||
if b.LastCommit == nil {
|
||||
return nil
|
||||
}
|
||||
b.fillHeader()
|
||||
@@ -312,7 +312,7 @@ func MaxDataBytes(maxBytes int64, valsCount, evidenceCount int) int64 {
|
||||
|
||||
if maxDataBytes < 0 {
|
||||
panic(fmt.Sprintf(
|
||||
"Negative MaxDataBytes. BlockSize.MaxBytes=%d is too small to accommodate header&lastCommit&evidence=%d",
|
||||
"Negative MaxDataBytes. Block.MaxBytes=%d is too small to accommodate header&lastCommit&evidence=%d",
|
||||
maxBytes,
|
||||
-(maxDataBytes - maxBytes),
|
||||
))
|
||||
@@ -337,7 +337,7 @@ func MaxDataBytesUnknownEvidence(maxBytes int64, valsCount int) int64 {
|
||||
|
||||
if maxDataBytes < 0 {
|
||||
panic(fmt.Sprintf(
|
||||
"Negative MaxDataBytesUnknownEvidence. BlockSize.MaxBytes=%d is too small to accommodate header&lastCommit&evidence=%d",
|
||||
"Negative MaxDataBytesUnknownEvidence. Block.MaxBytes=%d is too small to accommodate header&lastCommit&evidence=%d",
|
||||
maxBytes,
|
||||
-(maxDataBytes - maxBytes),
|
||||
))
|
||||
@@ -489,55 +489,140 @@ func (h *Header) StringIndented(indent string) string {
|
||||
|
||||
//-------------------------------------
|
||||
|
||||
// CommitSig is a vote included in a Commit.
|
||||
// For now, it is identical to a vote,
|
||||
// but in the future it will contain fewer fields
|
||||
// to eliminate the redundancy in commits.
|
||||
// See https://github.com/tendermint/tendermint/issues/1648.
|
||||
type CommitSig Vote
|
||||
|
||||
// String returns the underlying Vote.String()
|
||||
func (cs *CommitSig) String() string {
|
||||
return cs.toVote().String()
|
||||
}
|
||||
|
||||
// toVote converts the CommitSig to a vote.
|
||||
// TODO: deprecate for #1648. Converting to Vote will require
|
||||
// access to ValidatorSet.
|
||||
func (cs *CommitSig) toVote() *Vote {
|
||||
if cs == nil {
|
||||
return nil
|
||||
}
|
||||
v := Vote(*cs)
|
||||
return &v
|
||||
}
|
||||
|
||||
//-------------------------------------
|
||||
|
||||
// Commit contains the evidence that a block was committed by a set of validators.
|
||||
// NOTE: Commit is empty for height 1, but never nil.
|
||||
type Commit struct {
|
||||
// NOTE: The Precommits are in order of address to preserve the bonded ValidatorSet order.
|
||||
// Any peer with a block can gossip precommits by index with a peer without recalculating the
|
||||
// active ValidatorSet.
|
||||
BlockID BlockID `json:"block_id"`
|
||||
Precommits []*Vote `json:"precommits"`
|
||||
BlockID BlockID `json:"block_id"`
|
||||
Precommits []*CommitSig `json:"precommits"`
|
||||
|
||||
// Volatile
|
||||
firstPrecommit *Vote
|
||||
hash cmn.HexBytes
|
||||
bitArray *cmn.BitArray
|
||||
// memoized in first call to corresponding method
|
||||
// NOTE: can't memoize in constructor because constructor
|
||||
// isn't used for unmarshaling
|
||||
height int64
|
||||
round int
|
||||
hash cmn.HexBytes
|
||||
bitArray *cmn.BitArray
|
||||
}
|
||||
|
||||
// FirstPrecommit returns the first non-nil precommit in the commit.
|
||||
// If all precommits are nil, it returns an empty precommit with height 0.
|
||||
func (commit *Commit) FirstPrecommit() *Vote {
|
||||
if len(commit.Precommits) == 0 {
|
||||
// NewCommit returns a new Commit with the given blockID and precommits.
|
||||
// TODO: memoize ValidatorSet in constructor so votes can be easily reconstructed
|
||||
// from CommitSig after #1648.
|
||||
func NewCommit(blockID BlockID, precommits []*CommitSig) *Commit {
|
||||
return &Commit{
|
||||
BlockID: blockID,
|
||||
Precommits: precommits,
|
||||
}
|
||||
}
|
||||
|
||||
// Construct a VoteSet from the Commit and validator set. Panics
|
||||
// if precommits from the commit can't be added to the voteset.
|
||||
// Inverse of VoteSet.MakeCommit().
|
||||
func CommitToVoteSet(chainID string, commit *Commit, vals *ValidatorSet) *VoteSet {
|
||||
height, round, typ := commit.Height(), commit.Round(), PrecommitType
|
||||
voteSet := NewVoteSet(chainID, height, round, typ, vals)
|
||||
for idx, precommit := range commit.Precommits {
|
||||
if precommit == nil {
|
||||
continue
|
||||
}
|
||||
added, err := voteSet.AddVote(commit.GetVote(idx))
|
||||
if !added || err != nil {
|
||||
panic(fmt.Sprintf("Failed to reconstruct LastCommit: %v", err))
|
||||
}
|
||||
}
|
||||
return voteSet
|
||||
}
|
||||
|
||||
// GetVote converts the CommitSig for the given valIdx to a Vote.
|
||||
// Returns nil if the precommit at valIdx is nil.
|
||||
// Panics if valIdx >= commit.Size().
|
||||
func (commit *Commit) GetVote(valIdx int) *Vote {
|
||||
commitSig := commit.Precommits[valIdx]
|
||||
if commitSig == nil {
|
||||
return nil
|
||||
}
|
||||
if commit.firstPrecommit != nil {
|
||||
return commit.firstPrecommit
|
||||
|
||||
// NOTE: this commitSig might be for a nil blockID,
|
||||
// so we can't just use commit.BlockID here.
|
||||
// For #1648, CommitSig will need to indicate what BlockID it's for !
|
||||
blockID := commitSig.BlockID
|
||||
commit.memoizeHeightRound()
|
||||
return &Vote{
|
||||
Type: PrecommitType,
|
||||
Height: commit.height,
|
||||
Round: commit.round,
|
||||
BlockID: blockID,
|
||||
Timestamp: commitSig.Timestamp,
|
||||
ValidatorAddress: commitSig.ValidatorAddress,
|
||||
ValidatorIndex: valIdx,
|
||||
Signature: commitSig.Signature,
|
||||
}
|
||||
}
|
||||
|
||||
// VoteSignBytes constructs the SignBytes for the given CommitSig.
|
||||
// The only unique part of the SignBytes is the Timestamp - all other fields
|
||||
// signed over are otherwise the same for all validators.
|
||||
// Panics if valIdx >= commit.Size().
|
||||
func (commit *Commit) VoteSignBytes(chainID string, valIdx int) []byte {
|
||||
return commit.GetVote(valIdx).SignBytes(chainID)
|
||||
}
|
||||
|
||||
// memoizeHeightRound memoizes the height and round of the commit using
|
||||
// the first non-nil vote.
|
||||
// Should be called before any attempt to access `commit.height` or `commit.round`.
|
||||
func (commit *Commit) memoizeHeightRound() {
|
||||
if len(commit.Precommits) == 0 {
|
||||
return
|
||||
}
|
||||
if commit.height > 0 {
|
||||
return
|
||||
}
|
||||
for _, precommit := range commit.Precommits {
|
||||
if precommit != nil {
|
||||
commit.firstPrecommit = precommit
|
||||
return precommit
|
||||
commit.height = precommit.Height
|
||||
commit.round = precommit.Round
|
||||
return
|
||||
}
|
||||
}
|
||||
return &Vote{
|
||||
Type: PrecommitType,
|
||||
}
|
||||
}
|
||||
|
||||
// Height returns the height of the commit
|
||||
func (commit *Commit) Height() int64 {
|
||||
if len(commit.Precommits) == 0 {
|
||||
return 0
|
||||
}
|
||||
return commit.FirstPrecommit().Height
|
||||
commit.memoizeHeightRound()
|
||||
return commit.height
|
||||
}
|
||||
|
||||
// Round returns the round of the commit
|
||||
func (commit *Commit) Round() int {
|
||||
if len(commit.Precommits) == 0 {
|
||||
return 0
|
||||
}
|
||||
return commit.FirstPrecommit().Round
|
||||
commit.memoizeHeightRound()
|
||||
return commit.round
|
||||
}
|
||||
|
||||
// Type returns the vote type of the commit, which is always VoteTypePrecommit
|
||||
@@ -566,12 +651,14 @@ func (commit *Commit) BitArray() *cmn.BitArray {
|
||||
return commit.bitArray
|
||||
}
|
||||
|
||||
// GetByIndex returns the vote corresponding to a given validator index
|
||||
func (commit *Commit) GetByIndex(index int) *Vote {
|
||||
return commit.Precommits[index]
|
||||
// GetByIndex returns the vote corresponding to a given validator index.
|
||||
// Panics if `index >= commit.Size()`.
|
||||
// Implements VoteSetReader.
|
||||
func (commit *Commit) GetByIndex(valIdx int) *Vote {
|
||||
return commit.GetVote(valIdx)
|
||||
}
|
||||
|
||||
// IsCommit returns true if there is at least one vote
|
||||
// IsCommit returns true if there is at least one vote.
|
||||
func (commit *Commit) IsCommit() bool {
|
||||
return len(commit.Precommits) != 0
|
||||
}
|
||||
|
||||
+28
-3
@@ -1,6 +1,8 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
// it is ok to use math/rand here: we do not need a cryptographically secure random
|
||||
// number generator here and we can run the tests a bit faster
|
||||
"crypto/rand"
|
||||
"math"
|
||||
"os"
|
||||
@@ -162,8 +164,8 @@ func TestBlockString(t *testing.T) {
|
||||
func makeBlockIDRandom() BlockID {
|
||||
blockHash := make([]byte, tmhash.Size)
|
||||
partSetHash := make([]byte, tmhash.Size)
|
||||
rand.Read(blockHash)
|
||||
rand.Read(partSetHash)
|
||||
rand.Read(blockHash) //nolint: gosec
|
||||
rand.Read(partSetHash) //nolint: gosec
|
||||
blockPartsHeader := PartSetHeader{123, partSetHash}
|
||||
return BlockID{blockHash, blockPartsHeader}
|
||||
}
|
||||
@@ -198,7 +200,6 @@ func TestCommit(t *testing.T) {
|
||||
commit, err := MakeCommit(lastID, h-1, 1, voteSet, vals)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.NotNil(t, commit.FirstPrecommit())
|
||||
assert.Equal(t, h-1, commit.Height())
|
||||
assert.Equal(t, 1, commit.Round())
|
||||
assert.Equal(t, PrecommitType, SignedMsgType(commit.Type()))
|
||||
@@ -341,3 +342,27 @@ func TestBlockMaxDataBytesUnknownEvidence(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommitToVoteSet(t *testing.T) {
|
||||
lastID := makeBlockIDRandom()
|
||||
h := int64(3)
|
||||
|
||||
voteSet, valSet, vals := randVoteSet(h-1, 1, PrecommitType, 10, 1)
|
||||
commit, err := MakeCommit(lastID, h-1, 1, voteSet, vals)
|
||||
assert.NoError(t, err)
|
||||
|
||||
chainID := voteSet.ChainID()
|
||||
voteSet2 := CommitToVoteSet(chainID, commit, valSet)
|
||||
|
||||
for i := 0; i < len(vals); i++ {
|
||||
vote1 := voteSet.GetByIndex(i)
|
||||
vote2 := voteSet2.GetByIndex(i)
|
||||
vote3 := commit.GetVote(i)
|
||||
|
||||
vote1bz := cdc.MustMarshalBinaryBare(vote1)
|
||||
vote2bz := cdc.MustMarshalBinaryBare(vote2)
|
||||
vote3bz := cdc.MustMarshalBinaryBare(vote3)
|
||||
assert.Equal(t, vote1bz, vote2bz)
|
||||
assert.Equal(t, vote1bz, vote3bz)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package types
|
||||
|
||||
import "fmt"
|
||||
|
||||
type (
|
||||
// ErrInvalidCommitHeight is returned when we encounter a commit with an
|
||||
// unexpected height.
|
||||
ErrInvalidCommitHeight struct {
|
||||
Expected int64
|
||||
Actual int64
|
||||
}
|
||||
|
||||
// ErrInvalidCommitPrecommits is returned when we encounter a commit where
|
||||
// the number of precommits doesn't match the number of validators.
|
||||
ErrInvalidCommitPrecommits struct {
|
||||
Expected int
|
||||
Actual int
|
||||
}
|
||||
)
|
||||
|
||||
func NewErrInvalidCommitHeight(expected, actual int64) ErrInvalidCommitHeight {
|
||||
return ErrInvalidCommitHeight{
|
||||
Expected: expected,
|
||||
Actual: actual,
|
||||
}
|
||||
}
|
||||
|
||||
func (e ErrInvalidCommitHeight) Error() string {
|
||||
return fmt.Sprintf("Invalid commit -- wrong height: %v vs %v", e.Expected, e.Actual)
|
||||
}
|
||||
|
||||
func NewErrInvalidCommitPrecommits(expected, actual int) ErrInvalidCommitPrecommits {
|
||||
return ErrInvalidCommitPrecommits{
|
||||
Expected: expected,
|
||||
Actual: actual,
|
||||
}
|
||||
}
|
||||
|
||||
func (e ErrInvalidCommitPrecommits) Error() string {
|
||||
return fmt.Sprintf("Invalid commit -- wrong set size: %v vs %v", e.Expected, e.Actual)
|
||||
}
|
||||
+63
-43
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/tendermint/tendermint/abci/types"
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
tmpubsub "github.com/tendermint/tendermint/libs/pubsub"
|
||||
@@ -12,9 +13,18 @@ import (
|
||||
const defaultCapacity = 0
|
||||
|
||||
type EventBusSubscriber interface {
|
||||
Subscribe(ctx context.Context, subscriber string, query tmpubsub.Query, out chan<- interface{}) error
|
||||
Subscribe(ctx context.Context, subscriber string, query tmpubsub.Query, outCapacity ...int) (Subscription, error)
|
||||
Unsubscribe(ctx context.Context, subscriber string, query tmpubsub.Query) error
|
||||
UnsubscribeAll(ctx context.Context, subscriber string) error
|
||||
|
||||
NumClients() int
|
||||
NumClientSubscriptions(clientID string) int
|
||||
}
|
||||
|
||||
type Subscription interface {
|
||||
Out() <-chan tmpubsub.Message
|
||||
Cancelled() <-chan struct{}
|
||||
Err() error
|
||||
}
|
||||
|
||||
// EventBus is a common bus for all events going through the system. All calls
|
||||
@@ -52,8 +62,22 @@ func (b *EventBus) OnStop() {
|
||||
b.pubsub.Stop()
|
||||
}
|
||||
|
||||
func (b *EventBus) Subscribe(ctx context.Context, subscriber string, query tmpubsub.Query, out chan<- interface{}) error {
|
||||
return b.pubsub.Subscribe(ctx, subscriber, query, out)
|
||||
func (b *EventBus) NumClients() int {
|
||||
return b.pubsub.NumClients()
|
||||
}
|
||||
|
||||
func (b *EventBus) NumClientSubscriptions(clientID string) int {
|
||||
return b.pubsub.NumClientSubscriptions(clientID)
|
||||
}
|
||||
|
||||
func (b *EventBus) Subscribe(ctx context.Context, subscriber string, query tmpubsub.Query, outCapacity ...int) (Subscription, error) {
|
||||
return b.pubsub.Subscribe(ctx, subscriber, query, outCapacity...)
|
||||
}
|
||||
|
||||
// This method can be used for a local consensus explorer and synchronous
|
||||
// testing. Do not use for for public facing / untrusted subscriptions!
|
||||
func (b *EventBus) SubscribeUnbuffered(ctx context.Context, subscriber string, query tmpubsub.Query) (Subscription, error) {
|
||||
return b.pubsub.SubscribeUnbuffered(ctx, subscriber, query)
|
||||
}
|
||||
|
||||
func (b *EventBus) Unsubscribe(ctx context.Context, subscriber string, query tmpubsub.Query) error {
|
||||
@@ -67,20 +91,32 @@ func (b *EventBus) UnsubscribeAll(ctx context.Context, subscriber string) error
|
||||
func (b *EventBus) Publish(eventType string, eventData TMEventData) error {
|
||||
// no explicit deadline for publishing events
|
||||
ctx := context.Background()
|
||||
b.pubsub.PublishWithTags(ctx, eventData, tmpubsub.NewTagMap(map[string]string{EventTypeKey: eventType}))
|
||||
return nil
|
||||
return b.pubsub.PublishWithEvents(ctx, eventData, map[string][]string{EventTypeKey: {eventType}})
|
||||
}
|
||||
|
||||
func (b *EventBus) validateAndStringifyTags(tags []cmn.KVPair, logger log.Logger) map[string]string {
|
||||
result := make(map[string]string)
|
||||
for _, tag := range tags {
|
||||
// basic validation
|
||||
if len(tag.Key) == 0 {
|
||||
logger.Debug("Got tag with an empty key (skipping)", "tag", tag)
|
||||
// validateAndStringifyEvents takes a slice of event objects and creates a
|
||||
// map of stringified events where each key is composed of the event
|
||||
// type and each of the event's attributes keys in the form of
|
||||
// "{event.Type}.{attribute.Key}" and the value is each attribute's value.
|
||||
func (b *EventBus) validateAndStringifyEvents(events []types.Event, logger log.Logger) map[string][]string {
|
||||
result := make(map[string][]string)
|
||||
for _, event := range events {
|
||||
if len(event.Type) == 0 {
|
||||
logger.Debug("Got an event with an empty type (skipping)", "event", event)
|
||||
continue
|
||||
}
|
||||
result[string(tag.Key)] = string(tag.Value)
|
||||
|
||||
for _, attr := range event.Attributes {
|
||||
if len(attr.Key) == 0 {
|
||||
logger.Debug("Got an event attribute with an empty key(skipping)", "event", event)
|
||||
continue
|
||||
}
|
||||
|
||||
compositeTag := fmt.Sprintf("%s.%s", event.Type, string(attr.Key))
|
||||
result[compositeTag] = append(result[compositeTag], string(attr.Value))
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -88,31 +124,27 @@ func (b *EventBus) PublishEventNewBlock(data EventDataNewBlock) error {
|
||||
// no explicit deadline for publishing events
|
||||
ctx := context.Background()
|
||||
|
||||
resultTags := append(data.ResultBeginBlock.Tags, data.ResultEndBlock.Tags...)
|
||||
tags := b.validateAndStringifyTags(resultTags, b.Logger.With("block", data.Block.StringShort()))
|
||||
resultEvents := append(data.ResultBeginBlock.Events, data.ResultEndBlock.Events...)
|
||||
events := b.validateAndStringifyEvents(resultEvents, b.Logger.With("block", data.Block.StringShort()))
|
||||
|
||||
// add predefined tags
|
||||
logIfTagExists(EventTypeKey, tags, b.Logger)
|
||||
tags[EventTypeKey] = EventNewBlock
|
||||
// add predefined new block event
|
||||
events[EventTypeKey] = append(events[EventTypeKey], EventNewBlock)
|
||||
|
||||
b.pubsub.PublishWithTags(ctx, data, tmpubsub.NewTagMap(tags))
|
||||
return nil
|
||||
return b.pubsub.PublishWithEvents(ctx, data, events)
|
||||
}
|
||||
|
||||
func (b *EventBus) PublishEventNewBlockHeader(data EventDataNewBlockHeader) error {
|
||||
// no explicit deadline for publishing events
|
||||
ctx := context.Background()
|
||||
|
||||
resultTags := append(data.ResultBeginBlock.Tags, data.ResultEndBlock.Tags...)
|
||||
resultTags := append(data.ResultBeginBlock.Events, data.ResultEndBlock.Events...)
|
||||
// TODO: Create StringShort method for Header and use it in logger.
|
||||
tags := b.validateAndStringifyTags(resultTags, b.Logger.With("header", data.Header))
|
||||
events := b.validateAndStringifyEvents(resultTags, b.Logger.With("header", data.Header))
|
||||
|
||||
// add predefined tags
|
||||
logIfTagExists(EventTypeKey, tags, b.Logger)
|
||||
tags[EventTypeKey] = EventNewBlockHeader
|
||||
// add predefined new block header event
|
||||
events[EventTypeKey] = append(events[EventTypeKey], EventNewBlockHeader)
|
||||
|
||||
b.pubsub.PublishWithTags(ctx, data, tmpubsub.NewTagMap(tags))
|
||||
return nil
|
||||
return b.pubsub.PublishWithEvents(ctx, data, events)
|
||||
}
|
||||
|
||||
func (b *EventBus) PublishEventVote(data EventDataVote) error {
|
||||
@@ -130,20 +162,14 @@ func (b *EventBus) PublishEventTx(data EventDataTx) error {
|
||||
// no explicit deadline for publishing events
|
||||
ctx := context.Background()
|
||||
|
||||
tags := b.validateAndStringifyTags(data.Result.Tags, b.Logger.With("tx", data.Tx))
|
||||
events := b.validateAndStringifyEvents(data.Result.Events, b.Logger.With("tx", data.Tx))
|
||||
|
||||
// add predefined tags
|
||||
logIfTagExists(EventTypeKey, tags, b.Logger)
|
||||
tags[EventTypeKey] = EventTx
|
||||
events[EventTypeKey] = append(events[EventTypeKey], EventTx)
|
||||
events[TxHashKey] = append(events[TxHashKey], fmt.Sprintf("%X", data.Tx.Hash()))
|
||||
events[TxHeightKey] = append(events[TxHeightKey], fmt.Sprintf("%d", data.Height))
|
||||
|
||||
logIfTagExists(TxHashKey, tags, b.Logger)
|
||||
tags[TxHashKey] = fmt.Sprintf("%X", data.Tx.Hash())
|
||||
|
||||
logIfTagExists(TxHeightKey, tags, b.Logger)
|
||||
tags[TxHeightKey] = fmt.Sprintf("%d", data.Height)
|
||||
|
||||
b.pubsub.PublishWithTags(ctx, data, tmpubsub.NewTagMap(tags))
|
||||
return nil
|
||||
return b.pubsub.PublishWithEvents(ctx, data, events)
|
||||
}
|
||||
|
||||
func (b *EventBus) PublishEventNewRoundStep(data EventDataRoundState) error {
|
||||
@@ -186,12 +212,6 @@ func (b *EventBus) PublishEventValidatorSetUpdates(data EventDataValidatorSetUpd
|
||||
return b.Publish(EventValidatorSetUpdates, data)
|
||||
}
|
||||
|
||||
func logIfTagExists(tag string, tags map[string]string, logger log.Logger) {
|
||||
if value, ok := tags[tag]; ok {
|
||||
logger.Error("Found predefined tag (value will be overwritten)", "tag", tag, "value", value)
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
type NopEventBus struct{}
|
||||
|
||||
|
||||
+169
-49
@@ -22,25 +22,27 @@ func TestEventBusPublishEventTx(t *testing.T) {
|
||||
defer eventBus.Stop()
|
||||
|
||||
tx := Tx("foo")
|
||||
result := abci.ResponseDeliverTx{Data: []byte("bar"), Tags: []cmn.KVPair{{Key: []byte("baz"), Value: []byte("1")}}}
|
||||
|
||||
txEventsCh := make(chan interface{})
|
||||
result := abci.ResponseDeliverTx{
|
||||
Data: []byte("bar"),
|
||||
Events: []abci.Event{
|
||||
{Type: "testType", Attributes: []cmn.KVPair{{Key: []byte("baz"), Value: []byte("1")}}},
|
||||
},
|
||||
}
|
||||
|
||||
// PublishEventTx adds all these 3 tags, so the query below should work
|
||||
query := fmt.Sprintf("tm.event='Tx' AND tx.height=1 AND tx.hash='%X' AND baz=1", tx.Hash())
|
||||
err = eventBus.Subscribe(context.Background(), "test", tmquery.MustParse(query), txEventsCh)
|
||||
query := fmt.Sprintf("tm.event='Tx' AND tx.height=1 AND tx.hash='%X' AND testType.baz=1", tx.Hash())
|
||||
txsSub, err := eventBus.Subscribe(context.Background(), "test", tmquery.MustParse(query))
|
||||
require.NoError(t, err)
|
||||
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
for e := range txEventsCh {
|
||||
edt := e.(EventDataTx)
|
||||
assert.Equal(t, int64(1), edt.Height)
|
||||
assert.Equal(t, uint32(0), edt.Index)
|
||||
assert.Equal(t, tx, edt.Tx)
|
||||
assert.Equal(t, result, edt.Result)
|
||||
close(done)
|
||||
}
|
||||
msg := <-txsSub.Out()
|
||||
edt := msg.Data().(EventDataTx)
|
||||
assert.Equal(t, int64(1), edt.Height)
|
||||
assert.Equal(t, uint32(0), edt.Index)
|
||||
assert.Equal(t, tx, edt.Tx)
|
||||
assert.Equal(t, result, edt.Result)
|
||||
close(done)
|
||||
}()
|
||||
|
||||
err = eventBus.PublishEventTx(EventDataTx{TxResult{
|
||||
@@ -65,25 +67,30 @@ func TestEventBusPublishEventNewBlock(t *testing.T) {
|
||||
defer eventBus.Stop()
|
||||
|
||||
block := MakeBlock(0, []Tx{}, nil, []Evidence{})
|
||||
resultBeginBlock := abci.ResponseBeginBlock{Tags: []cmn.KVPair{{Key: []byte("baz"), Value: []byte("1")}}}
|
||||
resultEndBlock := abci.ResponseEndBlock{Tags: []cmn.KVPair{{Key: []byte("foz"), Value: []byte("2")}}}
|
||||
|
||||
txEventsCh := make(chan interface{})
|
||||
resultBeginBlock := abci.ResponseBeginBlock{
|
||||
Events: []abci.Event{
|
||||
{Type: "testType", Attributes: []cmn.KVPair{{Key: []byte("baz"), Value: []byte("1")}}},
|
||||
},
|
||||
}
|
||||
resultEndBlock := abci.ResponseEndBlock{
|
||||
Events: []abci.Event{
|
||||
{Type: "testType", Attributes: []cmn.KVPair{{Key: []byte("foz"), Value: []byte("2")}}},
|
||||
},
|
||||
}
|
||||
|
||||
// PublishEventNewBlock adds the tm.event tag, so the query below should work
|
||||
query := "tm.event='NewBlock' AND baz=1 AND foz=2"
|
||||
err = eventBus.Subscribe(context.Background(), "test", tmquery.MustParse(query), txEventsCh)
|
||||
query := "tm.event='NewBlock' AND testType.baz=1 AND testType.foz=2"
|
||||
blocksSub, err := eventBus.Subscribe(context.Background(), "test", tmquery.MustParse(query))
|
||||
require.NoError(t, err)
|
||||
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
for e := range txEventsCh {
|
||||
edt := e.(EventDataNewBlock)
|
||||
assert.Equal(t, block, edt.Block)
|
||||
assert.Equal(t, resultBeginBlock, edt.ResultBeginBlock)
|
||||
assert.Equal(t, resultEndBlock, edt.ResultEndBlock)
|
||||
close(done)
|
||||
}
|
||||
msg := <-blocksSub.Out()
|
||||
edt := msg.Data().(EventDataNewBlock)
|
||||
assert.Equal(t, block, edt.Block)
|
||||
assert.Equal(t, resultBeginBlock, edt.ResultBeginBlock)
|
||||
assert.Equal(t, resultEndBlock, edt.ResultEndBlock)
|
||||
close(done)
|
||||
}()
|
||||
|
||||
err = eventBus.PublishEventNewBlock(EventDataNewBlock{
|
||||
@@ -100,6 +107,106 @@ func TestEventBusPublishEventNewBlock(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestEventBusPublishEventTxDuplicateKeys(t *testing.T) {
|
||||
eventBus := NewEventBus()
|
||||
err := eventBus.Start()
|
||||
require.NoError(t, err)
|
||||
defer eventBus.Stop()
|
||||
|
||||
tx := Tx("foo")
|
||||
result := abci.ResponseDeliverTx{
|
||||
Data: []byte("bar"),
|
||||
Events: []abci.Event{
|
||||
{
|
||||
Type: "transfer",
|
||||
Attributes: []cmn.KVPair{
|
||||
{Key: []byte("sender"), Value: []byte("foo")},
|
||||
{Key: []byte("recipient"), Value: []byte("bar")},
|
||||
{Key: []byte("amount"), Value: []byte("5")},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: "transfer",
|
||||
Attributes: []cmn.KVPair{
|
||||
{Key: []byte("sender"), Value: []byte("baz")},
|
||||
{Key: []byte("recipient"), Value: []byte("cat")},
|
||||
{Key: []byte("amount"), Value: []byte("13")},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: "withdraw.rewards",
|
||||
Attributes: []cmn.KVPair{
|
||||
{Key: []byte("address"), Value: []byte("bar")},
|
||||
{Key: []byte("source"), Value: []byte("iceman")},
|
||||
{Key: []byte("amount"), Value: []byte("33")},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
query string
|
||||
expectResults bool
|
||||
}{
|
||||
{
|
||||
"tm.event='Tx' AND tx.height=1 AND transfer.sender='DoesNotExist'",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"tm.event='Tx' AND tx.height=1 AND transfer.sender='foo'",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"tm.event='Tx' AND tx.height=1 AND transfer.sender='baz'",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"tm.event='Tx' AND tx.height=1 AND transfer.sender='foo' AND transfer.sender='baz'",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"tm.event='Tx' AND tx.height=1 AND transfer.sender='foo' AND transfer.sender='DoesNotExist'",
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range testCases {
|
||||
sub, err := eventBus.Subscribe(context.Background(), fmt.Sprintf("client-%d", i), tmquery.MustParse(tc.query))
|
||||
require.NoError(t, err)
|
||||
|
||||
done := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
msg := <-sub.Out()
|
||||
data := msg.Data().(EventDataTx)
|
||||
assert.Equal(t, int64(1), data.Height)
|
||||
assert.Equal(t, uint32(0), data.Index)
|
||||
assert.Equal(t, tx, data.Tx)
|
||||
assert.Equal(t, result, data.Result)
|
||||
close(done)
|
||||
}()
|
||||
|
||||
err = eventBus.PublishEventTx(EventDataTx{TxResult{
|
||||
Height: 1,
|
||||
Index: 0,
|
||||
Tx: tx,
|
||||
Result: result,
|
||||
}})
|
||||
assert.NoError(t, err)
|
||||
|
||||
select {
|
||||
case <-done:
|
||||
if !tc.expectResults {
|
||||
require.Fail(t, "unexpected transaction result(s) from subscription")
|
||||
}
|
||||
case <-time.After(1 * time.Second):
|
||||
if tc.expectResults {
|
||||
require.Fail(t, "failed to receive a transaction after 1 second")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEventBusPublishEventNewBlockHeader(t *testing.T) {
|
||||
eventBus := NewEventBus()
|
||||
err := eventBus.Start()
|
||||
@@ -107,25 +214,30 @@ func TestEventBusPublishEventNewBlockHeader(t *testing.T) {
|
||||
defer eventBus.Stop()
|
||||
|
||||
block := MakeBlock(0, []Tx{}, nil, []Evidence{})
|
||||
resultBeginBlock := abci.ResponseBeginBlock{Tags: []cmn.KVPair{{Key: []byte("baz"), Value: []byte("1")}}}
|
||||
resultEndBlock := abci.ResponseEndBlock{Tags: []cmn.KVPair{{Key: []byte("foz"), Value: []byte("2")}}}
|
||||
|
||||
txEventsCh := make(chan interface{})
|
||||
resultBeginBlock := abci.ResponseBeginBlock{
|
||||
Events: []abci.Event{
|
||||
{Type: "testType", Attributes: []cmn.KVPair{{Key: []byte("baz"), Value: []byte("1")}}},
|
||||
},
|
||||
}
|
||||
resultEndBlock := abci.ResponseEndBlock{
|
||||
Events: []abci.Event{
|
||||
{Type: "testType", Attributes: []cmn.KVPair{{Key: []byte("foz"), Value: []byte("2")}}},
|
||||
},
|
||||
}
|
||||
|
||||
// PublishEventNewBlockHeader adds the tm.event tag, so the query below should work
|
||||
query := "tm.event='NewBlockHeader' AND baz=1 AND foz=2"
|
||||
err = eventBus.Subscribe(context.Background(), "test", tmquery.MustParse(query), txEventsCh)
|
||||
query := "tm.event='NewBlockHeader' AND testType.baz=1 AND testType.foz=2"
|
||||
headersSub, err := eventBus.Subscribe(context.Background(), "test", tmquery.MustParse(query))
|
||||
require.NoError(t, err)
|
||||
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
for e := range txEventsCh {
|
||||
edt := e.(EventDataNewBlockHeader)
|
||||
assert.Equal(t, block.Header, edt.Header)
|
||||
assert.Equal(t, resultBeginBlock, edt.ResultBeginBlock)
|
||||
assert.Equal(t, resultEndBlock, edt.ResultEndBlock)
|
||||
close(done)
|
||||
}
|
||||
msg := <-headersSub.Out()
|
||||
edt := msg.Data().(EventDataNewBlockHeader)
|
||||
assert.Equal(t, block.Header, edt.Header)
|
||||
assert.Equal(t, resultBeginBlock, edt.ResultBeginBlock)
|
||||
assert.Equal(t, resultEndBlock, edt.ResultEndBlock)
|
||||
close(done)
|
||||
}()
|
||||
|
||||
err = eventBus.PublishEventNewBlockHeader(EventDataNewBlockHeader{
|
||||
@@ -148,18 +260,19 @@ func TestEventBusPublish(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
defer eventBus.Stop()
|
||||
|
||||
eventsCh := make(chan interface{})
|
||||
err = eventBus.Subscribe(context.Background(), "test", tmquery.Empty{}, eventsCh)
|
||||
const numEventsExpected = 14
|
||||
|
||||
sub, err := eventBus.Subscribe(context.Background(), "test", tmquery.Empty{}, numEventsExpected)
|
||||
require.NoError(t, err)
|
||||
|
||||
const numEventsExpected = 14
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
numEvents := 0
|
||||
for range eventsCh {
|
||||
for range sub.Out() {
|
||||
numEvents++
|
||||
if numEvents >= numEventsExpected {
|
||||
close(done)
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
@@ -243,15 +356,22 @@ func benchmarkEventBus(numClients int, randQueries bool, randEvents bool, b *tes
|
||||
q := EventQueryNewBlock
|
||||
|
||||
for i := 0; i < numClients; i++ {
|
||||
ch := make(chan interface{})
|
||||
go func() {
|
||||
for range ch {
|
||||
}
|
||||
}()
|
||||
if randQueries {
|
||||
q = randQuery()
|
||||
}
|
||||
eventBus.Subscribe(ctx, fmt.Sprintf("client-%d", i), q, ch)
|
||||
sub, err := eventBus.Subscribe(ctx, fmt.Sprintf("client-%d", i), q)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-sub.Out():
|
||||
case <-sub.Cancelled():
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
eventType := EventNewBlock
|
||||
|
||||
+20
-14
@@ -11,21 +11,30 @@ import (
|
||||
|
||||
// Reserved event types (alphabetically sorted).
|
||||
const (
|
||||
EventCompleteProposal = "CompleteProposal"
|
||||
EventLock = "Lock"
|
||||
// Block level events for mass consumption by users.
|
||||
// These events are triggered from the state package,
|
||||
// after a block has been committed.
|
||||
// These are also used by the tx indexer for async indexing.
|
||||
// All of this data can be fetched through the rpc.
|
||||
EventNewBlock = "NewBlock"
|
||||
EventNewBlockHeader = "NewBlockHeader"
|
||||
EventNewRound = "NewRound"
|
||||
EventNewRoundStep = "NewRoundStep"
|
||||
EventPolka = "Polka"
|
||||
EventRelock = "Relock"
|
||||
EventTimeoutPropose = "TimeoutPropose"
|
||||
EventTimeoutWait = "TimeoutWait"
|
||||
EventTx = "Tx"
|
||||
EventUnlock = "Unlock"
|
||||
EventValidBlock = "ValidBlock"
|
||||
EventValidatorSetUpdates = "ValidatorSetUpdates"
|
||||
EventVote = "Vote"
|
||||
|
||||
// Internal consensus events.
|
||||
// These are used for testing the consensus state machine.
|
||||
// They can also be used to build real-time consensus visualizers.
|
||||
EventCompleteProposal = "CompleteProposal"
|
||||
EventLock = "Lock"
|
||||
EventNewRound = "NewRound"
|
||||
EventNewRoundStep = "NewRoundStep"
|
||||
EventPolka = "Polka"
|
||||
EventRelock = "Relock"
|
||||
EventTimeoutPropose = "TimeoutPropose"
|
||||
EventTimeoutWait = "TimeoutWait"
|
||||
EventUnlock = "Unlock"
|
||||
EventValidBlock = "ValidBlock"
|
||||
EventVote = "Vote"
|
||||
)
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
@@ -78,9 +87,6 @@ type EventDataRoundState struct {
|
||||
Height int64 `json:"height"`
|
||||
Round int `json:"round"`
|
||||
Step string `json:"step"`
|
||||
|
||||
// private, not exposed to websockets
|
||||
RoundState interface{} `json:"-"`
|
||||
}
|
||||
|
||||
type ValidatorInfo struct {
|
||||
|
||||
@@ -15,9 +15,9 @@ import (
|
||||
func TestGenesisBad(t *testing.T) {
|
||||
// test some bad ones from raw json
|
||||
testCases := [][]byte{
|
||||
[]byte{}, // empty
|
||||
[]byte{1, 1, 1, 1, 1}, // junk
|
||||
[]byte(`{}`), // empty
|
||||
{}, // empty
|
||||
{1, 1, 1, 1, 1}, // junk
|
||||
[]byte(`{}`), // empty
|
||||
[]byte(`{"chain_id":"mychain","validators":[{}]}`), // invalid validator
|
||||
// missing pub_key type
|
||||
[]byte(`{"validators":[{"pub_key":{"value":"AT/+aaL1eB0477Mud9JMm8Sh8BIvOYlPGC9KkIUmFaE="},"power":"10","name":""}]}`),
|
||||
@@ -65,7 +65,7 @@ func TestGenesisGood(t *testing.T) {
|
||||
assert.NoError(t, err, "expected no error for valid genDoc json")
|
||||
|
||||
// test with invalid consensus params
|
||||
genDoc.ConsensusParams.BlockSize.MaxBytes = 0
|
||||
genDoc.ConsensusParams.Block.MaxBytes = 0
|
||||
genDocBytes, err = cdc.MarshalJSON(genDoc)
|
||||
assert.NoError(t, err, "error marshalling genDoc")
|
||||
genDoc, err = GenesisDocFromJSON(genDocBytes)
|
||||
|
||||
+35
-25
@@ -17,7 +17,7 @@ const (
|
||||
// ConsensusParams contains consensus critical parameters that determine the
|
||||
// validity of blocks.
|
||||
type ConsensusParams struct {
|
||||
BlockSize BlockSizeParams `json:"block_size"`
|
||||
Block BlockParams `json:"block"`
|
||||
Evidence EvidenceParams `json:"evidence"`
|
||||
Validator ValidatorParams `json:"validator"`
|
||||
}
|
||||
@@ -30,13 +30,17 @@ type HashedParams struct {
|
||||
BlockMaxGas int64
|
||||
}
|
||||
|
||||
// BlockSizeParams define limits on the block size.
|
||||
type BlockSizeParams struct {
|
||||
// BlockParams define limits on the block size and gas plus minimum time
|
||||
// between blocks.
|
||||
type BlockParams struct {
|
||||
MaxBytes int64 `json:"max_bytes"`
|
||||
MaxGas int64 `json:"max_gas"`
|
||||
// Minimum time increment between consecutive blocks (in milliseconds)
|
||||
// Not exposed to the application.
|
||||
TimeIotaMs int64 `json:"time_iota_ms"`
|
||||
}
|
||||
|
||||
// EvidenceParams determine how we handle evidence of malfeasance
|
||||
// EvidenceParams determine how we handle evidence of malfeasance.
|
||||
type EvidenceParams struct {
|
||||
MaxAge int64 `json:"max_age"` // only accept new evidence more recent than this
|
||||
}
|
||||
@@ -50,17 +54,18 @@ type ValidatorParams struct {
|
||||
// DefaultConsensusParams returns a default ConsensusParams.
|
||||
func DefaultConsensusParams() *ConsensusParams {
|
||||
return &ConsensusParams{
|
||||
DefaultBlockSizeParams(),
|
||||
DefaultBlockParams(),
|
||||
DefaultEvidenceParams(),
|
||||
DefaultValidatorParams(),
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultBlockSizeParams returns a default BlockSizeParams.
|
||||
func DefaultBlockSizeParams() BlockSizeParams {
|
||||
return BlockSizeParams{
|
||||
MaxBytes: 22020096, // 21MB
|
||||
MaxGas: -1,
|
||||
// DefaultBlockParams returns a default BlockParams.
|
||||
func DefaultBlockParams() BlockParams {
|
||||
return BlockParams{
|
||||
MaxBytes: 22020096, // 21MB
|
||||
MaxGas: -1,
|
||||
TimeIotaMs: 1000, // 1s
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,18 +94,23 @@ func (params *ValidatorParams) IsValidPubkeyType(pubkeyType string) bool {
|
||||
// Validate validates the ConsensusParams to ensure all values are within their
|
||||
// allowed limits, and returns an error if they are not.
|
||||
func (params *ConsensusParams) Validate() error {
|
||||
if params.BlockSize.MaxBytes <= 0 {
|
||||
return cmn.NewError("BlockSize.MaxBytes must be greater than 0. Got %d",
|
||||
params.BlockSize.MaxBytes)
|
||||
if params.Block.MaxBytes <= 0 {
|
||||
return cmn.NewError("Block.MaxBytes must be greater than 0. Got %d",
|
||||
params.Block.MaxBytes)
|
||||
}
|
||||
if params.BlockSize.MaxBytes > MaxBlockSizeBytes {
|
||||
return cmn.NewError("BlockSize.MaxBytes is too big. %d > %d",
|
||||
params.BlockSize.MaxBytes, MaxBlockSizeBytes)
|
||||
if params.Block.MaxBytes > MaxBlockSizeBytes {
|
||||
return cmn.NewError("Block.MaxBytes is too big. %d > %d",
|
||||
params.Block.MaxBytes, MaxBlockSizeBytes)
|
||||
}
|
||||
|
||||
if params.BlockSize.MaxGas < -1 {
|
||||
return cmn.NewError("BlockSize.MaxGas must be greater or equal to -1. Got %d",
|
||||
params.BlockSize.MaxGas)
|
||||
if params.Block.MaxGas < -1 {
|
||||
return cmn.NewError("Block.MaxGas must be greater or equal to -1. Got %d",
|
||||
params.Block.MaxGas)
|
||||
}
|
||||
|
||||
if params.Block.TimeIotaMs <= 0 {
|
||||
return cmn.NewError("Block.TimeIotaMs must be greater than 0. Got %v",
|
||||
params.Block.TimeIotaMs)
|
||||
}
|
||||
|
||||
if params.Evidence.MaxAge <= 0 {
|
||||
@@ -131,8 +141,8 @@ func (params *ConsensusParams) Validate() error {
|
||||
func (params *ConsensusParams) Hash() []byte {
|
||||
hasher := tmhash.New()
|
||||
bz := cdcEncode(HashedParams{
|
||||
params.BlockSize.MaxBytes,
|
||||
params.BlockSize.MaxGas,
|
||||
params.Block.MaxBytes,
|
||||
params.Block.MaxGas,
|
||||
})
|
||||
if bz == nil {
|
||||
panic("cannot fail to encode ConsensusParams")
|
||||
@@ -142,7 +152,7 @@ func (params *ConsensusParams) Hash() []byte {
|
||||
}
|
||||
|
||||
func (params *ConsensusParams) Equals(params2 *ConsensusParams) bool {
|
||||
return params.BlockSize == params2.BlockSize &&
|
||||
return params.Block == params2.Block &&
|
||||
params.Evidence == params2.Evidence &&
|
||||
cmn.StringSliceEqual(params.Validator.PubKeyTypes, params2.Validator.PubKeyTypes)
|
||||
}
|
||||
@@ -157,9 +167,9 @@ func (params ConsensusParams) Update(params2 *abci.ConsensusParams) ConsensusPar
|
||||
}
|
||||
|
||||
// we must defensively consider any structs may be nil
|
||||
if params2.BlockSize != nil {
|
||||
res.BlockSize.MaxBytes = params2.BlockSize.MaxBytes
|
||||
res.BlockSize.MaxGas = params2.BlockSize.MaxGas
|
||||
if params2.Block != nil {
|
||||
res.Block.MaxBytes = params2.Block.MaxBytes
|
||||
res.Block.MaxGas = params2.Block.MaxGas
|
||||
}
|
||||
if params2.Evidence != nil {
|
||||
res.Evidence.MaxAge = params2.Evidence.MaxAge
|
||||
|
||||
+38
-31
@@ -19,22 +19,23 @@ func TestConsensusParamsValidation(t *testing.T) {
|
||||
params ConsensusParams
|
||||
valid bool
|
||||
}{
|
||||
// test block size
|
||||
0: {makeParams(1, 0, 1, valEd25519), true},
|
||||
1: {makeParams(0, 0, 1, valEd25519), false},
|
||||
2: {makeParams(47*1024*1024, 0, 1, valEd25519), true},
|
||||
3: {makeParams(10, 0, 1, valEd25519), true},
|
||||
4: {makeParams(100*1024*1024, 0, 1, valEd25519), true},
|
||||
5: {makeParams(101*1024*1024, 0, 1, valEd25519), false},
|
||||
6: {makeParams(1024*1024*1024, 0, 1, valEd25519), false},
|
||||
7: {makeParams(1024*1024*1024, 0, -1, valEd25519), false},
|
||||
// test evidence age
|
||||
8: {makeParams(1, 0, 0, valEd25519), false},
|
||||
9: {makeParams(1, 0, -1, valEd25519), false},
|
||||
// test block params
|
||||
0: {makeParams(1, 0, 10, 1, valEd25519), true},
|
||||
1: {makeParams(0, 0, 10, 1, valEd25519), false},
|
||||
2: {makeParams(47*1024*1024, 0, 10, 1, valEd25519), true},
|
||||
3: {makeParams(10, 0, 10, 1, valEd25519), true},
|
||||
4: {makeParams(100*1024*1024, 0, 10, 1, valEd25519), true},
|
||||
5: {makeParams(101*1024*1024, 0, 10, 1, valEd25519), false},
|
||||
6: {makeParams(1024*1024*1024, 0, 10, 1, valEd25519), false},
|
||||
7: {makeParams(1024*1024*1024, 0, 10, -1, valEd25519), false},
|
||||
8: {makeParams(1, 0, -10, 1, valEd25519), false},
|
||||
// test evidence params
|
||||
9: {makeParams(1, 0, 10, 0, valEd25519), false},
|
||||
10: {makeParams(1, 0, 10, -1, valEd25519), false},
|
||||
// test no pubkey type provided
|
||||
10: {makeParams(1, 0, 1, []string{}), false},
|
||||
11: {makeParams(1, 0, 10, 1, []string{}), false},
|
||||
// test invalid pubkey type provided
|
||||
11: {makeParams(1, 0, 1, []string{"potatoes make good pubkeys"}), false},
|
||||
12: {makeParams(1, 0, 10, 1, []string{"potatoes make good pubkeys"}), false},
|
||||
}
|
||||
for i, tc := range testCases {
|
||||
if tc.valid {
|
||||
@@ -45,11 +46,17 @@ func TestConsensusParamsValidation(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func makeParams(blockBytes, blockGas, evidenceAge int64, pubkeyTypes []string) ConsensusParams {
|
||||
func makeParams(
|
||||
blockBytes, blockGas int64,
|
||||
blockTimeIotaMs int64,
|
||||
evidenceAge int64,
|
||||
pubkeyTypes []string,
|
||||
) ConsensusParams {
|
||||
return ConsensusParams{
|
||||
BlockSize: BlockSizeParams{
|
||||
MaxBytes: blockBytes,
|
||||
MaxGas: blockGas,
|
||||
Block: BlockParams{
|
||||
MaxBytes: blockBytes,
|
||||
MaxGas: blockGas,
|
||||
TimeIotaMs: blockTimeIotaMs,
|
||||
},
|
||||
Evidence: EvidenceParams{
|
||||
MaxAge: evidenceAge,
|
||||
@@ -62,14 +69,14 @@ func makeParams(blockBytes, blockGas, evidenceAge int64, pubkeyTypes []string) C
|
||||
|
||||
func TestConsensusParamsHash(t *testing.T) {
|
||||
params := []ConsensusParams{
|
||||
makeParams(4, 2, 3, valEd25519),
|
||||
makeParams(1, 4, 3, valEd25519),
|
||||
makeParams(1, 2, 4, valEd25519),
|
||||
makeParams(2, 5, 7, valEd25519),
|
||||
makeParams(1, 7, 6, valEd25519),
|
||||
makeParams(9, 5, 4, valEd25519),
|
||||
makeParams(7, 8, 9, valEd25519),
|
||||
makeParams(4, 6, 5, valEd25519),
|
||||
makeParams(4, 2, 10, 3, valEd25519),
|
||||
makeParams(1, 4, 10, 3, valEd25519),
|
||||
makeParams(1, 2, 10, 4, valEd25519),
|
||||
makeParams(2, 5, 10, 7, valEd25519),
|
||||
makeParams(1, 7, 10, 6, valEd25519),
|
||||
makeParams(9, 5, 10, 4, valEd25519),
|
||||
makeParams(7, 8, 10, 9, valEd25519),
|
||||
makeParams(4, 6, 10, 5, valEd25519),
|
||||
}
|
||||
|
||||
hashes := make([][]byte, len(params))
|
||||
@@ -95,15 +102,15 @@ func TestConsensusParamsUpdate(t *testing.T) {
|
||||
}{
|
||||
// empty updates
|
||||
{
|
||||
makeParams(1, 2, 3, valEd25519),
|
||||
makeParams(1, 2, 10, 3, valEd25519),
|
||||
&abci.ConsensusParams{},
|
||||
makeParams(1, 2, 3, valEd25519),
|
||||
makeParams(1, 2, 10, 3, valEd25519),
|
||||
},
|
||||
// fine updates
|
||||
{
|
||||
makeParams(1, 2, 3, valEd25519),
|
||||
makeParams(1, 2, 10, 3, valEd25519),
|
||||
&abci.ConsensusParams{
|
||||
BlockSize: &abci.BlockSizeParams{
|
||||
Block: &abci.BlockParams{
|
||||
MaxBytes: 100,
|
||||
MaxGas: 200,
|
||||
},
|
||||
@@ -114,7 +121,7 @@ func TestConsensusParamsUpdate(t *testing.T) {
|
||||
PubKeyTypes: valSecp256k1,
|
||||
},
|
||||
},
|
||||
makeParams(100, 200, 300, valSecp256k1),
|
||||
makeParams(100, 200, 10, 300, valSecp256k1),
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
|
||||
+1
-4
@@ -21,9 +21,6 @@ type Part struct {
|
||||
Index int `json:"index"`
|
||||
Bytes cmn.HexBytes `json:"bytes"`
|
||||
Proof merkle.SimpleProof `json:"proof"`
|
||||
|
||||
// Cache
|
||||
hash []byte
|
||||
}
|
||||
|
||||
// ValidateBasic performs basic validation.
|
||||
@@ -229,7 +226,7 @@ func (ps *PartSet) IsComplete() bool {
|
||||
|
||||
func (ps *PartSet) GetReader() io.Reader {
|
||||
if !ps.IsComplete() {
|
||||
cmn.PanicSanity("Cannot GetReader() on incomplete PartSet")
|
||||
panic("Cannot GetReader() on incomplete PartSet")
|
||||
}
|
||||
return NewPartSetReader(ps.parts)
|
||||
}
|
||||
|
||||
+22
-5
@@ -43,11 +43,20 @@ func (pvs PrivValidatorsByAddress) Swap(i, j int) {
|
||||
// MockPV implements PrivValidator without any safety or persistence.
|
||||
// Only use it for testing.
|
||||
type MockPV struct {
|
||||
privKey crypto.PrivKey
|
||||
privKey crypto.PrivKey
|
||||
breakProposalSigning bool
|
||||
breakVoteSigning bool
|
||||
}
|
||||
|
||||
func NewMockPV() *MockPV {
|
||||
return &MockPV{ed25519.GenPrivKey()}
|
||||
return &MockPV{ed25519.GenPrivKey(), false, false}
|
||||
}
|
||||
|
||||
// NewMockPVWithParams allows one to create a MockPV instance, but with finer
|
||||
// grained control over the operation of the mock validator. This is useful for
|
||||
// mocking test failures.
|
||||
func NewMockPVWithParams(privKey crypto.PrivKey, breakProposalSigning, breakVoteSigning bool) *MockPV {
|
||||
return &MockPV{privKey, breakProposalSigning, breakVoteSigning}
|
||||
}
|
||||
|
||||
// Implements PrivValidator.
|
||||
@@ -57,7 +66,11 @@ func (pv *MockPV) GetPubKey() crypto.PubKey {
|
||||
|
||||
// Implements PrivValidator.
|
||||
func (pv *MockPV) SignVote(chainID string, vote *Vote) error {
|
||||
signBytes := vote.SignBytes(chainID)
|
||||
useChainID := chainID
|
||||
if pv.breakVoteSigning {
|
||||
useChainID = "incorrect-chain-id"
|
||||
}
|
||||
signBytes := vote.SignBytes(useChainID)
|
||||
sig, err := pv.privKey.Sign(signBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -68,7 +81,11 @@ func (pv *MockPV) SignVote(chainID string, vote *Vote) error {
|
||||
|
||||
// Implements PrivValidator.
|
||||
func (pv *MockPV) SignProposal(chainID string, proposal *Proposal) error {
|
||||
signBytes := proposal.SignBytes(chainID)
|
||||
useChainID := chainID
|
||||
if pv.breakProposalSigning {
|
||||
useChainID = "incorrect-chain-id"
|
||||
}
|
||||
signBytes := proposal.SignBytes(useChainID)
|
||||
sig, err := pv.privKey.Sign(signBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -107,5 +124,5 @@ func (pv *erroringMockPV) SignProposal(chainID string, proposal *Proposal) error
|
||||
|
||||
// NewErroringMockPV returns a MockPV that fails on each signing request. Again, for testing only.
|
||||
func NewErroringMockPV() *erroringMockPV {
|
||||
return &erroringMockPV{&MockPV{ed25519.GenPrivKey()}}
|
||||
return &erroringMockPV{&MockPV{ed25519.GenPrivKey(), false, false}}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ func (m *PartSetHeader) Reset() { *m = PartSetHeader{} }
|
||||
func (m *PartSetHeader) String() string { return proto.CompactTextString(m) }
|
||||
func (*PartSetHeader) ProtoMessage() {}
|
||||
func (*PartSetHeader) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_block_57c41dfc0fc285b3, []int{0}
|
||||
return fileDescriptor_block_1ca6cebf74619a45, []int{0}
|
||||
}
|
||||
func (m *PartSetHeader) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_PartSetHeader.Unmarshal(m, b)
|
||||
@@ -76,7 +76,7 @@ func (m *BlockID) Reset() { *m = BlockID{} }
|
||||
func (m *BlockID) String() string { return proto.CompactTextString(m) }
|
||||
func (*BlockID) ProtoMessage() {}
|
||||
func (*BlockID) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_block_57c41dfc0fc285b3, []int{1}
|
||||
return fileDescriptor_block_1ca6cebf74619a45, []int{1}
|
||||
}
|
||||
func (m *BlockID) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_BlockID.Unmarshal(m, b)
|
||||
@@ -141,7 +141,7 @@ func (m *Header) Reset() { *m = Header{} }
|
||||
func (m *Header) String() string { return proto.CompactTextString(m) }
|
||||
func (*Header) ProtoMessage() {}
|
||||
func (*Header) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_block_57c41dfc0fc285b3, []int{2}
|
||||
return fileDescriptor_block_1ca6cebf74619a45, []int{2}
|
||||
}
|
||||
func (m *Header) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_Header.Unmarshal(m, b)
|
||||
@@ -285,7 +285,7 @@ func (m *Version) Reset() { *m = Version{} }
|
||||
func (m *Version) String() string { return proto.CompactTextString(m) }
|
||||
func (*Version) ProtoMessage() {}
|
||||
func (*Version) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_block_57c41dfc0fc285b3, []int{3}
|
||||
return fileDescriptor_block_1ca6cebf74619a45, []int{3}
|
||||
}
|
||||
func (m *Version) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_Version.Unmarshal(m, b)
|
||||
@@ -336,7 +336,7 @@ func (m *Timestamp) Reset() { *m = Timestamp{} }
|
||||
func (m *Timestamp) String() string { return proto.CompactTextString(m) }
|
||||
func (*Timestamp) ProtoMessage() {}
|
||||
func (*Timestamp) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_block_57c41dfc0fc285b3, []int{4}
|
||||
return fileDescriptor_block_1ca6cebf74619a45, []int{4}
|
||||
}
|
||||
func (m *Timestamp) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_Timestamp.Unmarshal(m, b)
|
||||
@@ -378,9 +378,9 @@ func init() {
|
||||
proto.RegisterType((*Timestamp)(nil), "proto3.Timestamp")
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("types/proto3/block.proto", fileDescriptor_block_57c41dfc0fc285b3) }
|
||||
func init() { proto.RegisterFile("types/proto3/block.proto", fileDescriptor_block_1ca6cebf74619a45) }
|
||||
|
||||
var fileDescriptor_block_57c41dfc0fc285b3 = []byte{
|
||||
var fileDescriptor_block_1ca6cebf74619a45 = []byte{
|
||||
// 451 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x53, 0x5f, 0x6f, 0xd3, 0x30,
|
||||
0x10, 0x57, 0x68, 0xda, 0xae, 0x97, 0x76, 0x1d, 0x27, 0x40, 0x16, 0x4f, 0x55, 0x04, 0xa8, 0xbc,
|
||||
|
||||
+3
-18
@@ -125,9 +125,9 @@ func (tm2pb) ValidatorUpdates(vals *ValidatorSet) []abci.ValidatorUpdate {
|
||||
|
||||
func (tm2pb) ConsensusParams(params *ConsensusParams) *abci.ConsensusParams {
|
||||
return &abci.ConsensusParams{
|
||||
BlockSize: &abci.BlockSizeParams{
|
||||
MaxBytes: params.BlockSize.MaxBytes,
|
||||
MaxGas: params.BlockSize.MaxGas,
|
||||
Block: &abci.BlockParams{
|
||||
MaxBytes: params.Block.MaxBytes,
|
||||
MaxGas: params.Block.MaxGas,
|
||||
},
|
||||
Evidence: &abci.EvidenceParams{
|
||||
MaxAge: params.Evidence.MaxAge,
|
||||
@@ -220,18 +220,3 @@ func (pb2tm) ValidatorUpdates(vals []abci.ValidatorUpdate) ([]*Validator, error)
|
||||
}
|
||||
return tmVals, nil
|
||||
}
|
||||
|
||||
func (pb2tm) ConsensusParams(csp *abci.ConsensusParams) ConsensusParams {
|
||||
return ConsensusParams{
|
||||
BlockSize: BlockSizeParams{
|
||||
MaxBytes: csp.BlockSize.MaxBytes,
|
||||
MaxGas: csp.BlockSize.MaxGas,
|
||||
},
|
||||
Evidence: EvidenceParams{
|
||||
MaxAge: csp.Evidence.MaxAge,
|
||||
},
|
||||
Validator: ValidatorParams{
|
||||
PubKeyTypes: csp.Validator.PubKeyTypes,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,8 +7,7 @@ import (
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/tendermint/go-amino"
|
||||
|
||||
amino "github.com/tendermint/go-amino"
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
"github.com/tendermint/tendermint/crypto/ed25519"
|
||||
@@ -65,7 +64,7 @@ func TestABCIValidators(t *testing.T) {
|
||||
func TestABCIConsensusParams(t *testing.T) {
|
||||
cp := DefaultConsensusParams()
|
||||
abciCP := TM2PB.ConsensusParams(cp)
|
||||
cp2 := PB2TM.ConsensusParams(abciCP)
|
||||
cp2 := cp.Update(abciCP)
|
||||
|
||||
assert.Equal(t, *cp, cp2)
|
||||
}
|
||||
@@ -91,7 +90,7 @@ func TestABCIHeader(t *testing.T) {
|
||||
height, numTxs,
|
||||
[]byte("lastCommitHash"), []byte("dataHash"), []byte("evidenceHash"),
|
||||
)
|
||||
protocolVersion := version.Consensus{7, 8}
|
||||
protocolVersion := version.Consensus{Block: 7, App: 8}
|
||||
timestamp := time.Now()
|
||||
lastBlockID := BlockID{
|
||||
Hash: []byte("hash"),
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/tendermint/go-amino"
|
||||
amino "github.com/tendermint/go-amino"
|
||||
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
"github.com/tendermint/tendermint/crypto/merkle"
|
||||
|
||||
+12
-2
@@ -3,6 +3,7 @@ package types
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
@@ -51,8 +52,7 @@ func (v *Validator) CompareProposerPriority(other *Validator) *Validator {
|
||||
} else if result > 0 {
|
||||
return other
|
||||
} else {
|
||||
cmn.PanicSanity("Cannot compare identical validators")
|
||||
return nil
|
||||
panic("Cannot compare identical validators")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -68,6 +68,16 @@ func (v *Validator) String() string {
|
||||
v.ProposerPriority)
|
||||
}
|
||||
|
||||
// ValidatorListString returns a prettified validator list for logging purposes.
|
||||
func ValidatorListString(vals []*Validator) string {
|
||||
chunks := make([]string, len(vals))
|
||||
for i, val := range vals {
|
||||
chunks[i] = fmt.Sprintf("%s:%d", val.Address, val.VotingPower)
|
||||
}
|
||||
|
||||
return strings.Join(chunks, ",")
|
||||
}
|
||||
|
||||
// Bytes computes the unique encoding of a validator with a given voting power.
|
||||
// These are the bytes that gets hashed in consensus. It excludes address
|
||||
// as its redundant with the pubkey. This also excludes ProposerPriority
|
||||
|
||||
+349
-147
@@ -2,6 +2,7 @@ package types
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/big"
|
||||
@@ -12,19 +13,26 @@ import (
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
)
|
||||
|
||||
// The maximum allowed total voting power.
|
||||
// It needs to be sufficiently small to, in all cases::
|
||||
// MaxTotalVotingPower - the maximum allowed total voting power.
|
||||
// It needs to be sufficiently small to, in all cases:
|
||||
// 1. prevent clipping in incrementProposerPriority()
|
||||
// 2. let (diff+diffMax-1) not overflow in IncrementPropposerPriotity()
|
||||
// 2. let (diff+diffMax-1) not overflow in IncrementProposerPriority()
|
||||
// (Proof of 1 is tricky, left to the reader).
|
||||
// It could be higher, but this is sufficiently large for our purposes,
|
||||
// and leaves room for defensive purposes.
|
||||
const MaxTotalVotingPower = int64(math.MaxInt64) / 8
|
||||
// PriorityWindowSizeFactor - is a constant that when multiplied with the total voting power gives
|
||||
// the maximum allowed distance between validator priorities.
|
||||
|
||||
const (
|
||||
MaxTotalVotingPower = int64(math.MaxInt64) / 8
|
||||
PriorityWindowSizeFactor = 2
|
||||
)
|
||||
|
||||
// ValidatorSet represent a set of *Validator at a given height.
|
||||
// The validators can be fetched by address or index.
|
||||
// The index is in order of .Address, so the indices are fixed
|
||||
// for all rounds of a given blockchain height.
|
||||
// for all rounds of a given blockchain height - ie. the validators
|
||||
// are sorted by their address.
|
||||
// On the other hand, the .ProposerPriority of each validator and
|
||||
// the designated .GetProposer() of a set changes every round,
|
||||
// upon calling .IncrementProposerPriority().
|
||||
@@ -42,19 +50,17 @@ type ValidatorSet struct {
|
||||
// NewValidatorSet initializes a ValidatorSet by copying over the
|
||||
// values from `valz`, a list of Validators. If valz is nil or empty,
|
||||
// the new ValidatorSet will have an empty list of Validators.
|
||||
// The addresses of validators in `valz` must be unique otherwise the
|
||||
// function panics.
|
||||
func NewValidatorSet(valz []*Validator) *ValidatorSet {
|
||||
validators := make([]*Validator, len(valz))
|
||||
for i, val := range valz {
|
||||
validators[i] = val.Copy()
|
||||
}
|
||||
sort.Sort(ValidatorsByAddress(validators))
|
||||
vals := &ValidatorSet{
|
||||
Validators: validators,
|
||||
vals := &ValidatorSet{}
|
||||
err := vals.updateWithChangeSet(valz, false)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("cannot create validator set: %s", err))
|
||||
}
|
||||
if len(valz) > 0 {
|
||||
vals.IncrementProposerPriority(1)
|
||||
}
|
||||
|
||||
return vals
|
||||
}
|
||||
|
||||
@@ -74,6 +80,9 @@ func (vals *ValidatorSet) CopyIncrementProposerPriority(times int) *ValidatorSet
|
||||
// proposer. Panics if validator set is empty.
|
||||
// `times` must be positive.
|
||||
func (vals *ValidatorSet) IncrementProposerPriority(times int) {
|
||||
if vals.IsNilOrEmpty() {
|
||||
panic("empty validator set")
|
||||
}
|
||||
if times <= 0 {
|
||||
panic("Cannot call IncrementProposerPriority with non-positive times")
|
||||
}
|
||||
@@ -81,20 +90,23 @@ func (vals *ValidatorSet) IncrementProposerPriority(times int) {
|
||||
// Cap the difference between priorities to be proportional to 2*totalPower by
|
||||
// re-normalizing priorities, i.e., rescale all priorities by multiplying with:
|
||||
// 2*totalVotingPower/(maxPriority - minPriority)
|
||||
diffMax := 2 * vals.TotalVotingPower()
|
||||
diffMax := PriorityWindowSizeFactor * vals.TotalVotingPower()
|
||||
vals.RescalePriorities(diffMax)
|
||||
vals.shiftByAvgProposerPriority()
|
||||
|
||||
var proposer *Validator
|
||||
// call IncrementProposerPriority(1) times times:
|
||||
// Call IncrementProposerPriority(1) times times.
|
||||
for i := 0; i < times; i++ {
|
||||
proposer = vals.incrementProposerPriority()
|
||||
}
|
||||
vals.shiftByAvgProposerPriority()
|
||||
|
||||
vals.Proposer = proposer
|
||||
}
|
||||
|
||||
func (vals *ValidatorSet) RescalePriorities(diffMax int64) {
|
||||
if vals.IsNilOrEmpty() {
|
||||
panic("empty validator set")
|
||||
}
|
||||
// NOTE: This check is merely a sanity check which could be
|
||||
// removed if all tests would init. voting power appropriately;
|
||||
// i.e. diffMax should always be > 0
|
||||
@@ -102,14 +114,14 @@ func (vals *ValidatorSet) RescalePriorities(diffMax int64) {
|
||||
return
|
||||
}
|
||||
|
||||
// Caculating ceil(diff/diffMax):
|
||||
// Calculating ceil(diff/diffMax):
|
||||
// Re-normalization is performed by dividing by an integer for simplicity.
|
||||
// NOTE: This may make debugging priority issues easier as well.
|
||||
diff := computeMaxMinPriorityDiff(vals)
|
||||
ratio := (diff + diffMax - 1) / diffMax
|
||||
if ratio > 1 {
|
||||
if diff > diffMax {
|
||||
for _, val := range vals.Validators {
|
||||
val.ProposerPriority /= ratio
|
||||
val.ProposerPriority = val.ProposerPriority / ratio
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -120,15 +132,15 @@ func (vals *ValidatorSet) incrementProposerPriority() *Validator {
|
||||
newPrio := safeAddClip(val.ProposerPriority, val.VotingPower)
|
||||
val.ProposerPriority = newPrio
|
||||
}
|
||||
// Decrement the validator with most ProposerPriority:
|
||||
// Decrement the validator with most ProposerPriority.
|
||||
mostest := vals.getValWithMostPriority()
|
||||
// mind underflow
|
||||
// Mind the underflow.
|
||||
mostest.ProposerPriority = safeSubClip(mostest.ProposerPriority, vals.TotalVotingPower())
|
||||
|
||||
return mostest
|
||||
}
|
||||
|
||||
// should not be called on an empty validator set
|
||||
// Should not be called on an empty validator set.
|
||||
func (vals *ValidatorSet) computeAvgProposerPriority() int64 {
|
||||
n := int64(len(vals.Validators))
|
||||
sum := big.NewInt(0)
|
||||
@@ -140,12 +152,15 @@ func (vals *ValidatorSet) computeAvgProposerPriority() int64 {
|
||||
return avg.Int64()
|
||||
}
|
||||
|
||||
// this should never happen: each val.ProposerPriority is in bounds of int64
|
||||
// This should never happen: each val.ProposerPriority is in bounds of int64.
|
||||
panic(fmt.Sprintf("Cannot represent avg ProposerPriority as an int64 %v", avg))
|
||||
}
|
||||
|
||||
// compute the difference between the max and min ProposerPriority of that set
|
||||
// Compute the difference between the max and min ProposerPriority of that set.
|
||||
func computeMaxMinPriorityDiff(vals *ValidatorSet) int64 {
|
||||
if vals.IsNilOrEmpty() {
|
||||
panic("empty validator set")
|
||||
}
|
||||
max := int64(math.MinInt64)
|
||||
min := int64(math.MaxInt64)
|
||||
for _, v := range vals.Validators {
|
||||
@@ -173,21 +188,31 @@ func (vals *ValidatorSet) getValWithMostPriority() *Validator {
|
||||
}
|
||||
|
||||
func (vals *ValidatorSet) shiftByAvgProposerPriority() {
|
||||
if vals.IsNilOrEmpty() {
|
||||
panic("empty validator set")
|
||||
}
|
||||
avgProposerPriority := vals.computeAvgProposerPriority()
|
||||
for _, val := range vals.Validators {
|
||||
val.ProposerPriority = safeSubClip(val.ProposerPriority, avgProposerPriority)
|
||||
}
|
||||
}
|
||||
|
||||
// Copy each validator into a new ValidatorSet
|
||||
func (vals *ValidatorSet) Copy() *ValidatorSet {
|
||||
validators := make([]*Validator, len(vals.Validators))
|
||||
for i, val := range vals.Validators {
|
||||
// NOTE: must copy, since IncrementProposerPriority updates in place.
|
||||
validators[i] = val.Copy()
|
||||
// Makes a copy of the validator list.
|
||||
func validatorListCopy(valsList []*Validator) []*Validator {
|
||||
if valsList == nil {
|
||||
return nil
|
||||
}
|
||||
valsCopy := make([]*Validator, len(valsList))
|
||||
for i, val := range valsList {
|
||||
valsCopy[i] = val.Copy()
|
||||
}
|
||||
return valsCopy
|
||||
}
|
||||
|
||||
// Copy each validator into a new ValidatorSet.
|
||||
func (vals *ValidatorSet) Copy() *ValidatorSet {
|
||||
return &ValidatorSet{
|
||||
Validators: validators,
|
||||
Validators: validatorListCopy(vals.Validators),
|
||||
Proposer: vals.Proposer,
|
||||
totalVotingPower: vals.totalVotingPower,
|
||||
}
|
||||
@@ -230,21 +255,29 @@ func (vals *ValidatorSet) Size() int {
|
||||
return len(vals.Validators)
|
||||
}
|
||||
|
||||
// TotalVotingPower returns the sum of the voting powers of all validators.
|
||||
func (vals *ValidatorSet) TotalVotingPower() int64 {
|
||||
if vals.totalVotingPower == 0 {
|
||||
sum := int64(0)
|
||||
for _, val := range vals.Validators {
|
||||
// mind overflow
|
||||
sum = safeAddClip(sum, val.VotingPower)
|
||||
}
|
||||
// Force recalculation of the set's total voting power.
|
||||
func (vals *ValidatorSet) updateTotalVotingPower() {
|
||||
|
||||
sum := int64(0)
|
||||
for _, val := range vals.Validators {
|
||||
// mind overflow
|
||||
sum = safeAddClip(sum, val.VotingPower)
|
||||
if sum > MaxTotalVotingPower {
|
||||
panic(fmt.Sprintf(
|
||||
"Total voting power should be guarded to not exceed %v; got: %v",
|
||||
MaxTotalVotingPower,
|
||||
sum))
|
||||
}
|
||||
vals.totalVotingPower = sum
|
||||
}
|
||||
|
||||
vals.totalVotingPower = sum
|
||||
}
|
||||
|
||||
// TotalVotingPower returns the sum of the voting powers of all validators.
|
||||
// It recomputes the total voting power if required.
|
||||
func (vals *ValidatorSet) TotalVotingPower() int64 {
|
||||
if vals.totalVotingPower == 0 {
|
||||
vals.updateTotalVotingPower()
|
||||
}
|
||||
return vals.totalVotingPower
|
||||
}
|
||||
@@ -284,78 +317,6 @@ func (vals *ValidatorSet) Hash() []byte {
|
||||
return merkle.SimpleHashFromByteSlices(bzs)
|
||||
}
|
||||
|
||||
// Add adds val to the validator set and returns true. It returns false if val
|
||||
// is already in the set.
|
||||
func (vals *ValidatorSet) Add(val *Validator) (added bool) {
|
||||
val = val.Copy()
|
||||
idx := sort.Search(len(vals.Validators), func(i int) bool {
|
||||
return bytes.Compare(val.Address, vals.Validators[i].Address) <= 0
|
||||
})
|
||||
if idx >= len(vals.Validators) {
|
||||
vals.Validators = append(vals.Validators, val)
|
||||
// Invalidate cache
|
||||
vals.Proposer = nil
|
||||
vals.totalVotingPower = 0
|
||||
return true
|
||||
} else if bytes.Equal(vals.Validators[idx].Address, val.Address) {
|
||||
return false
|
||||
} else {
|
||||
newValidators := make([]*Validator, len(vals.Validators)+1)
|
||||
copy(newValidators[:idx], vals.Validators[:idx])
|
||||
newValidators[idx] = val
|
||||
copy(newValidators[idx+1:], vals.Validators[idx:])
|
||||
vals.Validators = newValidators
|
||||
// Invalidate cache
|
||||
vals.Proposer = nil
|
||||
vals.totalVotingPower = 0
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Update updates the ValidatorSet by copying in the val.
|
||||
// If the val is not found, it returns false; otherwise,
|
||||
// it returns true. The val.ProposerPriority field is ignored
|
||||
// and unchanged by this method.
|
||||
func (vals *ValidatorSet) Update(val *Validator) (updated bool) {
|
||||
index, sameVal := vals.GetByAddress(val.Address)
|
||||
if sameVal == nil {
|
||||
return false
|
||||
}
|
||||
// Overwrite the ProposerPriority so it doesn't change.
|
||||
// During block execution, the val passed in here comes
|
||||
// from ABCI via PB2TM.ValidatorUpdates. Since ABCI
|
||||
// doesn't know about ProposerPriority, PB2TM.ValidatorUpdates
|
||||
// uses the default value of 0, which would cause issues for
|
||||
// proposer selection every time a validator's voting power changes.
|
||||
val.ProposerPriority = sameVal.ProposerPriority
|
||||
vals.Validators[index] = val.Copy()
|
||||
// Invalidate cache
|
||||
vals.Proposer = nil
|
||||
vals.totalVotingPower = 0
|
||||
return true
|
||||
}
|
||||
|
||||
// Remove deletes the validator with address. It returns the validator removed
|
||||
// and true. If returns nil and false if validator is not present in the set.
|
||||
func (vals *ValidatorSet) Remove(address []byte) (val *Validator, removed bool) {
|
||||
idx := sort.Search(len(vals.Validators), func(i int) bool {
|
||||
return bytes.Compare(address, vals.Validators[i].Address) <= 0
|
||||
})
|
||||
if idx >= len(vals.Validators) || !bytes.Equal(vals.Validators[idx].Address, address) {
|
||||
return nil, false
|
||||
}
|
||||
removedVal := vals.Validators[idx]
|
||||
newValidators := vals.Validators[:idx]
|
||||
if idx+1 < len(vals.Validators) {
|
||||
newValidators = append(newValidators, vals.Validators[idx+1:]...)
|
||||
}
|
||||
vals.Validators = newValidators
|
||||
// Invalidate cache
|
||||
vals.Proposer = nil
|
||||
vals.totalVotingPower = 0
|
||||
return removedVal, true
|
||||
}
|
||||
|
||||
// Iterate will run the given function over the set.
|
||||
func (vals *ValidatorSet) Iterate(fn func(index int, val *Validator) bool) {
|
||||
for i, val := range vals.Validators {
|
||||
@@ -366,17 +327,279 @@ func (vals *ValidatorSet) Iterate(fn func(index int, val *Validator) bool) {
|
||||
}
|
||||
}
|
||||
|
||||
// Checks changes against duplicates, splits the changes in updates and removals, sorts them by address.
|
||||
//
|
||||
// Returns:
|
||||
// updates, removals - the sorted lists of updates and removals
|
||||
// err - non-nil if duplicate entries or entries with negative voting power are seen
|
||||
//
|
||||
// No changes are made to 'origChanges'.
|
||||
func processChanges(origChanges []*Validator) (updates, removals []*Validator, err error) {
|
||||
// Make a deep copy of the changes and sort by address.
|
||||
changes := validatorListCopy(origChanges)
|
||||
sort.Sort(ValidatorsByAddress(changes))
|
||||
|
||||
removals = make([]*Validator, 0, len(changes))
|
||||
updates = make([]*Validator, 0, len(changes))
|
||||
var prevAddr Address
|
||||
|
||||
// Scan changes by address and append valid validators to updates or removals lists.
|
||||
for _, valUpdate := range changes {
|
||||
if bytes.Equal(valUpdate.Address, prevAddr) {
|
||||
err = fmt.Errorf("duplicate entry %v in %v", valUpdate, changes)
|
||||
return nil, nil, err
|
||||
}
|
||||
if valUpdate.VotingPower < 0 {
|
||||
err = fmt.Errorf("voting power can't be negative: %v", valUpdate)
|
||||
return nil, nil, err
|
||||
}
|
||||
if valUpdate.VotingPower > MaxTotalVotingPower {
|
||||
err = fmt.Errorf("to prevent clipping/ overflow, voting power can't be higher than %v: %v ",
|
||||
MaxTotalVotingPower, valUpdate)
|
||||
return nil, nil, err
|
||||
}
|
||||
if valUpdate.VotingPower == 0 {
|
||||
removals = append(removals, valUpdate)
|
||||
} else {
|
||||
updates = append(updates, valUpdate)
|
||||
}
|
||||
prevAddr = valUpdate.Address
|
||||
}
|
||||
return updates, removals, err
|
||||
}
|
||||
|
||||
// Verifies a list of updates against a validator set, making sure the allowed
|
||||
// total voting power would not be exceeded if these updates would be applied to the set.
|
||||
//
|
||||
// Returns:
|
||||
// updatedTotalVotingPower - the new total voting power if these updates would be applied
|
||||
// numNewValidators - number of new validators
|
||||
// err - non-nil if the maximum allowed total voting power would be exceeded
|
||||
//
|
||||
// 'updates' should be a list of proper validator changes, i.e. they have been verified
|
||||
// by processChanges for duplicates and invalid values.
|
||||
// No changes are made to the validator set 'vals'.
|
||||
func verifyUpdates(updates []*Validator, vals *ValidatorSet) (updatedTotalVotingPower int64, numNewValidators int, err error) {
|
||||
|
||||
updatedTotalVotingPower = vals.TotalVotingPower()
|
||||
|
||||
for _, valUpdate := range updates {
|
||||
address := valUpdate.Address
|
||||
_, val := vals.GetByAddress(address)
|
||||
if val == nil {
|
||||
// New validator, add its voting power the the total.
|
||||
updatedTotalVotingPower += valUpdate.VotingPower
|
||||
numNewValidators++
|
||||
} else {
|
||||
// Updated validator, add the difference in power to the total.
|
||||
updatedTotalVotingPower += valUpdate.VotingPower - val.VotingPower
|
||||
}
|
||||
overflow := updatedTotalVotingPower > MaxTotalVotingPower
|
||||
if overflow {
|
||||
err = fmt.Errorf(
|
||||
"failed to add/update validator %v, total voting power would exceed the max allowed %v",
|
||||
valUpdate, MaxTotalVotingPower)
|
||||
return 0, 0, err
|
||||
}
|
||||
}
|
||||
|
||||
return updatedTotalVotingPower, numNewValidators, nil
|
||||
}
|
||||
|
||||
// Computes the proposer priority for the validators not present in the set based on 'updatedTotalVotingPower'.
|
||||
// Leaves unchanged the priorities of validators that are changed.
|
||||
//
|
||||
// 'updates' parameter must be a list of unique validators to be added or updated.
|
||||
// No changes are made to the validator set 'vals'.
|
||||
func computeNewPriorities(updates []*Validator, vals *ValidatorSet, updatedTotalVotingPower int64) {
|
||||
|
||||
for _, valUpdate := range updates {
|
||||
address := valUpdate.Address
|
||||
_, val := vals.GetByAddress(address)
|
||||
if val == nil {
|
||||
// add val
|
||||
// Set ProposerPriority to -C*totalVotingPower (with C ~= 1.125) to make sure validators can't
|
||||
// un-bond and then re-bond to reset their (potentially previously negative) ProposerPriority to zero.
|
||||
//
|
||||
// Contract: updatedVotingPower < MaxTotalVotingPower to ensure ProposerPriority does
|
||||
// not exceed the bounds of int64.
|
||||
//
|
||||
// Compute ProposerPriority = -1.125*totalVotingPower == -(updatedVotingPower + (updatedVotingPower >> 3)).
|
||||
valUpdate.ProposerPriority = -(updatedTotalVotingPower + (updatedTotalVotingPower >> 3))
|
||||
} else {
|
||||
valUpdate.ProposerPriority = val.ProposerPriority
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Merges the vals' validator list with the updates list.
|
||||
// When two elements with same address are seen, the one from updates is selected.
|
||||
// Expects updates to be a list of updates sorted by address with no duplicates or errors,
|
||||
// must have been validated with verifyUpdates() and priorities computed with computeNewPriorities().
|
||||
func (vals *ValidatorSet) applyUpdates(updates []*Validator) {
|
||||
|
||||
existing := vals.Validators
|
||||
merged := make([]*Validator, len(existing)+len(updates))
|
||||
i := 0
|
||||
|
||||
for len(existing) > 0 && len(updates) > 0 {
|
||||
if bytes.Compare(existing[0].Address, updates[0].Address) < 0 { // unchanged validator
|
||||
merged[i] = existing[0]
|
||||
existing = existing[1:]
|
||||
} else {
|
||||
// Apply add or update.
|
||||
merged[i] = updates[0]
|
||||
if bytes.Equal(existing[0].Address, updates[0].Address) {
|
||||
// Validator is present in both, advance existing.
|
||||
existing = existing[1:]
|
||||
}
|
||||
updates = updates[1:]
|
||||
}
|
||||
i++
|
||||
}
|
||||
|
||||
// Add the elements which are left.
|
||||
for j := 0; j < len(existing); j++ {
|
||||
merged[i] = existing[j]
|
||||
i++
|
||||
}
|
||||
// OR add updates which are left.
|
||||
for j := 0; j < len(updates); j++ {
|
||||
merged[i] = updates[j]
|
||||
i++
|
||||
}
|
||||
|
||||
vals.Validators = merged[:i]
|
||||
}
|
||||
|
||||
// Checks that the validators to be removed are part of the validator set.
|
||||
// No changes are made to the validator set 'vals'.
|
||||
func verifyRemovals(deletes []*Validator, vals *ValidatorSet) error {
|
||||
|
||||
for _, valUpdate := range deletes {
|
||||
address := valUpdate.Address
|
||||
_, val := vals.GetByAddress(address)
|
||||
if val == nil {
|
||||
return fmt.Errorf("failed to find validator %X to remove", address)
|
||||
}
|
||||
}
|
||||
if len(deletes) > len(vals.Validators) {
|
||||
panic("more deletes than validators")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Removes the validators specified in 'deletes' from validator set 'vals'.
|
||||
// Should not fail as verification has been done before.
|
||||
func (vals *ValidatorSet) applyRemovals(deletes []*Validator) {
|
||||
|
||||
existing := vals.Validators
|
||||
|
||||
merged := make([]*Validator, len(existing)-len(deletes))
|
||||
i := 0
|
||||
|
||||
// Loop over deletes until we removed all of them.
|
||||
for len(deletes) > 0 {
|
||||
if bytes.Equal(existing[0].Address, deletes[0].Address) {
|
||||
deletes = deletes[1:]
|
||||
} else { // Leave it in the resulting slice.
|
||||
merged[i] = existing[0]
|
||||
i++
|
||||
}
|
||||
existing = existing[1:]
|
||||
}
|
||||
|
||||
// Add the elements which are left.
|
||||
for j := 0; j < len(existing); j++ {
|
||||
merged[i] = existing[j]
|
||||
i++
|
||||
}
|
||||
|
||||
vals.Validators = merged[:i]
|
||||
}
|
||||
|
||||
// Main function used by UpdateWithChangeSet() and NewValidatorSet().
|
||||
// If 'allowDeletes' is false then delete operations (identified by validators with voting power 0)
|
||||
// are not allowed and will trigger an error if present in 'changes'.
|
||||
// The 'allowDeletes' flag is set to false by NewValidatorSet() and to true by UpdateWithChangeSet().
|
||||
func (vals *ValidatorSet) updateWithChangeSet(changes []*Validator, allowDeletes bool) error {
|
||||
|
||||
if len(changes) <= 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check for duplicates within changes, split in 'updates' and 'deletes' lists (sorted).
|
||||
updates, deletes, err := processChanges(changes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !allowDeletes && len(deletes) != 0 {
|
||||
return fmt.Errorf("cannot process validators with voting power 0: %v", deletes)
|
||||
}
|
||||
|
||||
// Verify that applying the 'deletes' against 'vals' will not result in error.
|
||||
if err := verifyRemovals(deletes, vals); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Verify that applying the 'updates' against 'vals' will not result in error.
|
||||
updatedTotalVotingPower, numNewValidators, err := verifyUpdates(updates, vals)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Check that the resulting set will not be empty.
|
||||
if numNewValidators == 0 && len(vals.Validators) == len(deletes) {
|
||||
return errors.New("applying the validator changes would result in empty set")
|
||||
}
|
||||
|
||||
// Compute the priorities for updates.
|
||||
computeNewPriorities(updates, vals, updatedTotalVotingPower)
|
||||
|
||||
// Apply updates and removals.
|
||||
vals.applyUpdates(updates)
|
||||
vals.applyRemovals(deletes)
|
||||
|
||||
vals.updateTotalVotingPower()
|
||||
|
||||
// Scale and center.
|
||||
vals.RescalePriorities(PriorityWindowSizeFactor * vals.TotalVotingPower())
|
||||
vals.shiftByAvgProposerPriority()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateWithChangeSet attempts to update the validator set with 'changes'.
|
||||
// It performs the following steps:
|
||||
// - validates the changes making sure there are no duplicates and splits them in updates and deletes
|
||||
// - verifies that applying the changes will not result in errors
|
||||
// - computes the total voting power BEFORE removals to ensure that in the next steps the priorities
|
||||
// across old and newly added validators are fair
|
||||
// - computes the priorities of new validators against the final set
|
||||
// - applies the updates against the validator set
|
||||
// - applies the removals against the validator set
|
||||
// - performs scaling and centering of priority values
|
||||
// If an error is detected during verification steps, it is returned and the validator set
|
||||
// is not changed.
|
||||
func (vals *ValidatorSet) UpdateWithChangeSet(changes []*Validator) error {
|
||||
return vals.updateWithChangeSet(changes, true)
|
||||
}
|
||||
|
||||
// Verify that +2/3 of the set had signed the given signBytes.
|
||||
func (vals *ValidatorSet) VerifyCommit(chainID string, blockID BlockID, height int64, commit *Commit) error {
|
||||
|
||||
// If the ValidatorSet size is different than the commit.Precommits size somthing is wrong
|
||||
if err := commit.ValidateBasic(); err != nil {
|
||||
return err
|
||||
}
|
||||
if vals.Size() != len(commit.Precommits) {
|
||||
return fmt.Errorf("Invalid commit -- wrong set size: %v vs %v", vals.Size(), len(commit.Precommits))
|
||||
return NewErrInvalidCommitPrecommits(vals.Size(), len(commit.Precommits))
|
||||
}
|
||||
|
||||
// If the height to check is different than the commit height return an error
|
||||
if height != commit.Height() {
|
||||
return fmt.Errorf("Invalid commit -- wrong height: %v vs %v", height, commit.Height())
|
||||
return NewErrInvalidCommitHeight(height, commit.Height())
|
||||
}
|
||||
|
||||
// If the blockHash is not equal to the commit block hash return an error
|
||||
@@ -385,38 +608,17 @@ func (vals *ValidatorSet) VerifyCommit(chainID string, blockID BlockID, height i
|
||||
blockID, commit.BlockID)
|
||||
}
|
||||
|
||||
var talliedVotingPower int64
|
||||
round := commit.Round()
|
||||
talliedVotingPower := int64(0)
|
||||
|
||||
for idx, precommit := range commit.Precommits {
|
||||
// Some precommits will likely be missing, skip those
|
||||
if precommit == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Malicous data checking for height
|
||||
if precommit.Height != height {
|
||||
return fmt.Errorf("Invalid commit -- wrong height: want %v got %v", height, precommit.Height)
|
||||
}
|
||||
|
||||
// Malicous data checking for round
|
||||
if precommit.Round != round {
|
||||
return fmt.Errorf("Invalid commit -- wrong round: want %v got %v", round, precommit.Round)
|
||||
}
|
||||
|
||||
// Malicous data checking for precommit
|
||||
if precommit.Type != PrecommitType {
|
||||
return fmt.Errorf("Invalid commit -- not precommit @ index %v", idx)
|
||||
}
|
||||
|
||||
_, val := vals.GetByIndex(idx)
|
||||
// Validate signature.
|
||||
_, val := vals.GetByAddress(precommit.ValidatorAddress)
|
||||
if val == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// verify that the valiator signed the precommit
|
||||
if !val.PubKey.VerifyBytes(precommit.SignBytes(chainID), precommit.Signature) {
|
||||
precommitSignBytes := commit.VoteSignBytes(chainID, idx)
|
||||
if !val.PubKey.VerifyBytes(precommitSignBytes, precommit.Signature) {
|
||||
return fmt.Errorf("Invalid commit -- invalid signature: %v", precommit)
|
||||
}
|
||||
|
||||
@@ -494,14 +696,14 @@ func (vals *ValidatorSet) VerifyFutureCommit(newVals *ValidatorSet, chainID stri
|
||||
return cmn.NewError("Invalid commit -- not precommit @ index %v", idx)
|
||||
}
|
||||
// See if this validator is in oldVals.
|
||||
idx, val := oldVals.GetByAddress(precommit.ValidatorAddress)
|
||||
if val == nil || seen[idx] {
|
||||
oldIdx, val := oldVals.GetByAddress(precommit.ValidatorAddress)
|
||||
if val == nil || seen[oldIdx] {
|
||||
continue // missing or double vote...
|
||||
}
|
||||
seen[idx] = true
|
||||
seen[oldIdx] = true
|
||||
|
||||
// Validate signature.
|
||||
precommitSignBytes := precommit.SignBytes(chainID)
|
||||
precommitSignBytes := commit.VoteSignBytes(chainID, idx)
|
||||
if !val.PubKey.VerifyBytes(precommitSignBytes, precommit.Signature) {
|
||||
return cmn.NewError("Invalid commit -- invalid signature: %v", precommit)
|
||||
}
|
||||
@@ -575,7 +777,7 @@ func (vals *ValidatorSet) StringIndented(indent string) string {
|
||||
//-------------------------------------
|
||||
// Implements sort for sorting validators by address.
|
||||
|
||||
// Sort validators by address
|
||||
// Sort validators by address.
|
||||
type ValidatorsByAddress []*Validator
|
||||
|
||||
func (valz ValidatorsByAddress) Len() int {
|
||||
@@ -593,7 +795,7 @@ func (valz ValidatorsByAddress) Swap(i, j int) {
|
||||
}
|
||||
|
||||
//----------------------------------------
|
||||
// For testing
|
||||
// for testing
|
||||
|
||||
// RandValidatorSet returns a randomized validator set, useful for testing.
|
||||
// NOTE: PrivValidator are in order.
|
||||
@@ -612,7 +814,7 @@ func RandValidatorSet(numValidators int, votingPower int64) (*ValidatorSet, []Pr
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Safe addition/subtraction
|
||||
// safe addition/subtraction
|
||||
|
||||
func safeAdd(a, b int64) (int64, bool) {
|
||||
if b > 0 && a > math.MaxInt64-b {
|
||||
|
||||
+700
-29
@@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"math"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
"testing/quick"
|
||||
@@ -45,41 +46,32 @@ func TestValidatorSetBasic(t *testing.T) {
|
||||
assert.Nil(t, vset.Hash())
|
||||
|
||||
// add
|
||||
|
||||
val = randValidator_(vset.TotalVotingPower())
|
||||
assert.True(t, vset.Add(val))
|
||||
assert.NoError(t, vset.UpdateWithChangeSet([]*Validator{val}))
|
||||
|
||||
assert.True(t, vset.HasAddress(val.Address))
|
||||
idx, val2 := vset.GetByAddress(val.Address)
|
||||
idx, _ = vset.GetByAddress(val.Address)
|
||||
assert.Equal(t, 0, idx)
|
||||
assert.Equal(t, val, val2)
|
||||
addr, val2 = vset.GetByIndex(0)
|
||||
addr, _ = vset.GetByIndex(0)
|
||||
assert.Equal(t, []byte(val.Address), addr)
|
||||
assert.Equal(t, val, val2)
|
||||
assert.Equal(t, 1, vset.Size())
|
||||
assert.Equal(t, val.VotingPower, vset.TotalVotingPower())
|
||||
assert.Equal(t, val, vset.GetProposer())
|
||||
assert.NotNil(t, vset.Hash())
|
||||
assert.NotPanics(t, func() { vset.IncrementProposerPriority(1) })
|
||||
assert.Equal(t, val.Address, vset.GetProposer().Address)
|
||||
|
||||
// update
|
||||
assert.False(t, vset.Update(randValidator_(vset.TotalVotingPower())))
|
||||
val = randValidator_(vset.TotalVotingPower())
|
||||
assert.NoError(t, vset.UpdateWithChangeSet([]*Validator{val}))
|
||||
_, val = vset.GetByAddress(val.Address)
|
||||
val.VotingPower += 100
|
||||
proposerPriority := val.ProposerPriority
|
||||
// Mimic update from types.PB2TM.ValidatorUpdates which does not know about ProposerPriority
|
||||
// and hence defaults to 0.
|
||||
|
||||
val.ProposerPriority = 0
|
||||
assert.True(t, vset.Update(val))
|
||||
assert.NoError(t, vset.UpdateWithChangeSet([]*Validator{val}))
|
||||
_, val = vset.GetByAddress(val.Address)
|
||||
assert.Equal(t, proposerPriority, val.ProposerPriority)
|
||||
|
||||
// remove
|
||||
val2, removed := vset.Remove(randValidator_(vset.TotalVotingPower()).Address)
|
||||
assert.Nil(t, val2)
|
||||
assert.False(t, removed)
|
||||
val2, removed = vset.Remove(val.Address)
|
||||
assert.Equal(t, val.Address, val2.Address)
|
||||
assert.True(t, removed)
|
||||
}
|
||||
|
||||
func TestCopy(t *testing.T) {
|
||||
@@ -116,8 +108,9 @@ func BenchmarkValidatorSetCopy(b *testing.B) {
|
||||
for i := 0; i < 1000; i++ {
|
||||
privKey := ed25519.GenPrivKey()
|
||||
pubKey := privKey.PubKey()
|
||||
val := NewValidator(pubKey, 0)
|
||||
if !vset.Add(val) {
|
||||
val := NewValidator(pubKey, 10)
|
||||
err := vset.UpdateWithChangeSet([]*Validator{val})
|
||||
if err != nil {
|
||||
panic("Failed to add validator")
|
||||
}
|
||||
}
|
||||
@@ -284,7 +277,7 @@ func randPubKey() crypto.PubKey {
|
||||
func randValidator_(totalVotingPower int64) *Validator {
|
||||
// this modulo limits the ProposerPriority/VotingPower to stay in the
|
||||
// bounds of MaxTotalVotingPower minus the already existing voting power:
|
||||
val := NewValidator(randPubKey(), cmn.RandInt64()%(MaxTotalVotingPower-totalVotingPower))
|
||||
val := NewValidator(randPubKey(), int64(cmn.RandUint64()%uint64((MaxTotalVotingPower-totalVotingPower))))
|
||||
val.ProposerPriority = cmn.RandInt64() % (MaxTotalVotingPower - totalVotingPower)
|
||||
return val
|
||||
}
|
||||
@@ -563,18 +556,12 @@ func TestValidatorSetVerifyCommit(t *testing.T) {
|
||||
sig, err := privKey.Sign(vote.SignBytes(chainID))
|
||||
assert.NoError(t, err)
|
||||
vote.Signature = sig
|
||||
commit := &Commit{
|
||||
BlockID: blockID,
|
||||
Precommits: []*Vote{vote},
|
||||
}
|
||||
commit := NewCommit(blockID, []*CommitSig{vote.CommitSig()})
|
||||
|
||||
badChainID := "notmychainID"
|
||||
badBlockID := BlockID{Hash: []byte("goodbye")}
|
||||
badHeight := height + 1
|
||||
badCommit := &Commit{
|
||||
BlockID: blockID,
|
||||
Precommits: []*Vote{nil},
|
||||
}
|
||||
badCommit := NewCommit(blockID, []*CommitSig{nil})
|
||||
|
||||
// test some error cases
|
||||
// TODO: test more cases!
|
||||
@@ -599,3 +586,687 @@ func TestValidatorSetVerifyCommit(t *testing.T) {
|
||||
err = vset.VerifyCommit(chainID, blockID, height, commit)
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
func TestEmptySet(t *testing.T) {
|
||||
|
||||
var valList []*Validator
|
||||
valSet := NewValidatorSet(valList)
|
||||
assert.Panics(t, func() { valSet.IncrementProposerPriority(1) })
|
||||
assert.Panics(t, func() { valSet.RescalePriorities(100) })
|
||||
assert.Panics(t, func() { valSet.shiftByAvgProposerPriority() })
|
||||
assert.Panics(t, func() { assert.Zero(t, computeMaxMinPriorityDiff(valSet)) })
|
||||
valSet.GetProposer()
|
||||
|
||||
// Add to empty set
|
||||
v1 := newValidator([]byte("v1"), 100)
|
||||
v2 := newValidator([]byte("v2"), 100)
|
||||
valList = []*Validator{v1, v2}
|
||||
assert.NoError(t, valSet.UpdateWithChangeSet(valList))
|
||||
verifyValidatorSet(t, valSet)
|
||||
|
||||
// Delete all validators from set
|
||||
v1 = newValidator([]byte("v1"), 0)
|
||||
v2 = newValidator([]byte("v2"), 0)
|
||||
delList := []*Validator{v1, v2}
|
||||
assert.Error(t, valSet.UpdateWithChangeSet(delList))
|
||||
|
||||
// Attempt delete from empty set
|
||||
assert.Error(t, valSet.UpdateWithChangeSet(delList))
|
||||
|
||||
}
|
||||
|
||||
func TestUpdatesForNewValidatorSet(t *testing.T) {
|
||||
|
||||
v1 := newValidator([]byte("v1"), 100)
|
||||
v2 := newValidator([]byte("v2"), 100)
|
||||
valList := []*Validator{v1, v2}
|
||||
valSet := NewValidatorSet(valList)
|
||||
verifyValidatorSet(t, valSet)
|
||||
|
||||
// Verify duplicates are caught in NewValidatorSet() and it panics
|
||||
v111 := newValidator([]byte("v1"), 100)
|
||||
v112 := newValidator([]byte("v1"), 123)
|
||||
v113 := newValidator([]byte("v1"), 234)
|
||||
valList = []*Validator{v111, v112, v113}
|
||||
assert.Panics(t, func() { NewValidatorSet(valList) })
|
||||
|
||||
// Verify set including validator with voting power 0 cannot be created
|
||||
v1 = newValidator([]byte("v1"), 0)
|
||||
v2 = newValidator([]byte("v2"), 22)
|
||||
v3 := newValidator([]byte("v3"), 33)
|
||||
valList = []*Validator{v1, v2, v3}
|
||||
assert.Panics(t, func() { NewValidatorSet(valList) })
|
||||
|
||||
// Verify set including validator with negative voting power cannot be created
|
||||
v1 = newValidator([]byte("v1"), 10)
|
||||
v2 = newValidator([]byte("v2"), -20)
|
||||
v3 = newValidator([]byte("v3"), 30)
|
||||
valList = []*Validator{v1, v2, v3}
|
||||
assert.Panics(t, func() { NewValidatorSet(valList) })
|
||||
|
||||
}
|
||||
|
||||
type testVal struct {
|
||||
name string
|
||||
power int64
|
||||
}
|
||||
|
||||
func permutation(valList []testVal) []testVal {
|
||||
if len(valList) == 0 {
|
||||
return nil
|
||||
}
|
||||
permList := make([]testVal, len(valList))
|
||||
perm := cmn.RandPerm(len(valList))
|
||||
for i, v := range perm {
|
||||
permList[v] = valList[i]
|
||||
}
|
||||
return permList
|
||||
}
|
||||
|
||||
func createNewValidatorList(testValList []testVal) []*Validator {
|
||||
valList := make([]*Validator, 0, len(testValList))
|
||||
for _, val := range testValList {
|
||||
valList = append(valList, newValidator([]byte(val.name), val.power))
|
||||
}
|
||||
return valList
|
||||
}
|
||||
|
||||
func createNewValidatorSet(testValList []testVal) *ValidatorSet {
|
||||
return NewValidatorSet(createNewValidatorList(testValList))
|
||||
}
|
||||
|
||||
func valSetTotalProposerPriority(valSet *ValidatorSet) int64 {
|
||||
sum := int64(0)
|
||||
for _, val := range valSet.Validators {
|
||||
// mind overflow
|
||||
sum = safeAddClip(sum, val.ProposerPriority)
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
||||
func verifyValidatorSet(t *testing.T, valSet *ValidatorSet) {
|
||||
// verify that the capacity and length of validators is the same
|
||||
assert.Equal(t, len(valSet.Validators), cap(valSet.Validators))
|
||||
|
||||
// verify that the set's total voting power has been updated
|
||||
tvp := valSet.totalVotingPower
|
||||
valSet.updateTotalVotingPower()
|
||||
expectedTvp := valSet.TotalVotingPower()
|
||||
assert.Equal(t, expectedTvp, tvp,
|
||||
"expected TVP %d. Got %d, valSet=%s", expectedTvp, tvp, valSet)
|
||||
|
||||
// verify that validator priorities are centered
|
||||
valsCount := int64(len(valSet.Validators))
|
||||
tpp := valSetTotalProposerPriority(valSet)
|
||||
assert.True(t, tpp < valsCount && tpp > -valsCount,
|
||||
"expected total priority in (-%d, %d). Got %d", valsCount, valsCount, tpp)
|
||||
|
||||
// verify that priorities are scaled
|
||||
dist := computeMaxMinPriorityDiff(valSet)
|
||||
assert.True(t, dist <= PriorityWindowSizeFactor*tvp,
|
||||
"expected priority distance < %d. Got %d", PriorityWindowSizeFactor*tvp, dist)
|
||||
}
|
||||
|
||||
func toTestValList(valList []*Validator) []testVal {
|
||||
testList := make([]testVal, len(valList))
|
||||
for i, val := range valList {
|
||||
testList[i].name = string(val.Address)
|
||||
testList[i].power = val.VotingPower
|
||||
}
|
||||
return testList
|
||||
}
|
||||
|
||||
func testValSet(nVals int, power int64) []testVal {
|
||||
vals := make([]testVal, nVals)
|
||||
for i := 0; i < nVals; i++ {
|
||||
vals[i] = testVal{fmt.Sprintf("v%d", i+1), power}
|
||||
}
|
||||
return vals
|
||||
}
|
||||
|
||||
type valSetErrTestCase struct {
|
||||
startVals []testVal
|
||||
updateVals []testVal
|
||||
}
|
||||
|
||||
func executeValSetErrTestCase(t *testing.T, idx int, tt valSetErrTestCase) {
|
||||
// create a new set and apply updates, keeping copies for the checks
|
||||
valSet := createNewValidatorSet(tt.startVals)
|
||||
valSetCopy := valSet.Copy()
|
||||
valList := createNewValidatorList(tt.updateVals)
|
||||
valListCopy := validatorListCopy(valList)
|
||||
err := valSet.UpdateWithChangeSet(valList)
|
||||
|
||||
// for errors check the validator set has not been changed
|
||||
assert.Error(t, err, "test %d", idx)
|
||||
assert.Equal(t, valSet, valSetCopy, "test %v", idx)
|
||||
|
||||
// check the parameter list has not changed
|
||||
assert.Equal(t, valList, valListCopy, "test %v", idx)
|
||||
}
|
||||
|
||||
func TestValSetUpdatesDuplicateEntries(t *testing.T) {
|
||||
testCases := []valSetErrTestCase{
|
||||
// Duplicate entries in changes
|
||||
{ // first entry is duplicated change
|
||||
testValSet(2, 10),
|
||||
[]testVal{{"v1", 11}, {"v1", 22}},
|
||||
},
|
||||
{ // second entry is duplicated change
|
||||
testValSet(2, 10),
|
||||
[]testVal{{"v2", 11}, {"v2", 22}},
|
||||
},
|
||||
{ // change duplicates are separated by a valid change
|
||||
testValSet(2, 10),
|
||||
[]testVal{{"v1", 11}, {"v2", 22}, {"v1", 12}},
|
||||
},
|
||||
{ // change duplicates are separated by a valid change
|
||||
testValSet(3, 10),
|
||||
[]testVal{{"v1", 11}, {"v3", 22}, {"v1", 12}},
|
||||
},
|
||||
|
||||
// Duplicate entries in remove
|
||||
{ // first entry is duplicated remove
|
||||
testValSet(2, 10),
|
||||
[]testVal{{"v1", 0}, {"v1", 0}},
|
||||
},
|
||||
{ // second entry is duplicated remove
|
||||
testValSet(2, 10),
|
||||
[]testVal{{"v2", 0}, {"v2", 0}},
|
||||
},
|
||||
{ // remove duplicates are separated by a valid remove
|
||||
testValSet(2, 10),
|
||||
[]testVal{{"v1", 0}, {"v2", 0}, {"v1", 0}},
|
||||
},
|
||||
{ // remove duplicates are separated by a valid remove
|
||||
testValSet(3, 10),
|
||||
[]testVal{{"v1", 0}, {"v3", 0}, {"v1", 0}},
|
||||
},
|
||||
|
||||
{ // remove and update same val
|
||||
testValSet(2, 10),
|
||||
[]testVal{{"v1", 0}, {"v2", 20}, {"v1", 30}},
|
||||
},
|
||||
{ // duplicate entries in removes + changes
|
||||
testValSet(2, 10),
|
||||
[]testVal{{"v1", 0}, {"v2", 20}, {"v2", 30}, {"v1", 0}},
|
||||
},
|
||||
{ // duplicate entries in removes + changes
|
||||
testValSet(3, 10),
|
||||
[]testVal{{"v1", 0}, {"v3", 5}, {"v2", 20}, {"v2", 30}, {"v1", 0}},
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range testCases {
|
||||
executeValSetErrTestCase(t, i, tt)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValSetUpdatesOverflows(t *testing.T) {
|
||||
maxVP := MaxTotalVotingPower
|
||||
testCases := []valSetErrTestCase{
|
||||
{ // single update leading to overflow
|
||||
testValSet(2, 10),
|
||||
[]testVal{{"v1", math.MaxInt64}},
|
||||
},
|
||||
{ // single update leading to overflow
|
||||
testValSet(2, 10),
|
||||
[]testVal{{"v2", math.MaxInt64}},
|
||||
},
|
||||
{ // add validator leading to overflow
|
||||
testValSet(1, maxVP),
|
||||
[]testVal{{"v2", math.MaxInt64}},
|
||||
},
|
||||
{ // add validator leading to exceed Max
|
||||
testValSet(1, maxVP-1),
|
||||
[]testVal{{"v2", 5}},
|
||||
},
|
||||
{ // add validator leading to exceed Max
|
||||
testValSet(2, maxVP/3),
|
||||
[]testVal{{"v3", maxVP / 2}},
|
||||
},
|
||||
{ // add validator leading to exceed Max
|
||||
testValSet(1, maxVP),
|
||||
[]testVal{{"v2", maxVP}},
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range testCases {
|
||||
executeValSetErrTestCase(t, i, tt)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValSetUpdatesOtherErrors(t *testing.T) {
|
||||
testCases := []valSetErrTestCase{
|
||||
{ // update with negative voting power
|
||||
testValSet(2, 10),
|
||||
[]testVal{{"v1", -123}},
|
||||
},
|
||||
{ // update with negative voting power
|
||||
testValSet(2, 10),
|
||||
[]testVal{{"v2", -123}},
|
||||
},
|
||||
{ // remove non-existing validator
|
||||
testValSet(2, 10),
|
||||
[]testVal{{"v3", 0}},
|
||||
},
|
||||
{ // delete all validators
|
||||
[]testVal{{"v1", 10}, {"v2", 20}, {"v3", 30}},
|
||||
[]testVal{{"v1", 0}, {"v2", 0}, {"v3", 0}},
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range testCases {
|
||||
executeValSetErrTestCase(t, i, tt)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValSetUpdatesBasicTestsExecute(t *testing.T) {
|
||||
valSetUpdatesBasicTests := []struct {
|
||||
startVals []testVal
|
||||
updateVals []testVal
|
||||
expectedVals []testVal
|
||||
}{
|
||||
{ // no changes
|
||||
testValSet(2, 10),
|
||||
[]testVal{},
|
||||
testValSet(2, 10),
|
||||
},
|
||||
{ // voting power changes
|
||||
testValSet(2, 10),
|
||||
[]testVal{{"v1", 11}, {"v2", 22}},
|
||||
[]testVal{{"v1", 11}, {"v2", 22}},
|
||||
},
|
||||
{ // add new validators
|
||||
[]testVal{{"v1", 10}, {"v2", 20}},
|
||||
[]testVal{{"v3", 30}, {"v4", 40}},
|
||||
[]testVal{{"v1", 10}, {"v2", 20}, {"v3", 30}, {"v4", 40}},
|
||||
},
|
||||
{ // add new validator to middle
|
||||
[]testVal{{"v1", 10}, {"v3", 20}},
|
||||
[]testVal{{"v2", 30}},
|
||||
[]testVal{{"v1", 10}, {"v2", 30}, {"v3", 20}},
|
||||
},
|
||||
{ // add new validator to beginning
|
||||
[]testVal{{"v2", 10}, {"v3", 20}},
|
||||
[]testVal{{"v1", 30}},
|
||||
[]testVal{{"v1", 30}, {"v2", 10}, {"v3", 20}},
|
||||
},
|
||||
{ // delete validators
|
||||
[]testVal{{"v1", 10}, {"v2", 20}, {"v3", 30}},
|
||||
[]testVal{{"v2", 0}},
|
||||
[]testVal{{"v1", 10}, {"v3", 30}},
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range valSetUpdatesBasicTests {
|
||||
// create a new set and apply updates, keeping copies for the checks
|
||||
valSet := createNewValidatorSet(tt.startVals)
|
||||
valList := createNewValidatorList(tt.updateVals)
|
||||
err := valSet.UpdateWithChangeSet(valList)
|
||||
assert.NoError(t, err, "test %d", i)
|
||||
|
||||
valListCopy := validatorListCopy(valSet.Validators)
|
||||
// check that the voting power in the set's validators is not changing if the voting power
|
||||
// is changed in the list of validators previously passed as parameter to UpdateWithChangeSet.
|
||||
// this is to make sure copies of the validators are made by UpdateWithChangeSet.
|
||||
if len(valList) > 0 {
|
||||
valList[0].VotingPower++
|
||||
assert.Equal(t, toTestValList(valListCopy), toTestValList(valSet.Validators), "test %v", i)
|
||||
|
||||
}
|
||||
|
||||
// check the final validator list is as expected and the set is properly scaled and centered.
|
||||
assert.Equal(t, tt.expectedVals, toTestValList(valSet.Validators), "test %v", i)
|
||||
verifyValidatorSet(t, valSet)
|
||||
}
|
||||
}
|
||||
|
||||
// Test that different permutations of an update give the same result.
|
||||
func TestValSetUpdatesOrderIndependenceTestsExecute(t *testing.T) {
|
||||
// startVals - initial validators to create the set with
|
||||
// updateVals - a sequence of updates to be applied to the set.
|
||||
// updateVals is shuffled a number of times during testing to check for same resulting validator set.
|
||||
valSetUpdatesOrderTests := []struct {
|
||||
startVals []testVal
|
||||
updateVals []testVal
|
||||
}{
|
||||
0: { // order of changes should not matter, the final validator sets should be the same
|
||||
[]testVal{{"v1", 10}, {"v2", 10}, {"v3", 30}, {"v4", 40}},
|
||||
[]testVal{{"v1", 11}, {"v2", 22}, {"v3", 33}, {"v4", 44}}},
|
||||
|
||||
1: { // order of additions should not matter
|
||||
[]testVal{{"v1", 10}, {"v2", 20}},
|
||||
[]testVal{{"v3", 30}, {"v4", 40}, {"v5", 50}, {"v6", 60}}},
|
||||
|
||||
2: { // order of removals should not matter
|
||||
[]testVal{{"v1", 10}, {"v2", 20}, {"v3", 30}, {"v4", 40}},
|
||||
[]testVal{{"v1", 0}, {"v3", 0}, {"v4", 0}}},
|
||||
|
||||
3: { // order of mixed operations should not matter
|
||||
[]testVal{{"v1", 10}, {"v2", 20}, {"v3", 30}, {"v4", 40}},
|
||||
[]testVal{{"v1", 0}, {"v3", 0}, {"v2", 22}, {"v5", 50}, {"v4", 44}}},
|
||||
}
|
||||
|
||||
for i, tt := range valSetUpdatesOrderTests {
|
||||
// create a new set and apply updates
|
||||
valSet := createNewValidatorSet(tt.startVals)
|
||||
valSetCopy := valSet.Copy()
|
||||
valList := createNewValidatorList(tt.updateVals)
|
||||
assert.NoError(t, valSetCopy.UpdateWithChangeSet(valList))
|
||||
|
||||
// save the result as expected for next updates
|
||||
valSetExp := valSetCopy.Copy()
|
||||
|
||||
// perform at most 20 permutations on the updates and call UpdateWithChangeSet()
|
||||
n := len(tt.updateVals)
|
||||
maxNumPerms := cmn.MinInt(20, n*n)
|
||||
for j := 0; j < maxNumPerms; j++ {
|
||||
// create a copy of original set and apply a random permutation of updates
|
||||
valSetCopy := valSet.Copy()
|
||||
valList := createNewValidatorList(permutation(tt.updateVals))
|
||||
|
||||
// check there was no error and the set is properly scaled and centered.
|
||||
assert.NoError(t, valSetCopy.UpdateWithChangeSet(valList),
|
||||
"test %v failed for permutation %v", i, valList)
|
||||
verifyValidatorSet(t, valSetCopy)
|
||||
|
||||
// verify the resulting test is same as the expected
|
||||
assert.Equal(t, valSetCopy, valSetExp,
|
||||
"test %v failed for permutation %v", i, valList)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This tests the private function validator_set.go:applyUpdates() function, used only for additions and changes.
|
||||
// Should perform a proper merge of updatedVals and startVals
|
||||
func TestValSetApplyUpdatesTestsExecute(t *testing.T) {
|
||||
valSetUpdatesBasicTests := []struct {
|
||||
startVals []testVal
|
||||
updateVals []testVal
|
||||
expectedVals []testVal
|
||||
}{
|
||||
// additions
|
||||
0: { // prepend
|
||||
[]testVal{{"v4", 44}, {"v5", 55}},
|
||||
[]testVal{{"v1", 11}},
|
||||
[]testVal{{"v1", 11}, {"v4", 44}, {"v5", 55}}},
|
||||
1: { // append
|
||||
[]testVal{{"v4", 44}, {"v5", 55}},
|
||||
[]testVal{{"v6", 66}},
|
||||
[]testVal{{"v4", 44}, {"v5", 55}, {"v6", 66}}},
|
||||
2: { // insert
|
||||
[]testVal{{"v4", 44}, {"v6", 66}},
|
||||
[]testVal{{"v5", 55}},
|
||||
[]testVal{{"v4", 44}, {"v5", 55}, {"v6", 66}}},
|
||||
3: { // insert multi
|
||||
[]testVal{{"v4", 44}, {"v6", 66}, {"v9", 99}},
|
||||
[]testVal{{"v5", 55}, {"v7", 77}, {"v8", 88}},
|
||||
[]testVal{{"v4", 44}, {"v5", 55}, {"v6", 66}, {"v7", 77}, {"v8", 88}, {"v9", 99}}},
|
||||
// changes
|
||||
4: { // head
|
||||
[]testVal{{"v1", 111}, {"v2", 22}},
|
||||
[]testVal{{"v1", 11}},
|
||||
[]testVal{{"v1", 11}, {"v2", 22}}},
|
||||
5: { // tail
|
||||
[]testVal{{"v1", 11}, {"v2", 222}},
|
||||
[]testVal{{"v2", 22}},
|
||||
[]testVal{{"v1", 11}, {"v2", 22}}},
|
||||
6: { // middle
|
||||
[]testVal{{"v1", 11}, {"v2", 222}, {"v3", 33}},
|
||||
[]testVal{{"v2", 22}},
|
||||
[]testVal{{"v1", 11}, {"v2", 22}, {"v3", 33}}},
|
||||
7: { // multi
|
||||
[]testVal{{"v1", 111}, {"v2", 222}, {"v3", 333}},
|
||||
[]testVal{{"v1", 11}, {"v2", 22}, {"v3", 33}},
|
||||
[]testVal{{"v1", 11}, {"v2", 22}, {"v3", 33}}},
|
||||
// additions and changes
|
||||
8: {
|
||||
[]testVal{{"v1", 111}, {"v2", 22}},
|
||||
[]testVal{{"v1", 11}, {"v3", 33}, {"v4", 44}},
|
||||
[]testVal{{"v1", 11}, {"v2", 22}, {"v3", 33}, {"v4", 44}}},
|
||||
}
|
||||
|
||||
for i, tt := range valSetUpdatesBasicTests {
|
||||
// create a new validator set with the start values
|
||||
valSet := createNewValidatorSet(tt.startVals)
|
||||
|
||||
// applyUpdates() with the update values
|
||||
valList := createNewValidatorList(tt.updateVals)
|
||||
valSet.applyUpdates(valList)
|
||||
|
||||
// check the new list of validators for proper merge
|
||||
assert.Equal(t, toTestValList(valSet.Validators), tt.expectedVals, "test %v", i)
|
||||
}
|
||||
}
|
||||
|
||||
type testVSetCfg struct {
|
||||
startVals []testVal
|
||||
deletedVals []testVal
|
||||
updatedVals []testVal
|
||||
addedVals []testVal
|
||||
expectedVals []testVal
|
||||
}
|
||||
|
||||
func randTestVSetCfg(t *testing.T, nBase, nAddMax int) testVSetCfg {
|
||||
if nBase <= 0 || nAddMax < 0 {
|
||||
panic(fmt.Sprintf("bad parameters %v %v", nBase, nAddMax))
|
||||
}
|
||||
|
||||
const maxPower = 1000
|
||||
var nOld, nDel, nChanged, nAdd int
|
||||
|
||||
nOld = int(cmn.RandUint()%uint(nBase)) + 1
|
||||
if nBase-nOld > 0 {
|
||||
nDel = int(cmn.RandUint() % uint(nBase-nOld))
|
||||
}
|
||||
nChanged = nBase - nOld - nDel
|
||||
|
||||
if nAddMax > 0 {
|
||||
nAdd = cmn.RandInt()%nAddMax + 1
|
||||
}
|
||||
|
||||
cfg := testVSetCfg{}
|
||||
|
||||
cfg.startVals = make([]testVal, nBase)
|
||||
cfg.deletedVals = make([]testVal, nDel)
|
||||
cfg.addedVals = make([]testVal, nAdd)
|
||||
cfg.updatedVals = make([]testVal, nChanged)
|
||||
cfg.expectedVals = make([]testVal, nBase-nDel+nAdd)
|
||||
|
||||
for i := 0; i < nBase; i++ {
|
||||
cfg.startVals[i] = testVal{fmt.Sprintf("v%d", i), int64(cmn.RandUint()%maxPower + 1)}
|
||||
if i < nOld {
|
||||
cfg.expectedVals[i] = cfg.startVals[i]
|
||||
}
|
||||
if i >= nOld && i < nOld+nChanged {
|
||||
cfg.updatedVals[i-nOld] = testVal{fmt.Sprintf("v%d", i), int64(cmn.RandUint()%maxPower + 1)}
|
||||
cfg.expectedVals[i] = cfg.updatedVals[i-nOld]
|
||||
}
|
||||
if i >= nOld+nChanged {
|
||||
cfg.deletedVals[i-nOld-nChanged] = testVal{fmt.Sprintf("v%d", i), 0}
|
||||
}
|
||||
}
|
||||
|
||||
for i := nBase; i < nBase+nAdd; i++ {
|
||||
cfg.addedVals[i-nBase] = testVal{fmt.Sprintf("v%d", i), int64(cmn.RandUint()%maxPower + 1)}
|
||||
cfg.expectedVals[i-nDel] = cfg.addedVals[i-nBase]
|
||||
}
|
||||
|
||||
sort.Sort(testValsByAddress(cfg.startVals))
|
||||
sort.Sort(testValsByAddress(cfg.deletedVals))
|
||||
sort.Sort(testValsByAddress(cfg.updatedVals))
|
||||
sort.Sort(testValsByAddress(cfg.addedVals))
|
||||
sort.Sort(testValsByAddress(cfg.expectedVals))
|
||||
|
||||
return cfg
|
||||
|
||||
}
|
||||
|
||||
func applyChangesToValSet(t *testing.T, valSet *ValidatorSet, valsLists ...[]testVal) {
|
||||
changes := make([]testVal, 0)
|
||||
for _, valsList := range valsLists {
|
||||
changes = append(changes, valsList...)
|
||||
}
|
||||
valList := createNewValidatorList(changes)
|
||||
err := valSet.UpdateWithChangeSet(valList)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestValSetUpdatePriorityOrderTests(t *testing.T) {
|
||||
const nMaxElections = 5000
|
||||
|
||||
testCases := []testVSetCfg{
|
||||
0: { // remove high power validator, keep old equal lower power validators
|
||||
startVals: []testVal{{"v1", 1}, {"v2", 1}, {"v3", 1000}},
|
||||
deletedVals: []testVal{{"v3", 0}},
|
||||
updatedVals: []testVal{},
|
||||
addedVals: []testVal{},
|
||||
expectedVals: []testVal{{"v1", 1}, {"v2", 1}},
|
||||
},
|
||||
1: { // remove high power validator, keep old different power validators
|
||||
startVals: []testVal{{"v1", 1}, {"v2", 10}, {"v3", 1000}},
|
||||
deletedVals: []testVal{{"v3", 0}},
|
||||
updatedVals: []testVal{},
|
||||
addedVals: []testVal{},
|
||||
expectedVals: []testVal{{"v1", 1}, {"v2", 10}},
|
||||
},
|
||||
2: { // remove high power validator, add new low power validators, keep old lower power
|
||||
startVals: []testVal{{"v1", 1}, {"v2", 2}, {"v3", 1000}},
|
||||
deletedVals: []testVal{{"v3", 0}},
|
||||
updatedVals: []testVal{{"v2", 1}},
|
||||
addedVals: []testVal{{"v4", 40}, {"v5", 50}},
|
||||
expectedVals: []testVal{{"v1", 1}, {"v2", 1}, {"v4", 40}, {"v5", 50}},
|
||||
},
|
||||
|
||||
// generate a configuration with 100 validators,
|
||||
// randomly select validators for updates and deletes, and
|
||||
// generate 10 new validators to be added
|
||||
3: randTestVSetCfg(t, 100, 10),
|
||||
|
||||
4: randTestVSetCfg(t, 1000, 100),
|
||||
|
||||
5: randTestVSetCfg(t, 10, 100),
|
||||
|
||||
6: randTestVSetCfg(t, 100, 1000),
|
||||
|
||||
7: randTestVSetCfg(t, 1000, 1000),
|
||||
|
||||
8: randTestVSetCfg(t, 10000, 1000),
|
||||
|
||||
9: randTestVSetCfg(t, 1000, 10000),
|
||||
}
|
||||
|
||||
for _, cfg := range testCases {
|
||||
|
||||
// create a new validator set
|
||||
valSet := createNewValidatorSet(cfg.startVals)
|
||||
verifyValidatorSet(t, valSet)
|
||||
|
||||
// run election up to nMaxElections times, apply changes and verify that the priority order is correct
|
||||
verifyValSetUpdatePriorityOrder(t, valSet, cfg, nMaxElections)
|
||||
}
|
||||
}
|
||||
|
||||
func verifyValSetUpdatePriorityOrder(t *testing.T, valSet *ValidatorSet, cfg testVSetCfg, nMaxElections int) {
|
||||
|
||||
// Run election up to nMaxElections times, sort validators by priorities
|
||||
valSet.IncrementProposerPriority(cmn.RandInt()%nMaxElections + 1)
|
||||
origValsPriSorted := validatorListCopy(valSet.Validators)
|
||||
sort.Sort(validatorsByPriority(origValsPriSorted))
|
||||
|
||||
// apply the changes, get the updated validators, sort by priorities
|
||||
applyChangesToValSet(t, valSet, cfg.addedVals, cfg.updatedVals, cfg.deletedVals)
|
||||
updatedValsPriSorted := validatorListCopy(valSet.Validators)
|
||||
sort.Sort(validatorsByPriority(updatedValsPriSorted))
|
||||
|
||||
// basic checks
|
||||
assert.Equal(t, toTestValList(valSet.Validators), cfg.expectedVals)
|
||||
verifyValidatorSet(t, valSet)
|
||||
|
||||
// verify that the added validators have the smallest priority:
|
||||
// - they should be at the beginning of valListNewPriority since it is sorted by priority
|
||||
if len(cfg.addedVals) > 0 {
|
||||
addedValsPriSlice := updatedValsPriSorted[:len(cfg.addedVals)]
|
||||
sort.Sort(ValidatorsByAddress(addedValsPriSlice))
|
||||
assert.Equal(t, cfg.addedVals, toTestValList(addedValsPriSlice))
|
||||
|
||||
// - and should all have the same priority
|
||||
expectedPri := addedValsPriSlice[0].ProposerPriority
|
||||
for _, val := range addedValsPriSlice[1:] {
|
||||
assert.Equal(t, expectedPri, val.ProposerPriority)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//---------------------
|
||||
// Sort validators by priority and address
|
||||
type validatorsByPriority []*Validator
|
||||
|
||||
func (valz validatorsByPriority) Len() int {
|
||||
return len(valz)
|
||||
}
|
||||
|
||||
func (valz validatorsByPriority) Less(i, j int) bool {
|
||||
if valz[i].ProposerPriority < valz[j].ProposerPriority {
|
||||
return true
|
||||
}
|
||||
if valz[i].ProposerPriority > valz[j].ProposerPriority {
|
||||
return false
|
||||
}
|
||||
return bytes.Compare(valz[i].Address, valz[j].Address) < 0
|
||||
}
|
||||
|
||||
func (valz validatorsByPriority) Swap(i, j int) {
|
||||
it := valz[i]
|
||||
valz[i] = valz[j]
|
||||
valz[j] = it
|
||||
}
|
||||
|
||||
//-------------------------------------
|
||||
// Sort testVal-s by address.
|
||||
type testValsByAddress []testVal
|
||||
|
||||
func (tvals testValsByAddress) Len() int {
|
||||
return len(tvals)
|
||||
}
|
||||
|
||||
func (tvals testValsByAddress) Less(i, j int) bool {
|
||||
return bytes.Compare([]byte(tvals[i].name), []byte(tvals[j].name)) == -1
|
||||
}
|
||||
|
||||
func (tvals testValsByAddress) Swap(i, j int) {
|
||||
it := tvals[i]
|
||||
tvals[i] = tvals[j]
|
||||
tvals[j] = it
|
||||
}
|
||||
|
||||
//-------------------------------------
|
||||
// Benchmark tests
|
||||
//
|
||||
func BenchmarkUpdates(b *testing.B) {
|
||||
const (
|
||||
n = 100
|
||||
m = 2000
|
||||
)
|
||||
// Init with n validators
|
||||
vs := make([]*Validator, n)
|
||||
for j := 0; j < n; j++ {
|
||||
vs[j] = newValidator([]byte(fmt.Sprintf("v%d", j)), 100)
|
||||
}
|
||||
valSet := NewValidatorSet(vs)
|
||||
l := len(valSet.Validators)
|
||||
|
||||
// Make m new validators
|
||||
newValList := make([]*Validator, m)
|
||||
for j := 0; j < m; j++ {
|
||||
newValList[j] = newValidator([]byte(fmt.Sprintf("v%d", j+l)), 1000)
|
||||
}
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
// Add m validators to valSetCopy
|
||||
valSetCopy := valSet.Copy()
|
||||
assert.NoError(b, valSetCopy.UpdateWithChangeSet(newValList))
|
||||
}
|
||||
}
|
||||
|
||||
+11
-1
@@ -59,6 +59,16 @@ type Vote struct {
|
||||
Signature []byte `json:"signature"`
|
||||
}
|
||||
|
||||
// CommitSig converts the Vote to a CommitSig.
|
||||
// If the Vote is nil, the CommitSig will be nil.
|
||||
func (vote *Vote) CommitSig() *CommitSig {
|
||||
if vote == nil {
|
||||
return nil
|
||||
}
|
||||
cs := CommitSig(*vote)
|
||||
return &cs
|
||||
}
|
||||
|
||||
func (vote *Vote) SignBytes(chainID string) []byte {
|
||||
bz, err := cdc.MarshalBinaryLengthPrefixed(CanonicalizeVote(chainID, vote))
|
||||
if err != nil {
|
||||
@@ -83,7 +93,7 @@ func (vote *Vote) String() string {
|
||||
case PrecommitType:
|
||||
typeString = "Precommit"
|
||||
default:
|
||||
cmn.PanicSanity("Unknown vote type")
|
||||
panic("Unknown vote type")
|
||||
}
|
||||
|
||||
return fmt.Sprintf("Vote{%v:%X %v/%02d/%v(%v) %X %X @ %s}",
|
||||
|
||||
+15
-13
@@ -70,7 +70,7 @@ type VoteSet struct {
|
||||
// Constructs a new VoteSet struct used to accumulate votes for given height/round.
|
||||
func NewVoteSet(chainID string, height int64, round int, type_ SignedMsgType, valSet *ValidatorSet) *VoteSet {
|
||||
if height == 0 {
|
||||
cmn.PanicSanity("Cannot make VoteSet for height == 0, doesn't make sense.")
|
||||
panic("Cannot make VoteSet for height == 0, doesn't make sense.")
|
||||
}
|
||||
return &VoteSet{
|
||||
chainID: chainID,
|
||||
@@ -130,7 +130,7 @@ func (voteSet *VoteSet) Size() int {
|
||||
// NOTE: Vote must not be nil
|
||||
func (voteSet *VoteSet) AddVote(vote *Vote) (added bool, err error) {
|
||||
if voteSet == nil {
|
||||
cmn.PanicSanity("AddVote() on nil VoteSet")
|
||||
panic("AddVote() on nil VoteSet")
|
||||
}
|
||||
voteSet.mtx.Lock()
|
||||
defer voteSet.mtx.Unlock()
|
||||
@@ -196,7 +196,7 @@ func (voteSet *VoteSet) addVote(vote *Vote) (added bool, err error) {
|
||||
return added, NewConflictingVoteError(val, conflicting, vote)
|
||||
}
|
||||
if !added {
|
||||
cmn.PanicSanity("Expected to add non-conflicting vote")
|
||||
panic("Expected to add non-conflicting vote")
|
||||
}
|
||||
return added, nil
|
||||
}
|
||||
@@ -220,7 +220,7 @@ func (voteSet *VoteSet) addVerifiedVote(vote *Vote, blockKey string, votingPower
|
||||
// Already exists in voteSet.votes?
|
||||
if existing := voteSet.votes[valIndex]; existing != nil {
|
||||
if existing.BlockID.Equals(vote.BlockID) {
|
||||
cmn.PanicSanity("addVerifiedVote does not expect duplicate votes")
|
||||
panic("addVerifiedVote does not expect duplicate votes")
|
||||
} else {
|
||||
conflicting = existing
|
||||
}
|
||||
@@ -290,7 +290,7 @@ func (voteSet *VoteSet) addVerifiedVote(vote *Vote, blockKey string, votingPower
|
||||
// NOTE: VoteSet must not be nil
|
||||
func (voteSet *VoteSet) SetPeerMaj23(peerID P2PID, blockID BlockID) error {
|
||||
if voteSet == nil {
|
||||
cmn.PanicSanity("SetPeerMaj23() on nil VoteSet")
|
||||
panic("SetPeerMaj23() on nil VoteSet")
|
||||
}
|
||||
voteSet.mtx.Lock()
|
||||
defer voteSet.mtx.Unlock()
|
||||
@@ -363,7 +363,7 @@ func (voteSet *VoteSet) GetByAddress(address []byte) *Vote {
|
||||
defer voteSet.mtx.Unlock()
|
||||
valIndex, val := voteSet.valSet.GetByAddress(address)
|
||||
if val == nil {
|
||||
cmn.PanicSanity("GetByAddress(address) returned nil")
|
||||
panic("GetByAddress(address) returned nil")
|
||||
}
|
||||
return voteSet.votes[valIndex]
|
||||
}
|
||||
@@ -528,25 +528,27 @@ func (voteSet *VoteSet) sumTotalFrac() (int64, int64, float64) {
|
||||
//--------------------------------------------------------------------------------
|
||||
// Commit
|
||||
|
||||
// MakeCommit constructs a Commit from the VoteSet.
|
||||
// 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 {
|
||||
if voteSet.type_ != PrecommitType {
|
||||
cmn.PanicSanity("Cannot MakeCommit() unless VoteSet.Type is PrecommitType")
|
||||
panic("Cannot MakeCommit() unless VoteSet.Type is PrecommitType")
|
||||
}
|
||||
voteSet.mtx.Lock()
|
||||
defer voteSet.mtx.Unlock()
|
||||
|
||||
// Make sure we have a 2/3 majority
|
||||
if voteSet.maj23 == nil {
|
||||
cmn.PanicSanity("Cannot MakeCommit() unless a blockhash has +2/3")
|
||||
panic("Cannot MakeCommit() unless a blockhash has +2/3")
|
||||
}
|
||||
|
||||
// For every validator, get the precommit
|
||||
votesCopy := make([]*Vote, len(voteSet.votes))
|
||||
copy(votesCopy, voteSet.votes)
|
||||
return &Commit{
|
||||
BlockID: *voteSet.maj23,
|
||||
Precommits: votesCopy,
|
||||
commitSigs := make([]*CommitSig, len(voteSet.votes))
|
||||
for i, v := range voteSet.votes {
|
||||
commitSigs[i] = v.CommitSig()
|
||||
}
|
||||
return NewCommit(*voteSet.maj23, commitSigs)
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
|
||||
+34
-19
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
amino "github.com/tendermint/go-amino"
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
"github.com/tendermint/tendermint/crypto/ed25519"
|
||||
"github.com/tendermint/tendermint/crypto/tmhash"
|
||||
@@ -43,6 +44,19 @@ func exampleVote(t byte) *Vote {
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that Vote and CommitSig have the same encoding.
|
||||
// This ensures using CommitSig isn't a breaking change.
|
||||
// This test will fail and can be removed once CommitSig contains only sigs and
|
||||
// timestamps.
|
||||
func TestVoteEncoding(t *testing.T) {
|
||||
vote := examplePrecommit()
|
||||
commitSig := vote.CommitSig()
|
||||
cdc := amino.NewCodec()
|
||||
bz1 := cdc.MustMarshalBinaryBare(vote)
|
||||
bz2 := cdc.MustMarshalBinaryBare(commitSig)
|
||||
assert.Equal(t, bz1, bz2)
|
||||
}
|
||||
|
||||
func TestVoteSignable(t *testing.T) {
|
||||
vote := examplePrecommit()
|
||||
signBytes := vote.SignBytes("test_chain_id")
|
||||
@@ -53,23 +67,23 @@ func TestVoteSignable(t *testing.T) {
|
||||
require.Equal(t, expected, signBytes, "Got unexpected sign bytes for Vote.")
|
||||
}
|
||||
|
||||
func TestVoteSignableTestVectors(t *testing.T) {
|
||||
vote := CanonicalizeVote("", &Vote{Height: 1, Round: 1})
|
||||
func TestVoteSignBytesTestVectors(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
canonicalVote CanonicalVote
|
||||
want []byte
|
||||
chainID string
|
||||
vote *Vote
|
||||
want []byte
|
||||
}{
|
||||
{
|
||||
CanonicalizeVote("", &Vote{}),
|
||||
0: {
|
||||
"", &Vote{},
|
||||
// NOTE: Height and Round are skipped here. This case needs to be considered while parsing.
|
||||
// []byte{0x2a, 0x9, 0x9, 0x0, 0x9, 0x6e, 0x88, 0xf1, 0xff, 0xff, 0xff},
|
||||
[]byte{0x2a, 0xb, 0x8, 0x80, 0x92, 0xb8, 0xc3, 0x98, 0xfe, 0xff, 0xff, 0xff, 0x1},
|
||||
[]byte{0xd, 0x2a, 0xb, 0x8, 0x80, 0x92, 0xb8, 0xc3, 0x98, 0xfe, 0xff, 0xff, 0xff, 0x1},
|
||||
},
|
||||
// with proper (fixed size) height and round (PreCommit):
|
||||
{
|
||||
CanonicalizeVote("", &Vote{Height: 1, Round: 1, Type: PrecommitType}),
|
||||
1: {
|
||||
"", &Vote{Height: 1, Round: 1, Type: PrecommitType},
|
||||
[]byte{
|
||||
0x21, // length
|
||||
0x8, // (field_number << 3) | wire_type
|
||||
0x2, // PrecommitType
|
||||
0x11, // (field_number << 3) | wire_type
|
||||
@@ -81,9 +95,10 @@ func TestVoteSignableTestVectors(t *testing.T) {
|
||||
0xb, 0x8, 0x80, 0x92, 0xb8, 0xc3, 0x98, 0xfe, 0xff, 0xff, 0xff, 0x1},
|
||||
},
|
||||
// with proper (fixed size) height and round (PreVote):
|
||||
{
|
||||
CanonicalizeVote("", &Vote{Height: 1, Round: 1, Type: PrevoteType}),
|
||||
2: {
|
||||
"", &Vote{Height: 1, Round: 1, Type: PrevoteType},
|
||||
[]byte{
|
||||
0x21, // length
|
||||
0x8, // (field_number << 3) | wire_type
|
||||
0x1, // PrevoteType
|
||||
0x11, // (field_number << 3) | wire_type
|
||||
@@ -94,9 +109,10 @@ func TestVoteSignableTestVectors(t *testing.T) {
|
||||
// remaining fields (timestamp):
|
||||
0xb, 0x8, 0x80, 0x92, 0xb8, 0xc3, 0x98, 0xfe, 0xff, 0xff, 0xff, 0x1},
|
||||
},
|
||||
{
|
||||
vote,
|
||||
3: {
|
||||
"", &Vote{Height: 1, Round: 1},
|
||||
[]byte{
|
||||
0x1f, // length
|
||||
0x11, // (field_number << 3) | wire_type
|
||||
0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // height
|
||||
0x19, // (field_number << 3) | wire_type
|
||||
@@ -106,9 +122,10 @@ func TestVoteSignableTestVectors(t *testing.T) {
|
||||
0xb, 0x8, 0x80, 0x92, 0xb8, 0xc3, 0x98, 0xfe, 0xff, 0xff, 0xff, 0x1},
|
||||
},
|
||||
// containing non-empty chain_id:
|
||||
{
|
||||
CanonicalizeVote("test_chain_id", &Vote{Height: 1, Round: 1}),
|
||||
4: {
|
||||
"test_chain_id", &Vote{Height: 1, Round: 1},
|
||||
[]byte{
|
||||
0x2e, // length
|
||||
0x11, // (field_number << 3) | wire_type
|
||||
0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // height
|
||||
0x19, // (field_number << 3) | wire_type
|
||||
@@ -121,9 +138,7 @@ func TestVoteSignableTestVectors(t *testing.T) {
|
||||
},
|
||||
}
|
||||
for i, tc := range tests {
|
||||
got, err := cdc.MarshalBinaryBare(tc.canonicalVote)
|
||||
require.NoError(t, err)
|
||||
|
||||
got := tc.vote.SignBytes(tc.chainID)
|
||||
require.Equal(t, tc.want, got, "test case #%v: got unexpected sign bytes for Vote.", i)
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ package types
|
||||
|
||||
import (
|
||||
amino "github.com/tendermint/go-amino"
|
||||
"github.com/tendermint/tendermint/crypto/encoding/amino"
|
||||
cryptoAmino "github.com/tendermint/tendermint/crypto/encoding/amino"
|
||||
)
|
||||
|
||||
var cdc = amino.NewCodec()
|
||||
|
||||
Reference in New Issue
Block a user