Revert "delete everything" (includes everything non-go-crypto)

This reverts commit 96a3502
This commit is contained in:
Liamsi
2018-06-20 17:35:30 -07:00
parent 587505d4d2
commit d2c05bc5b9
533 changed files with 69873 additions and 162 deletions
+577
View File
@@ -0,0 +1,577 @@
package types
import (
"bytes"
"errors"
"fmt"
"strings"
"sync"
"time"
cmn "github.com/tendermint/tmlibs/common"
"github.com/tendermint/tmlibs/merkle"
"golang.org/x/crypto/ripemd160"
)
// Block defines the atomic unit of a Tendermint blockchain.
// TODO: add Version byte
type Block struct {
mtx sync.Mutex
*Header `json:"header"`
*Data `json:"data"`
Evidence EvidenceData `json:"evidence"`
LastCommit *Commit `json:"last_commit"`
}
// MakeBlock returns a new block with an empty header, except what can be computed from itself.
// It populates the same set of fields validated by ValidateBasic
func MakeBlock(height int64, txs []Tx, commit *Commit) *Block {
block := &Block{
Header: &Header{
Height: height,
Time: time.Now(),
NumTxs: int64(len(txs)),
},
LastCommit: commit,
Data: &Data{
Txs: txs,
},
}
block.fillHeader()
return block
}
// AddEvidence appends the given evidence to the block
func (b *Block) AddEvidence(evidence []Evidence) {
b.Evidence.Evidence = append(b.Evidence.Evidence, evidence...)
}
// ValidateBasic performs basic validation that doesn't involve state data.
// It checks the internal consistency of the block.
func (b *Block) ValidateBasic() error {
if b == nil {
return errors.New("Nil blocks are invalid")
}
b.mtx.Lock()
defer b.mtx.Unlock()
newTxs := int64(len(b.Data.Txs))
if b.NumTxs != newTxs {
return fmt.Errorf("Wrong Block.Header.NumTxs. Expected %v, got %v", newTxs, b.NumTxs)
}
if !bytes.Equal(b.LastCommitHash, b.LastCommit.Hash()) {
return fmt.Errorf("Wrong Block.Header.LastCommitHash. Expected %v, got %v", b.LastCommitHash, b.LastCommit.Hash())
}
if b.Header.Height != 1 {
if err := b.LastCommit.ValidateBasic(); err != nil {
return err
}
}
if !bytes.Equal(b.DataHash, b.Data.Hash()) {
return fmt.Errorf("Wrong Block.Header.DataHash. Expected %v, got %v", b.DataHash, b.Data.Hash())
}
if !bytes.Equal(b.EvidenceHash, b.Evidence.Hash()) {
return errors.New(cmn.Fmt("Wrong Block.Header.EvidenceHash. Expected %v, got %v", b.EvidenceHash, b.Evidence.Hash()))
}
return nil
}
// fillHeader fills in any remaining header fields that are a function of the block data
func (b *Block) fillHeader() {
if b.LastCommitHash == nil {
b.LastCommitHash = b.LastCommit.Hash()
}
if b.DataHash == nil {
b.DataHash = b.Data.Hash()
}
if b.EvidenceHash == nil {
b.EvidenceHash = b.Evidence.Hash()
}
}
// Hash computes and returns the block hash.
// If the block is incomplete, block hash is nil for safety.
func (b *Block) Hash() cmn.HexBytes {
if b == nil {
return nil
}
b.mtx.Lock()
defer b.mtx.Unlock()
if b == nil || b.Header == nil || b.Data == nil || b.LastCommit == nil {
return nil
}
b.fillHeader()
return b.Header.Hash()
}
// MakePartSet returns a PartSet containing parts of a serialized block.
// This is the form in which the block is gossipped to peers.
func (b *Block) MakePartSet(partSize int) *PartSet {
if b == nil {
return nil
}
b.mtx.Lock()
defer b.mtx.Unlock()
// We prefix the byte length, so that unmarshaling
// can easily happen via a reader.
bz, err := cdc.MarshalBinary(b)
if err != nil {
panic(err)
}
return NewPartSetFromData(bz, partSize)
}
// HashesTo is a convenience function that checks if a block hashes to the given argument.
// Returns false if the block is nil or the hash is empty.
func (b *Block) HashesTo(hash []byte) bool {
if len(hash) == 0 {
return false
}
if b == nil {
return false
}
return bytes.Equal(b.Hash(), hash)
}
// Size returns size of the block in bytes.
func (b *Block) Size() int {
bz, err := cdc.MarshalBinaryBare(b)
if err != nil {
return 0
}
return len(bz)
}
// String returns a string representation of the block
func (b *Block) String() string {
return b.StringIndented("")
}
// StringIndented returns a string representation of the block
func (b *Block) StringIndented(indent string) string {
if b == nil {
return "nil-Block"
}
return fmt.Sprintf(`Block{
%s %v
%s %v
%s %v
%s %v
%s}#%v`,
indent, b.Header.StringIndented(indent+" "),
indent, b.Data.StringIndented(indent+" "),
indent, b.Evidence.StringIndented(indent+" "),
indent, b.LastCommit.StringIndented(indent+" "),
indent, b.Hash())
}
// StringShort returns a shortened string representation of the block
func (b *Block) StringShort() string {
if b == nil {
return "nil-Block"
}
return fmt.Sprintf("Block#%v", b.Hash())
}
//-----------------------------------------------------------------------------
// Header defines the structure of a Tendermint block header
// TODO: limit header size
// NOTE: changes to the Header should be duplicated in the abci Header
type Header struct {
// basic block info
ChainID string `json:"chain_id"`
Height int64 `json:"height"`
Time time.Time `json:"time"`
NumTxs int64 `json:"num_txs"`
// prev block info
LastBlockID BlockID `json:"last_block_id"`
TotalTxs int64 `json:"total_txs"`
// hashes of block data
LastCommitHash cmn.HexBytes `json:"last_commit_hash"` // commit from validators from the last block
DataHash cmn.HexBytes `json:"data_hash"` // transactions
// hashes from the app output from the prev block
ValidatorsHash cmn.HexBytes `json:"validators_hash"` // validators for the current block
ConsensusHash cmn.HexBytes `json:"consensus_hash"` // consensus params for current block
AppHash cmn.HexBytes `json:"app_hash"` // state after txs from the previous block
LastResultsHash cmn.HexBytes `json:"last_results_hash"` // root hash of all results from the txs from the previous block
// consensus info
EvidenceHash cmn.HexBytes `json:"evidence_hash"` // evidence included in the block
}
// Hash returns the hash of the header.
// Returns nil if ValidatorHash is missing,
// since a Header is not valid unless there is
// a ValidaotrsHash (corresponding to the validator set).
func (h *Header) Hash() cmn.HexBytes {
if h == nil || len(h.ValidatorsHash) == 0 {
return nil
}
return merkle.SimpleHashFromMap(map[string]merkle.Hasher{
"ChainID": aminoHasher(h.ChainID),
"Height": aminoHasher(h.Height),
"Time": aminoHasher(h.Time),
"NumTxs": aminoHasher(h.NumTxs),
"TotalTxs": aminoHasher(h.TotalTxs),
"LastBlockID": aminoHasher(h.LastBlockID),
"LastCommit": aminoHasher(h.LastCommitHash),
"Data": aminoHasher(h.DataHash),
"Validators": aminoHasher(h.ValidatorsHash),
"App": aminoHasher(h.AppHash),
"Consensus": aminoHasher(h.ConsensusHash),
"Results": aminoHasher(h.LastResultsHash),
"Evidence": aminoHasher(h.EvidenceHash),
})
}
// StringIndented returns a string representation of the header
func (h *Header) StringIndented(indent string) string {
if h == nil {
return "nil-Header"
}
return fmt.Sprintf(`Header{
%s ChainID: %v
%s Height: %v
%s Time: %v
%s NumTxs: %v
%s TotalTxs: %v
%s LastBlockID: %v
%s LastCommit: %v
%s Data: %v
%s Validators: %v
%s App: %v
%s Consensus: %v
%s Results: %v
%s Evidence: %v
%s}#%v`,
indent, h.ChainID,
indent, h.Height,
indent, h.Time,
indent, h.NumTxs,
indent, h.TotalTxs,
indent, h.LastBlockID,
indent, h.LastCommitHash,
indent, h.DataHash,
indent, h.ValidatorsHash,
indent, h.AppHash,
indent, h.ConsensusHash,
indent, h.LastResultsHash,
indent, h.EvidenceHash,
indent, h.Hash())
}
//-------------------------------------
// 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"`
// Volatile
firstPrecommit *Vote
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 {
return nil
}
if commit.firstPrecommit != nil {
return commit.firstPrecommit
}
for _, precommit := range commit.Precommits {
if precommit != nil {
commit.firstPrecommit = precommit
return precommit
}
}
return &Vote{
Type: VoteTypePrecommit,
}
}
// Height returns the height of the commit
func (commit *Commit) Height() int64 {
if len(commit.Precommits) == 0 {
return 0
}
return commit.FirstPrecommit().Height
}
// Round returns the round of the commit
func (commit *Commit) Round() int {
if len(commit.Precommits) == 0 {
return 0
}
return commit.FirstPrecommit().Round
}
// Type returns the vote type of the commit, which is always VoteTypePrecommit
func (commit *Commit) Type() byte {
return VoteTypePrecommit
}
// Size returns the number of votes in the commit
func (commit *Commit) Size() int {
if commit == nil {
return 0
}
return len(commit.Precommits)
}
// BitArray returns a BitArray of which validators voted in this commit
func (commit *Commit) BitArray() *cmn.BitArray {
if commit.bitArray == nil {
commit.bitArray = cmn.NewBitArray(len(commit.Precommits))
for i, precommit := range commit.Precommits {
// TODO: need to check the BlockID otherwise we could be counting conflicts,
// not just the one with +2/3 !
commit.bitArray.SetIndex(i, precommit != nil)
}
}
return commit.bitArray
}
// GetByIndex returns the vote corresponding to a given validator index
func (commit *Commit) GetByIndex(index int) *Vote {
return commit.Precommits[index]
}
// IsCommit returns true if there is at least one vote
func (commit *Commit) IsCommit() bool {
return len(commit.Precommits) != 0
}
// ValidateBasic performs basic validation that doesn't involve state data.
func (commit *Commit) ValidateBasic() error {
if commit.BlockID.IsZero() {
return errors.New("Commit cannot be for nil block")
}
if len(commit.Precommits) == 0 {
return errors.New("No precommits in commit")
}
height, round := commit.Height(), commit.Round()
// validate the precommits
for _, precommit := range commit.Precommits {
// It's OK for precommits to be missing.
if precommit == nil {
continue
}
// Ensure that all votes are precommits
if precommit.Type != VoteTypePrecommit {
return fmt.Errorf("Invalid commit vote. Expected precommit, got %v",
precommit.Type)
}
// Ensure that all heights are the same
if precommit.Height != height {
return fmt.Errorf("Invalid commit precommit height. Expected %v, got %v",
height, precommit.Height)
}
// Ensure that all rounds are the same
if precommit.Round != round {
return fmt.Errorf("Invalid commit precommit round. Expected %v, got %v",
round, precommit.Round)
}
}
return nil
}
// Hash returns the hash of the commit
func (commit *Commit) Hash() cmn.HexBytes {
if commit.hash == nil {
bs := make([]merkle.Hasher, len(commit.Precommits))
for i, precommit := range commit.Precommits {
bs[i] = aminoHasher(precommit)
}
commit.hash = merkle.SimpleHashFromHashers(bs)
}
return commit.hash
}
// StringIndented returns a string representation of the commit
func (commit *Commit) StringIndented(indent string) string {
if commit == nil {
return "nil-Commit"
}
precommitStrings := make([]string, len(commit.Precommits))
for i, precommit := range commit.Precommits {
precommitStrings[i] = precommit.String()
}
return fmt.Sprintf(`Commit{
%s BlockID: %v
%s Precommits: %v
%s}#%v`,
indent, commit.BlockID,
indent, strings.Join(precommitStrings, "\n"+indent+" "),
indent, commit.hash)
}
//-----------------------------------------------------------------------------
// SignedHeader is a header along with the commits that prove it
type SignedHeader struct {
Header *Header `json:"header"`
Commit *Commit `json:"commit"`
}
//-----------------------------------------------------------------------------
// Data contains the set of transactions included in the block
type Data struct {
// Txs that will be applied by state @ block.Height+1.
// NOTE: not all txs here are valid. We're just agreeing on the order first.
// This means that block.AppHash does not include these txs.
Txs Txs `json:"txs"`
// Volatile
hash cmn.HexBytes
}
// Hash returns the hash of the data
func (data *Data) Hash() cmn.HexBytes {
if data == nil {
return (Txs{}).Hash()
}
if data.hash == nil {
data.hash = data.Txs.Hash() // NOTE: leaves of merkle tree are TxIDs
}
return data.hash
}
// StringIndented returns a string representation of the transactions
func (data *Data) StringIndented(indent string) string {
if data == nil {
return "nil-Data"
}
txStrings := make([]string, cmn.MinInt(len(data.Txs), 21))
for i, tx := range data.Txs {
if i == 20 {
txStrings[i] = fmt.Sprintf("... (%v total)", len(data.Txs))
break
}
txStrings[i] = fmt.Sprintf("Tx:%v", tx)
}
return fmt.Sprintf(`Data{
%s %v
%s}#%v`,
indent, strings.Join(txStrings, "\n"+indent+" "),
indent, data.hash)
}
//-----------------------------------------------------------------------------
// EvidenceData contains any evidence of malicious wrong-doing by validators
type EvidenceData struct {
Evidence EvidenceList `json:"evidence"`
// Volatile
hash cmn.HexBytes
}
// Hash returns the hash of the data.
func (data *EvidenceData) Hash() cmn.HexBytes {
if data.hash == nil {
data.hash = data.Evidence.Hash()
}
return data.hash
}
// StringIndented returns a string representation of the evidence.
func (data *EvidenceData) StringIndented(indent string) string {
if data == nil {
return "nil-Evidence"
}
evStrings := make([]string, cmn.MinInt(len(data.Evidence), 21))
for i, ev := range data.Evidence {
if i == 20 {
evStrings[i] = fmt.Sprintf("... (%v total)", len(data.Evidence))
break
}
evStrings[i] = fmt.Sprintf("Evidence:%v", ev)
}
return fmt.Sprintf(`Data{
%s %v
%s}#%v`,
indent, strings.Join(evStrings, "\n"+indent+" "),
indent, data.hash)
return ""
}
//--------------------------------------------------------------------------------
// BlockID defines the unique ID of a block as its Hash and its PartSetHeader
type BlockID struct {
Hash cmn.HexBytes `json:"hash"`
PartsHeader PartSetHeader `json:"parts"`
}
// IsZero returns true if this is the BlockID for a nil-block
func (blockID BlockID) IsZero() bool {
return len(blockID.Hash) == 0 && blockID.PartsHeader.IsZero()
}
// Equals returns true if the BlockID matches the given BlockID
func (blockID BlockID) Equals(other BlockID) bool {
return bytes.Equal(blockID.Hash, other.Hash) &&
blockID.PartsHeader.Equals(other.PartsHeader)
}
// Key returns a machine-readable string representation of the BlockID
func (blockID BlockID) Key() string {
bz, err := cdc.MarshalBinaryBare(blockID.PartsHeader)
if err != nil {
panic(err)
}
return string(blockID.Hash) + string(bz)
}
// String returns a human readable string representation of the BlockID
func (blockID BlockID) String() string {
return fmt.Sprintf(`%v:%v`, blockID.Hash, blockID.PartsHeader)
}
//-------------------------------------------------------
type hasher struct {
item interface{}
}
func (h hasher) Hash() []byte {
hasher := ripemd160.New()
if h.item != nil && !cmn.IsTypedNil(h.item) && !cmn.IsEmpty(h.item) {
bz, err := cdc.MarshalBinaryBare(h.item)
if err != nil {
panic(err)
}
_, err = hasher.Write(bz)
if err != nil {
panic(err)
}
}
return hasher.Sum(nil)
}
func aminoHash(item interface{}) []byte {
h := hasher{item}
return h.Hash()
}
func aminoHasher(item interface{}) merkle.Hasher {
return hasher{item}
}
+15
View File
@@ -0,0 +1,15 @@
package types
// BlockMeta contains meta information about a block - namely, it's ID and Header.
type BlockMeta struct {
BlockID BlockID `json:"block_id"` // the block hash and partsethash
Header *Header `json:"header"` // The block's Header
}
// NewBlockMeta returns a new BlockMeta from the block and its blockParts.
func NewBlockMeta(block *Block, blockParts *PartSet) *BlockMeta {
return &BlockMeta{
BlockID: BlockID{block.Hash(), blockParts.Header()},
Header: block.Header,
}
}
+88
View File
@@ -0,0 +1,88 @@
package types
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
crypto "github.com/tendermint/tendermint/crypto"
cmn "github.com/tendermint/tmlibs/common"
)
func TestValidateBlock(t *testing.T) {
txs := []Tx{Tx("foo"), Tx("bar")}
lastID := makeBlockIDRandom()
h := int64(3)
voteSet, _, vals := randVoteSet(h-1, 1, VoteTypePrecommit, 10, 1)
commit, err := MakeCommit(lastID, h-1, 1, voteSet, vals)
require.NoError(t, err)
block := MakeBlock(h, txs, commit)
require.NotNil(t, block)
// proper block must pass
err = block.ValidateBasic()
require.NoError(t, err)
// tamper with NumTxs
block = MakeBlock(h, txs, commit)
block.NumTxs++
err = block.ValidateBasic()
require.Error(t, err)
// remove 1/2 the commits
block = MakeBlock(h, txs, commit)
block.LastCommit.Precommits = commit.Precommits[:commit.Size()/2]
block.LastCommit.hash = nil // clear hash or change wont be noticed
err = block.ValidateBasic()
require.Error(t, err)
// tamper with LastCommitHash
block = MakeBlock(h, txs, commit)
block.LastCommitHash = []byte("something else")
err = block.ValidateBasic()
require.Error(t, err)
// tamper with data
block = MakeBlock(h, txs, commit)
block.Data.Txs[0] = Tx("something else")
block.Data.hash = nil // clear hash or change wont be noticed
err = block.ValidateBasic()
require.Error(t, err)
// tamper with DataHash
block = MakeBlock(h, txs, commit)
block.DataHash = cmn.RandBytes(len(block.DataHash))
err = block.ValidateBasic()
require.Error(t, err)
}
func makeBlockIDRandom() BlockID {
blockHash, blockPartsHeader := crypto.CRandBytes(32), PartSetHeader{123, crypto.CRandBytes(32)}
return BlockID{blockHash, blockPartsHeader}
}
func makeBlockID(hash string, partSetSize int, partSetHash string) BlockID {
return BlockID{
Hash: []byte(hash),
PartsHeader: PartSetHeader{
Total: partSetSize,
Hash: []byte(partSetHash),
},
}
}
var nilBytes []byte
func TestNilHeaderHashDoesntCrash(t *testing.T) {
assert.Equal(t, []byte((*Header)(nil).Hash()), nilBytes)
assert.Equal(t, []byte((new(Header)).Hash()), nilBytes)
}
func TestNilDataHashDoesntCrash(t *testing.T) {
assert.Equal(t, []byte((*Data)(nil).Hash()), nilBytes)
assert.Equal(t, []byte(new(Data).Hash()), nilBytes)
}
+114
View File
@@ -0,0 +1,114 @@
package types
import (
"time"
cmn "github.com/tendermint/tmlibs/common"
)
// Canonical json is amino's json for structs with fields in alphabetical order
// TimeFormat is used for generating the sigs
const TimeFormat = "2006-01-02T15:04:05.000Z"
type CanonicalJSONBlockID struct {
Hash cmn.HexBytes `json:"hash,omitempty"`
PartsHeader CanonicalJSONPartSetHeader `json:"parts,omitempty"`
}
type CanonicalJSONPartSetHeader struct {
Hash cmn.HexBytes `json:"hash,omitempty"`
Total int `json:"total,omitempty"`
}
type CanonicalJSONProposal struct {
ChainID string `json:"@chain_id"`
Type string `json:"@type"`
BlockPartsHeader CanonicalJSONPartSetHeader `json:"block_parts_header"`
Height int64 `json:"height"`
POLBlockID CanonicalJSONBlockID `json:"pol_block_id"`
POLRound int `json:"pol_round"`
Round int `json:"round"`
Timestamp string `json:"timestamp"`
}
type CanonicalJSONVote struct {
ChainID string `json:"@chain_id"`
Type string `json:"@type"`
BlockID CanonicalJSONBlockID `json:"block_id"`
Height int64 `json:"height"`
Round int `json:"round"`
Timestamp string `json:"timestamp"`
VoteType byte `json:"type"`
}
type CanonicalJSONHeartbeat struct {
ChainID string `json:"@chain_id"`
Type string `json:"@type"`
Height int64 `json:"height"`
Round int `json:"round"`
Sequence int `json:"sequence"`
ValidatorAddress Address `json:"validator_address"`
ValidatorIndex int `json:"validator_index"`
}
//-----------------------------------
// Canonicalize the structs
func CanonicalBlockID(blockID BlockID) CanonicalJSONBlockID {
return CanonicalJSONBlockID{
Hash: blockID.Hash,
PartsHeader: CanonicalPartSetHeader(blockID.PartsHeader),
}
}
func CanonicalPartSetHeader(psh PartSetHeader) CanonicalJSONPartSetHeader {
return CanonicalJSONPartSetHeader{
psh.Hash,
psh.Total,
}
}
func CanonicalProposal(chainID string, proposal *Proposal) CanonicalJSONProposal {
return CanonicalJSONProposal{
ChainID: chainID,
Type: "proposal",
BlockPartsHeader: CanonicalPartSetHeader(proposal.BlockPartsHeader),
Height: proposal.Height,
Timestamp: CanonicalTime(proposal.Timestamp),
POLBlockID: CanonicalBlockID(proposal.POLBlockID),
POLRound: proposal.POLRound,
Round: proposal.Round,
}
}
func CanonicalVote(chainID string, vote *Vote) CanonicalJSONVote {
return CanonicalJSONVote{
ChainID: chainID,
Type: "vote",
BlockID: CanonicalBlockID(vote.BlockID),
Height: vote.Height,
Round: vote.Round,
Timestamp: CanonicalTime(vote.Timestamp),
VoteType: vote.Type,
}
}
func CanonicalHeartbeat(chainID string, heartbeat *Heartbeat) CanonicalJSONHeartbeat {
return CanonicalJSONHeartbeat{
ChainID: chainID,
Type: "heartbeat",
Height: heartbeat.Height,
Round: heartbeat.Round,
Sequence: heartbeat.Sequence,
ValidatorAddress: heartbeat.ValidatorAddress,
ValidatorIndex: heartbeat.ValidatorIndex,
}
}
func CanonicalTime(t time.Time) string {
// Note that sending time over amino resets it to
// local time, we need to force UTC here, so the
// signatures match
return t.UTC().Format(TimeFormat)
}
+50
View File
@@ -0,0 +1,50 @@
package types
// Interface assertions
var _ TxEventPublisher = (*TxEventBuffer)(nil)
// TxEventBuffer is a buffer of events, which uses a slice to temporarily store
// events.
type TxEventBuffer struct {
next TxEventPublisher
capacity int
events []EventDataTx
}
// NewTxEventBuffer accepts a TxEventPublisher and returns a new buffer with the given
// capacity.
func NewTxEventBuffer(next TxEventPublisher, capacity int) *TxEventBuffer {
return &TxEventBuffer{
next: next,
capacity: capacity,
events: make([]EventDataTx, 0, capacity),
}
}
// Len returns the number of events cached.
func (b TxEventBuffer) Len() int {
return len(b.events)
}
// PublishEventTx buffers an event to be fired upon finality.
func (b *TxEventBuffer) PublishEventTx(e EventDataTx) error {
b.events = append(b.events, e)
return nil
}
// Flush publishes events by running next.PublishWithTags on all cached events.
// Blocks. Clears cached events.
func (b *TxEventBuffer) Flush() error {
for _, e := range b.events {
err := b.next.PublishEventTx(e)
if err != nil {
return err
}
}
// Clear out the elements and set the length to 0
// but maintain the underlying slice's capacity.
// See Issue https://github.com/tendermint/tendermint/issues/1189
b.events = b.events[:0]
return nil
}
+21
View File
@@ -0,0 +1,21 @@
package types
import (
"testing"
"github.com/stretchr/testify/assert"
)
type eventBusMock struct{}
func (eventBusMock) PublishEventTx(e EventDataTx) error {
return nil
}
func TestEventBuffer(t *testing.T) {
b := NewTxEventBuffer(eventBusMock{}, 1)
b.PublishEventTx(EventDataTx{})
assert.Equal(t, 1, b.Len())
b.Flush()
assert.Equal(t, 0, b.Len())
}
+167
View File
@@ -0,0 +1,167 @@
package types
import (
"context"
"fmt"
tmpubsub "github.com/tendermint/tendermint/libs/pubsub"
cmn "github.com/tendermint/tmlibs/common"
"github.com/tendermint/tmlibs/log"
)
const defaultCapacity = 0
type EventBusSubscriber interface {
Subscribe(ctx context.Context, subscriber string, query tmpubsub.Query, out chan<- interface{}) error
Unsubscribe(ctx context.Context, subscriber string, query tmpubsub.Query) error
UnsubscribeAll(ctx context.Context, subscriber string) error
}
// EventBus is a common bus for all events going through the system. All calls
// are proxied to underlying pubsub server. All events must be published using
// EventBus to ensure correct data types.
type EventBus struct {
cmn.BaseService
pubsub *tmpubsub.Server
}
// NewEventBus returns a new event bus.
func NewEventBus() *EventBus {
return NewEventBusWithBufferCapacity(defaultCapacity)
}
// NewEventBusWithBufferCapacity returns a new event bus with the given buffer capacity.
func NewEventBusWithBufferCapacity(cap int) *EventBus {
// capacity could be exposed later if needed
pubsub := tmpubsub.NewServer(tmpubsub.BufferCapacity(cap))
b := &EventBus{pubsub: pubsub}
b.BaseService = *cmn.NewBaseService(nil, "EventBus", b)
return b
}
func (b *EventBus) SetLogger(l log.Logger) {
b.BaseService.SetLogger(l)
b.pubsub.SetLogger(l.With("module", "pubsub"))
}
func (b *EventBus) OnStart() error {
return b.pubsub.OnStart()
}
func (b *EventBus) OnStop() {
b.pubsub.OnStop()
}
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) Unsubscribe(ctx context.Context, subscriber string, query tmpubsub.Query) error {
return b.pubsub.Unsubscribe(ctx, subscriber, query)
}
func (b *EventBus) UnsubscribeAll(ctx context.Context, subscriber string) error {
return b.pubsub.UnsubscribeAll(ctx, subscriber)
}
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
}
//--- block, tx, and vote events
func (b *EventBus) PublishEventNewBlock(event EventDataNewBlock) error {
return b.Publish(EventNewBlock, event)
}
func (b *EventBus) PublishEventNewBlockHeader(event EventDataNewBlockHeader) error {
return b.Publish(EventNewBlockHeader, event)
}
func (b *EventBus) PublishEventVote(event EventDataVote) error {
return b.Publish(EventVote, event)
}
// PublishEventTx publishes tx event with tags from Result. Note it will add
// predefined tags (EventTypeKey, TxHashKey). Existing tags with the same names
// will be overwritten.
func (b *EventBus) PublishEventTx(event EventDataTx) error {
// no explicit deadline for publishing events
ctx := context.Background()
tags := make(map[string]string)
// validate and fill tags from tx result
for _, tag := range event.Result.Tags {
// basic validation
if len(tag.Key) == 0 {
b.Logger.Info("Got tag with an empty key (skipping)", "tag", tag, "tx", event.Tx)
continue
}
tags[string(tag.Key)] = string(tag.Value)
}
// add predefined tags
logIfTagExists(EventTypeKey, tags, b.Logger)
tags[EventTypeKey] = EventTx
logIfTagExists(TxHashKey, tags, b.Logger)
tags[TxHashKey] = fmt.Sprintf("%X", event.Tx.Hash())
logIfTagExists(TxHeightKey, tags, b.Logger)
tags[TxHeightKey] = fmt.Sprintf("%d", event.Height)
b.pubsub.PublishWithTags(ctx, event, tmpubsub.NewTagMap(tags))
return nil
}
func (b *EventBus) PublishEventProposalHeartbeat(event EventDataProposalHeartbeat) error {
return b.Publish(EventProposalHeartbeat, event)
}
//--- EventDataRoundState events
func (b *EventBus) PublishEventNewRoundStep(event EventDataRoundState) error {
return b.Publish(EventNewRoundStep, event)
}
func (b *EventBus) PublishEventTimeoutPropose(event EventDataRoundState) error {
return b.Publish(EventTimeoutPropose, event)
}
func (b *EventBus) PublishEventTimeoutWait(event EventDataRoundState) error {
return b.Publish(EventTimeoutWait, event)
}
func (b *EventBus) PublishEventNewRound(event EventDataRoundState) error {
return b.Publish(EventNewRound, event)
}
func (b *EventBus) PublishEventCompleteProposal(event EventDataRoundState) error {
return b.Publish(EventCompleteProposal, event)
}
func (b *EventBus) PublishEventPolka(event EventDataRoundState) error {
return b.Publish(EventPolka, event)
}
func (b *EventBus) PublishEventUnlock(event EventDataRoundState) error {
return b.Publish(EventUnlock, event)
}
func (b *EventBus) PublishEventRelock(event EventDataRoundState) error {
return b.Publish(EventRelock, event)
}
func (b *EventBus) PublishEventLock(event EventDataRoundState) error {
return b.Publish(EventLock, event)
}
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)
}
}
+171
View File
@@ -0,0 +1,171 @@
package types
import (
"context"
"fmt"
"math/rand"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
abci "github.com/tendermint/abci/types"
tmpubsub "github.com/tendermint/tendermint/libs/pubsub"
tmquery "github.com/tendermint/tendermint/libs/pubsub/query"
cmn "github.com/tendermint/tmlibs/common"
)
func TestEventBusPublishEventTx(t *testing.T) {
eventBus := NewEventBus()
err := eventBus.Start()
require.NoError(t, err)
defer eventBus.Stop()
tx := Tx("foo")
result := abci.ResponseDeliverTx{Data: []byte("bar"), Tags: []cmn.KVPair{{[]byte("baz"), []byte("1")}}, Fee: cmn.KI64Pair{Key: []uint8{}, Value: 0}}
txEventsCh := make(chan interface{})
// 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)
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)
}
}()
err = eventBus.PublishEventTx(EventDataTx{TxResult{
Height: 1,
Index: 0,
Tx: tx,
Result: result,
}})
assert.NoError(t, err)
select {
case <-done:
case <-time.After(1 * time.Second):
t.Fatal("did not receive a transaction after 1 sec.")
}
}
func BenchmarkEventBus(b *testing.B) {
benchmarks := []struct {
name string
numClients int
randQueries bool
randEvents bool
}{
{"10Clients1Query1Event", 10, false, false},
{"100Clients", 100, false, false},
{"1000Clients", 1000, false, false},
{"10ClientsRandQueries1Event", 10, true, false},
{"100Clients", 100, true, false},
{"1000Clients", 1000, true, false},
{"10ClientsRandQueriesRandEvents", 10, true, true},
{"100Clients", 100, true, true},
{"1000Clients", 1000, true, true},
{"10Clients1QueryRandEvents", 10, false, true},
{"100Clients", 100, false, true},
{"1000Clients", 1000, false, true},
}
for _, bm := range benchmarks {
b.Run(bm.name, func(b *testing.B) {
benchmarkEventBus(bm.numClients, bm.randQueries, bm.randEvents, b)
})
}
}
func benchmarkEventBus(numClients int, randQueries bool, randEvents bool, b *testing.B) {
// for random* functions
rand.Seed(time.Now().Unix())
eventBus := NewEventBusWithBufferCapacity(0) // set buffer capacity to 0 so we are not testing cache
eventBus.Start()
defer eventBus.Stop()
ctx := context.Background()
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)
}
eventType := EventNewBlock
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
if randEvents {
eventType = randEvent()
}
eventBus.Publish(eventType, EventDataString("Gamora"))
}
}
var events = []string{EventBond,
EventUnbond,
EventRebond,
EventDupeout,
EventFork,
EventNewBlock,
EventNewBlockHeader,
EventNewRound,
EventNewRoundStep,
EventTimeoutPropose,
EventCompleteProposal,
EventPolka,
EventUnlock,
EventLock,
EventRelock,
EventTimeoutWait,
EventVote}
func randEvent() string {
return events[rand.Intn(len(events))]
}
var queries = []tmpubsub.Query{EventQueryBond,
EventQueryUnbond,
EventQueryRebond,
EventQueryDupeout,
EventQueryFork,
EventQueryNewBlock,
EventQueryNewBlockHeader,
EventQueryNewRound,
EventQueryNewRoundStep,
EventQueryTimeoutPropose,
EventQueryCompleteProposal,
EventQueryPolka,
EventQueryUnlock,
EventQueryLock,
EventQueryRelock,
EventQueryTimeoutWait,
EventQueryVote}
func randQuery() tmpubsub.Query {
return queries[rand.Intn(len(queries))]
}
+154
View File
@@ -0,0 +1,154 @@
package types
import (
"fmt"
amino "github.com/tendermint/go-amino"
tmpubsub "github.com/tendermint/tendermint/libs/pubsub"
tmquery "github.com/tendermint/tendermint/libs/pubsub/query"
)
// Reserved event types
const (
EventBond = "Bond"
EventCompleteProposal = "CompleteProposal"
EventDupeout = "Dupeout"
EventFork = "Fork"
EventLock = "Lock"
EventNewBlock = "NewBlock"
EventNewBlockHeader = "NewBlockHeader"
EventNewRound = "NewRound"
EventNewRoundStep = "NewRoundStep"
EventPolka = "Polka"
EventRebond = "Rebond"
EventRelock = "Relock"
EventTimeoutPropose = "TimeoutPropose"
EventTimeoutWait = "TimeoutWait"
EventTx = "Tx"
EventUnbond = "Unbond"
EventUnlock = "Unlock"
EventVote = "Vote"
EventProposalHeartbeat = "ProposalHeartbeat"
)
///////////////////////////////////////////////////////////////////////////////
// ENCODING / DECODING
///////////////////////////////////////////////////////////////////////////////
// implements events.EventData
type TMEventData interface {
AssertIsTMEventData()
// empty interface
}
func (_ EventDataNewBlock) AssertIsTMEventData() {}
func (_ EventDataNewBlockHeader) AssertIsTMEventData() {}
func (_ EventDataTx) AssertIsTMEventData() {}
func (_ EventDataRoundState) AssertIsTMEventData() {}
func (_ EventDataVote) AssertIsTMEventData() {}
func (_ EventDataProposalHeartbeat) AssertIsTMEventData() {}
func (_ EventDataString) AssertIsTMEventData() {}
func RegisterEventDatas(cdc *amino.Codec) {
cdc.RegisterInterface((*TMEventData)(nil), nil)
cdc.RegisterConcrete(EventDataNewBlock{}, "tendermint/event/NewBlock", nil)
cdc.RegisterConcrete(EventDataNewBlockHeader{}, "tendermint/event/NewBlockHeader", nil)
cdc.RegisterConcrete(EventDataTx{}, "tendermint/event/Tx", nil)
cdc.RegisterConcrete(EventDataRoundState{}, "tendermint/event/RoundState", nil)
cdc.RegisterConcrete(EventDataVote{}, "tendermint/event/Vote", nil)
cdc.RegisterConcrete(EventDataProposalHeartbeat{}, "tendermint/event/ProposalHeartbeat", nil)
cdc.RegisterConcrete(EventDataString(""), "tendermint/event/ProposalString", nil)
}
// Most event messages are basic types (a block, a transaction)
// but some (an input to a call tx or a receive) are more exotic
type EventDataNewBlock struct {
Block *Block `json:"block"`
}
// light weight event for benchmarking
type EventDataNewBlockHeader struct {
Header *Header `json:"header"`
}
// All txs fire EventDataTx
type EventDataTx struct {
TxResult
}
type EventDataProposalHeartbeat struct {
Heartbeat *Heartbeat
}
// NOTE: This goes into the replay WAL
type EventDataRoundState struct {
Height int64 `json:"height"`
Round int `json:"round"`
Step string `json:"step"`
// private, not exposed to websockets
RoundState interface{} `json:"-"`
}
type EventDataVote struct {
Vote *Vote
}
type EventDataString string
///////////////////////////////////////////////////////////////////////////////
// PUBSUB
///////////////////////////////////////////////////////////////////////////////
const (
// EventTypeKey is a reserved key, used to specify event type in tags.
EventTypeKey = "tm.event"
// TxHashKey is a reserved key, used to specify transaction's hash.
// see EventBus#PublishEventTx
TxHashKey = "tx.hash"
// TxHeightKey is a reserved key, used to specify transaction block's height.
// see EventBus#PublishEventTx
TxHeightKey = "tx.height"
)
var (
EventQueryBond = QueryForEvent(EventBond)
EventQueryUnbond = QueryForEvent(EventUnbond)
EventQueryRebond = QueryForEvent(EventRebond)
EventQueryDupeout = QueryForEvent(EventDupeout)
EventQueryFork = QueryForEvent(EventFork)
EventQueryNewBlock = QueryForEvent(EventNewBlock)
EventQueryNewBlockHeader = QueryForEvent(EventNewBlockHeader)
EventQueryNewRound = QueryForEvent(EventNewRound)
EventQueryNewRoundStep = QueryForEvent(EventNewRoundStep)
EventQueryTimeoutPropose = QueryForEvent(EventTimeoutPropose)
EventQueryCompleteProposal = QueryForEvent(EventCompleteProposal)
EventQueryPolka = QueryForEvent(EventPolka)
EventQueryUnlock = QueryForEvent(EventUnlock)
EventQueryLock = QueryForEvent(EventLock)
EventQueryRelock = QueryForEvent(EventRelock)
EventQueryTimeoutWait = QueryForEvent(EventTimeoutWait)
EventQueryVote = QueryForEvent(EventVote)
EventQueryProposalHeartbeat = QueryForEvent(EventProposalHeartbeat)
EventQueryTx = QueryForEvent(EventTx)
)
func EventQueryTxFor(tx Tx) tmpubsub.Query {
return tmquery.MustParse(fmt.Sprintf("%s='%s' AND %s='%X'", EventTypeKey, EventTx, TxHashKey, tx.Hash()))
}
func QueryForEvent(eventType string) tmpubsub.Query {
return tmquery.MustParse(fmt.Sprintf("%s='%s'", EventTypeKey, eventType))
}
// BlockEventPublisher publishes all block related events
type BlockEventPublisher interface {
PublishEventNewBlock(block EventDataNewBlock) error
PublishEventNewBlockHeader(header EventDataNewBlockHeader) error
PublishEventTx(EventDataTx) error
}
type TxEventPublisher interface {
PublishEventTx(EventDataTx) error
}
+212
View File
@@ -0,0 +1,212 @@
package types
import (
"bytes"
"fmt"
"github.com/tendermint/go-amino"
"github.com/tendermint/tendermint/crypto"
"github.com/tendermint/tmlibs/merkle"
)
// ErrEvidenceInvalid wraps a piece of evidence and the error denoting how or why it is invalid.
type ErrEvidenceInvalid struct {
Evidence Evidence
ErrorValue error
}
func NewEvidenceInvalidErr(ev Evidence, err error) *ErrEvidenceInvalid {
return &ErrEvidenceInvalid{ev, err}
}
// Error returns a string representation of the error.
func (err *ErrEvidenceInvalid) Error() string {
return fmt.Sprintf("Invalid evidence: %v. Evidence: %v", err.ErrorValue, err.Evidence)
}
//-------------------------------------------
// Evidence represents any provable malicious activity by a validator
type Evidence interface {
Height() int64 // height of the equivocation
Address() []byte // address of the equivocating validator
Hash() []byte // hash of the evidence
Verify(chainID string, pubKey crypto.PubKey) error // verify the evidence
Equal(Evidence) bool // check equality of evidence
String() string
}
func RegisterEvidences(cdc *amino.Codec) {
cdc.RegisterInterface((*Evidence)(nil), nil)
cdc.RegisterConcrete(&DuplicateVoteEvidence{}, "tendermint/DuplicateVoteEvidence", nil)
}
//-------------------------------------------
// DuplicateVoteEvidence contains evidence a validator signed two conflicting votes.
type DuplicateVoteEvidence struct {
PubKey crypto.PubKey
VoteA *Vote
VoteB *Vote
}
// String returns a string representation of the evidence.
func (dve *DuplicateVoteEvidence) String() string {
return fmt.Sprintf("VoteA: %v; VoteB: %v", dve.VoteA, dve.VoteB)
}
// Height returns the height this evidence refers to.
func (dve *DuplicateVoteEvidence) Height() int64 {
return dve.VoteA.Height
}
// Address returns the address of the validator.
func (dve *DuplicateVoteEvidence) Address() []byte {
return dve.PubKey.Address()
}
// Hash returns the hash of the evidence.
func (dve *DuplicateVoteEvidence) Hash() []byte {
return aminoHasher(dve).Hash()
}
// Verify returns an error if the two votes aren't conflicting.
// To be conflicting, they must be from the same validator, for the same H/R/S, but for different blocks.
func (dve *DuplicateVoteEvidence) Verify(chainID string, pubKey crypto.PubKey) error {
// H/R/S must be the same
if dve.VoteA.Height != dve.VoteB.Height ||
dve.VoteA.Round != dve.VoteB.Round ||
dve.VoteA.Type != dve.VoteB.Type {
return fmt.Errorf("DuplicateVoteEvidence Error: H/R/S does not match. Got %v and %v", dve.VoteA, dve.VoteB)
}
// Address must be the same
if !bytes.Equal(dve.VoteA.ValidatorAddress, dve.VoteB.ValidatorAddress) {
return fmt.Errorf("DuplicateVoteEvidence Error: Validator addresses do not match. Got %X and %X", dve.VoteA.ValidatorAddress, dve.VoteB.ValidatorAddress)
}
// Index must be the same
if dve.VoteA.ValidatorIndex != dve.VoteB.ValidatorIndex {
return fmt.Errorf("DuplicateVoteEvidence Error: Validator indices do not match. Got %d and %d", dve.VoteA.ValidatorIndex, dve.VoteB.ValidatorIndex)
}
// BlockIDs must be different
if dve.VoteA.BlockID.Equals(dve.VoteB.BlockID) {
return fmt.Errorf("DuplicateVoteEvidence Error: BlockIDs are the same (%v) - not a real duplicate vote", dve.VoteA.BlockID)
}
// pubkey must match address (this should already be true, sanity check)
addr := dve.VoteA.ValidatorAddress
if !bytes.Equal(pubKey.Address(), addr) {
return fmt.Errorf("DuplicateVoteEvidence FAILED SANITY CHECK - address (%X) doesn't match pubkey (%v - %X)",
addr, pubKey, pubKey.Address())
}
// Signatures must be valid
if !pubKey.VerifyBytes(dve.VoteA.SignBytes(chainID), dve.VoteA.Signature) {
return fmt.Errorf("DuplicateVoteEvidence Error verifying VoteA: %v", ErrVoteInvalidSignature)
}
if !pubKey.VerifyBytes(dve.VoteB.SignBytes(chainID), dve.VoteB.Signature) {
return fmt.Errorf("DuplicateVoteEvidence Error verifying VoteB: %v", ErrVoteInvalidSignature)
}
return nil
}
// Equal checks if two pieces of evidence are equal.
func (dve *DuplicateVoteEvidence) Equal(ev Evidence) bool {
if _, ok := ev.(*DuplicateVoteEvidence); !ok {
return false
}
// just check their hashes
dveHash := aminoHasher(dve).Hash()
evHash := aminoHasher(ev).Hash()
return bytes.Equal(dveHash, evHash)
}
//-----------------------------------------------------------------
// UNSTABLE
type MockGoodEvidence struct {
Height_ int64
Address_ []byte
}
// UNSTABLE
func NewMockGoodEvidence(height int64, idx int, address []byte) MockGoodEvidence {
return MockGoodEvidence{height, address}
}
func (e MockGoodEvidence) Height() int64 { return e.Height_ }
func (e MockGoodEvidence) Address() []byte { return e.Address_ }
func (e MockGoodEvidence) Hash() []byte {
return []byte(fmt.Sprintf("%d-%x", e.Height_, e.Address_))
}
func (e MockGoodEvidence) Verify(chainID string, pubKey crypto.PubKey) error { return nil }
func (e MockGoodEvidence) Equal(ev Evidence) bool {
e2 := ev.(MockGoodEvidence)
return e.Height_ == e2.Height_ &&
bytes.Equal(e.Address_, e2.Address_)
}
func (e MockGoodEvidence) String() string {
return fmt.Sprintf("GoodEvidence: %d/%s", e.Height_, e.Address_)
}
// UNSTABLE
type MockBadEvidence struct {
MockGoodEvidence
}
func (e MockBadEvidence) Verify(chainID string, pubKey crypto.PubKey) error {
return fmt.Errorf("MockBadEvidence")
}
func (e MockBadEvidence) Equal(ev Evidence) bool {
e2 := ev.(MockBadEvidence)
return e.Height_ == e2.Height_ &&
bytes.Equal(e.Address_, e2.Address_)
}
func (e MockBadEvidence) String() string {
return fmt.Sprintf("BadEvidence: %d/%s", e.Height_, e.Address_)
}
//-------------------------------------------
// EvidenceList is a list of Evidence. Evidences is not a word.
type EvidenceList []Evidence
// Hash returns the simple merkle root hash of the EvidenceList.
func (evl EvidenceList) Hash() []byte {
// Recursive impl.
// Copied from tmlibs/merkle to avoid allocations
switch len(evl) {
case 0:
return nil
case 1:
return evl[0].Hash()
default:
left := EvidenceList(evl[:(len(evl)+1)/2]).Hash()
right := EvidenceList(evl[(len(evl)+1)/2:]).Hash()
return merkle.SimpleHashFromTwoHashes(left, right)
}
}
func (evl EvidenceList) String() string {
s := ""
for _, e := range evl {
s += fmt.Sprintf("%s\t\t", e)
}
return s
}
// Has returns true if the evidence is in the EvidenceList.
func (evl EvidenceList) Has(evidence Evidence) bool {
for _, ev := range evl {
if ev.Equal(evidence) {
return true
}
}
return false
}
+74
View File
@@ -0,0 +1,74 @@
package types
import (
"testing"
"github.com/stretchr/testify/assert"
)
type voteData struct {
vote1 *Vote
vote2 *Vote
valid bool
}
func makeVote(val PrivValidator, chainID string, valIndex int, height int64, round, step int, blockID BlockID) *Vote {
v := &Vote{
ValidatorAddress: val.GetAddress(),
ValidatorIndex: valIndex,
Height: height,
Round: round,
Type: byte(step),
BlockID: blockID,
}
err := val.SignVote(chainID, v)
if err != nil {
panic(err)
}
return v
}
func TestEvidence(t *testing.T) {
val := NewMockPV()
val2 := NewMockPV()
blockID := makeBlockID("blockhash", 1000, "partshash")
blockID2 := makeBlockID("blockhash2", 1000, "partshash")
blockID3 := makeBlockID("blockhash", 10000, "partshash")
blockID4 := makeBlockID("blockhash", 10000, "partshash2")
chainID := "mychain"
vote1 := makeVote(val, chainID, 0, 10, 2, 1, blockID)
badVote := makeVote(val, chainID, 0, 10, 2, 1, blockID)
err := val2.SignVote(chainID, badVote)
if err != nil {
panic(err)
}
cases := []voteData{
{vote1, makeVote(val, chainID, 0, 10, 2, 1, blockID2), true}, // different block ids
{vote1, makeVote(val, chainID, 0, 10, 2, 1, blockID3), true},
{vote1, makeVote(val, chainID, 0, 10, 2, 1, blockID4), true},
{vote1, makeVote(val, chainID, 0, 10, 2, 1, blockID), false}, // wrong block id
{vote1, makeVote(val, "mychain2", 0, 10, 2, 1, blockID2), false}, // wrong chain id
{vote1, makeVote(val, chainID, 1, 10, 2, 1, blockID2), false}, // wrong val index
{vote1, makeVote(val, chainID, 0, 11, 2, 1, blockID2), false}, // wrong height
{vote1, makeVote(val, chainID, 0, 10, 3, 1, blockID2), false}, // wrong round
{vote1, makeVote(val, chainID, 0, 10, 2, 2, blockID2), false}, // wrong step
{vote1, makeVote(val2, chainID, 0, 10, 2, 1, blockID), false}, // wrong validator
{vote1, badVote, false}, // signed by wrong key
}
pubKey := val.GetPubKey()
for _, c := range cases {
ev := &DuplicateVoteEvidence{
VoteA: c.vote1,
VoteB: c.vote2,
}
if c.valid {
assert.Nil(t, ev.Verify(chainID, pubKey), "evidence should be valid")
} else {
assert.NotNil(t, ev.Verify(chainID, pubKey), "evidence should be invalid")
}
}
}
+123
View File
@@ -0,0 +1,123 @@
package types
import (
"encoding/json"
"io/ioutil"
"time"
"github.com/tendermint/tendermint/crypto"
cmn "github.com/tendermint/tmlibs/common"
)
//------------------------------------------------------------
// core types for a genesis definition
// GenesisValidator is an initial validator.
type GenesisValidator struct {
PubKey crypto.PubKey `json:"pub_key"`
Power int64 `json:"power"`
Name string `json:"name"`
}
// GenesisDoc defines the initial conditions for a tendermint blockchain, in particular its validator set.
type GenesisDoc struct {
GenesisTime time.Time `json:"genesis_time"`
ChainID string `json:"chain_id"`
ConsensusParams *ConsensusParams `json:"consensus_params,omitempty"`
Validators []GenesisValidator `json:"validators"`
AppHash cmn.HexBytes `json:"app_hash"`
AppStateJSON json.RawMessage `json:"app_state,omitempty"`
AppOptions json.RawMessage `json:"app_options,omitempty"` // DEPRECATED
}
// AppState returns raw application state.
// TODO: replace with AppState field during next breaking release (0.18)
func (genDoc *GenesisDoc) AppState() json.RawMessage {
if len(genDoc.AppOptions) > 0 {
return genDoc.AppOptions
}
return genDoc.AppStateJSON
}
// SaveAs is a utility method for saving GenensisDoc as a JSON file.
func (genDoc *GenesisDoc) SaveAs(file string) error {
genDocBytes, err := cdc.MarshalJSONIndent(genDoc, "", " ")
if err != nil {
return err
}
return cmn.WriteFile(file, genDocBytes, 0644)
}
// ValidatorHash returns the hash of the validator set contained in the GenesisDoc
func (genDoc *GenesisDoc) ValidatorHash() []byte {
vals := make([]*Validator, len(genDoc.Validators))
for i, v := range genDoc.Validators {
vals[i] = NewValidator(v.PubKey, v.Power)
}
vset := NewValidatorSet(vals)
return vset.Hash()
}
// ValidateAndComplete checks that all necessary fields are present
// and fills in defaults for optional fields left empty
func (genDoc *GenesisDoc) ValidateAndComplete() error {
if genDoc.ChainID == "" {
return cmn.NewError("Genesis doc must include non-empty chain_id")
}
if genDoc.ConsensusParams == nil {
genDoc.ConsensusParams = DefaultConsensusParams()
} else {
if err := genDoc.ConsensusParams.Validate(); err != nil {
return err
}
}
if len(genDoc.Validators) == 0 {
return cmn.NewError("The genesis file must have at least one validator")
}
for _, v := range genDoc.Validators {
if v.Power == 0 {
return cmn.NewError("The genesis file cannot contain validators with no voting power: %v", v)
}
}
if genDoc.GenesisTime.IsZero() {
genDoc.GenesisTime = time.Now()
}
return nil
}
//------------------------------------------------------------
// Make genesis state from file
// GenesisDocFromJSON unmarshalls JSON data into a GenesisDoc.
func GenesisDocFromJSON(jsonBlob []byte) (*GenesisDoc, error) {
genDoc := GenesisDoc{}
err := cdc.UnmarshalJSON(jsonBlob, &genDoc)
if err != nil {
return nil, err
}
if err := genDoc.ValidateAndComplete(); err != nil {
return nil, err
}
return &genDoc, err
}
// GenesisDocFromFile reads JSON data from a file and unmarshalls it into a GenesisDoc.
func GenesisDocFromFile(genDocFile string) (*GenesisDoc, error) {
jsonBlob, err := ioutil.ReadFile(genDocFile)
if err != nil {
return nil, cmn.ErrorWrap(err, "Couldn't read GenesisDoc file")
}
genDoc, err := GenesisDocFromJSON(jsonBlob)
if err != nil {
return nil, cmn.ErrorWrap(err, cmn.Fmt("Error reading GenesisDoc at %v", genDocFile))
}
return genDoc, nil
}
+61
View File
@@ -0,0 +1,61 @@
package types
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/tendermint/tendermint/crypto"
)
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
[]byte(`{"chain_id":"mychain"}`), // missing validators
[]byte(`{"chain_id":"mychain","validators":[]}`), // missing validators
[]byte(`{"chain_id":"mychain","validators":[{}]}`), // missing validators
[]byte(`{"chain_id":"mychain","validators":null}`), // missing validators
[]byte(`{"chain_id":"mychain"}`), // missing validators
[]byte(`{"validators":[{"pub_key":{"type":"tendermint/PubKeyEd25519","value":"AT/+aaL1eB0477Mud9JMm8Sh8BIvOYlPGC9KkIUmFaE="},"power":"10","name":""}]}`), // missing chain_id
}
for _, testCase := range testCases {
_, err := GenesisDocFromJSON(testCase)
assert.Error(t, err, "expected error for empty genDoc json")
}
}
func TestGenesisGood(t *testing.T) {
// test a good one by raw json
genDocBytes := []byte(`{"genesis_time":"0001-01-01T00:00:00Z","chain_id":"test-chain-QDKdJr","consensus_params":null,"validators":[{"pub_key":{"type":"tendermint/PubKeyEd25519","value":"AT/+aaL1eB0477Mud9JMm8Sh8BIvOYlPGC9KkIUmFaE="},"power":"10","name":""}],"app_hash":"","app_state":{"account_owner": "Bob"}}`)
_, err := GenesisDocFromJSON(genDocBytes)
assert.NoError(t, err, "expected no error for good genDoc json")
// create a base gendoc from struct
baseGenDoc := &GenesisDoc{
ChainID: "abc",
Validators: []GenesisValidator{{crypto.GenPrivKeyEd25519().PubKey(), 10, "myval"}},
}
genDocBytes, err = cdc.MarshalJSON(baseGenDoc)
assert.NoError(t, err, "error marshalling genDoc")
// test base gendoc and check consensus params were filled
genDoc, err := GenesisDocFromJSON(genDocBytes)
assert.NoError(t, err, "expected no error for valid genDoc json")
assert.NotNil(t, genDoc.ConsensusParams, "expected consensus params to be filled in")
// create json with consensus params filled
genDocBytes, err = cdc.MarshalJSON(genDoc)
assert.NoError(t, err, "error marshalling genDoc")
genDoc, err = GenesisDocFromJSON(genDocBytes)
assert.NoError(t, err, "expected no error for valid genDoc json")
// test with invalid consensus params
genDoc.ConsensusParams.BlockSize.MaxBytes = 0
genDocBytes, err = cdc.MarshalJSON(genDoc)
assert.NoError(t, err, "error marshalling genDoc")
genDoc, err = GenesisDocFromJSON(genDocBytes)
assert.Error(t, err, "expected error for genDoc json with block size of 0")
}
+52
View File
@@ -0,0 +1,52 @@
package types
import (
"fmt"
"github.com/tendermint/tendermint/crypto"
cmn "github.com/tendermint/tmlibs/common"
)
// Heartbeat is a simple vote-like structure so validators can
// alert others that they are alive and waiting for transactions.
// Note: We aren't adding ",omitempty" to Heartbeat's
// json field tags because we always want the JSON
// representation to be in its canonical form.
type Heartbeat struct {
ValidatorAddress Address `json:"validator_address"`
ValidatorIndex int `json:"validator_index"`
Height int64 `json:"height"`
Round int `json:"round"`
Sequence int `json:"sequence"`
Signature crypto.Signature `json:"signature"`
}
// SignBytes returns the Heartbeat bytes for signing.
// It panics if the Heartbeat is nil.
func (heartbeat *Heartbeat) SignBytes(chainID string) []byte {
bz, err := cdc.MarshalJSON(CanonicalHeartbeat(chainID, heartbeat))
if err != nil {
panic(err)
}
return bz
}
// Copy makes a copy of the Heartbeat.
func (heartbeat *Heartbeat) Copy() *Heartbeat {
if heartbeat == nil {
return nil
}
heartbeatCopy := *heartbeat
return &heartbeatCopy
}
// String returns a string representation of the Heartbeat.
func (heartbeat *Heartbeat) String() string {
if heartbeat == nil {
return "nil-heartbeat"
}
return fmt.Sprintf("Heartbeat{%v:%X %v/%02d (%v) %v}",
heartbeat.ValidatorIndex, cmn.Fingerprint(heartbeat.ValidatorAddress),
heartbeat.Height, heartbeat.Round, heartbeat.Sequence, heartbeat.Signature)
}
+54
View File
@@ -0,0 +1,54 @@
package types
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/tendermint/tendermint/crypto"
)
func TestHeartbeatCopy(t *testing.T) {
hb := &Heartbeat{ValidatorIndex: 1, Height: 10, Round: 1}
hbCopy := hb.Copy()
require.Equal(t, hbCopy, hb, "heartbeat copy should be the same")
hbCopy.Round = hb.Round + 10
require.NotEqual(t, hbCopy, hb, "heartbeat copy mutation should not change original")
var nilHb *Heartbeat
nilHbCopy := nilHb.Copy()
require.Nil(t, nilHbCopy, "copy of nil should also return nil")
}
func TestHeartbeatString(t *testing.T) {
var nilHb *Heartbeat
require.Contains(t, nilHb.String(), "nil", "expecting a string and no panic")
hb := &Heartbeat{ValidatorIndex: 1, Height: 11, Round: 2}
require.Equal(t, hb.String(), "Heartbeat{1:000000000000 11/02 (0) <nil>}")
var key crypto.PrivKeyEd25519
sig, err := key.Sign([]byte("Tendermint"))
require.NoError(t, err)
hb.Signature = sig
require.Equal(t, hb.String(), "Heartbeat{1:000000000000 11/02 (0) /FF41E371B9BF.../}")
}
func TestHeartbeatWriteSignBytes(t *testing.T) {
hb := &Heartbeat{ValidatorIndex: 1, Height: 10, Round: 1}
bz := hb.SignBytes("0xdeadbeef")
// XXX HMMMMMMM
require.Equal(t, string(bz), `{"@chain_id":"0xdeadbeef","@type":"heartbeat","height":"10","round":"1","sequence":"0","validator_address":"","validator_index":"1"}`)
plainHb := &Heartbeat{}
bz = plainHb.SignBytes("0xdeadbeef")
require.Equal(t, string(bz), `{"@chain_id":"0xdeadbeef","@type":"heartbeat","height":"0","round":"0","sequence":"0","validator_address":"","validator_index":"0"}`)
require.Panics(t, func() {
var nilHb *Heartbeat
bz := nilHb.SignBytes("0xdeadbeef")
require.Equal(t, string(bz), "null")
})
}
+7
View File
@@ -0,0 +1,7 @@
package types
// UNSTABLE
var (
PeerStateKey = "ConsensusReactor.peerState"
PeerMempoolChKey = "MempoolReactor.peerMempoolCh"
)
+77
View File
@@ -0,0 +1,77 @@
package types
import (
"context"
tmpubsub "github.com/tendermint/tendermint/libs/pubsub"
)
type NopEventBus struct{}
func (NopEventBus) Subscribe(ctx context.Context, subscriber string, query tmpubsub.Query, out chan<- interface{}) error {
return nil
}
func (NopEventBus) Unsubscribe(ctx context.Context, subscriber string, query tmpubsub.Query) error {
return nil
}
func (NopEventBus) UnsubscribeAll(ctx context.Context, subscriber string) error {
return nil
}
//--- block, tx, and vote events
func (NopEventBus) PublishEventNewBlock(block EventDataNewBlock) error {
return nil
}
func (NopEventBus) PublishEventNewBlockHeader(header EventDataNewBlockHeader) error {
return nil
}
func (NopEventBus) PublishEventVote(vote EventDataVote) error {
return nil
}
func (NopEventBus) PublishEventTx(tx EventDataTx) error {
return nil
}
//--- EventDataRoundState events
func (NopEventBus) PublishEventNewRoundStep(rs EventDataRoundState) error {
return nil
}
func (NopEventBus) PublishEventTimeoutPropose(rs EventDataRoundState) error {
return nil
}
func (NopEventBus) PublishEventTimeoutWait(rs EventDataRoundState) error {
return nil
}
func (NopEventBus) PublishEventNewRound(rs EventDataRoundState) error {
return nil
}
func (NopEventBus) PublishEventCompleteProposal(rs EventDataRoundState) error {
return nil
}
func (NopEventBus) PublishEventPolka(rs EventDataRoundState) error {
return nil
}
func (NopEventBus) PublishEventUnlock(rs EventDataRoundState) error {
return nil
}
func (NopEventBus) PublishEventRelock(rs EventDataRoundState) error {
return nil
}
func (NopEventBus) PublishEventLock(rs EventDataRoundState) error {
return nil
}
+155
View File
@@ -0,0 +1,155 @@
package types
import (
abci "github.com/tendermint/abci/types"
cmn "github.com/tendermint/tmlibs/common"
"github.com/tendermint/tmlibs/merkle"
)
const (
MaxBlockSizeBytes = 104857600 // 100MB
)
// ConsensusParams contains consensus critical parameters
// that determine the validity of blocks.
type ConsensusParams struct {
BlockSize `json:"block_size_params"`
TxSize `json:"tx_size_params"`
BlockGossip `json:"block_gossip_params"`
EvidenceParams `json:"evidence_params"`
}
// BlockSize contain limits on the block size.
type BlockSize struct {
MaxBytes int `json:"max_bytes"` // NOTE: must not be 0 nor greater than 100MB
MaxTxs int `json:"max_txs"`
MaxGas int64 `json:"max_gas"`
}
// TxSize contain limits on the tx size.
type TxSize struct {
MaxBytes int `json:"max_bytes"`
MaxGas int64 `json:"max_gas"`
}
// BlockGossip determine consensus critical elements of how blocks are gossiped
type BlockGossip struct {
BlockPartSizeBytes int `json:"block_part_size_bytes"` // NOTE: must not be 0
}
// EvidenceParams determine how we handle evidence of malfeasance
type EvidenceParams struct {
MaxAge int64 `json:"max_age"` // only accept new evidence more recent than this
}
// DefaultConsensusParams returns a default ConsensusParams.
func DefaultConsensusParams() *ConsensusParams {
return &ConsensusParams{
DefaultBlockSize(),
DefaultTxSize(),
DefaultBlockGossip(),
DefaultEvidenceParams(),
}
}
// DefaultBlockSize returns a default BlockSize.
func DefaultBlockSize() BlockSize {
return BlockSize{
MaxBytes: 22020096, // 21MB
MaxTxs: 100000,
MaxGas: -1,
}
}
// DefaultTxSize returns a default TxSize.
func DefaultTxSize() TxSize {
return TxSize{
MaxBytes: 10240, // 10kB
MaxGas: -1,
}
}
// DefaultBlockGossip returns a default BlockGossip.
func DefaultBlockGossip() BlockGossip {
return BlockGossip{
BlockPartSizeBytes: 65536, // 64kB,
}
}
// DefaultEvidence Params returns a default EvidenceParams.
func DefaultEvidenceParams() EvidenceParams {
return EvidenceParams{
MaxAge: 100000, // 27.8 hrs at 1block/s
}
}
// 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 {
// ensure some values are greater than 0
if params.BlockSize.MaxBytes <= 0 {
return cmn.NewError("BlockSize.MaxBytes must be greater than 0. Got %d", params.BlockSize.MaxBytes)
}
if params.BlockGossip.BlockPartSizeBytes <= 0 {
return cmn.NewError("BlockGossip.BlockPartSizeBytes must be greater than 0. Got %d", params.BlockGossip.BlockPartSizeBytes)
}
// ensure blocks aren't too big
if params.BlockSize.MaxBytes > MaxBlockSizeBytes {
return cmn.NewError("BlockSize.MaxBytes is too big. %d > %d",
params.BlockSize.MaxBytes, MaxBlockSizeBytes)
}
return nil
}
// Hash returns a merkle hash of the parameters to store
// in the block header
func (params *ConsensusParams) Hash() []byte {
return merkle.SimpleHashFromMap(map[string]merkle.Hasher{
"block_gossip_part_size_bytes": aminoHasher(params.BlockGossip.BlockPartSizeBytes),
"block_size_max_bytes": aminoHasher(params.BlockSize.MaxBytes),
"block_size_max_gas": aminoHasher(params.BlockSize.MaxGas),
"block_size_max_txs": aminoHasher(params.BlockSize.MaxTxs),
"tx_size_max_bytes": aminoHasher(params.TxSize.MaxBytes),
"tx_size_max_gas": aminoHasher(params.TxSize.MaxGas),
})
}
// Update returns a copy of the params with updates from the non-zero fields of p2.
// NOTE: note: must not modify the original
func (params ConsensusParams) Update(params2 *abci.ConsensusParams) ConsensusParams {
res := params // explicit copy
if params2 == nil {
return res
}
// we must defensively consider any structs may be nil
// XXX: it's cast city over here. It's ok because we only do int32->int
// but still, watch it champ.
if params2.BlockSize != nil {
if params2.BlockSize.MaxBytes > 0 {
res.BlockSize.MaxBytes = int(params2.BlockSize.MaxBytes)
}
if params2.BlockSize.MaxTxs > 0 {
res.BlockSize.MaxTxs = int(params2.BlockSize.MaxTxs)
}
if params2.BlockSize.MaxGas > 0 {
res.BlockSize.MaxGas = params2.BlockSize.MaxGas
}
}
if params2.TxSize != nil {
if params2.TxSize.MaxBytes > 0 {
res.TxSize.MaxBytes = int(params2.TxSize.MaxBytes)
}
if params2.TxSize.MaxGas > 0 {
res.TxSize.MaxGas = params2.TxSize.MaxGas
}
}
if params2.BlockGossip != nil {
if params2.BlockGossip.BlockPartSizeBytes > 0 {
res.BlockGossip.BlockPartSizeBytes = int(params2.BlockGossip.BlockPartSizeBytes)
}
}
return res
}
+88
View File
@@ -0,0 +1,88 @@
package types
import (
"bytes"
"sort"
"testing"
"github.com/stretchr/testify/assert"
)
func newConsensusParams(blockSize, partSize int) ConsensusParams {
return ConsensusParams{
BlockSize: BlockSize{MaxBytes: blockSize},
BlockGossip: BlockGossip{BlockPartSizeBytes: partSize},
}
}
func TestConsensusParamsValidation(t *testing.T) {
testCases := []struct {
params ConsensusParams
valid bool
}{
{newConsensusParams(1, 1), true},
{newConsensusParams(1, 0), false},
{newConsensusParams(0, 1), false},
{newConsensusParams(0, 0), false},
{newConsensusParams(0, 10), false},
{newConsensusParams(10, -1), false},
{newConsensusParams(47*1024*1024, 400), true},
{newConsensusParams(10, 400), true},
{newConsensusParams(100*1024*1024, 400), true},
{newConsensusParams(101*1024*1024, 400), false},
{newConsensusParams(1024*1024*1024, 400), false},
}
for _, testCase := range testCases {
if testCase.valid {
assert.NoError(t, testCase.params.Validate(), "expected no error for valid params")
} else {
assert.Error(t, testCase.params.Validate(), "expected error for non valid params")
}
}
}
func makeParams(blockBytes, blockTx, blockGas, txBytes,
txGas, partSize int) ConsensusParams {
return ConsensusParams{
BlockSize: BlockSize{
MaxBytes: blockBytes,
MaxTxs: blockTx,
MaxGas: int64(blockGas),
},
TxSize: TxSize{
MaxBytes: txBytes,
MaxGas: int64(txGas),
},
BlockGossip: BlockGossip{
BlockPartSizeBytes: partSize,
},
}
}
func TestConsensusParamsHash(t *testing.T) {
params := []ConsensusParams{
makeParams(1, 2, 3, 4, 5, 6),
makeParams(7, 2, 3, 4, 5, 6),
makeParams(1, 7, 3, 4, 5, 6),
makeParams(1, 2, 7, 4, 5, 6),
makeParams(1, 2, 3, 7, 5, 6),
makeParams(1, 2, 3, 4, 7, 6),
makeParams(1, 2, 3, 4, 5, 7),
makeParams(6, 5, 4, 3, 2, 1),
}
hashes := make([][]byte, len(params))
for i := range params {
hashes[i] = params[i].Hash()
}
// make sure there are no duplicates...
// sort, then check in order for matches
sort.Slice(hashes, func(i, j int) bool {
return bytes.Compare(hashes[i], hashes[j]) < 0
})
for i := 0; i < len(hashes)-1; i++ {
assert.NotEqual(t, hashes[i], hashes[i+1])
}
}
+281
View File
@@ -0,0 +1,281 @@
package types
import (
"bytes"
"errors"
"fmt"
"io"
"sync"
"golang.org/x/crypto/ripemd160"
cmn "github.com/tendermint/tmlibs/common"
"github.com/tendermint/tmlibs/merkle"
)
var (
ErrPartSetUnexpectedIndex = errors.New("Error part set unexpected index")
ErrPartSetInvalidProof = errors.New("Error part set invalid proof")
)
type Part struct {
Index int `json:"index"`
Bytes cmn.HexBytes `json:"bytes"`
Proof merkle.SimpleProof `json:"proof"`
// Cache
hash []byte
}
func (part *Part) Hash() []byte {
if part.hash != nil {
return part.hash
}
hasher := ripemd160.New()
hasher.Write(part.Bytes) // nolint: errcheck, gas
part.hash = hasher.Sum(nil)
return part.hash
}
func (part *Part) String() string {
return part.StringIndented("")
}
func (part *Part) StringIndented(indent string) string {
return fmt.Sprintf(`Part{#%v
%s Bytes: %X...
%s Proof: %v
%s}`,
part.Index,
indent, cmn.Fingerprint(part.Bytes),
indent, part.Proof.StringIndented(indent+" "),
indent)
}
//-------------------------------------
type PartSetHeader struct {
Total int `json:"total"`
Hash cmn.HexBytes `json:"hash"`
}
func (psh PartSetHeader) String() string {
return fmt.Sprintf("%v:%X", psh.Total, cmn.Fingerprint(psh.Hash))
}
func (psh PartSetHeader) IsZero() bool {
return psh.Total == 0
}
func (psh PartSetHeader) Equals(other PartSetHeader) bool {
return psh.Total == other.Total && bytes.Equal(psh.Hash, other.Hash)
}
//-------------------------------------
type PartSet struct {
total int
hash []byte
mtx sync.Mutex
parts []*Part
partsBitArray *cmn.BitArray
count int
}
// Returns an immutable, full PartSet from the data bytes.
// The data bytes are split into "partSize" chunks, and merkle tree computed.
func NewPartSetFromData(data []byte, partSize int) *PartSet {
// divide data into 4kb parts.
total := (len(data) + partSize - 1) / partSize
parts := make([]*Part, total)
parts_ := make([]merkle.Hasher, total)
partsBitArray := cmn.NewBitArray(total)
for i := 0; i < total; i++ {
part := &Part{
Index: i,
Bytes: data[i*partSize : cmn.MinInt(len(data), (i+1)*partSize)],
}
parts[i] = part
parts_[i] = part
partsBitArray.SetIndex(i, true)
}
// Compute merkle proofs
root, proofs := merkle.SimpleProofsFromHashers(parts_)
for i := 0; i < total; i++ {
parts[i].Proof = *proofs[i]
}
return &PartSet{
total: total,
hash: root,
parts: parts,
partsBitArray: partsBitArray,
count: total,
}
}
// Returns an empty PartSet ready to be populated.
func NewPartSetFromHeader(header PartSetHeader) *PartSet {
return &PartSet{
total: header.Total,
hash: header.Hash,
parts: make([]*Part, header.Total),
partsBitArray: cmn.NewBitArray(header.Total),
count: 0,
}
}
func (ps *PartSet) Header() PartSetHeader {
if ps == nil {
return PartSetHeader{}
}
return PartSetHeader{
Total: ps.total,
Hash: ps.hash,
}
}
func (ps *PartSet) HasHeader(header PartSetHeader) bool {
if ps == nil {
return false
}
return ps.Header().Equals(header)
}
func (ps *PartSet) BitArray() *cmn.BitArray {
ps.mtx.Lock()
defer ps.mtx.Unlock()
return ps.partsBitArray.Copy()
}
func (ps *PartSet) Hash() []byte {
if ps == nil {
return nil
}
return ps.hash
}
func (ps *PartSet) HashesTo(hash []byte) bool {
if ps == nil {
return false
}
return bytes.Equal(ps.hash, hash)
}
func (ps *PartSet) Count() int {
if ps == nil {
return 0
}
return ps.count
}
func (ps *PartSet) Total() int {
if ps == nil {
return 0
}
return ps.total
}
func (ps *PartSet) AddPart(part *Part) (bool, error) {
ps.mtx.Lock()
defer ps.mtx.Unlock()
// Invalid part index
if part.Index >= ps.total {
return false, ErrPartSetUnexpectedIndex
}
// If part already exists, return false.
if ps.parts[part.Index] != nil {
return false, nil
}
// Check hash proof
if !part.Proof.Verify(part.Index, ps.total, part.Hash(), ps.Hash()) {
return false, ErrPartSetInvalidProof
}
// Add part
ps.parts[part.Index] = part
ps.partsBitArray.SetIndex(part.Index, true)
ps.count++
return true, nil
}
func (ps *PartSet) GetPart(index int) *Part {
ps.mtx.Lock()
defer ps.mtx.Unlock()
return ps.parts[index]
}
func (ps *PartSet) IsComplete() bool {
return ps.count == ps.total
}
func (ps *PartSet) GetReader() io.Reader {
if !ps.IsComplete() {
cmn.PanicSanity("Cannot GetReader() on incomplete PartSet")
}
return NewPartSetReader(ps.parts)
}
type PartSetReader struct {
i int
parts []*Part
reader *bytes.Reader
}
func NewPartSetReader(parts []*Part) *PartSetReader {
return &PartSetReader{
i: 0,
parts: parts,
reader: bytes.NewReader(parts[0].Bytes),
}
}
func (psr *PartSetReader) Read(p []byte) (n int, err error) {
readerLen := psr.reader.Len()
if readerLen >= len(p) {
return psr.reader.Read(p)
} else if readerLen > 0 {
n1, err := psr.Read(p[:readerLen])
if err != nil {
return n1, err
}
n2, err := psr.Read(p[readerLen:])
return n1 + n2, err
}
psr.i++
if psr.i >= len(psr.parts) {
return 0, io.EOF
}
psr.reader = bytes.NewReader(psr.parts[psr.i].Bytes)
return psr.Read(p)
}
func (ps *PartSet) StringShort() string {
if ps == nil {
return "nil-PartSet"
}
ps.mtx.Lock()
defer ps.mtx.Unlock()
return fmt.Sprintf("(%v of %v)", ps.Count(), ps.Total())
}
func (ps *PartSet) MarshalJSON() ([]byte, error) {
if ps == nil {
return []byte("{}"), nil
}
ps.mtx.Lock()
defer ps.mtx.Unlock()
return cdc.MarshalJSON(struct {
CountTotal string `json:"count/total"`
PartsBitArray *cmn.BitArray `json:"parts_bit_array"`
}{
fmt.Sprintf("%d/%d", ps.Count(), ps.Total()),
ps.partsBitArray,
})
}
+90
View File
@@ -0,0 +1,90 @@
package types
import (
"bytes"
"io/ioutil"
"testing"
cmn "github.com/tendermint/tmlibs/common"
)
const (
testPartSize = 65536 // 64KB ... 4096 // 4KB
)
func TestBasicPartSet(t *testing.T) {
// Construct random data of size partSize * 100
data := cmn.RandBytes(testPartSize * 100)
partSet := NewPartSetFromData(data, testPartSize)
if len(partSet.Hash()) == 0 {
t.Error("Expected to get hash")
}
if partSet.Total() != 100 {
t.Errorf("Expected to get 100 parts, but got %v", partSet.Total())
}
if !partSet.IsComplete() {
t.Errorf("PartSet should be complete")
}
// Test adding parts to a new partSet.
partSet2 := NewPartSetFromHeader(partSet.Header())
for i := 0; i < partSet.Total(); i++ {
part := partSet.GetPart(i)
//t.Logf("\n%v", part)
added, err := partSet2.AddPart(part)
if !added || err != nil {
t.Errorf("Failed to add part %v, error: %v", i, err)
}
}
if !bytes.Equal(partSet.Hash(), partSet2.Hash()) {
t.Error("Expected to get same hash")
}
if partSet2.Total() != 100 {
t.Errorf("Expected to get 100 parts, but got %v", partSet2.Total())
}
if !partSet2.IsComplete() {
t.Errorf("Reconstructed PartSet should be complete")
}
// Reconstruct data, assert that they are equal.
data2Reader := partSet2.GetReader()
data2, err := ioutil.ReadAll(data2Reader)
if err != nil {
t.Errorf("Error reading data2Reader: %v", err)
}
if !bytes.Equal(data, data2) {
t.Errorf("Got wrong data.")
}
}
func TestWrongProof(t *testing.T) {
// Construct random data of size partSize * 100
data := cmn.RandBytes(testPartSize * 100)
partSet := NewPartSetFromData(data, testPartSize)
// Test adding a part with wrong data.
partSet2 := NewPartSetFromHeader(partSet.Header())
// Test adding a part with wrong trail.
part := partSet.GetPart(0)
part.Proof.Aunts[0][0] += byte(0x01)
added, err := partSet2.AddPart(part)
if added || err == nil {
t.Errorf("Expected to fail adding a part with bad trail.")
}
// Test adding a part with wrong bytes.
part = partSet.GetPart(1)
part.Bytes[0] += byte(0x01)
added, err = partSet2.AddPart(part)
if added || err == nil {
t.Errorf("Expected to fail adding a part with bad bytes.")
}
}
+104
View File
@@ -0,0 +1,104 @@
package types
import (
"bytes"
"fmt"
"github.com/tendermint/tendermint/crypto"
)
// PrivValidator defines the functionality of a local Tendermint validator
// that signs votes, proposals, and heartbeats, and never double signs.
type PrivValidator interface {
GetAddress() Address // redundant since .PubKey().Address()
GetPubKey() crypto.PubKey
SignVote(chainID string, vote *Vote) error
SignProposal(chainID string, proposal *Proposal) error
SignHeartbeat(chainID string, heartbeat *Heartbeat) error
}
//----------------------------------------
// Misc.
type PrivValidatorsByAddress []PrivValidator
func (pvs PrivValidatorsByAddress) Len() int {
return len(pvs)
}
func (pvs PrivValidatorsByAddress) Less(i, j int) bool {
return bytes.Compare(pvs[i].GetAddress(), pvs[j].GetAddress()) == -1
}
func (pvs PrivValidatorsByAddress) Swap(i, j int) {
it := pvs[i]
pvs[i] = pvs[j]
pvs[j] = it
}
//----------------------------------------
// MockPV
// MockPV implements PrivValidator without any safety or persistence.
// Only use it for testing.
type MockPV struct {
privKey crypto.PrivKey
}
func NewMockPV() *MockPV {
return &MockPV{crypto.GenPrivKeyEd25519()}
}
// Implements PrivValidator.
func (pv *MockPV) GetAddress() Address {
return pv.privKey.PubKey().Address()
}
// Implements PrivValidator.
func (pv *MockPV) GetPubKey() crypto.PubKey {
return pv.privKey.PubKey()
}
// Implements PrivValidator.
func (pv *MockPV) SignVote(chainID string, vote *Vote) error {
signBytes := vote.SignBytes(chainID)
sig, err := pv.privKey.Sign(signBytes)
if err != nil {
return err
}
vote.Signature = sig
return nil
}
// Implements PrivValidator.
func (pv *MockPV) SignProposal(chainID string, proposal *Proposal) error {
signBytes := proposal.SignBytes(chainID)
sig, err := pv.privKey.Sign(signBytes)
if err != nil {
panic(err)
}
proposal.Signature = sig
return nil
}
// signHeartbeat signs the heartbeat without any checking.
func (pv *MockPV) SignHeartbeat(chainID string, heartbeat *Heartbeat) error {
sig, err := pv.privKey.Sign(heartbeat.SignBytes(chainID))
if err != nil {
panic(err)
}
heartbeat.Signature = sig
return nil
}
// String returns a string representation of the MockPV.
func (pv *MockPV) String() string {
return fmt.Sprintf("MockPV{%v}", pv.GetAddress())
}
// XXX: Implement.
func (pv *MockPV) DisableChecks() {
// Currently this does nothing,
// as MockPV has no safety checks at all.
}
+58
View File
@@ -0,0 +1,58 @@
package types
import (
"errors"
"fmt"
"time"
"github.com/tendermint/tendermint/crypto"
)
var (
ErrInvalidBlockPartSignature = errors.New("Error invalid block part signature")
ErrInvalidBlockPartHash = errors.New("Error invalid block part hash")
)
// Proposal defines a block proposal for the consensus.
// It refers to the block only by its PartSetHeader.
// It must be signed by the correct proposer for the given Height/Round
// to be considered valid. It may depend on votes from a previous round,
// a so-called Proof-of-Lock (POL) round, as noted in the POLRound and POLBlockID.
type Proposal struct {
Height int64 `json:"height"`
Round int `json:"round"`
Timestamp time.Time `json:"timestamp"`
BlockPartsHeader PartSetHeader `json:"block_parts_header"`
POLRound int `json:"pol_round"` // -1 if null.
POLBlockID BlockID `json:"pol_block_id"` // zero if null.
Signature crypto.Signature `json:"signature"`
}
// NewProposal returns a new Proposal.
// If there is no POLRound, polRound should be -1.
func NewProposal(height int64, round int, blockPartsHeader PartSetHeader, polRound int, polBlockID BlockID) *Proposal {
return &Proposal{
Height: height,
Round: round,
Timestamp: time.Now().UTC(),
BlockPartsHeader: blockPartsHeader,
POLRound: polRound,
POLBlockID: polBlockID,
}
}
// String returns a string representation of the Proposal.
func (p *Proposal) String() string {
return fmt.Sprintf("Proposal{%v/%v %v (%v,%v) %v @ %s}",
p.Height, p.Round, p.BlockPartsHeader, p.POLRound,
p.POLBlockID, p.Signature, CanonicalTime(p.Timestamp))
}
// SignBytes returns the Proposal bytes for signing
func (p *Proposal) SignBytes(chainID string) []byte {
bz, err := cdc.MarshalJSON(CanonicalProposal(chainID, p))
if err != nil {
panic(err)
}
return bz
}
+102
View File
@@ -0,0 +1,102 @@
package types
import (
"testing"
"time"
"github.com/stretchr/testify/require"
)
var testProposal *Proposal
func init() {
var stamp, err = time.Parse(TimeFormat, "2018-02-11T07:09:22.765Z")
if err != nil {
panic(err)
}
testProposal = &Proposal{
Height: 12345,
Round: 23456,
BlockPartsHeader: PartSetHeader{111, []byte("blockparts")},
POLRound: -1,
Timestamp: stamp,
}
}
func TestProposalSignable(t *testing.T) {
signBytes := testProposal.SignBytes("test_chain_id")
signStr := string(signBytes)
expected := `{"@chain_id":"test_chain_id","@type":"proposal","block_parts_header":{"hash":"626C6F636B7061727473","total":"111"},"height":"12345","pol_block_id":{},"pol_round":"-1","round":"23456","timestamp":"2018-02-11T07:09:22.765Z"}`
if signStr != expected {
t.Errorf("Got unexpected sign string for Proposal. Expected:\n%v\nGot:\n%v", expected, signStr)
}
if signStr != expected {
t.Errorf("Got unexpected sign string for Proposal. Expected:\n%v\nGot:\n%v", expected, signStr)
}
}
func TestProposalString(t *testing.T) {
str := testProposal.String()
expected := `Proposal{12345/23456 111:626C6F636B70 (-1,:0:000000000000) <nil> @ 2018-02-11T07:09:22.765Z}`
if str != expected {
t.Errorf("Got unexpected string for Proposal. Expected:\n%v\nGot:\n%v", expected, str)
}
}
func TestProposalVerifySignature(t *testing.T) {
privVal := NewMockPV()
pubKey := privVal.GetPubKey()
prop := NewProposal(4, 2, PartSetHeader{777, []byte("proper")}, 2, BlockID{})
signBytes := prop.SignBytes("test_chain_id")
// sign it
err := privVal.SignProposal("test_chain_id", prop)
require.NoError(t, err)
// verify the same proposal
valid := pubKey.VerifyBytes(signBytes, prop.Signature)
require.True(t, valid)
// serialize, deserialize and verify again....
newProp := new(Proposal)
bs, err := cdc.MarshalBinary(prop)
require.NoError(t, err)
err = cdc.UnmarshalBinary(bs, &newProp)
require.NoError(t, err)
// verify the transmitted proposal
newSignBytes := newProp.SignBytes("test_chain_id")
require.Equal(t, string(signBytes), string(newSignBytes))
valid = pubKey.VerifyBytes(newSignBytes, newProp.Signature)
require.True(t, valid)
}
func BenchmarkProposalWriteSignBytes(b *testing.B) {
for i := 0; i < b.N; i++ {
testProposal.SignBytes("test_chain_id")
}
}
func BenchmarkProposalSign(b *testing.B) {
privVal := NewMockPV()
for i := 0; i < b.N; i++ {
err := privVal.SignProposal("test_chain_id", testProposal)
if err != nil {
b.Error(err)
}
}
}
func BenchmarkProposalVerifySignature(b *testing.B) {
privVal := NewMockPV()
err := privVal.SignProposal("test_chain_id", testProposal)
require.Nil(b, err)
pubKey := privVal.GetPubKey()
for i := 0; i < b.N; i++ {
pubKey.VerifyBytes(testProposal.SignBytes("test_chain_id"), testProposal.Signature)
}
}
+221
View File
@@ -0,0 +1,221 @@
package types
import (
"bytes"
"fmt"
"reflect"
"time"
abci "github.com/tendermint/abci/types"
crypto "github.com/tendermint/tendermint/crypto"
)
//-------------------------------------------------------
// Use strings to distinguish types in ABCI messages
const (
ABCIEvidenceTypeDuplicateVote = "duplicate/vote"
ABCIEvidenceTypeMockGood = "mock/good"
)
const (
ABCIPubKeyTypeEd25519 = "ed25519"
ABCIPubKeyTypeSecp256k1 = "secp256k1"
)
//-------------------------------------------------------
// TM2PB is used for converting Tendermint ABCI to protobuf ABCI.
// UNSTABLE
var TM2PB = tm2pb{}
type tm2pb struct{}
func (tm2pb) Header(header *Header) abci.Header {
return abci.Header{
ChainID: header.ChainID,
Height: header.Height,
Time: header.Time.Unix(),
NumTxs: int32(header.NumTxs), // XXX: overflow
TotalTxs: header.TotalTxs,
LastBlockHash: header.LastBlockID.Hash,
ValidatorsHash: header.ValidatorsHash,
AppHash: header.AppHash,
// Proposer: TODO
}
}
// XXX: panics on unknown pubkey type
func (tm2pb) Validator(val *Validator) abci.Validator {
return abci.Validator{
Address: val.PubKey.Address(),
PubKey: TM2PB.PubKey(val.PubKey),
Power: val.VotingPower,
}
}
// XXX: panics on nil or unknown pubkey type
// TODO: add cases when new pubkey types are added to go-crypto
func (tm2pb) PubKey(pubKey crypto.PubKey) abci.PubKey {
switch pk := pubKey.(type) {
case crypto.PubKeyEd25519:
return abci.PubKey{
Type: ABCIPubKeyTypeEd25519,
Data: pk[:],
}
case crypto.PubKeySecp256k1:
return abci.PubKey{
Type: ABCIPubKeyTypeSecp256k1,
Data: pk[:],
}
default:
panic(fmt.Sprintf("unknown pubkey type: %v %v", pubKey, reflect.TypeOf(pubKey)))
}
}
// XXX: panics on nil or unknown pubkey type
func (tm2pb) Validators(vals *ValidatorSet) []abci.Validator {
validators := make([]abci.Validator, len(vals.Validators))
for i, val := range vals.Validators {
validators[i] = TM2PB.Validator(val)
}
return validators
}
func (tm2pb) ConsensusParams(params *ConsensusParams) *abci.ConsensusParams {
return &abci.ConsensusParams{
BlockSize: &abci.BlockSize{
MaxBytes: int32(params.BlockSize.MaxBytes),
MaxTxs: int32(params.BlockSize.MaxTxs),
MaxGas: params.BlockSize.MaxGas,
},
TxSize: &abci.TxSize{
MaxBytes: int32(params.TxSize.MaxBytes),
MaxGas: params.TxSize.MaxGas,
},
BlockGossip: &abci.BlockGossip{
BlockPartSizeBytes: int32(params.BlockGossip.BlockPartSizeBytes),
},
}
}
// ABCI Evidence includes information from the past that's not included in the evidence itself
// so Evidence types stays compact.
// XXX: panics on nil or unknown pubkey type
func (tm2pb) Evidence(ev Evidence, valSet *ValidatorSet, evTime time.Time) abci.Evidence {
_, val := valSet.GetByAddress(ev.Address())
if val == nil {
// should already have checked this
panic(val)
}
// set type
var evType string
switch ev.(type) {
case *DuplicateVoteEvidence:
evType = ABCIEvidenceTypeDuplicateVote
case MockGoodEvidence:
// XXX: not great to have test types in production paths ...
evType = ABCIEvidenceTypeMockGood
default:
panic(fmt.Sprintf("Unknown evidence type: %v %v", ev, reflect.TypeOf(ev)))
}
return abci.Evidence{
Type: evType,
Validator: TM2PB.Validator(val),
Height: ev.Height(),
Time: evTime.Unix(),
TotalVotingPower: valSet.TotalVotingPower(),
}
}
// XXX: panics on nil or unknown pubkey type
func (tm2pb) ValidatorFromPubKeyAndPower(pubkey crypto.PubKey, power int64) abci.Validator {
pubkeyABCI := TM2PB.PubKey(pubkey)
return abci.Validator{
Address: pubkey.Address(),
PubKey: pubkeyABCI,
Power: power,
}
}
//----------------------------------------------------------------------------
// PB2TM is used for converting protobuf ABCI to Tendermint ABCI.
// UNSTABLE
var PB2TM = pb2tm{}
type pb2tm struct{}
func (pb2tm) PubKey(pubKey abci.PubKey) (crypto.PubKey, error) {
// TODO: define these in go-crypto and use them
sizeEd := 32
sizeSecp := 33
switch pubKey.Type {
case ABCIPubKeyTypeEd25519:
if len(pubKey.Data) != sizeEd {
return nil, fmt.Errorf("Invalid size for PubKeyEd25519. Got %d, expected %d", len(pubKey.Data), sizeEd)
}
var pk crypto.PubKeyEd25519
copy(pk[:], pubKey.Data)
return pk, nil
case ABCIPubKeyTypeSecp256k1:
if len(pubKey.Data) != sizeSecp {
return nil, fmt.Errorf("Invalid size for PubKeyEd25519. Got %d, expected %d", len(pubKey.Data), sizeSecp)
}
var pk crypto.PubKeySecp256k1
copy(pk[:], pubKey.Data)
return pk, nil
default:
return nil, fmt.Errorf("Unknown pubkey type %v", pubKey.Type)
}
}
func (pb2tm) Validators(vals []abci.Validator) ([]*Validator, error) {
tmVals := make([]*Validator, len(vals))
for i, v := range vals {
pub, err := PB2TM.PubKey(v.PubKey)
if err != nil {
return nil, err
}
// If the app provided an address too, it must match.
// This is just a sanity check.
if len(v.Address) > 0 {
if !bytes.Equal(pub.Address(), v.Address) {
return nil, fmt.Errorf("Validator.Address (%X) does not match PubKey.Address (%X)",
v.Address, pub.Address())
}
}
tmVals[i] = &Validator{
Address: pub.Address(),
PubKey: pub,
VotingPower: v.Power,
}
}
return tmVals, nil
}
func (pb2tm) ConsensusParams(csp *abci.ConsensusParams) ConsensusParams {
return ConsensusParams{
BlockSize: BlockSize{
MaxBytes: int(csp.BlockSize.MaxBytes), // XXX
MaxTxs: int(csp.BlockSize.MaxTxs), // XXX
MaxGas: csp.BlockSize.MaxGas,
},
TxSize: TxSize{
MaxBytes: int(csp.TxSize.MaxBytes), // XXX
MaxGas: csp.TxSize.MaxGas,
},
BlockGossip: BlockGossip{
BlockPartSizeBytes: int(csp.BlockGossip.BlockPartSizeBytes), // XXX
},
// TODO: EvidenceParams: EvidenceParams{
// MaxAge: int(csp.Evidence.MaxAge), // XXX
// },
}
}
+69
View File
@@ -0,0 +1,69 @@
package types
import (
"testing"
"github.com/stretchr/testify/assert"
abci "github.com/tendermint/abci/types"
crypto "github.com/tendermint/tendermint/crypto"
)
func TestABCIPubKey(t *testing.T) {
pkEd := crypto.GenPrivKeyEd25519().PubKey()
pkSecp := crypto.GenPrivKeySecp256k1().PubKey()
testABCIPubKey(t, pkEd, ABCIPubKeyTypeEd25519)
testABCIPubKey(t, pkSecp, ABCIPubKeyTypeSecp256k1)
}
func testABCIPubKey(t *testing.T, pk crypto.PubKey, typeStr string) {
abciPubKey := TM2PB.PubKey(pk)
pk2, err := PB2TM.PubKey(abciPubKey)
assert.Nil(t, err)
assert.Equal(t, pk, pk2)
}
func TestABCIValidators(t *testing.T) {
pkEd := crypto.GenPrivKeyEd25519().PubKey()
// correct validator
tmValExpected := &Validator{
Address: pkEd.Address(),
PubKey: pkEd,
VotingPower: 10,
}
tmVal := &Validator{
Address: pkEd.Address(),
PubKey: pkEd,
VotingPower: 10,
}
abciVal := TM2PB.Validator(tmVal)
tmVals, err := PB2TM.Validators([]abci.Validator{abciVal})
assert.Nil(t, err)
assert.Equal(t, tmValExpected, tmVals[0])
// val with address
tmVal.Address = pkEd.Address()
abciVal = TM2PB.Validator(tmVal)
tmVals, err = PB2TM.Validators([]abci.Validator{abciVal})
assert.Nil(t, err)
assert.Equal(t, tmValExpected, tmVals[0])
// val with incorrect address
abciVal = TM2PB.Validator(tmVal)
abciVal.Address = []byte("incorrect!")
tmVals, err = PB2TM.Validators([]abci.Validator{abciVal})
assert.NotNil(t, err)
assert.Nil(t, tmVals)
}
func TestABCIConsensusParams(t *testing.T) {
cp := DefaultConsensusParams()
cp.EvidenceParams.MaxAge = 0 // TODO add this to ABCI
abciCP := TM2PB.ConsensusParams(cp)
cp2 := PB2TM.ConsensusParams(abciCP)
assert.Equal(t, *cp, cp2)
}
+70
View File
@@ -0,0 +1,70 @@
package types
import (
abci "github.com/tendermint/abci/types"
cmn "github.com/tendermint/tmlibs/common"
"github.com/tendermint/tmlibs/merkle"
)
//-----------------------------------------------------------------------------
// ABCIResult is the deterministic component of a ResponseDeliverTx.
// TODO: add Tags
type ABCIResult struct {
Code uint32 `json:"code"`
Data cmn.HexBytes `json:"data"`
}
// Hash returns the canonical hash of the ABCIResult
func (a ABCIResult) Hash() []byte {
bz := aminoHash(a)
return bz
}
// ABCIResults wraps the deliver tx results to return a proof
type ABCIResults []ABCIResult
// NewResults creates ABCIResults from ResponseDeliverTx
func NewResults(del []*abci.ResponseDeliverTx) ABCIResults {
res := make(ABCIResults, len(del))
for i, d := range del {
res[i] = NewResultFromResponse(d)
}
return res
}
func NewResultFromResponse(response *abci.ResponseDeliverTx) ABCIResult {
return ABCIResult{
Code: response.Code,
Data: response.Data,
}
}
// Bytes serializes the ABCIResponse using wire
func (a ABCIResults) Bytes() []byte {
bz, err := cdc.MarshalBinary(a)
if err != nil {
panic(err)
}
return bz
}
// Hash returns a merkle hash of all results
func (a ABCIResults) Hash() []byte {
return merkle.SimpleHashFromHashers(a.toHashers())
}
// ProveResult returns a merkle proof of one result from the set
func (a ABCIResults) ProveResult(i int) merkle.SimpleProof {
_, proofs := merkle.SimpleProofsFromHashers(a.toHashers())
return *proofs[i]
}
func (a ABCIResults) toHashers() []merkle.Hasher {
l := len(a)
hashers := make([]merkle.Hasher, l)
for i := 0; i < l; i++ {
hashers[i] = a[i]
}
return hashers
}
+43
View File
@@ -0,0 +1,43 @@
package types
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestABCIResults(t *testing.T) {
a := ABCIResult{Code: 0, Data: nil}
b := ABCIResult{Code: 0, Data: []byte{}}
c := ABCIResult{Code: 0, Data: []byte("one")}
d := ABCIResult{Code: 14, Data: nil}
e := ABCIResult{Code: 14, Data: []byte("foo")}
f := ABCIResult{Code: 14, Data: []byte("bar")}
// Nil and []byte{} should produce the same hash.
require.Equal(t, a.Hash(), a.Hash())
require.Equal(t, b.Hash(), b.Hash())
require.Equal(t, a.Hash(), b.Hash())
// a and b should be the same, don't go in results.
results := ABCIResults{a, c, d, e, f}
// Make sure each result hashes properly.
var last []byte
for i, res := range results {
h := res.Hash()
assert.NotEqual(t, last, h, "%d", i)
last = h
}
// Make sure that we can get a root hash from results and verify proofs.
root := results.Hash()
assert.NotEmpty(t, root)
for i, res := range results {
proof := results.ProveResult(i)
valid := proof.Verify(i, len(results), res.Hash(), root)
assert.True(t, valid, "%d", i)
}
}
+11
View File
@@ -0,0 +1,11 @@
package types
// Signable is an interface for all signable things.
// It typically removes signatures before serializing.
// SignBytes returns the bytes to be signed
// NOTE: chainIDs are part of the SignBytes but not
// necessarily the object themselves.
// NOTE: Expected to panic if there is an error marshalling.
type Signable interface {
SignBytes(chainID string) []byte
}
+37
View File
@@ -0,0 +1,37 @@
package types
import "time"
func MakeCommit(blockID BlockID, height int64, round int,
voteSet *VoteSet,
validators []PrivValidator) (*Commit, error) {
// all sign
for i := 0; i < len(validators); i++ {
vote := &Vote{
ValidatorAddress: validators[i].GetAddress(),
ValidatorIndex: i,
Height: height,
Round: round,
Type: VoteTypePrecommit,
BlockID: blockID,
Timestamp: time.Now().UTC(),
}
_, err := signAddVote(validators[i], vote, voteSet)
if err != nil {
return nil, err
}
}
return voteSet.MakeCommit(), nil
}
func signAddVote(privVal PrivValidator, vote *Vote, voteSet *VoteSet) (signed bool, err error) {
err = privVal.SignVote(voteSet.ChainID(), vote)
if err != nil {
return false, err
}
return voteSet.AddVote(vote)
}
+128
View File
@@ -0,0 +1,128 @@
package types
import (
"bytes"
"errors"
"fmt"
abci "github.com/tendermint/abci/types"
cmn "github.com/tendermint/tmlibs/common"
"github.com/tendermint/tmlibs/merkle"
)
// Tx is an arbitrary byte array.
// NOTE: Tx has no types at this level, so when wire encoded it's just length-prefixed.
// Alternatively, it may make sense to add types here and let
// []byte be type 0x1 so we can have versioned txs if need be in the future.
type Tx []byte
// Hash computes the RIPEMD160 hash of the wire encoded transaction.
func (tx Tx) Hash() []byte {
return aminoHasher(tx).Hash()
}
// String returns the hex-encoded transaction as a string.
func (tx Tx) String() string {
return fmt.Sprintf("Tx{%X}", []byte(tx))
}
// Txs is a slice of Tx.
type Txs []Tx
// Hash returns the simple Merkle root hash of the transactions.
func (txs Txs) Hash() []byte {
// Recursive impl.
// Copied from tmlibs/merkle to avoid allocations
switch len(txs) {
case 0:
return nil
case 1:
return txs[0].Hash()
default:
left := Txs(txs[:(len(txs)+1)/2]).Hash()
right := Txs(txs[(len(txs)+1)/2:]).Hash()
return merkle.SimpleHashFromTwoHashes(left, right)
}
}
// Index returns the index of this transaction in the list, or -1 if not found
func (txs Txs) Index(tx Tx) int {
for i := range txs {
if bytes.Equal(txs[i], tx) {
return i
}
}
return -1
}
// IndexByHash returns the index of this transaction hash in the list, or -1 if not found
func (txs Txs) IndexByHash(hash []byte) int {
for i := range txs {
if bytes.Equal(txs[i].Hash(), hash) {
return i
}
}
return -1
}
// Proof returns a simple merkle proof for this node.
// Panics if i < 0 or i >= len(txs)
// TODO: optimize this!
func (txs Txs) Proof(i int) TxProof {
l := len(txs)
hashers := make([]merkle.Hasher, l)
for i := 0; i < l; i++ {
hashers[i] = txs[i]
}
root, proofs := merkle.SimpleProofsFromHashers(hashers)
return TxProof{
Index: i,
Total: l,
RootHash: root,
Data: txs[i],
Proof: *proofs[i],
}
}
// TxProof represents a Merkle proof of the presence of a transaction in the Merkle tree.
type TxProof struct {
Index, Total int
RootHash cmn.HexBytes
Data Tx
Proof merkle.SimpleProof
}
// LeadHash returns the hash of the this proof refers to.
func (tp TxProof) LeafHash() []byte {
return tp.Data.Hash()
}
// Validate verifies the proof. It returns nil if the RootHash matches the dataHash argument,
// and if the proof is internally consistent. Otherwise, it returns a sensible error.
func (tp TxProof) Validate(dataHash []byte) error {
if !bytes.Equal(dataHash, tp.RootHash) {
return errors.New("Proof matches different data hash")
}
if tp.Index < 0 {
return errors.New("Proof index cannot be negative")
}
if tp.Total <= 0 {
return errors.New("Proof total must be positive")
}
valid := tp.Proof.Verify(tp.Index, tp.Total, tp.LeafHash(), tp.RootHash)
if !valid {
return errors.New("Proof is not internally consistent")
}
return nil
}
// TxResult contains results of executing the transaction.
//
// One usage is indexing transaction results.
type TxResult struct {
Height int64 `json:"height"`
Index uint32 `json:"index"`
Tx Tx `json:"tx"`
Result abci.ResponseDeliverTx `json:"result"`
}
+125
View File
@@ -0,0 +1,125 @@
package types
import (
"bytes"
"testing"
"github.com/stretchr/testify/assert"
cmn "github.com/tendermint/tmlibs/common"
ctest "github.com/tendermint/tmlibs/test"
)
func makeTxs(cnt, size int) Txs {
txs := make(Txs, cnt)
for i := 0; i < cnt; i++ {
txs[i] = cmn.RandBytes(size)
}
return txs
}
func randInt(low, high int) int {
off := cmn.RandInt() % (high - low)
return low + off
}
func TestTxIndex(t *testing.T) {
assert := assert.New(t)
for i := 0; i < 20; i++ {
txs := makeTxs(15, 60)
for j := 0; j < len(txs); j++ {
tx := txs[j]
idx := txs.Index(tx)
assert.Equal(j, idx)
}
assert.Equal(-1, txs.Index(nil))
assert.Equal(-1, txs.Index(Tx("foodnwkf")))
}
}
func TestValidTxProof(t *testing.T) {
assert := assert.New(t)
cases := []struct {
txs Txs
}{
{Txs{{1, 4, 34, 87, 163, 1}}},
{Txs{{5, 56, 165, 2}, {4, 77}}},
{Txs{Tx("foo"), Tx("bar"), Tx("baz")}},
{makeTxs(20, 5)},
{makeTxs(7, 81)},
{makeTxs(61, 15)},
}
for h, tc := range cases {
txs := tc.txs
root := txs.Hash()
// make sure valid proof for every tx
for i := range txs {
leaf := txs[i]
leafHash := leaf.Hash()
proof := txs.Proof(i)
assert.Equal(i, proof.Index, "%d: %d", h, i)
assert.Equal(len(txs), proof.Total, "%d: %d", h, i)
assert.EqualValues(root, proof.RootHash, "%d: %d", h, i)
assert.EqualValues(leaf, proof.Data, "%d: %d", h, i)
assert.EqualValues(leafHash, proof.LeafHash(), "%d: %d", h, i)
assert.Nil(proof.Validate(root), "%d: %d", h, i)
assert.NotNil(proof.Validate([]byte("foobar")), "%d: %d", h, i)
// read-write must also work
var p2 TxProof
bin, err := cdc.MarshalBinary(proof)
assert.Nil(err)
err = cdc.UnmarshalBinary(bin, &p2)
if assert.Nil(err, "%d: %d: %+v", h, i, err) {
assert.Nil(p2.Validate(root), "%d: %d", h, i)
}
}
}
}
func TestTxProofUnchangable(t *testing.T) {
// run the other test a bunch...
for i := 0; i < 40; i++ {
testTxProofUnchangable(t)
}
}
func testTxProofUnchangable(t *testing.T) {
assert := assert.New(t)
// make some proof
txs := makeTxs(randInt(2, 100), randInt(16, 128))
root := txs.Hash()
i := randInt(0, len(txs)-1)
proof := txs.Proof(i)
// make sure it is valid to start with
assert.Nil(proof.Validate(root))
bin, err := cdc.MarshalBinary(proof)
assert.Nil(err)
// try mutating the data and make sure nothing breaks
for j := 0; j < 500; j++ {
bad := ctest.MutateByteSlice(bin)
if !bytes.Equal(bad, bin) {
assertBadProof(t, root, bad, proof)
}
}
}
// This makes sure that the proof doesn't deserialize into something valid.
func assertBadProof(t *testing.T, root []byte, bad []byte, good TxProof) {
var proof TxProof
err := cdc.UnmarshalBinary(bad, &proof)
if err == nil {
err = proof.Validate(root)
if err == nil {
// XXX Fix simple merkle proofs so the following is *not* OK.
// This can happen if we have a slightly different total (where the
// path ends up the same). If it is something else, we have a real
// problem.
assert.NotEqual(t, proof.Total, good.Total, "bad: %#v\ngood: %#v", proof, good)
}
}
}
+98
View File
@@ -0,0 +1,98 @@
package types
import (
"bytes"
"fmt"
"github.com/tendermint/tendermint/crypto"
cmn "github.com/tendermint/tmlibs/common"
)
// Volatile state for each Validator
// NOTE: The Accum is not included in Validator.Hash();
// make sure to update that method if changes are made here
type Validator struct {
Address Address `json:"address"`
PubKey crypto.PubKey `json:"pub_key"`
VotingPower int64 `json:"voting_power"`
Accum int64 `json:"accum"`
}
func NewValidator(pubKey crypto.PubKey, votingPower int64) *Validator {
return &Validator{
Address: pubKey.Address(),
PubKey: pubKey,
VotingPower: votingPower,
Accum: 0,
}
}
// Creates a new copy of the validator so we can mutate accum.
// Panics if the validator is nil.
func (v *Validator) Copy() *Validator {
vCopy := *v
return &vCopy
}
// Returns the one with higher Accum.
func (v *Validator) CompareAccum(other *Validator) *Validator {
if v == nil {
return other
}
if v.Accum > other.Accum {
return v
} else if v.Accum < other.Accum {
return other
} else {
result := bytes.Compare(v.Address, other.Address)
if result < 0 {
return v
} else if result > 0 {
return other
} else {
cmn.PanicSanity("Cannot compare identical validators")
return nil
}
}
}
func (v *Validator) String() string {
if v == nil {
return "nil-Validator"
}
return fmt.Sprintf("Validator{%v %v VP:%v A:%v}",
v.Address,
v.PubKey,
v.VotingPower,
v.Accum)
}
// Hash computes the unique ID of a validator with a given voting power.
// It excludes the Accum value, which changes with every round.
func (v *Validator) Hash() []byte {
return aminoHash(struct {
Address Address
PubKey crypto.PubKey
VotingPower int64
}{
v.Address,
v.PubKey,
v.VotingPower,
})
}
//----------------------------------------
// RandValidator
// RandValidator returns a randomized validator, useful for testing.
// UNSTABLE
func RandValidator(randPower bool, minPower int64) (*Validator, PrivValidator) {
privVal := NewMockPV()
votePower := minPower
if randPower {
votePower += int64(cmn.RandUint32())
}
val := NewValidator(privVal.GetPubKey(), votePower)
return val, privVal
}
+517
View File
@@ -0,0 +1,517 @@
package types
import (
"bytes"
"fmt"
"math"
"sort"
"strings"
cmn "github.com/tendermint/tmlibs/common"
"github.com/tendermint/tmlibs/merkle"
)
// 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.
// On the other hand, the .AccumPower of each validator and
// the designated .GetProposer() of a set changes every round,
// upon calling .IncrementAccum().
// NOTE: Not goroutine-safe.
// NOTE: All get/set to validators should copy the value for safety.
type ValidatorSet struct {
// NOTE: persisted via reflect, must be exported.
Validators []*Validator `json:"validators"`
Proposer *Validator `json:"proposer"`
// cached (unexported)
totalVotingPower int64
}
func NewValidatorSet(vals []*Validator) *ValidatorSet {
validators := make([]*Validator, len(vals))
for i, val := range vals {
validators[i] = val.Copy()
}
sort.Sort(ValidatorsByAddress(validators))
vs := &ValidatorSet{
Validators: validators,
}
if vals != nil {
vs.IncrementAccum(1)
}
return vs
}
// incrementAccum and update the proposer
func (valSet *ValidatorSet) IncrementAccum(times int) {
// Add VotingPower * times to each validator and order into heap.
validatorsHeap := cmn.NewHeap()
for _, val := range valSet.Validators {
// check for overflow both multiplication and sum
val.Accum = safeAddClip(val.Accum, safeMulClip(val.VotingPower, int64(times)))
validatorsHeap.PushComparable(val, accumComparable{val})
}
// Decrement the validator with most accum times times
for i := 0; i < times; i++ {
mostest := validatorsHeap.Peek().(*Validator)
// mind underflow
mostest.Accum = safeSubClip(mostest.Accum, valSet.TotalVotingPower())
if i == times-1 {
valSet.Proposer = mostest
} else {
validatorsHeap.Update(mostest, accumComparable{mostest})
}
}
}
// Copy each validator into a new ValidatorSet
func (valSet *ValidatorSet) Copy() *ValidatorSet {
validators := make([]*Validator, len(valSet.Validators))
for i, val := range valSet.Validators {
// NOTE: must copy, since IncrementAccum updates in place.
validators[i] = val.Copy()
}
return &ValidatorSet{
Validators: validators,
Proposer: valSet.Proposer,
totalVotingPower: valSet.totalVotingPower,
}
}
// HasAddress returns true if address given is in the validator set, false -
// otherwise.
func (valSet *ValidatorSet) HasAddress(address []byte) bool {
idx := sort.Search(len(valSet.Validators), func(i int) bool {
return bytes.Compare(address, valSet.Validators[i].Address) <= 0
})
return idx < len(valSet.Validators) && bytes.Equal(valSet.Validators[idx].Address, address)
}
// GetByAddress returns an index of the validator with address and validator
// itself if found. Otherwise, -1 and nil are returned.
func (valSet *ValidatorSet) GetByAddress(address []byte) (index int, val *Validator) {
idx := sort.Search(len(valSet.Validators), func(i int) bool {
return bytes.Compare(address, valSet.Validators[i].Address) <= 0
})
if idx < len(valSet.Validators) && bytes.Equal(valSet.Validators[idx].Address, address) {
return idx, valSet.Validators[idx].Copy()
}
return -1, nil
}
// GetByIndex returns the validator's address and validator itself by index.
// It returns nil values if index is less than 0 or greater or equal to
// len(ValidatorSet.Validators).
func (valSet *ValidatorSet) GetByIndex(index int) (address []byte, val *Validator) {
if index < 0 || index >= len(valSet.Validators) {
return nil, nil
}
val = valSet.Validators[index]
return val.Address, val.Copy()
}
// Size returns the length of the validator set.
func (valSet *ValidatorSet) Size() int {
return len(valSet.Validators)
}
// TotalVotingPower returns the sum of the voting powers of all validators.
func (valSet *ValidatorSet) TotalVotingPower() int64 {
if valSet.totalVotingPower == 0 {
for _, val := range valSet.Validators {
// mind overflow
valSet.totalVotingPower = safeAddClip(valSet.totalVotingPower, val.VotingPower)
}
}
return valSet.totalVotingPower
}
// GetProposer returns the current proposer. If the validator set is empty, nil
// is returned.
func (valSet *ValidatorSet) GetProposer() (proposer *Validator) {
if len(valSet.Validators) == 0 {
return nil
}
if valSet.Proposer == nil {
valSet.Proposer = valSet.findProposer()
}
return valSet.Proposer.Copy()
}
func (valSet *ValidatorSet) findProposer() *Validator {
var proposer *Validator
for _, val := range valSet.Validators {
if proposer == nil || !bytes.Equal(val.Address, proposer.Address) {
proposer = proposer.CompareAccum(val)
}
}
return proposer
}
// Hash returns the Merkle root hash build using validators (as leaves) in the
// set.
func (valSet *ValidatorSet) Hash() []byte {
if len(valSet.Validators) == 0 {
return nil
}
hashers := make([]merkle.Hasher, len(valSet.Validators))
for i, val := range valSet.Validators {
hashers[i] = val
}
return merkle.SimpleHashFromHashers(hashers)
}
// Add adds val to the validator set and returns true. It returns false if val
// is already in the set.
func (valSet *ValidatorSet) Add(val *Validator) (added bool) {
val = val.Copy()
idx := sort.Search(len(valSet.Validators), func(i int) bool {
return bytes.Compare(val.Address, valSet.Validators[i].Address) <= 0
})
if idx >= len(valSet.Validators) {
valSet.Validators = append(valSet.Validators, val)
// Invalidate cache
valSet.Proposer = nil
valSet.totalVotingPower = 0
return true
} else if bytes.Equal(valSet.Validators[idx].Address, val.Address) {
return false
} else {
newValidators := make([]*Validator, len(valSet.Validators)+1)
copy(newValidators[:idx], valSet.Validators[:idx])
newValidators[idx] = val
copy(newValidators[idx+1:], valSet.Validators[idx:])
valSet.Validators = newValidators
// Invalidate cache
valSet.Proposer = nil
valSet.totalVotingPower = 0
return true
}
}
// Update updates val and returns true. It returns false if val is not present
// in the set.
func (valSet *ValidatorSet) Update(val *Validator) (updated bool) {
index, sameVal := valSet.GetByAddress(val.Address)
if sameVal == nil {
return false
}
valSet.Validators[index] = val.Copy()
// Invalidate cache
valSet.Proposer = nil
valSet.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 (valSet *ValidatorSet) Remove(address []byte) (val *Validator, removed bool) {
idx := sort.Search(len(valSet.Validators), func(i int) bool {
return bytes.Compare(address, valSet.Validators[i].Address) <= 0
})
if idx >= len(valSet.Validators) || !bytes.Equal(valSet.Validators[idx].Address, address) {
return nil, false
}
removedVal := valSet.Validators[idx]
newValidators := valSet.Validators[:idx]
if idx+1 < len(valSet.Validators) {
newValidators = append(newValidators, valSet.Validators[idx+1:]...)
}
valSet.Validators = newValidators
// Invalidate cache
valSet.Proposer = nil
valSet.totalVotingPower = 0
return removedVal, true
}
// Iterate will run the given function over the set.
func (valSet *ValidatorSet) Iterate(fn func(index int, val *Validator) bool) {
for i, val := range valSet.Validators {
stop := fn(i, val.Copy())
if stop {
break
}
}
}
// Verify that +2/3 of the set had signed the given signBytes
func (valSet *ValidatorSet) VerifyCommit(chainID string, blockID BlockID, height int64, commit *Commit) error {
if valSet.Size() != len(commit.Precommits) {
return fmt.Errorf("Invalid commit -- wrong set size: %v vs %v", valSet.Size(), len(commit.Precommits))
}
if height != commit.Height() {
return fmt.Errorf("Invalid commit -- wrong height: %v vs %v", height, commit.Height())
}
talliedVotingPower := int64(0)
round := commit.Round()
for idx, precommit := range commit.Precommits {
// may be nil if validator skipped.
if precommit == nil {
continue
}
if precommit.Height != height {
return fmt.Errorf("Invalid commit -- wrong height: %v vs %v", height, precommit.Height)
}
if precommit.Round != round {
return fmt.Errorf("Invalid commit -- wrong round: %v vs %v", round, precommit.Round)
}
if precommit.Type != VoteTypePrecommit {
return fmt.Errorf("Invalid commit -- not precommit @ index %v", idx)
}
_, val := valSet.GetByIndex(idx)
// Validate signature
precommitSignBytes := precommit.SignBytes(chainID)
if !val.PubKey.VerifyBytes(precommitSignBytes, precommit.Signature) {
return fmt.Errorf("Invalid commit -- invalid signature: %v", precommit)
}
if !blockID.Equals(precommit.BlockID) {
continue // Not an error, but doesn't count
}
// Good precommit!
talliedVotingPower += val.VotingPower
}
if talliedVotingPower > valSet.TotalVotingPower()*2/3 {
return nil
}
return fmt.Errorf("Invalid commit -- insufficient voting power: got %v, needed %v",
talliedVotingPower, (valSet.TotalVotingPower()*2/3 + 1))
}
// VerifyCommitAny will check to see if the set would
// be valid with a different validator set.
//
// valSet is the validator set that we know
// * over 2/3 of the power in old signed this block
//
// newSet is the validator set that signed this block
// * only votes from old are sufficient for 2/3 majority
// in the new set as well
//
// That means that:
// * 10% of the valset can't just declare themselves kings
// * If the validator set is 3x old size, we need more proof to trust
func (valSet *ValidatorSet) VerifyCommitAny(newSet *ValidatorSet, chainID string,
blockID BlockID, height int64, commit *Commit) error {
if newSet.Size() != len(commit.Precommits) {
return cmn.NewError("Invalid commit -- wrong set size: %v vs %v", newSet.Size(), len(commit.Precommits))
}
if height != commit.Height() {
return cmn.NewError("Invalid commit -- wrong height: %v vs %v", height, commit.Height())
}
oldVotingPower := int64(0)
newVotingPower := int64(0)
seen := map[int]bool{}
round := commit.Round()
for idx, precommit := range commit.Precommits {
// first check as in VerifyCommit
if precommit == nil {
continue
}
if precommit.Height != height {
// return certerr.ErrHeightMismatch(height, precommit.Height)
return cmn.NewError("Blocks don't match - %d vs %d", round, precommit.Round)
}
if precommit.Round != round {
return cmn.NewError("Invalid commit -- wrong round: %v vs %v", round, precommit.Round)
}
if precommit.Type != VoteTypePrecommit {
return cmn.NewError("Invalid commit -- not precommit @ index %v", idx)
}
if !blockID.Equals(precommit.BlockID) {
continue // Not an error, but doesn't count
}
// we only grab by address, ignoring unknown validators
vi, ov := valSet.GetByAddress(precommit.ValidatorAddress)
if ov == nil || seen[vi] {
continue // missing or double vote...
}
seen[vi] = true
// Validate signature old school
precommitSignBytes := precommit.SignBytes(chainID)
if !ov.PubKey.VerifyBytes(precommitSignBytes, precommit.Signature) {
return cmn.NewError("Invalid commit -- invalid signature: %v", precommit)
}
// Good precommit!
oldVotingPower += ov.VotingPower
// check new school
_, cv := newSet.GetByIndex(idx)
if cv.PubKey.Equals(ov.PubKey) {
// make sure this is properly set in the current block as well
newVotingPower += cv.VotingPower
}
}
if oldVotingPower <= valSet.TotalVotingPower()*2/3 {
return cmn.NewError("Invalid commit -- insufficient old voting power: got %v, needed %v",
oldVotingPower, (valSet.TotalVotingPower()*2/3 + 1))
} else if newVotingPower <= newSet.TotalVotingPower()*2/3 {
return cmn.NewError("Invalid commit -- insufficient cur voting power: got %v, needed %v",
newVotingPower, (newSet.TotalVotingPower()*2/3 + 1))
}
return nil
}
func (valSet *ValidatorSet) String() string {
return valSet.StringIndented("")
}
// String
func (valSet *ValidatorSet) StringIndented(indent string) string {
if valSet == nil {
return "nil-ValidatorSet"
}
valStrings := []string{}
valSet.Iterate(func(index int, val *Validator) bool {
valStrings = append(valStrings, val.String())
return false
})
return fmt.Sprintf(`ValidatorSet{
%s Proposer: %v
%s Validators:
%s %v
%s}`,
indent, valSet.GetProposer().String(),
indent,
indent, strings.Join(valStrings, "\n"+indent+" "),
indent)
}
//-------------------------------------
// Implements sort for sorting validators by address.
// Sort validators by address
type ValidatorsByAddress []*Validator
func (vs ValidatorsByAddress) Len() int {
return len(vs)
}
func (vs ValidatorsByAddress) Less(i, j int) bool {
return bytes.Compare(vs[i].Address, vs[j].Address) == -1
}
func (vs ValidatorsByAddress) Swap(i, j int) {
it := vs[i]
vs[i] = vs[j]
vs[j] = it
}
//-------------------------------------
// Use with Heap for sorting validators by accum
type accumComparable struct {
*Validator
}
// We want to find the validator with the greatest accum.
func (ac accumComparable) Less(o interface{}) bool {
other := o.(accumComparable).Validator
larger := ac.CompareAccum(other)
return bytes.Equal(larger.Address, ac.Address)
}
//----------------------------------------
// For testing
// RandValidatorSet returns a randomized validator set, useful for testing.
// NOTE: PrivValidator are in order.
// UNSTABLE
func RandValidatorSet(numValidators int, votingPower int64) (*ValidatorSet, []PrivValidator) {
vals := make([]*Validator, numValidators)
privValidators := make([]PrivValidator, numValidators)
for i := 0; i < numValidators; i++ {
val, privValidator := RandValidator(false, votingPower)
vals[i] = val
privValidators[i] = privValidator
}
valSet := NewValidatorSet(vals)
sort.Sort(PrivValidatorsByAddress(privValidators))
return valSet, privValidators
}
///////////////////////////////////////////////////////////////////////////////
// Safe multiplication and addition/subtraction
func safeMul(a, b int64) (int64, bool) {
if a == 0 || b == 0 {
return 0, false
}
if a == 1 {
return b, false
}
if b == 1 {
return a, false
}
if a == math.MinInt64 || b == math.MinInt64 {
return -1, true
}
c := a * b
return c, c/b != a
}
func safeAdd(a, b int64) (int64, bool) {
if b > 0 && a > math.MaxInt64-b {
return -1, true
} else if b < 0 && a < math.MinInt64-b {
return -1, true
}
return a + b, false
}
func safeSub(a, b int64) (int64, bool) {
if b > 0 && a < math.MinInt64+b {
return -1, true
} else if b < 0 && a > math.MaxInt64+b {
return -1, true
}
return a - b, false
}
func safeMulClip(a, b int64) int64 {
c, overflow := safeMul(a, b)
if overflow {
if (a < 0 || b < 0) && !(a < 0 && b < 0) {
return math.MinInt64
}
return math.MaxInt64
}
return c
}
func safeAddClip(a, b int64) int64 {
c, overflow := safeAdd(a, b)
if overflow {
if b < 0 {
return math.MinInt64
}
return math.MaxInt64
}
return c
}
func safeSubClip(a, b int64) int64 {
c, overflow := safeSub(a, b)
if overflow {
if b > 0 {
return math.MinInt64
}
return math.MaxInt64
}
return c
}
+372
View File
@@ -0,0 +1,372 @@
package types
import (
"bytes"
"math"
"strings"
"testing"
"testing/quick"
"time"
"github.com/stretchr/testify/assert"
crypto "github.com/tendermint/tendermint/crypto"
cmn "github.com/tendermint/tmlibs/common"
)
func TestCopy(t *testing.T) {
vset := randValidatorSet(10)
vsetHash := vset.Hash()
if len(vsetHash) == 0 {
t.Fatalf("ValidatorSet had unexpected zero hash")
}
vsetCopy := vset.Copy()
vsetCopyHash := vsetCopy.Hash()
if !bytes.Equal(vsetHash, vsetCopyHash) {
t.Fatalf("ValidatorSet copy had wrong hash. Orig: %X, Copy: %X", vsetHash, vsetCopyHash)
}
}
func BenchmarkValidatorSetCopy(b *testing.B) {
b.StopTimer()
vset := NewValidatorSet([]*Validator{})
for i := 0; i < 1000; i++ {
privKey := crypto.GenPrivKeyEd25519()
pubKey := privKey.PubKey()
val := NewValidator(pubKey, 0)
if !vset.Add(val) {
panic("Failed to add validator")
}
}
b.StartTimer()
for i := 0; i < b.N; i++ {
vset.Copy()
}
}
//-------------------------------------------------------------------
func TestProposerSelection1(t *testing.T) {
vset := NewValidatorSet([]*Validator{
newValidator([]byte("foo"), 1000),
newValidator([]byte("bar"), 300),
newValidator([]byte("baz"), 330),
})
proposers := []string{}
for i := 0; i < 99; i++ {
val := vset.GetProposer()
proposers = append(proposers, string(val.Address))
vset.IncrementAccum(1)
}
expected := `foo baz foo bar foo foo baz foo bar foo foo baz foo foo bar foo baz foo foo bar foo foo baz foo bar foo foo baz foo bar foo foo baz foo foo bar foo baz foo foo bar foo baz foo foo bar foo baz foo foo bar foo baz foo foo foo baz bar foo foo foo baz foo bar foo foo baz foo bar foo foo baz foo bar foo foo baz foo bar foo foo baz foo foo bar foo baz foo foo bar foo baz foo foo bar foo baz foo foo`
if expected != strings.Join(proposers, " ") {
t.Errorf("Expected sequence of proposers was\n%v\nbut got \n%v", expected, strings.Join(proposers, " "))
}
}
func TestProposerSelection2(t *testing.T) {
addr0 := []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
addr1 := []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}
addr2 := []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2}
// when all voting power is same, we go in order of addresses
val0, val1, val2 := newValidator(addr0, 100), newValidator(addr1, 100), newValidator(addr2, 100)
valList := []*Validator{val0, val1, val2}
vals := NewValidatorSet(valList)
for i := 0; i < len(valList)*5; i++ {
ii := (i) % len(valList)
prop := vals.GetProposer()
if !bytes.Equal(prop.Address, valList[ii].Address) {
t.Fatalf("(%d): Expected %X. Got %X", i, valList[ii].Address, prop.Address)
}
vals.IncrementAccum(1)
}
// One validator has more than the others, but not enough to propose twice in a row
*val2 = *newValidator(addr2, 400)
vals = NewValidatorSet(valList)
// vals.IncrementAccum(1)
prop := vals.GetProposer()
if !bytes.Equal(prop.Address, addr2) {
t.Fatalf("Expected address with highest voting power to be first proposer. Got %X", prop.Address)
}
vals.IncrementAccum(1)
prop = vals.GetProposer()
if !bytes.Equal(prop.Address, addr0) {
t.Fatalf("Expected smallest address to be validator. Got %X", prop.Address)
}
// One validator has more than the others, and enough to be proposer twice in a row
*val2 = *newValidator(addr2, 401)
vals = NewValidatorSet(valList)
prop = vals.GetProposer()
if !bytes.Equal(prop.Address, addr2) {
t.Fatalf("Expected address with highest voting power to be first proposer. Got %X", prop.Address)
}
vals.IncrementAccum(1)
prop = vals.GetProposer()
if !bytes.Equal(prop.Address, addr2) {
t.Fatalf("Expected address with highest voting power to be second proposer. Got %X", prop.Address)
}
vals.IncrementAccum(1)
prop = vals.GetProposer()
if !bytes.Equal(prop.Address, addr0) {
t.Fatalf("Expected smallest address to be validator. Got %X", prop.Address)
}
// each validator should be the proposer a proportional number of times
val0, val1, val2 = newValidator(addr0, 4), newValidator(addr1, 5), newValidator(addr2, 3)
valList = []*Validator{val0, val1, val2}
propCount := make([]int, 3)
vals = NewValidatorSet(valList)
N := 1
for i := 0; i < 120*N; i++ {
prop := vals.GetProposer()
ii := prop.Address[19]
propCount[ii]++
vals.IncrementAccum(1)
}
if propCount[0] != 40*N {
t.Fatalf("Expected prop count for validator with 4/12 of voting power to be %d/%d. Got %d/%d", 40*N, 120*N, propCount[0], 120*N)
}
if propCount[1] != 50*N {
t.Fatalf("Expected prop count for validator with 5/12 of voting power to be %d/%d. Got %d/%d", 50*N, 120*N, propCount[1], 120*N)
}
if propCount[2] != 30*N {
t.Fatalf("Expected prop count for validator with 3/12 of voting power to be %d/%d. Got %d/%d", 30*N, 120*N, propCount[2], 120*N)
}
}
func TestProposerSelection3(t *testing.T) {
vset := NewValidatorSet([]*Validator{
newValidator([]byte("a"), 1),
newValidator([]byte("b"), 1),
newValidator([]byte("c"), 1),
newValidator([]byte("d"), 1),
})
proposerOrder := make([]*Validator, 4)
for i := 0; i < 4; i++ {
proposerOrder[i] = vset.GetProposer()
vset.IncrementAccum(1)
}
// i for the loop
// j for the times
// we should go in order for ever, despite some IncrementAccums with times > 1
var i, j int
for ; i < 10000; i++ {
got := vset.GetProposer().Address
expected := proposerOrder[j%4].Address
if !bytes.Equal(got, expected) {
t.Fatalf(cmn.Fmt("vset.Proposer (%X) does not match expected proposer (%X) for (%d, %d)", got, expected, i, j))
}
// serialize, deserialize, check proposer
b := vset.toBytes()
vset.fromBytes(b)
computed := vset.GetProposer() // findGetProposer()
if i != 0 {
if !bytes.Equal(got, computed.Address) {
t.Fatalf(cmn.Fmt("vset.Proposer (%X) does not match computed proposer (%X) for (%d, %d)", got, computed.Address, i, j))
}
}
// times is usually 1
times := 1
mod := (cmn.RandInt() % 5) + 1
if cmn.RandInt()%mod > 0 {
// sometimes its up to 5
times = cmn.RandInt() % 5
}
vset.IncrementAccum(times)
j += times
}
}
func newValidator(address []byte, power int64) *Validator {
return &Validator{Address: address, VotingPower: power}
}
func randPubKey() crypto.PubKey {
var pubKey [32]byte
copy(pubKey[:], cmn.RandBytes(32))
return crypto.PubKeyEd25519(pubKey)
}
func randValidator_() *Validator {
val := NewValidator(randPubKey(), cmn.RandInt64())
val.Accum = cmn.RandInt64()
return val
}
func randValidatorSet(numValidators int) *ValidatorSet {
validators := make([]*Validator, numValidators)
for i := 0; i < numValidators; i++ {
validators[i] = randValidator_()
}
return NewValidatorSet(validators)
}
func (valSet *ValidatorSet) toBytes() []byte {
bz, err := cdc.MarshalBinary(valSet)
if err != nil {
panic(err)
}
return bz
}
func (valSet *ValidatorSet) fromBytes(b []byte) {
err := cdc.UnmarshalBinary(b, &valSet)
if err != nil {
// DATA HAS BEEN CORRUPTED OR THE SPEC HAS CHANGED
panic(err)
}
}
//-------------------------------------------------------------------
func TestValidatorSetTotalVotingPowerOverflows(t *testing.T) {
vset := NewValidatorSet([]*Validator{
{Address: []byte("a"), VotingPower: math.MaxInt64, Accum: 0},
{Address: []byte("b"), VotingPower: math.MaxInt64, Accum: 0},
{Address: []byte("c"), VotingPower: math.MaxInt64, Accum: 0},
})
assert.EqualValues(t, math.MaxInt64, vset.TotalVotingPower())
}
func TestValidatorSetIncrementAccumOverflows(t *testing.T) {
// NewValidatorSet calls IncrementAccum(1)
vset := NewValidatorSet([]*Validator{
// too much voting power
0: {Address: []byte("a"), VotingPower: math.MaxInt64, Accum: 0},
// too big accum
1: {Address: []byte("b"), VotingPower: 10, Accum: math.MaxInt64},
// almost too big accum
2: {Address: []byte("c"), VotingPower: 10, Accum: math.MaxInt64 - 5},
})
assert.Equal(t, int64(0), vset.Validators[0].Accum, "0") // because we decrement val with most voting power
assert.EqualValues(t, math.MaxInt64, vset.Validators[1].Accum, "1")
assert.EqualValues(t, math.MaxInt64, vset.Validators[2].Accum, "2")
}
func TestValidatorSetIncrementAccumUnderflows(t *testing.T) {
// NewValidatorSet calls IncrementAccum(1)
vset := NewValidatorSet([]*Validator{
0: {Address: []byte("a"), VotingPower: math.MaxInt64, Accum: math.MinInt64},
1: {Address: []byte("b"), VotingPower: 1, Accum: math.MinInt64},
})
vset.IncrementAccum(5)
assert.EqualValues(t, math.MinInt64, vset.Validators[0].Accum, "0")
assert.EqualValues(t, math.MinInt64, vset.Validators[1].Accum, "1")
}
func TestSafeMul(t *testing.T) {
f := func(a, b int64) bool {
c, overflow := safeMul(a, b)
return overflow || (!overflow && c == a*b)
}
if err := quick.Check(f, nil); err != nil {
t.Error(err)
}
}
func TestSafeAdd(t *testing.T) {
f := func(a, b int64) bool {
c, overflow := safeAdd(a, b)
return overflow || (!overflow && c == a+b)
}
if err := quick.Check(f, nil); err != nil {
t.Error(err)
}
}
func TestSafeMulClip(t *testing.T) {
assert.EqualValues(t, math.MaxInt64, safeMulClip(math.MinInt64, math.MinInt64))
assert.EqualValues(t, math.MinInt64, safeMulClip(math.MaxInt64, math.MinInt64))
assert.EqualValues(t, math.MinInt64, safeMulClip(math.MinInt64, math.MaxInt64))
assert.EqualValues(t, math.MaxInt64, safeMulClip(math.MaxInt64, 2))
}
func TestSafeAddClip(t *testing.T) {
assert.EqualValues(t, math.MaxInt64, safeAddClip(math.MaxInt64, 10))
assert.EqualValues(t, math.MaxInt64, safeAddClip(math.MaxInt64, math.MaxInt64))
assert.EqualValues(t, math.MinInt64, safeAddClip(math.MinInt64, -10))
}
func TestSafeSubClip(t *testing.T) {
assert.EqualValues(t, math.MinInt64, safeSubClip(math.MinInt64, 10))
assert.EqualValues(t, 0, safeSubClip(math.MinInt64, math.MinInt64))
assert.EqualValues(t, math.MinInt64, safeSubClip(math.MinInt64, math.MaxInt64))
assert.EqualValues(t, math.MaxInt64, safeSubClip(math.MaxInt64, -10))
}
//-------------------------------------------------------------------
func TestValidatorSetVerifyCommit(t *testing.T) {
privKey := crypto.GenPrivKeyEd25519()
pubKey := privKey.PubKey()
v1 := NewValidator(pubKey, 1000)
vset := NewValidatorSet([]*Validator{v1})
chainID := "mychainID"
blockID := BlockID{Hash: []byte("hello")}
height := int64(5)
vote := &Vote{
ValidatorAddress: v1.Address,
ValidatorIndex: 0,
Height: height,
Round: 0,
Timestamp: time.Now().UTC(),
Type: VoteTypePrecommit,
BlockID: blockID,
}
sig, err := privKey.Sign(vote.SignBytes(chainID))
assert.NoError(t, err)
vote.Signature = sig
commit := &Commit{
BlockID: blockID,
Precommits: []*Vote{vote},
}
badChainID := "notmychainID"
badBlockID := BlockID{Hash: []byte("goodbye")}
badHeight := height + 1
badCommit := &Commit{
BlockID: blockID,
Precommits: []*Vote{nil},
}
// test some error cases
// TODO: test more cases!
cases := []struct {
chainID string
blockID BlockID
height int64
commit *Commit
}{
{badChainID, blockID, height, commit},
{chainID, badBlockID, height, commit},
{chainID, blockID, badHeight, commit},
{chainID, blockID, height, badCommit},
}
for i, c := range cases {
err := vset.VerifyCommit(c.chainID, c.blockID, c.height, c.commit)
assert.NotNil(t, err, i)
}
// test a good one
err = vset.VerifyCommit(chainID, blockID, height, commit)
assert.Nil(t, err)
}
+117
View File
@@ -0,0 +1,117 @@
package types
import (
"bytes"
"errors"
"fmt"
"time"
crypto "github.com/tendermint/tendermint/crypto"
cmn "github.com/tendermint/tmlibs/common"
)
var (
ErrVoteUnexpectedStep = errors.New("Unexpected step")
ErrVoteInvalidValidatorIndex = errors.New("Invalid validator index")
ErrVoteInvalidValidatorAddress = errors.New("Invalid validator address")
ErrVoteInvalidSignature = errors.New("Invalid signature")
ErrVoteInvalidBlockHash = errors.New("Invalid block hash")
ErrVoteNonDeterministicSignature = errors.New("Non-deterministic signature")
ErrVoteNil = errors.New("Nil vote")
)
type ErrVoteConflictingVotes struct {
*DuplicateVoteEvidence
}
func (err *ErrVoteConflictingVotes) Error() string {
return fmt.Sprintf("Conflicting votes from validator %v", err.PubKey.Address())
}
func NewConflictingVoteError(val *Validator, voteA, voteB *Vote) *ErrVoteConflictingVotes {
return &ErrVoteConflictingVotes{
&DuplicateVoteEvidence{
PubKey: val.PubKey,
VoteA: voteA,
VoteB: voteB,
},
}
}
// Types of votes
// TODO Make a new type "VoteType"
const (
VoteTypePrevote = byte(0x01)
VoteTypePrecommit = byte(0x02)
)
func IsVoteTypeValid(type_ byte) bool {
switch type_ {
case VoteTypePrevote:
return true
case VoteTypePrecommit:
return true
default:
return false
}
}
// Address is hex bytes. TODO: crypto.Address
type Address = cmn.HexBytes
// Represents a prevote, precommit, or commit vote from validators for consensus.
type Vote struct {
ValidatorAddress Address `json:"validator_address"`
ValidatorIndex int `json:"validator_index"`
Height int64 `json:"height"`
Round int `json:"round"`
Timestamp time.Time `json:"timestamp"`
Type byte `json:"type"`
BlockID BlockID `json:"block_id"` // zero if vote is nil.
Signature crypto.Signature `json:"signature"`
}
func (vote *Vote) SignBytes(chainID string) []byte {
bz, err := cdc.MarshalJSON(CanonicalVote(chainID, vote))
if err != nil {
panic(err)
}
return bz
}
func (vote *Vote) Copy() *Vote {
voteCopy := *vote
return &voteCopy
}
func (vote *Vote) String() string {
if vote == nil {
return "nil-Vote"
}
var typeString string
switch vote.Type {
case VoteTypePrevote:
typeString = "Prevote"
case VoteTypePrecommit:
typeString = "Precommit"
default:
cmn.PanicSanity("Unknown vote type")
}
return fmt.Sprintf("Vote{%v:%X %v/%02d/%v(%v) %X %v @ %s}",
vote.ValidatorIndex, cmn.Fingerprint(vote.ValidatorAddress),
vote.Height, vote.Round, vote.Type, typeString,
cmn.Fingerprint(vote.BlockID.Hash), vote.Signature,
CanonicalTime(vote.Timestamp))
}
func (vote *Vote) Verify(chainID string, pubKey crypto.PubKey) error {
if !bytes.Equal(pubKey.Address(), vote.ValidatorAddress) {
return ErrVoteInvalidValidatorAddress
}
if !pubKey.VerifyBytes(vote.SignBytes(chainID), vote.Signature) {
return ErrVoteInvalidSignature
}
return nil
}
+603
View File
@@ -0,0 +1,603 @@
package types
import (
"bytes"
"fmt"
"strings"
"sync"
"github.com/pkg/errors"
cmn "github.com/tendermint/tmlibs/common"
)
// UNSTABLE
// XXX: duplicate of p2p.ID to avoid dependence between packages.
// Perhaps we can have a minimal types package containing this (and other things?)
// that both `types` and `p2p` import ?
type P2PID string
/*
VoteSet helps collect signatures from validators at each height+round for a
predefined vote type.
We need VoteSet to be able to keep track of conflicting votes when validators
double-sign. Yet, we can't keep track of *all* the votes seen, as that could
be a DoS attack vector.
There are two storage areas for votes.
1. voteSet.votes
2. voteSet.votesByBlock
`.votes` is the "canonical" list of votes. It always has at least one vote,
if a vote from a validator had been seen at all. Usually it keeps track of
the first vote seen, but when a 2/3 majority is found, votes for that get
priority and are copied over from `.votesByBlock`.
`.votesByBlock` keeps track of a list of votes for a particular block. There
are two ways a &blockVotes{} gets created in `.votesByBlock`.
1. the first vote seen by a validator was for the particular block.
2. a peer claims to have seen 2/3 majority for the particular block.
Since the first vote from a validator will always get added in `.votesByBlock`
, all votes in `.votes` will have a corresponding entry in `.votesByBlock`.
When a &blockVotes{} in `.votesByBlock` reaches a 2/3 majority quorum, its
votes are copied into `.votes`.
All this is memory bounded because conflicting votes only get added if a peer
told us to track that block, each peer only gets to tell us 1 such block, and,
there's only a limited number of peers.
NOTE: Assumes that the sum total of voting power does not exceed MaxUInt64.
*/
type VoteSet struct {
chainID string
height int64
round int
type_ byte
valSet *ValidatorSet
mtx sync.Mutex
votesBitArray *cmn.BitArray
votes []*Vote // Primary votes to share
sum int64 // Sum of voting power for seen votes, discounting conflicts
maj23 *BlockID // First 2/3 majority seen
votesByBlock map[string]*blockVotes // string(blockHash|blockParts) -> blockVotes
peerMaj23s map[P2PID]BlockID // Maj23 for each peer
}
// Constructs a new VoteSet struct used to accumulate votes for given height/round.
func NewVoteSet(chainID string, height int64, round int, type_ byte, valSet *ValidatorSet) *VoteSet {
if height == 0 {
cmn.PanicSanity("Cannot make VoteSet for height == 0, doesn't make sense.")
}
return &VoteSet{
chainID: chainID,
height: height,
round: round,
type_: type_,
valSet: valSet,
votesBitArray: cmn.NewBitArray(valSet.Size()),
votes: make([]*Vote, valSet.Size()),
sum: 0,
maj23: nil,
votesByBlock: make(map[string]*blockVotes, valSet.Size()),
peerMaj23s: make(map[P2PID]BlockID),
}
}
func (voteSet *VoteSet) ChainID() string {
return voteSet.chainID
}
func (voteSet *VoteSet) Height() int64 {
if voteSet == nil {
return 0
}
return voteSet.height
}
func (voteSet *VoteSet) Round() int {
if voteSet == nil {
return -1
}
return voteSet.round
}
func (voteSet *VoteSet) Type() byte {
if voteSet == nil {
return 0x00
}
return voteSet.type_
}
func (voteSet *VoteSet) Size() int {
if voteSet == nil {
return 0
}
return voteSet.valSet.Size()
}
// Returns added=true if vote is valid and new.
// Otherwise returns err=ErrVote[
// UnexpectedStep | InvalidIndex | InvalidAddress |
// InvalidSignature | InvalidBlockHash | ConflictingVotes ]
// Duplicate votes return added=false, err=nil.
// Conflicting votes return added=*, err=ErrVoteConflictingVotes.
// NOTE: vote should not be mutated after adding.
// NOTE: VoteSet must not be nil
// 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")
}
voteSet.mtx.Lock()
defer voteSet.mtx.Unlock()
return voteSet.addVote(vote)
}
// NOTE: Validates as much as possible before attempting to verify the signature.
func (voteSet *VoteSet) addVote(vote *Vote) (added bool, err error) {
if vote == nil {
return false, ErrVoteNil
}
valIndex := vote.ValidatorIndex
valAddr := vote.ValidatorAddress
blockKey := vote.BlockID.Key()
// Ensure that validator index was set
if valIndex < 0 {
return false, errors.Wrap(ErrVoteInvalidValidatorIndex, "Index < 0")
} else if len(valAddr) == 0 {
return false, errors.Wrap(ErrVoteInvalidValidatorAddress, "Empty address")
}
// Make sure the step matches.
if (vote.Height != voteSet.height) ||
(vote.Round != voteSet.round) ||
(vote.Type != voteSet.type_) {
return false, errors.Wrapf(ErrVoteUnexpectedStep, "Got %d/%d/%d, expected %d/%d/%d",
voteSet.height, voteSet.round, voteSet.type_,
vote.Height, vote.Round, vote.Type)
}
// Ensure that signer is a validator.
lookupAddr, val := voteSet.valSet.GetByIndex(valIndex)
if val == nil {
return false, errors.Wrapf(ErrVoteInvalidValidatorIndex,
"Cannot find validator %d in valSet of size %d", valIndex, voteSet.valSet.Size())
}
// Ensure that the signer has the right address
if !bytes.Equal(valAddr, lookupAddr) {
return false, errors.Wrapf(ErrVoteInvalidValidatorAddress,
"vote.ValidatorAddress (%X) does not match address (%X) for vote.ValidatorIndex (%d)\nEnsure the genesis file is correct across all validators.",
valAddr, lookupAddr, valIndex)
}
// If we already know of this vote, return false.
if existing, ok := voteSet.getVote(valIndex, blockKey); ok {
if existing.Signature.Equals(vote.Signature) {
return false, nil // duplicate
}
return false, errors.Wrapf(ErrVoteNonDeterministicSignature, "Existing vote: %v; New vote: %v", existing, vote)
}
// Check signature.
if err := vote.Verify(voteSet.chainID, val.PubKey); err != nil {
return false, errors.Wrapf(err, "Failed to verify vote with ChainID %s and PubKey %s", voteSet.chainID, val.PubKey)
}
// Add vote and get conflicting vote if any
added, conflicting := voteSet.addVerifiedVote(vote, blockKey, val.VotingPower)
if conflicting != nil {
return added, NewConflictingVoteError(val, conflicting, vote)
}
if !added {
cmn.PanicSanity("Expected to add non-conflicting vote")
}
return added, nil
}
// Returns (vote, true) if vote exists for valIndex and blockKey
func (voteSet *VoteSet) getVote(valIndex int, blockKey string) (vote *Vote, ok bool) {
if existing := voteSet.votes[valIndex]; existing != nil && existing.BlockID.Key() == blockKey {
return existing, true
}
if existing := voteSet.votesByBlock[blockKey].getByIndex(valIndex); existing != nil {
return existing, true
}
return nil, false
}
// Assumes signature is valid.
// If conflicting vote exists, returns it.
func (voteSet *VoteSet) addVerifiedVote(vote *Vote, blockKey string, votingPower int64) (added bool, conflicting *Vote) {
valIndex := vote.ValidatorIndex
// 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")
} else {
conflicting = existing
}
// Replace vote if blockKey matches voteSet.maj23.
if voteSet.maj23 != nil && voteSet.maj23.Key() == blockKey {
voteSet.votes[valIndex] = vote
voteSet.votesBitArray.SetIndex(valIndex, true)
}
// Otherwise don't add it to voteSet.votes
} else {
// Add to voteSet.votes and incr .sum
voteSet.votes[valIndex] = vote
voteSet.votesBitArray.SetIndex(valIndex, true)
voteSet.sum += votingPower
}
votesByBlock, ok := voteSet.votesByBlock[blockKey]
if ok {
if conflicting != nil && !votesByBlock.peerMaj23 {
// There's a conflict and no peer claims that this block is special.
return false, conflicting
}
// We'll add the vote in a bit.
} else {
// .votesByBlock doesn't exist...
if conflicting != nil {
// ... and there's a conflicting vote.
// We're not even tracking this blockKey, so just forget it.
return false, conflicting
}
// ... and there's no conflicting vote.
// Start tracking this blockKey
votesByBlock = newBlockVotes(false, voteSet.valSet.Size())
voteSet.votesByBlock[blockKey] = votesByBlock
// We'll add the vote in a bit.
}
// Before adding to votesByBlock, see if we'll exceed quorum
origSum := votesByBlock.sum
quorum := voteSet.valSet.TotalVotingPower()*2/3 + 1
// Add vote to votesByBlock
votesByBlock.addVerifiedVote(vote, votingPower)
// If we just crossed the quorum threshold and have 2/3 majority...
if origSum < quorum && quorum <= votesByBlock.sum {
// Only consider the first quorum reached
if voteSet.maj23 == nil {
maj23BlockID := vote.BlockID
voteSet.maj23 = &maj23BlockID
// And also copy votes over to voteSet.votes
for i, vote := range votesByBlock.votes {
if vote != nil {
voteSet.votes[i] = vote
}
}
}
}
return true, conflicting
}
// If a peer claims that it has 2/3 majority for given blockKey, call this.
// NOTE: if there are too many peers, or too much peer churn,
// this can cause memory issues.
// TODO: implement ability to remove peers too
// NOTE: VoteSet must not be nil
func (voteSet *VoteSet) SetPeerMaj23(peerID P2PID, blockID BlockID) error {
if voteSet == nil {
cmn.PanicSanity("SetPeerMaj23() on nil VoteSet")
}
voteSet.mtx.Lock()
defer voteSet.mtx.Unlock()
blockKey := blockID.Key()
// Make sure peer hasn't already told us something.
if existing, ok := voteSet.peerMaj23s[peerID]; ok {
if existing.Equals(blockID) {
return nil // Nothing to do
}
return fmt.Errorf("SetPeerMaj23: Received conflicting blockID from peer %v. Got %v, expected %v",
peerID, blockID, existing)
}
voteSet.peerMaj23s[peerID] = blockID
// Create .votesByBlock entry if needed.
votesByBlock, ok := voteSet.votesByBlock[blockKey]
if ok {
if votesByBlock.peerMaj23 {
return nil // Nothing to do
}
votesByBlock.peerMaj23 = true
// No need to copy votes, already there.
} else {
votesByBlock = newBlockVotes(true, voteSet.valSet.Size())
voteSet.votesByBlock[blockKey] = votesByBlock
// No need to copy votes, no votes to copy over.
}
return nil
}
func (voteSet *VoteSet) BitArray() *cmn.BitArray {
if voteSet == nil {
return nil
}
voteSet.mtx.Lock()
defer voteSet.mtx.Unlock()
return voteSet.votesBitArray.Copy()
}
func (voteSet *VoteSet) BitArrayByBlockID(blockID BlockID) *cmn.BitArray {
if voteSet == nil {
return nil
}
voteSet.mtx.Lock()
defer voteSet.mtx.Unlock()
votesByBlock, ok := voteSet.votesByBlock[blockID.Key()]
if ok {
return votesByBlock.bitArray.Copy()
}
return nil
}
// NOTE: if validator has conflicting votes, returns "canonical" vote
func (voteSet *VoteSet) GetByIndex(valIndex int) *Vote {
if voteSet == nil {
return nil
}
voteSet.mtx.Lock()
defer voteSet.mtx.Unlock()
return voteSet.votes[valIndex]
}
func (voteSet *VoteSet) GetByAddress(address []byte) *Vote {
if voteSet == nil {
return nil
}
voteSet.mtx.Lock()
defer voteSet.mtx.Unlock()
valIndex, val := voteSet.valSet.GetByAddress(address)
if val == nil {
cmn.PanicSanity("GetByAddress(address) returned nil")
}
return voteSet.votes[valIndex]
}
func (voteSet *VoteSet) HasTwoThirdsMajority() bool {
if voteSet == nil {
return false
}
voteSet.mtx.Lock()
defer voteSet.mtx.Unlock()
return voteSet.maj23 != nil
}
func (voteSet *VoteSet) IsCommit() bool {
if voteSet == nil {
return false
}
if voteSet.type_ != VoteTypePrecommit {
return false
}
voteSet.mtx.Lock()
defer voteSet.mtx.Unlock()
return voteSet.maj23 != nil
}
func (voteSet *VoteSet) HasTwoThirdsAny() bool {
if voteSet == nil {
return false
}
voteSet.mtx.Lock()
defer voteSet.mtx.Unlock()
return voteSet.sum > voteSet.valSet.TotalVotingPower()*2/3
}
func (voteSet *VoteSet) HasAll() bool {
voteSet.mtx.Lock()
defer voteSet.mtx.Unlock()
return voteSet.sum == voteSet.valSet.TotalVotingPower()
}
// If there was a +2/3 majority for blockID, return blockID and true.
// Else, return the empty BlockID{} and false.
func (voteSet *VoteSet) TwoThirdsMajority() (blockID BlockID, ok bool) {
if voteSet == nil {
return BlockID{}, false
}
voteSet.mtx.Lock()
defer voteSet.mtx.Unlock()
if voteSet.maj23 != nil {
return *voteSet.maj23, true
}
return BlockID{}, false
}
//--------------------------------------------------------------------------------
// Strings and JSON
func (voteSet *VoteSet) String() string {
if voteSet == nil {
return "nil-VoteSet"
}
return voteSet.StringIndented("")
}
func (voteSet *VoteSet) StringIndented(indent string) string {
voteSet.mtx.Lock()
defer voteSet.mtx.Unlock()
voteStrings := make([]string, len(voteSet.votes))
for i, vote := range voteSet.votes {
if vote == nil {
voteStrings[i] = "nil-Vote"
} else {
voteStrings[i] = vote.String()
}
}
return fmt.Sprintf(`VoteSet{
%s H:%v R:%v T:%v
%s %v
%s %v
%s %v
%s}`,
indent, voteSet.height, voteSet.round, voteSet.type_,
indent, strings.Join(voteStrings, "\n"+indent+" "),
indent, voteSet.votesBitArray,
indent, voteSet.peerMaj23s,
indent)
}
// Marshal the VoteSet to JSON. Same as String(), just in JSON,
// and without the height/round/type_ (since its already included in the votes).
func (voteSet *VoteSet) MarshalJSON() ([]byte, error) {
voteSet.mtx.Lock()
defer voteSet.mtx.Unlock()
return cdc.MarshalJSON(VoteSetJSON{
voteSet.voteStrings(),
voteSet.bitArrayString(),
voteSet.peerMaj23s,
})
}
// More human readable JSON of the vote set
// NOTE: insufficient for unmarshalling from (compressed votes)
// TODO: make the peerMaj23s nicer to read (eg just the block hash)
type VoteSetJSON struct {
Votes []string `json:"votes"`
VotesBitArray string `json:"votes_bit_array"`
PeerMaj23s map[P2PID]BlockID `json:"peer_maj_23s"`
}
// Return the bit-array of votes including
// the fraction of power that has voted like:
// "BA{29:xx__x__x_x___x__x_______xxx__} 856/1304 = 0.66"
func (voteSet *VoteSet) BitArrayString() string {
voteSet.mtx.Lock()
defer voteSet.mtx.Unlock()
return voteSet.bitArrayString()
}
func (voteSet *VoteSet) bitArrayString() string {
bAString := voteSet.votesBitArray.String()
voted, total, fracVoted := voteSet.sumTotalFrac()
return fmt.Sprintf("%s %d/%d = %.2f", bAString, voted, total, fracVoted)
}
// Returns a list of votes compressed to more readable strings.
func (voteSet *VoteSet) VoteStrings() []string {
voteSet.mtx.Lock()
defer voteSet.mtx.Unlock()
return voteSet.voteStrings()
}
func (voteSet *VoteSet) voteStrings() []string {
voteStrings := make([]string, len(voteSet.votes))
for i, vote := range voteSet.votes {
if vote == nil {
voteStrings[i] = "nil-Vote"
} else {
voteStrings[i] = vote.String()
}
}
return voteStrings
}
func (voteSet *VoteSet) StringShort() string {
if voteSet == nil {
return "nil-VoteSet"
}
voteSet.mtx.Lock()
defer voteSet.mtx.Unlock()
_, _, frac := voteSet.sumTotalFrac()
return fmt.Sprintf(`VoteSet{H:%v R:%v T:%v +2/3:%v(%v) %v %v}`,
voteSet.height, voteSet.round, voteSet.type_, voteSet.maj23, frac, voteSet.votesBitArray, voteSet.peerMaj23s)
}
// return the power voted, the total, and the fraction
func (voteSet *VoteSet) sumTotalFrac() (int64, int64, float64) {
voted, total := voteSet.sum, voteSet.valSet.TotalVotingPower()
fracVoted := float64(voted) / float64(total)
return voted, total, fracVoted
}
//--------------------------------------------------------------------------------
// Commit
func (voteSet *VoteSet) MakeCommit() *Commit {
if voteSet.type_ != VoteTypePrecommit {
cmn.PanicSanity("Cannot MakeCommit() unless VoteSet.Type is VoteTypePrecommit")
}
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")
}
// For every validator, get the precommit
votesCopy := make([]*Vote, len(voteSet.votes))
copy(votesCopy, voteSet.votes)
return &Commit{
BlockID: *voteSet.maj23,
Precommits: votesCopy,
}
}
//--------------------------------------------------------------------------------
/*
Votes for a particular block
There are two ways a *blockVotes gets created for a blockKey.
1. first (non-conflicting) vote of a validator w/ blockKey (peerMaj23=false)
2. A peer claims to have a 2/3 majority w/ blockKey (peerMaj23=true)
*/
type blockVotes struct {
peerMaj23 bool // peer claims to have maj23
bitArray *cmn.BitArray // valIndex -> hasVote?
votes []*Vote // valIndex -> *Vote
sum int64 // vote sum
}
func newBlockVotes(peerMaj23 bool, numValidators int) *blockVotes {
return &blockVotes{
peerMaj23: peerMaj23,
bitArray: cmn.NewBitArray(numValidators),
votes: make([]*Vote, numValidators),
sum: 0,
}
}
func (vs *blockVotes) addVerifiedVote(vote *Vote, votingPower int64) {
valIndex := vote.ValidatorIndex
if existing := vs.votes[valIndex]; existing == nil {
vs.bitArray.SetIndex(valIndex, true)
vs.votes[valIndex] = vote
vs.sum += votingPower
}
}
func (vs *blockVotes) getByIndex(index int) *Vote {
if vs == nil {
return nil
}
return vs.votes[index]
}
//--------------------------------------------------------------------------------
// Common interface between *consensus.VoteSet and types.Commit
type VoteSetReader interface {
Height() int64
Round() int
Type() byte
Size() int
BitArray() *cmn.BitArray
GetByIndex(int) *Vote
IsCommit() bool
}
+508
View File
@@ -0,0 +1,508 @@
package types
import (
"bytes"
"testing"
"time"
crypto "github.com/tendermint/tendermint/crypto"
cmn "github.com/tendermint/tmlibs/common"
tst "github.com/tendermint/tmlibs/test"
)
// NOTE: privValidators are in order
func randVoteSet(height int64, round int, type_ byte, numValidators int, votingPower int64) (*VoteSet, *ValidatorSet, []PrivValidator) {
valSet, privValidators := RandValidatorSet(numValidators, votingPower)
return NewVoteSet("test_chain_id", height, round, type_, valSet), valSet, privValidators
}
// Convenience: Return new vote with different validator address/index
func withValidator(vote *Vote, addr []byte, idx int) *Vote {
vote = vote.Copy()
vote.ValidatorAddress = addr
vote.ValidatorIndex = idx
return vote
}
// Convenience: Return new vote with different height
func withHeight(vote *Vote, height int64) *Vote {
vote = vote.Copy()
vote.Height = height
return vote
}
// Convenience: Return new vote with different round
func withRound(vote *Vote, round int) *Vote {
vote = vote.Copy()
vote.Round = round
return vote
}
// Convenience: Return new vote with different type
func withType(vote *Vote, type_ byte) *Vote {
vote = vote.Copy()
vote.Type = type_
return vote
}
// Convenience: Return new vote with different blockHash
func withBlockHash(vote *Vote, blockHash []byte) *Vote {
vote = vote.Copy()
vote.BlockID.Hash = blockHash
return vote
}
// Convenience: Return new vote with different blockParts
func withBlockPartsHeader(vote *Vote, blockPartsHeader PartSetHeader) *Vote {
vote = vote.Copy()
vote.BlockID.PartsHeader = blockPartsHeader
return vote
}
func TestAddVote(t *testing.T) {
height, round := int64(1), 0
voteSet, _, privValidators := randVoteSet(height, round, VoteTypePrevote, 10, 1)
val0 := privValidators[0]
// t.Logf(">> %v", voteSet)
if voteSet.GetByAddress(val0.GetAddress()) != nil {
t.Errorf("Expected GetByAddress(val0.Address) to be nil")
}
if voteSet.BitArray().GetIndex(0) {
t.Errorf("Expected BitArray.GetIndex(0) to be false")
}
blockID, ok := voteSet.TwoThirdsMajority()
if ok || !blockID.IsZero() {
t.Errorf("There should be no 2/3 majority")
}
vote := &Vote{
ValidatorAddress: val0.GetAddress(),
ValidatorIndex: 0, // since privValidators are in order
Height: height,
Round: round,
Type: VoteTypePrevote,
Timestamp: time.Now().UTC(),
BlockID: BlockID{nil, PartSetHeader{}},
}
_, err := signAddVote(val0, vote, voteSet)
if err != nil {
t.Error(err)
}
if voteSet.GetByAddress(val0.GetAddress()) == nil {
t.Errorf("Expected GetByAddress(val0.Address) to be present")
}
if !voteSet.BitArray().GetIndex(0) {
t.Errorf("Expected BitArray.GetIndex(0) to be true")
}
blockID, ok = voteSet.TwoThirdsMajority()
if ok || !blockID.IsZero() {
t.Errorf("There should be no 2/3 majority")
}
}
func Test2_3Majority(t *testing.T) {
height, round := int64(1), 0
voteSet, _, privValidators := randVoteSet(height, round, VoteTypePrevote, 10, 1)
voteProto := &Vote{
ValidatorAddress: nil, // NOTE: must fill in
ValidatorIndex: -1, // NOTE: must fill in
Height: height,
Round: round,
Type: VoteTypePrevote,
Timestamp: time.Now().UTC(),
BlockID: BlockID{nil, PartSetHeader{}},
}
// 6 out of 10 voted for nil.
for i := 0; i < 6; i++ {
vote := withValidator(voteProto, privValidators[i].GetAddress(), i)
_, err := signAddVote(privValidators[i], vote, voteSet)
if err != nil {
t.Error(err)
}
}
blockID, ok := voteSet.TwoThirdsMajority()
if ok || !blockID.IsZero() {
t.Errorf("There should be no 2/3 majority")
}
// 7th validator voted for some blockhash
{
vote := withValidator(voteProto, privValidators[6].GetAddress(), 6)
_, err := signAddVote(privValidators[6], withBlockHash(vote, cmn.RandBytes(32)), voteSet)
if err != nil {
t.Error(err)
}
blockID, ok = voteSet.TwoThirdsMajority()
if ok || !blockID.IsZero() {
t.Errorf("There should be no 2/3 majority")
}
}
// 8th validator voted for nil.
{
vote := withValidator(voteProto, privValidators[7].GetAddress(), 7)
_, err := signAddVote(privValidators[7], vote, voteSet)
if err != nil {
t.Error(err)
}
blockID, ok = voteSet.TwoThirdsMajority()
if !ok || !blockID.IsZero() {
t.Errorf("There should be 2/3 majority for nil")
}
}
}
func Test2_3MajorityRedux(t *testing.T) {
height, round := int64(1), 0
voteSet, _, privValidators := randVoteSet(height, round, VoteTypePrevote, 100, 1)
blockHash := crypto.CRandBytes(32)
blockPartsTotal := 123
blockPartsHeader := PartSetHeader{blockPartsTotal, crypto.CRandBytes(32)}
voteProto := &Vote{
ValidatorAddress: nil, // NOTE: must fill in
ValidatorIndex: -1, // NOTE: must fill in
Height: height,
Round: round,
Timestamp: time.Now().UTC(),
Type: VoteTypePrevote,
BlockID: BlockID{blockHash, blockPartsHeader},
}
// 66 out of 100 voted for nil.
for i := 0; i < 66; i++ {
vote := withValidator(voteProto, privValidators[i].GetAddress(), i)
_, err := signAddVote(privValidators[i], vote, voteSet)
if err != nil {
t.Error(err)
}
}
blockID, ok := voteSet.TwoThirdsMajority()
if ok || !blockID.IsZero() {
t.Errorf("There should be no 2/3 majority")
}
// 67th validator voted for nil
{
vote := withValidator(voteProto, privValidators[66].GetAddress(), 66)
_, err := signAddVote(privValidators[66], withBlockHash(vote, nil), voteSet)
if err != nil {
t.Error(err)
}
blockID, ok = voteSet.TwoThirdsMajority()
if ok || !blockID.IsZero() {
t.Errorf("There should be no 2/3 majority: last vote added was nil")
}
}
// 68th validator voted for a different BlockParts PartSetHeader
{
vote := withValidator(voteProto, privValidators[67].GetAddress(), 67)
blockPartsHeader := PartSetHeader{blockPartsTotal, crypto.CRandBytes(32)}
_, err := signAddVote(privValidators[67], withBlockPartsHeader(vote, blockPartsHeader), voteSet)
if err != nil {
t.Error(err)
}
blockID, ok = voteSet.TwoThirdsMajority()
if ok || !blockID.IsZero() {
t.Errorf("There should be no 2/3 majority: last vote added had different PartSetHeader Hash")
}
}
// 69th validator voted for different BlockParts Total
{
vote := withValidator(voteProto, privValidators[68].GetAddress(), 68)
blockPartsHeader := PartSetHeader{blockPartsTotal + 1, blockPartsHeader.Hash}
_, err := signAddVote(privValidators[68], withBlockPartsHeader(vote, blockPartsHeader), voteSet)
if err != nil {
t.Error(err)
}
blockID, ok = voteSet.TwoThirdsMajority()
if ok || !blockID.IsZero() {
t.Errorf("There should be no 2/3 majority: last vote added had different PartSetHeader Total")
}
}
// 70th validator voted for different BlockHash
{
vote := withValidator(voteProto, privValidators[69].GetAddress(), 69)
_, err := signAddVote(privValidators[69], withBlockHash(vote, cmn.RandBytes(32)), voteSet)
if err != nil {
t.Error(err)
}
blockID, ok = voteSet.TwoThirdsMajority()
if ok || !blockID.IsZero() {
t.Errorf("There should be no 2/3 majority: last vote added had different BlockHash")
}
}
// 71st validator voted for the right BlockHash & BlockPartsHeader
{
vote := withValidator(voteProto, privValidators[70].GetAddress(), 70)
_, err := signAddVote(privValidators[70], vote, voteSet)
if err != nil {
t.Error(err)
}
blockID, ok = voteSet.TwoThirdsMajority()
if !ok || !blockID.Equals(BlockID{blockHash, blockPartsHeader}) {
t.Errorf("There should be 2/3 majority")
}
}
}
func TestBadVotes(t *testing.T) {
height, round := int64(1), 0
voteSet, _, privValidators := randVoteSet(height, round, VoteTypePrevote, 10, 1)
voteProto := &Vote{
ValidatorAddress: nil,
ValidatorIndex: -1,
Height: height,
Round: round,
Timestamp: time.Now().UTC(),
Type: VoteTypePrevote,
BlockID: BlockID{nil, PartSetHeader{}},
}
// val0 votes for nil.
{
vote := withValidator(voteProto, privValidators[0].GetAddress(), 0)
added, err := signAddVote(privValidators[0], vote, voteSet)
if !added || err != nil {
t.Errorf("Expected VoteSet.Add to succeed")
}
}
// val0 votes again for some block.
{
vote := withValidator(voteProto, privValidators[0].GetAddress(), 0)
added, err := signAddVote(privValidators[0], withBlockHash(vote, cmn.RandBytes(32)), voteSet)
if added || err == nil {
t.Errorf("Expected VoteSet.Add to fail, conflicting vote.")
}
}
// val1 votes on another height
{
vote := withValidator(voteProto, privValidators[1].GetAddress(), 1)
added, err := signAddVote(privValidators[1], withHeight(vote, height+1), voteSet)
if added || err == nil {
t.Errorf("Expected VoteSet.Add to fail, wrong height")
}
}
// val2 votes on another round
{
vote := withValidator(voteProto, privValidators[2].GetAddress(), 2)
added, err := signAddVote(privValidators[2], withRound(vote, round+1), voteSet)
if added || err == nil {
t.Errorf("Expected VoteSet.Add to fail, wrong round")
}
}
// val3 votes of another type.
{
vote := withValidator(voteProto, privValidators[3].GetAddress(), 3)
added, err := signAddVote(privValidators[3], withType(vote, VoteTypePrecommit), voteSet)
if added || err == nil {
t.Errorf("Expected VoteSet.Add to fail, wrong type")
}
}
}
func TestConflicts(t *testing.T) {
height, round := int64(1), 0
voteSet, _, privValidators := randVoteSet(height, round, VoteTypePrevote, 4, 1)
blockHash1 := cmn.RandBytes(32)
blockHash2 := cmn.RandBytes(32)
voteProto := &Vote{
ValidatorAddress: nil,
ValidatorIndex: -1,
Height: height,
Round: round,
Timestamp: time.Now().UTC(),
Type: VoteTypePrevote,
BlockID: BlockID{nil, PartSetHeader{}},
}
// val0 votes for nil.
{
vote := withValidator(voteProto, privValidators[0].GetAddress(), 0)
added, err := signAddVote(privValidators[0], vote, voteSet)
if !added || err != nil {
t.Errorf("Expected VoteSet.Add to succeed")
}
}
// val0 votes again for blockHash1.
{
vote := withValidator(voteProto, privValidators[0].GetAddress(), 0)
added, err := signAddVote(privValidators[0], withBlockHash(vote, blockHash1), voteSet)
if added {
t.Errorf("Expected VoteSet.Add to fail, conflicting vote.")
}
if err == nil {
t.Errorf("Expected VoteSet.Add to return error, conflicting vote.")
}
}
// start tracking blockHash1
voteSet.SetPeerMaj23("peerA", BlockID{blockHash1, PartSetHeader{}})
// val0 votes again for blockHash1.
{
vote := withValidator(voteProto, privValidators[0].GetAddress(), 0)
added, err := signAddVote(privValidators[0], withBlockHash(vote, blockHash1), voteSet)
if !added {
t.Errorf("Expected VoteSet.Add to succeed, called SetPeerMaj23().")
}
if err == nil {
t.Errorf("Expected VoteSet.Add to return error, conflicting vote.")
}
}
// attempt tracking blockHash2, should fail because already set for peerA.
voteSet.SetPeerMaj23("peerA", BlockID{blockHash2, PartSetHeader{}})
// val0 votes again for blockHash1.
{
vote := withValidator(voteProto, privValidators[0].GetAddress(), 0)
added, err := signAddVote(privValidators[0], withBlockHash(vote, blockHash2), voteSet)
if added {
t.Errorf("Expected VoteSet.Add to fail, duplicate SetPeerMaj23() from peerA")
}
if err == nil {
t.Errorf("Expected VoteSet.Add to return error, conflicting vote.")
}
}
// val1 votes for blockHash1.
{
vote := withValidator(voteProto, privValidators[1].GetAddress(), 1)
added, err := signAddVote(privValidators[1], withBlockHash(vote, blockHash1), voteSet)
if !added || err != nil {
t.Errorf("Expected VoteSet.Add to succeed")
}
}
// check
if voteSet.HasTwoThirdsMajority() {
t.Errorf("We shouldn't have 2/3 majority yet")
}
if voteSet.HasTwoThirdsAny() {
t.Errorf("We shouldn't have 2/3 if any votes yet")
}
// val2 votes for blockHash2.
{
vote := withValidator(voteProto, privValidators[2].GetAddress(), 2)
added, err := signAddVote(privValidators[2], withBlockHash(vote, blockHash2), voteSet)
if !added || err != nil {
t.Errorf("Expected VoteSet.Add to succeed")
}
}
// check
if voteSet.HasTwoThirdsMajority() {
t.Errorf("We shouldn't have 2/3 majority yet")
}
if !voteSet.HasTwoThirdsAny() {
t.Errorf("We should have 2/3 if any votes")
}
// now attempt tracking blockHash1
voteSet.SetPeerMaj23("peerB", BlockID{blockHash1, PartSetHeader{}})
// val2 votes for blockHash1.
{
vote := withValidator(voteProto, privValidators[2].GetAddress(), 2)
added, err := signAddVote(privValidators[2], withBlockHash(vote, blockHash1), voteSet)
if !added {
t.Errorf("Expected VoteSet.Add to succeed")
}
if err == nil {
t.Errorf("Expected VoteSet.Add to return error, conflicting vote")
}
}
// check
if !voteSet.HasTwoThirdsMajority() {
t.Errorf("We should have 2/3 majority for blockHash1")
}
blockIDMaj23, _ := voteSet.TwoThirdsMajority()
if !bytes.Equal(blockIDMaj23.Hash, blockHash1) {
t.Errorf("Got the wrong 2/3 majority blockhash")
}
if !voteSet.HasTwoThirdsAny() {
t.Errorf("We should have 2/3 if any votes")
}
}
func TestMakeCommit(t *testing.T) {
height, round := int64(1), 0
voteSet, _, privValidators := randVoteSet(height, round, VoteTypePrecommit, 10, 1)
blockHash, blockPartsHeader := crypto.CRandBytes(32), PartSetHeader{123, crypto.CRandBytes(32)}
voteProto := &Vote{
ValidatorAddress: nil,
ValidatorIndex: -1,
Height: height,
Round: round,
Timestamp: time.Now().UTC(),
Type: VoteTypePrecommit,
BlockID: BlockID{blockHash, blockPartsHeader},
}
// 6 out of 10 voted for some block.
for i := 0; i < 6; i++ {
vote := withValidator(voteProto, privValidators[i].GetAddress(), i)
_, err := signAddVote(privValidators[i], vote, voteSet)
if err != nil {
t.Error(err)
}
}
// MakeCommit should fail.
tst.AssertPanics(t, "Doesn't have +2/3 majority", func() { voteSet.MakeCommit() })
// 7th voted for some other block.
{
vote := withValidator(voteProto, privValidators[6].GetAddress(), 6)
vote = withBlockHash(vote, cmn.RandBytes(32))
vote = withBlockPartsHeader(vote, PartSetHeader{123, cmn.RandBytes(32)})
_, err := signAddVote(privValidators[6], vote, voteSet)
if err != nil {
t.Error(err)
}
}
// The 8th voted like everyone else.
{
vote := withValidator(voteProto, privValidators[7].GetAddress(), 7)
_, err := signAddVote(privValidators[7], vote, voteSet)
if err != nil {
t.Error(err)
}
}
commit := voteSet.MakeCommit()
// Commit should have 10 elements
if len(commit.Precommits) != 10 {
t.Errorf("Commit Precommits should have the same number of precommits as validators")
}
// Ensure that Commit precommits are ordered.
if err := commit.ValidateBasic(); err != nil {
t.Errorf("Error in Commit.ValidateBasic(): %v", err)
}
}
+121
View File
@@ -0,0 +1,121 @@
package types
import (
"testing"
"time"
"github.com/stretchr/testify/require"
)
func examplePrevote() *Vote {
return exampleVote(VoteTypePrevote)
}
func examplePrecommit() *Vote {
return exampleVote(VoteTypePrecommit)
}
func exampleVote(t byte) *Vote {
var stamp, err = time.Parse(TimeFormat, "2017-12-25T03:00:01.234Z")
if err != nil {
panic(err)
}
return &Vote{
ValidatorAddress: []byte("addr"),
ValidatorIndex: 56789,
Height: 12345,
Round: 2,
Timestamp: stamp,
Type: t,
BlockID: BlockID{
Hash: []byte("hash"),
PartsHeader: PartSetHeader{
Total: 1000000,
Hash: []byte("parts_hash"),
},
},
}
}
func TestVoteSignable(t *testing.T) {
vote := examplePrecommit()
signBytes := vote.SignBytes("test_chain_id")
signStr := string(signBytes)
expected := `{"@chain_id":"test_chain_id","@type":"vote","block_id":{"hash":"68617368","parts":{"hash":"70617274735F68617368","total":"1000000"}},"height":"12345","round":"2","timestamp":"2017-12-25T03:00:01.234Z","type":2}`
if signStr != expected {
// NOTE: when this fails, you probably want to fix up consensus/replay_test too
t.Errorf("Got unexpected sign string for Vote. Expected:\n%v\nGot:\n%v", expected, signStr)
}
}
func TestVoteString(t *testing.T) {
tc := []struct {
name string
in string
out string
}{
{"Precommit", examplePrecommit().String(), `Vote{56789:616464720000 12345/02/2(Precommit) 686173680000 <nil> @ 2017-12-25T03:00:01.234Z}`},
{"Prevote", examplePrevote().String(), `Vote{56789:616464720000 12345/02/1(Prevote) 686173680000 <nil> @ 2017-12-25T03:00:01.234Z}`},
}
for _, tt := range tc {
tt := tt
t.Run(tt.name, func(st *testing.T) {
if tt.in != tt.out {
t.Errorf("Got unexpected string for Proposal. Expected:\n%v\nGot:\n%v", tt.in, tt.out)
}
})
}
}
func TestVoteVerifySignature(t *testing.T) {
privVal := NewMockPV()
pubKey := privVal.GetPubKey()
vote := examplePrecommit()
signBytes := vote.SignBytes("test_chain_id")
// sign it
err := privVal.SignVote("test_chain_id", vote)
require.NoError(t, err)
// verify the same vote
valid := pubKey.VerifyBytes(vote.SignBytes("test_chain_id"), vote.Signature)
require.True(t, valid)
// serialize, deserialize and verify again....
precommit := new(Vote)
bs, err := cdc.MarshalBinary(vote)
require.NoError(t, err)
err = cdc.UnmarshalBinary(bs, &precommit)
require.NoError(t, err)
// verify the transmitted vote
newSignBytes := precommit.SignBytes("test_chain_id")
require.Equal(t, string(signBytes), string(newSignBytes))
valid = pubKey.VerifyBytes(newSignBytes, precommit.Signature)
require.True(t, valid)
}
func TestIsVoteTypeValid(t *testing.T) {
tc := []struct {
name string
in byte
out bool
}{
{"Prevote", VoteTypePrevote, true},
{"Precommit", VoteTypePrecommit, true},
{"InvalidType", byte(3), false},
}
for _, tt := range tc {
tt := tt
t.Run(tt.name, func(st *testing.T) {
if rs := IsVoteTypeValid(tt.in); rs != tt.out {
t.Errorf("Got unexpected Vote type. Expected:\n%v\nGot:\n%v", rs, tt.out)
}
})
}
}
+12
View File
@@ -0,0 +1,12 @@
package types
import (
"github.com/tendermint/go-amino"
"github.com/tendermint/tendermint/crypto"
)
var cdc = amino.NewCodec()
func init() {
crypto.RegisterAmino(cdc)
}