Fix conflicts with upsteam changes

This commit is contained in:
Zaki Manian
2019-07-05 20:23:23 -07:00
521 changed files with 41981 additions and 9903 deletions
+119 -32
View File
@@ -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
View File
@@ -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)
}
}
+41
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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 {
+4 -4
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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}}
}
+7 -7
View File
@@ -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
View File
@@ -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,
},
}
}
+3 -4
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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()