From 508b7f97589c43d00f8393699dcfe79c318c2481 Mon Sep 17 00:00:00 2001 From: Callum Waters Date: Mon, 9 Aug 2021 13:30:27 +0200 Subject: [PATCH] split up packages --- internal/test/factory/block.go | 16 +- internal/test/factory/commit.go | 24 +- internal/test/factory/factory_test.go | 4 +- internal/test/factory/genesis.go | 14 +- internal/test/factory/tx.go | 8 +- internal/test/factory/validator.go | 18 +- internal/test/factory/vote.go | 83 +- libs/math/safemath.go | 72 + libs/math/safemath_test.go | 60 + pkg/block/block.go | 850 +----------- pkg/block/block_meta.go | 17 +- pkg/block/block_meta_test.go | 43 +- pkg/block/block_test.go | 1173 ++--------------- pkg/block/encoding_helper.go | 47 - pkg/block/signable.go | 23 - pkg/block/signed_msg_type.go | 13 - pkg/block/test_util.go | 47 - pkg/block/utils.go | 29 - pkg/consensus/canonical.go | 32 + pkg/{block => consensus}/errors.go | 2 +- pkg/{block => consensus}/genesis.go | 12 +- pkg/{block => consensus}/genesis_test.go | 29 +- {types => pkg/consensus}/keys.go | 2 +- pkg/{block => consensus}/params.go | 22 +- pkg/{block => consensus}/params_test.go | 2 +- pkg/{block => consensus}/priv_validator.go | 2 +- pkg/{block => consensus}/proposal.go | 25 +- pkg/{block => consensus}/proposal_test.go | 77 +- pkg/{block => consensus}/protobuf.go | 2 +- pkg/{block => consensus}/protobuf_test.go | 21 +- pkg/{block => consensus}/validation.go | 60 +- pkg/{block => consensus}/validation_test.go | 59 +- pkg/{block => consensus}/validator.go | 2 +- pkg/{block => consensus}/validator_set.go | 87 +- pkg/consensus/validator_set_benchmark_test.go | 159 +++ .../validator_set_test.go | 225 +--- pkg/{block => consensus}/validator_test.go | 44 +- pkg/{block => consensus}/vote.go | 66 +- pkg/{block => consensus}/vote_set.go | 58 +- pkg/{block => consensus}/vote_set_test.go | 235 ++-- pkg/{block => consensus}/vote_test.go | 13 +- pkg/{block => events}/event_bus.go | 5 +- pkg/{block => events}/event_bus_test.go | 151 ++- pkg/{block => events}/events.go | 28 +- pkg/{block => events}/events_test.go | 6 +- pkg/{block => evidence}/evidence.go | 155 +-- pkg/{block => evidence}/evidence_test.go | 173 +-- pkg/{block => light}/light.go | 16 +- pkg/{block => light}/light_test.go | 98 +- pkg/{block => mempool}/tx.go | 10 +- pkg/{block => mempool}/tx_test.go | 2 +- pkg/{block => meta}/canonical.go | 28 +- pkg/{block => meta}/canonical_test.go | 5 +- pkg/meta/commit.go | 379 ++++++ pkg/meta/commit_test.go | 284 ++++ pkg/meta/header.go | 293 ++++ pkg/meta/header_test.go | 496 +++++++ pkg/meta/id.go | 124 ++ pkg/meta/id_test.go | 91 ++ pkg/{block => meta}/part_set.go | 10 +- pkg/{block => meta}/part_set_test.go | 2 +- pkg/meta/utils.go | 75 ++ types/errors_p2p.go => pkg/p2p/errors.go | 2 +- {types => pkg/p2p}/netaddress.go | 2 +- {types => pkg/p2p}/netaddress_test.go | 2 +- {types => pkg/p2p}/node_id.go | 2 +- {types => pkg/p2p}/node_info.go | 2 +- {types => pkg/p2p}/node_info_test.go | 2 +- {types => pkg/p2p}/node_key.go | 2 +- {types => pkg/p2p}/node_key_test.go | 16 +- 70 files changed, 3152 insertions(+), 3086 deletions(-) create mode 100644 libs/math/safemath_test.go delete mode 100644 pkg/block/encoding_helper.go delete mode 100644 pkg/block/signable.go delete mode 100644 pkg/block/signed_msg_type.go delete mode 100644 pkg/block/test_util.go delete mode 100644 pkg/block/utils.go create mode 100644 pkg/consensus/canonical.go rename pkg/{block => consensus}/errors.go (98%) rename pkg/{block => consensus}/genesis.go (96%) rename pkg/{block => consensus}/genesis_test.go (85%) rename {types => pkg/consensus}/keys.go (78%) rename pkg/{block => consensus}/params.go (92%) rename pkg/{block => consensus}/params_test.go (99%) rename pkg/{block => consensus}/priv_validator.go (99%) rename pkg/{block => consensus}/proposal.go (85%) rename pkg/{block => consensus}/proposal_test.go (60%) rename pkg/{block => consensus}/protobuf.go (98%) rename pkg/{block => consensus}/protobuf_test.go (66%) rename pkg/{block => consensus}/validation.go (87%) rename pkg/{block => consensus}/validation_test.go (77%) rename pkg/{block => consensus}/validator.go (99%) rename pkg/{block => consensus}/validator_set.go (94%) create mode 100644 pkg/consensus/validator_set_benchmark_test.go rename pkg/{block => consensus}/validator_set_test.go (87%) rename pkg/{block => consensus}/validator_test.go (64%) rename pkg/{block => consensus}/vote.go (77%) rename pkg/{block => consensus}/vote_set.go (91%) rename pkg/{block => consensus}/vote_set_test.go (63%) rename pkg/{block => consensus}/vote_test.go (96%) rename pkg/{block => events}/event_bus.go (98%) rename pkg/{block => events}/event_bus_test.go (72%) rename pkg/{block => events}/events.go (92%) rename pkg/{block => events}/events_test.go (83%) rename pkg/{block => evidence}/evidence.go (91%) rename pkg/{block => evidence}/evidence_test.go (61%) rename pkg/{block => light}/light.go (93%) rename pkg/{block => light}/light_test.go (58%) rename pkg/{block => mempool}/tx.go (92%) rename pkg/{block => mempool}/tx_test.go (99%) rename pkg/{block => meta}/canonical.go (56%) rename pkg/{block => meta}/canonical_test.go (88%) create mode 100644 pkg/meta/commit.go create mode 100644 pkg/meta/commit_test.go create mode 100644 pkg/meta/header.go create mode 100644 pkg/meta/header_test.go create mode 100644 pkg/meta/id.go create mode 100644 pkg/meta/id_test.go rename pkg/{block => meta}/part_set.go (97%) rename pkg/{block => meta}/part_set_test.go (99%) create mode 100644 pkg/meta/utils.go rename types/errors_p2p.go => pkg/p2p/errors.go (97%) rename {types => pkg/p2p}/netaddress.go (99%) rename {types => pkg/p2p}/netaddress_test.go (99%) rename {types => pkg/p2p}/node_id.go (99%) rename {types => pkg/p2p}/node_info.go (99%) rename {types => pkg/p2p}/node_info_test.go (99%) rename {types => pkg/p2p}/node_key.go (99%) rename {types => pkg/p2p}/node_key_test.go (71%) diff --git a/internal/test/factory/block.go b/internal/test/factory/block.go index f8772f189..ae933eb08 100644 --- a/internal/test/factory/block.go +++ b/internal/test/factory/block.go @@ -5,7 +5,7 @@ import ( "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/tmhash" - "github.com/tendermint/tendermint/types" + "github.com/tendermint/tendermint/pkg/meta" "github.com/tendermint/tendermint/version" ) @@ -32,14 +32,14 @@ func RandomHash() []byte { return crypto.CRandBytes(tmhash.Size) } -func MakeBlockID() types.BlockID { +func MakeBlockID() meta.BlockID { return MakeBlockIDWithHash(RandomHash()) } -func MakeBlockIDWithHash(hash []byte) types.BlockID { - return types.BlockID{ +func MakeBlockIDWithHash(hash []byte) meta.BlockID { + return meta.BlockID{ Hash: hash, - PartSetHeader: types.PartSetHeader{ + PartSetHeader: meta.PartSetHeader{ Total: 100, Hash: RandomHash(), }, @@ -48,7 +48,7 @@ func MakeBlockIDWithHash(hash []byte) types.BlockID { // MakeHeader fills the rest of the contents of the header such that it passes // validate basic -func MakeHeader(h *types.Header) (*types.Header, error) { +func MakeHeader(h *meta.Header) (*meta.Header, error) { if h.Version.Block == 0 { h.Version.Block = version.BlockProtocol } @@ -92,8 +92,8 @@ func MakeHeader(h *types.Header) (*types.Header, error) { return h, h.ValidateBasic() } -func MakeRandomHeader() *types.Header { - h, err := MakeHeader(&types.Header{}) +func MakeRandomHeader() *meta.Header { + h, err := MakeHeader(&meta.Header{}) if err != nil { panic(err) } diff --git a/internal/test/factory/commit.go b/internal/test/factory/commit.go index d3a392a20..67418f312 100644 --- a/internal/test/factory/commit.go +++ b/internal/test/factory/commit.go @@ -5,12 +5,24 @@ import ( "fmt" "time" + "github.com/tendermint/tendermint/pkg/consensus" + "github.com/tendermint/tendermint/pkg/meta" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" - "github.com/tendermint/tendermint/types" ) -func MakeCommit(blockID types.BlockID, height int64, round int32, - voteSet *types.VoteSet, validators []types.PrivValidator, now time.Time) (*types.Commit, error) { +func MakeRandomCommit(time time.Time) *meta.Commit { + lastID := MakeBlockID() + h := int64(3) + voteSet, _, vals := RandVoteSet(h-1, 1, tmproto.PrecommitType, 10, 1) + commit, err := MakeCommit(lastID, h-1, 1, voteSet, vals, time) + if err != nil { + panic(err) + } + return commit +} + +func MakeCommit(blockID meta.BlockID, height int64, round int32, + voteSet *consensus.VoteSet, validators []consensus.PrivValidator, now time.Time) (*meta.Commit, error) { // all sign for i := 0; i < len(validators); i++ { @@ -18,7 +30,7 @@ func MakeCommit(blockID types.BlockID, height int64, round int32, if err != nil { return nil, fmt.Errorf("can't get pubkey: %w", err) } - vote := &types.Vote{ + vote := &consensus.Vote{ ValidatorAddress: pubKey.Address(), ValidatorIndex: int32(i), Height: height, @@ -28,7 +40,7 @@ func MakeCommit(blockID types.BlockID, height int64, round int32, Timestamp: now, } - _, err = signAddVote(validators[i], vote, voteSet) + _, err = SignAddVote(validators[i], vote, voteSet) if err != nil { return nil, err } @@ -37,7 +49,7 @@ func MakeCommit(blockID types.BlockID, height int64, round int32, return voteSet.MakeCommit(), nil } -func signAddVote(privVal types.PrivValidator, vote *types.Vote, voteSet *types.VoteSet) (signed bool, err error) { +func SignAddVote(privVal consensus.PrivValidator, vote *consensus.Vote, voteSet *consensus.VoteSet) (signed bool, err error) { v := vote.ToProto() err = privVal.SignVote(context.Background(), voteSet.ChainID(), v) if err != nil { diff --git a/internal/test/factory/factory_test.go b/internal/test/factory/factory_test.go index 25f234508..a9a5040b0 100644 --- a/internal/test/factory/factory_test.go +++ b/internal/test/factory/factory_test.go @@ -5,10 +5,10 @@ import ( "github.com/stretchr/testify/assert" - "github.com/tendermint/tendermint/types" + "github.com/tendermint/tendermint/pkg/meta" ) func TestMakeHeader(t *testing.T) { - _, err := MakeHeader(&types.Header{}) + _, err := MakeHeader(&meta.Header{}) assert.NoError(t, err) } diff --git a/internal/test/factory/genesis.go b/internal/test/factory/genesis.go index 31ec1674f..ba6106ca6 100644 --- a/internal/test/factory/genesis.go +++ b/internal/test/factory/genesis.go @@ -5,28 +5,28 @@ import ( cfg "github.com/tendermint/tendermint/config" tmtime "github.com/tendermint/tendermint/libs/time" - "github.com/tendermint/tendermint/types" + "github.com/tendermint/tendermint/pkg/consensus" ) func RandGenesisDoc( config *cfg.Config, numValidators int, randPower bool, - minPower int64) (*types.GenesisDoc, []types.PrivValidator) { + minPower int64) (*consensus.GenesisDoc, []consensus.PrivValidator) { - validators := make([]types.GenesisValidator, numValidators) - privValidators := make([]types.PrivValidator, numValidators) + validators := make([]consensus.GenesisValidator, numValidators) + privValidators := make([]consensus.PrivValidator, numValidators) for i := 0; i < numValidators; i++ { val, privVal := RandValidator(randPower, minPower) - validators[i] = types.GenesisValidator{ + validators[i] = consensus.GenesisValidator{ PubKey: val.PubKey, Power: val.VotingPower, } privValidators[i] = privVal } - sort.Sort(types.PrivValidatorsByAddress(privValidators)) + sort.Sort(consensus.PrivValidatorsByAddress(privValidators)) - return &types.GenesisDoc{ + return &consensus.GenesisDoc{ GenesisTime: tmtime.Now(), InitialHeight: 1, ChainID: config.ChainID(), diff --git a/internal/test/factory/tx.go b/internal/test/factory/tx.go index c97aeefc9..2b8cf6231 100644 --- a/internal/test/factory/tx.go +++ b/internal/test/factory/tx.go @@ -1,16 +1,16 @@ package factory -import "github.com/tendermint/tendermint/types" +import "github.com/tendermint/tendermint/pkg/mempool" // MakeTxs is a helper function to generate mock transactions by given the block height // and the transaction numbers. -func MakeTxs(height int64, num int) (txs []types.Tx) { +func MakeTxs(height int64, num int) (txs []mempool.Tx) { for i := 0; i < num; i++ { - txs = append(txs, types.Tx([]byte{byte(height), byte(i)})) + txs = append(txs, mempool.Tx([]byte{byte(height), byte(i)})) } return txs } -func MakeTenTxs(height int64) (txs []types.Tx) { +func MakeTenTxs(height int64) (txs []mempool.Tx) { return MakeTxs(height, 10) } diff --git a/internal/test/factory/validator.go b/internal/test/factory/validator.go index 428d5d86e..786865e2f 100644 --- a/internal/test/factory/validator.go +++ b/internal/test/factory/validator.go @@ -6,11 +6,11 @@ import ( "math/rand" "sort" - "github.com/tendermint/tendermint/types" + "github.com/tendermint/tendermint/pkg/consensus" ) -func RandValidator(randPower bool, minPower int64) (*types.Validator, types.PrivValidator) { - privVal := types.NewMockPV() +func RandValidator(randPower bool, minPower int64) (*consensus.Validator, consensus.PrivValidator) { + privVal := consensus.NewMockPV() votePower := minPower if randPower { // nolint:gosec // G404: Use of weak random number generator @@ -20,14 +20,14 @@ func RandValidator(randPower bool, minPower int64) (*types.Validator, types.Priv if err != nil { panic(fmt.Errorf("could not retrieve pubkey %w", err)) } - val := types.NewValidator(pubKey, votePower) + val := consensus.NewValidator(pubKey, votePower) return val, privVal } -func RandValidatorSet(numValidators int, votingPower int64) (*types.ValidatorSet, []types.PrivValidator) { +func RandValidatorSet(numValidators int, votingPower int64) (*consensus.ValidatorSet, []consensus.PrivValidator) { var ( - valz = make([]*types.Validator, numValidators) - privValidators = make([]types.PrivValidator, numValidators) + valz = make([]*consensus.Validator, numValidators) + privValidators = make([]consensus.PrivValidator, numValidators) ) for i := 0; i < numValidators; i++ { @@ -36,7 +36,7 @@ func RandValidatorSet(numValidators int, votingPower int64) (*types.ValidatorSet privValidators[i] = privValidator } - sort.Sort(types.PrivValidatorsByAddress(privValidators)) + sort.Sort(consensus.PrivValidatorsByAddress(privValidators)) - return types.NewValidatorSet(valz), privValidators + return consensus.NewValidatorSet(valz), privValidators } diff --git a/internal/test/factory/vote.go b/internal/test/factory/vote.go index 8d07b008c..293a327b9 100644 --- a/internal/test/factory/vote.go +++ b/internal/test/factory/vote.go @@ -2,27 +2,32 @@ package factory import ( "context" + "fmt" + "sort" "time" + "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/crypto/ed25519" + "github.com/tendermint/tendermint/pkg/consensus" + "github.com/tendermint/tendermint/pkg/meta" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" - "github.com/tendermint/tendermint/types" ) func MakeVote( - val types.PrivValidator, + val consensus.PrivValidator, chainID string, valIndex int32, height int64, round int32, step int, - blockID types.BlockID, + blockID meta.BlockID, time time.Time, -) (*types.Vote, error) { +) (*consensus.Vote, error) { pubKey, err := val.GetPubKey(context.Background()) if err != nil { return nil, err } - v := &types.Vote{ + v := &consensus.Vote{ ValidatorAddress: pubKey.Address(), ValidatorIndex: valIndex, Height: height, @@ -40,3 +45,71 @@ func MakeVote( v.Signature = vpb.Signature return v, nil } + +func RandVoteSet( + height int64, + round int32, + signedMsgType tmproto.SignedMsgType, + numValidators int, + votingPower int64, +) (*consensus.VoteSet, *consensus.ValidatorSet, []consensus.PrivValidator) { + valSet, privValidators := RandValidatorPrivValSet(numValidators, votingPower) + return consensus.NewVoteSet("test_chain_id", height, round, signedMsgType, valSet), valSet, privValidators +} + +func RandValidatorPrivValSet(numValidators int, votingPower int64) (*consensus.ValidatorSet, []consensus.PrivValidator) { + var ( + valz = make([]*consensus.Validator, numValidators) + privValidators = make([]consensus.PrivValidator, numValidators) + ) + + for i := 0; i < numValidators; i++ { + val, privValidator := RandValidator(false, votingPower) + valz[i] = val + privValidators[i] = privValidator + } + + sort.Sort(consensus.PrivValidatorsByAddress(privValidators)) + + return consensus.NewValidatorSet(valz), privValidators +} + +func DeterministicVoteSet( + height int64, + round int32, + signedMsgType tmproto.SignedMsgType, + votingPower int64, +) (*consensus.VoteSet, *consensus.ValidatorSet, []consensus.PrivValidator) { + valSet, privValidators := DeterministicValidatorSet() + return consensus.NewVoteSet("test_chain_id", height, round, signedMsgType, valSet), valSet, privValidators +} + +func DeterministicValidatorSet() (*consensus.ValidatorSet, []consensus.PrivValidator) { + var ( + valz = make([]*consensus.Validator, 10) + privValidators = make([]consensus.PrivValidator, 10) + ) + + for i := 0; i < 10; i++ { + // val, privValidator := DeterministicValidator(ed25519.PrivKey([]byte(deterministicKeys[i]))) + val, privValidator := DeterministicValidator(ed25519.GenPrivKeyFromSecret([]byte(fmt.Sprintf("key: %x", i)))) + valz[i] = val + privValidators[i] = privValidator + } + + sort.Sort(consensus.PrivValidatorsByAddress(privValidators)) + + return consensus.NewValidatorSet(valz), privValidators +} + +func DeterministicValidator(key crypto.PrivKey) (*consensus.Validator, consensus.PrivValidator) { + privVal := consensus.NewMockPV() + privVal.PrivKey = key + var votePower int64 = 50 + pubKey, err := privVal.GetPubKey(context.TODO()) + if err != nil { + panic(fmt.Errorf("could not retrieve pubkey %w", err)) + } + val := consensus.NewValidator(pubKey, votePower) + return val, privVal +} diff --git a/libs/math/safemath.go b/libs/math/safemath.go index ff7f0908f..37bd743d8 100644 --- a/libs/math/safemath.go +++ b/libs/math/safemath.go @@ -9,6 +9,78 @@ var ErrOverflowInt32 = errors.New("int32 overflow") var ErrOverflowUint8 = errors.New("uint8 overflow") var ErrOverflowInt8 = errors.New("int8 overflow") +// SafeAdd adds two int64 numbers. If there is an overflow, +// the function will return -1, true +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 +} + +// SafeSub subtracts two int64 numbers. If there is an overflow, +// the function will return -1, true +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 +} + +// SafeAddClip performs SafeAdd, however if there is an overflow, +// it will return the maxInt64 +func SafeAddClip(a, b int64) int64 { + c, overflow := SafeAdd(a, b) + if overflow { + if b < 0 { + return math.MinInt64 + } + return math.MaxInt64 + } + return c +} + +// SafeSubClip performs SafeSub but will clip the result to either +// the min or max int64 number +func SafeSubClip(a, b int64) int64 { + c, overflow := SafeSub(a, b) + if overflow { + if b > 0 { + return math.MinInt64 + } + return math.MaxInt64 + } + return c +} + +// SafeMul multiplies two int64 numbers. It returns +// true if the resultant overflows. +func SafeMul(a, b int64) (int64, bool) { + if a == 0 || b == 0 { + return 0, false + } + + absOfB := b + if b < 0 { + absOfB = -b + } + + absOfA := a + if a < 0 { + absOfA = -a + } + + if absOfA > math.MaxInt64/absOfB { + return 0, true + } + + return a * b, false +} + // SafeAddInt32 adds two int32 integers // If there is an overflow this will panic func SafeAddInt32(a, b int32) int32 { diff --git a/libs/math/safemath_test.go b/libs/math/safemath_test.go new file mode 100644 index 000000000..1237cd0a4 --- /dev/null +++ b/libs/math/safemath_test.go @@ -0,0 +1,60 @@ +package math_test + +import ( + "math" + "testing" + "testing/quick" + + "github.com/stretchr/testify/assert" + + tmmath "github.com/tendermint/tendermint/libs/math" +) + +func TestSafeAdd(t *testing.T) { + f := func(a, b int64) bool { + c, overflow := tmmath.SafeAdd(a, b) + return overflow || (!overflow && c == a+b) + } + if err := quick.Check(f, nil); err != nil { + t.Error(err) + } +} + +func TestSafeAddClip(t *testing.T) { + assert.EqualValues(t, math.MaxInt64, tmmath.SafeAddClip(math.MaxInt64, 10)) + assert.EqualValues(t, math.MaxInt64, tmmath.SafeAddClip(math.MaxInt64, math.MaxInt64)) + assert.EqualValues(t, math.MinInt64, tmmath.SafeAddClip(math.MinInt64, -10)) +} + +func TestSafeSubClip(t *testing.T) { + assert.EqualValues(t, math.MinInt64, tmmath.SafeSubClip(math.MinInt64, 10)) + assert.EqualValues(t, 0, tmmath.SafeSubClip(math.MinInt64, math.MinInt64)) + assert.EqualValues(t, math.MinInt64, tmmath.SafeSubClip(math.MinInt64, math.MaxInt64)) + assert.EqualValues(t, math.MaxInt64, tmmath.SafeSubClip(math.MaxInt64, -10)) +} + +func TestSafeMul(t *testing.T) { + testCases := []struct { + a int64 + b int64 + c int64 + overflow bool + }{ + 0: {0, 0, 0, false}, + 1: {1, 0, 0, false}, + 2: {2, 3, 6, false}, + 3: {2, -3, -6, false}, + 4: {-2, -3, 6, false}, + 5: {-2, 3, -6, false}, + 6: {math.MaxInt64, 1, math.MaxInt64, false}, + 7: {math.MaxInt64 / 2, 2, math.MaxInt64 - 1, false}, + 8: {math.MaxInt64 / 2, 3, 0, true}, + 9: {math.MaxInt64, 2, 0, true}, + } + + for i, tc := range testCases { + c, overflow := tmmath.SafeMul(tc.a, tc.b) + assert.Equal(t, tc.c, c, "#%d", i) + assert.Equal(t, tc.overflow, overflow, "#%d", i) + } +} diff --git a/pkg/block/block.go b/pkg/block/block.go index 74fd18287..af3f8fdab 100644 --- a/pkg/block/block.go +++ b/pkg/block/block.go @@ -5,47 +5,27 @@ import ( "errors" "fmt" "strings" - "time" "github.com/gogo/protobuf/proto" - gogotypes "github.com/gogo/protobuf/types" - "github.com/tendermint/tendermint/crypto" - "github.com/tendermint/tendermint/crypto/merkle" - "github.com/tendermint/tendermint/crypto/tmhash" tmsync "github.com/tendermint/tendermint/internal/libs/sync" - "github.com/tendermint/tendermint/libs/bits" tmbytes "github.com/tendermint/tendermint/libs/bytes" tmmath "github.com/tendermint/tendermint/libs/math" + "github.com/tendermint/tendermint/pkg/evidence" + "github.com/tendermint/tendermint/pkg/mempool" + "github.com/tendermint/tendermint/pkg/meta" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" "github.com/tendermint/tendermint/version" ) -const ( - // MaxHeaderBytes is a maximum header size. - // NOTE: Because app hash can be of arbitrary size, the header is therefore not - // capped in size and thus this number should be seen as a soft max - MaxHeaderBytes int64 = 626 - - // MaxOverheadForBlock - maximum overhead to encode a block (up to - // MaxBlockSizeBytes in size) not including it's parts except Data. - // This means it also excludes the overhead for individual transactions. - // - // Uvarint length of MaxBlockSizeBytes: 4 bytes - // 2 fields (2 embedded): 2 bytes - // Uvarint length of Data.Txs: 4 bytes - // Data.Txs field: 1 byte - MaxOverheadForBlock int64 = 11 -) - // Block defines the atomic unit of a Tendermint blockchain. type Block struct { mtx tmsync.Mutex - Header `json:"header"` - Data `json:"data"` - Evidence EvidenceData `json:"evidence"` - LastCommit *Commit `json:"last_commit"` + meta.Header `json:"header"` + Data `json:"data"` + Evidence EvidenceData `json:"evidence"` + LastCommit *meta.Commit `json:"last_commit"` } // ValidateBasic performs basic validation that doesn't involve state data. @@ -126,7 +106,7 @@ func (b *Block) Hash() tmbytes.HexBytes { // MakePartSet returns a PartSet containing parts of a serialized block. // This is the form in which the block is gossipped to peers. // CONTRACT: partSize is greater than zero. -func (b *Block) MakePartSet(partSize uint32) *PartSet { +func (b *Block) MakePartSet(partSize uint32) *meta.PartSet { if b == nil { return nil } @@ -141,7 +121,7 @@ func (b *Block) MakePartSet(partSize uint32) *PartSet { if err != nil { panic(err) } - return NewPartSetFromData(bz, partSize) + return meta.NewPartSetFromData(bz, partSize) } // HashesTo is a convenience function that checks if a block hashes to the given argument. @@ -234,7 +214,7 @@ func BlockFromProto(bp *tmproto.Block) (*Block, error) { } b := new(Block) - h, err := HeaderFromProto(&bp.Header) + h, err := meta.HeaderFromProto(&bp.Header) if err != nil { return nil, err } @@ -249,7 +229,7 @@ func BlockFromProto(bp *tmproto.Block) (*Block, error) { } if bp.LastCommit != nil { - lc, err := CommitFromProto(bp.LastCommit) + lc, err := meta.CommitFromProto(bp.LastCommit) if err != nil { return nil, err } @@ -266,9 +246,9 @@ func BlockFromProto(bp *tmproto.Block) (*Block, error) { // XXX: Panics on negative result. func MaxDataBytes(maxBytes, evidenceBytes int64, valsCount int) int64 { maxDataBytes := maxBytes - - MaxOverheadForBlock - - MaxHeaderBytes - - MaxCommitBytes(valsCount) - + meta.MaxOverheadForBlock - + meta.MaxHeaderBytes - + meta.MaxCommitBytes(valsCount) - evidenceBytes if maxDataBytes < 0 { @@ -289,9 +269,9 @@ func MaxDataBytes(maxBytes, evidenceBytes int64, valsCount int) int64 { // XXX: Panics on negative result. func MaxDataBytesNoEvidence(maxBytes int64, valsCount int) int64 { maxDataBytes := maxBytes - - MaxOverheadForBlock - - MaxHeaderBytes - - MaxCommitBytes(valsCount) + meta.MaxOverheadForBlock - + meta.MaxHeaderBytes - + meta.MaxCommitBytes(valsCount) if maxDataBytes < 0 { panic(fmt.Sprintf( @@ -307,9 +287,9 @@ func MaxDataBytesNoEvidence(maxBytes int64, valsCount int) int64 { // 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, lastCommit *Commit, evidence []Evidence) *Block { +func MakeBlock(height int64, txs []mempool.Tx, lastCommit *meta.Commit, evidence []evidence.Evidence) *Block { block := &Block{ - Header: Header{ + Header: meta.Header{ Version: version.Consensus{Block: version.BlockProtocol, App: 0}, Height: height, }, @@ -325,685 +305,13 @@ func MakeBlock(height int64, txs []Tx, lastCommit *Commit, evidence []Evidence) //----------------------------------------------------------------------------- -// Header defines the structure of a Tendermint block header. -// NOTE: changes to the Header should be duplicated in: -// - header.Hash() -// - abci.Header -// - https://github.com/tendermint/spec/blob/master/spec/blockchain/blockchain.md -type Header struct { - // basic block info - Version version.Consensus `json:"version"` - ChainID string `json:"chain_id"` - Height int64 `json:"height"` - Time time.Time `json:"time"` - - // prev block info - LastBlockID BlockID `json:"last_block_id"` - - // hashes of block data - LastCommitHash tmbytes.HexBytes `json:"last_commit_hash"` // commit from validators from the last block - DataHash tmbytes.HexBytes `json:"data_hash"` // transactions - - // hashes from the app output from the prev block - ValidatorsHash tmbytes.HexBytes `json:"validators_hash"` // validators for the current block - NextValidatorsHash tmbytes.HexBytes `json:"next_validators_hash"` // validators for the next block - ConsensusHash tmbytes.HexBytes `json:"consensus_hash"` // consensus params for current block - AppHash tmbytes.HexBytes `json:"app_hash"` // state after txs from the previous block - // root hash of all results from the txs from the previous block - // see `deterministicResponseDeliverTx` to understand which parts of a tx is hashed into here - LastResultsHash tmbytes.HexBytes `json:"last_results_hash"` - - // consensus info - EvidenceHash tmbytes.HexBytes `json:"evidence_hash"` // evidence included in the block - ProposerAddress Address `json:"proposer_address"` // original proposer of the block -} - -// Populate the Header with state-derived data. -// Call this after MakeBlock to complete the Header. -func (h *Header) Populate( - version version.Consensus, chainID string, - timestamp time.Time, lastBlockID BlockID, - valHash, nextValHash []byte, - consensusHash, appHash, lastResultsHash []byte, - proposerAddress Address, -) { - h.Version = version - h.ChainID = chainID - h.Time = timestamp - h.LastBlockID = lastBlockID - h.ValidatorsHash = valHash - h.NextValidatorsHash = nextValHash - h.ConsensusHash = consensusHash - h.AppHash = appHash - h.LastResultsHash = lastResultsHash - h.ProposerAddress = proposerAddress -} - -// ValidateBasic performs stateless validation on a Header returning an error -// if any validation fails. -// -// NOTE: Timestamp validation is subtle and handled elsewhere. -func (h Header) ValidateBasic() error { - if h.Version.Block != version.BlockProtocol { - return fmt.Errorf("block protocol is incorrect: got: %d, want: %d ", h.Version.Block, version.BlockProtocol) - } - if len(h.ChainID) > MaxChainIDLen { - return fmt.Errorf("chainID is too long; got: %d, max: %d", len(h.ChainID), MaxChainIDLen) - } - - if h.Height < 0 { - return errors.New("negative Height") - } else if h.Height == 0 { - return errors.New("zero Height") - } - - if err := h.LastBlockID.ValidateBasic(); err != nil { - return fmt.Errorf("wrong LastBlockID: %w", err) - } - - if err := ValidateHash(h.LastCommitHash); err != nil { - return fmt.Errorf("wrong LastCommitHash: %v", err) - } - - if err := ValidateHash(h.DataHash); err != nil { - return fmt.Errorf("wrong DataHash: %v", err) - } - - if err := ValidateHash(h.EvidenceHash); err != nil { - return fmt.Errorf("wrong EvidenceHash: %v", err) - } - - if len(h.ProposerAddress) != crypto.AddressSize { - return fmt.Errorf( - "invalid ProposerAddress length; got: %d, expected: %d", - len(h.ProposerAddress), crypto.AddressSize, - ) - } - - // Basic validation of hashes related to application data. - // Will validate fully against state in state#ValidateBlock. - if err := ValidateHash(h.ValidatorsHash); err != nil { - return fmt.Errorf("wrong ValidatorsHash: %v", err) - } - if err := ValidateHash(h.NextValidatorsHash); err != nil { - return fmt.Errorf("wrong NextValidatorsHash: %v", err) - } - if err := ValidateHash(h.ConsensusHash); err != nil { - return fmt.Errorf("wrong ConsensusHash: %v", err) - } - // NOTE: AppHash is arbitrary length - if err := ValidateHash(h.LastResultsHash); err != nil { - return fmt.Errorf("wrong LastResultsHash: %v", err) - } - - return nil -} - -// Hash returns the hash of the header. -// It computes a Merkle tree from the header fields -// ordered as they appear in the Header. -// Returns nil if ValidatorHash is missing, -// since a Header is not valid unless there is -// a ValidatorsHash (corresponding to the validator set). -func (h *Header) Hash() tmbytes.HexBytes { - if h == nil || len(h.ValidatorsHash) == 0 { - return nil - } - hpb := h.Version.ToProto() - hbz, err := hpb.Marshal() - if err != nil { - return nil - } - - pbt, err := gogotypes.StdTimeMarshal(h.Time) - if err != nil { - return nil - } - - pbbi := h.LastBlockID.ToProto() - bzbi, err := pbbi.Marshal() - if err != nil { - return nil - } - return merkle.HashFromByteSlices([][]byte{ - hbz, - cdcEncode(h.ChainID), - cdcEncode(h.Height), - pbt, - bzbi, - cdcEncode(h.LastCommitHash), - cdcEncode(h.DataHash), - cdcEncode(h.ValidatorsHash), - cdcEncode(h.NextValidatorsHash), - cdcEncode(h.ConsensusHash), - cdcEncode(h.AppHash), - cdcEncode(h.LastResultsHash), - cdcEncode(h.EvidenceHash), - cdcEncode(h.ProposerAddress), - }) -} - -// StringIndented returns an indented string representation of the header. -func (h *Header) StringIndented(indent string) string { - if h == nil { - return "nil-Header" - } - return fmt.Sprintf(`Header{ -%s Version: %v -%s ChainID: %v -%s Height: %v -%s Time: %v -%s LastBlockID: %v -%s LastCommit: %v -%s Data: %v -%s Validators: %v -%s NextValidators: %v -%s App: %v -%s Consensus: %v -%s Results: %v -%s Evidence: %v -%s Proposer: %v -%s}#%v`, - indent, h.Version, - indent, h.ChainID, - indent, h.Height, - indent, h.Time, - indent, h.LastBlockID, - indent, h.LastCommitHash, - indent, h.DataHash, - indent, h.ValidatorsHash, - indent, h.NextValidatorsHash, - indent, h.AppHash, - indent, h.ConsensusHash, - indent, h.LastResultsHash, - indent, h.EvidenceHash, - indent, h.ProposerAddress, - indent, h.Hash()) -} - -// ToProto converts Header to protobuf -func (h *Header) ToProto() *tmproto.Header { - if h == nil { - return nil - } - - return &tmproto.Header{ - Version: h.Version.ToProto(), - ChainID: h.ChainID, - Height: h.Height, - Time: h.Time, - LastBlockId: h.LastBlockID.ToProto(), - ValidatorsHash: h.ValidatorsHash, - NextValidatorsHash: h.NextValidatorsHash, - ConsensusHash: h.ConsensusHash, - AppHash: h.AppHash, - DataHash: h.DataHash, - EvidenceHash: h.EvidenceHash, - LastResultsHash: h.LastResultsHash, - LastCommitHash: h.LastCommitHash, - ProposerAddress: h.ProposerAddress, - } -} - -// FromProto sets a protobuf Header to the given pointer. -// It returns an error if the header is invalid. -func HeaderFromProto(ph *tmproto.Header) (Header, error) { - if ph == nil { - return Header{}, errors.New("nil Header") - } - - h := new(Header) - - bi, err := BlockIDFromProto(&ph.LastBlockId) - if err != nil { - return Header{}, err - } - - h.Version = version.Consensus{Block: ph.Version.Block, App: ph.Version.App} - h.ChainID = ph.ChainID - h.Height = ph.Height - h.Time = ph.Time - h.Height = ph.Height - h.LastBlockID = *bi - h.ValidatorsHash = ph.ValidatorsHash - h.NextValidatorsHash = ph.NextValidatorsHash - h.ConsensusHash = ph.ConsensusHash - h.AppHash = ph.AppHash - h.DataHash = ph.DataHash - h.EvidenceHash = ph.EvidenceHash - h.LastResultsHash = ph.LastResultsHash - h.LastCommitHash = ph.LastCommitHash - h.ProposerAddress = ph.ProposerAddress - - return *h, h.ValidateBasic() -} - -//------------------------------------- - -// BlockIDFlag indicates which BlockID the signature is for. -type BlockIDFlag byte - -const ( - // BlockIDFlagAbsent - no vote was received from a validator. - BlockIDFlagAbsent BlockIDFlag = iota + 1 - // BlockIDFlagCommit - voted for the Commit.BlockID. - BlockIDFlagCommit - // BlockIDFlagNil - voted for nil. - BlockIDFlagNil -) - -const ( - // Max size of commit without any commitSigs -> 82 for BlockID, 8 for Height, 4 for Round. - MaxCommitOverheadBytes int64 = 94 - // Commit sig size is made up of 64 bytes for the signature, 20 bytes for the address, - // 1 byte for the flag and 14 bytes for the timestamp - MaxCommitSigBytes int64 = 109 -) - -// CommitSig is a part of the Vote included in a Commit. -type CommitSig struct { - BlockIDFlag BlockIDFlag `json:"block_id_flag"` - ValidatorAddress Address `json:"validator_address"` - Timestamp time.Time `json:"timestamp"` - Signature []byte `json:"signature"` -} - -// NewCommitSigForBlock returns new CommitSig with BlockIDFlagCommit. -func NewCommitSigForBlock(signature []byte, valAddr Address, ts time.Time) CommitSig { - return CommitSig{ - BlockIDFlag: BlockIDFlagCommit, - ValidatorAddress: valAddr, - Timestamp: ts, - Signature: signature, - } -} - -func MaxCommitBytes(valCount int) int64 { - // From the repeated commit sig field - var protoEncodingOverhead int64 = 2 - return MaxCommitOverheadBytes + ((MaxCommitSigBytes + protoEncodingOverhead) * int64(valCount)) -} - -// NewCommitSigAbsent returns new CommitSig with BlockIDFlagAbsent. Other -// fields are all empty. -func NewCommitSigAbsent() CommitSig { - return CommitSig{ - BlockIDFlag: BlockIDFlagAbsent, - } -} - -// ForBlock returns true if CommitSig is for the block. -func (cs CommitSig) ForBlock() bool { - return cs.BlockIDFlag == BlockIDFlagCommit -} - -// Absent returns true if CommitSig is absent. -func (cs CommitSig) Absent() bool { - return cs.BlockIDFlag == BlockIDFlagAbsent -} - -// CommitSig returns a string representation of CommitSig. -// -// 1. first 6 bytes of signature -// 2. first 6 bytes of validator address -// 3. block ID flag -// 4. timestamp -func (cs CommitSig) String() string { - return fmt.Sprintf("CommitSig{%X by %X on %v @ %s}", - tmbytes.Fingerprint(cs.Signature), - tmbytes.Fingerprint(cs.ValidatorAddress), - cs.BlockIDFlag, - CanonicalTime(cs.Timestamp)) -} - -// BlockID returns the Commit's BlockID if CommitSig indicates signing, -// otherwise - empty BlockID. -func (cs CommitSig) BlockID(commitBlockID BlockID) BlockID { - var blockID BlockID - switch cs.BlockIDFlag { - case BlockIDFlagAbsent: - blockID = BlockID{} - case BlockIDFlagCommit: - blockID = commitBlockID - case BlockIDFlagNil: - blockID = BlockID{} - default: - panic(fmt.Sprintf("Unknown BlockIDFlag: %v", cs.BlockIDFlag)) - } - return blockID -} - -// ValidateBasic performs basic validation. -func (cs CommitSig) ValidateBasic() error { - switch cs.BlockIDFlag { - case BlockIDFlagAbsent: - case BlockIDFlagCommit: - case BlockIDFlagNil: - default: - return fmt.Errorf("unknown BlockIDFlag: %v", cs.BlockIDFlag) - } - - switch cs.BlockIDFlag { - case BlockIDFlagAbsent: - if len(cs.ValidatorAddress) != 0 { - return errors.New("validator address is present") - } - if !cs.Timestamp.IsZero() { - return errors.New("time is present") - } - if len(cs.Signature) != 0 { - return errors.New("signature is present") - } - default: - if len(cs.ValidatorAddress) != crypto.AddressSize { - return fmt.Errorf("expected ValidatorAddress size to be %d bytes, got %d bytes", - crypto.AddressSize, - len(cs.ValidatorAddress), - ) - } - // NOTE: Timestamp validation is subtle and handled elsewhere. - if len(cs.Signature) == 0 { - return errors.New("signature is missing") - } - if len(cs.Signature) > MaxSignatureSize { - return fmt.Errorf("signature is too big (max: %d)", MaxSignatureSize) - } - } - - return nil -} - -// ToProto converts CommitSig to protobuf -func (cs *CommitSig) ToProto() *tmproto.CommitSig { - if cs == nil { - return nil - } - - return &tmproto.CommitSig{ - BlockIdFlag: tmproto.BlockIDFlag(cs.BlockIDFlag), - ValidatorAddress: cs.ValidatorAddress, - Timestamp: cs.Timestamp, - Signature: cs.Signature, - } -} - -// FromProto sets a protobuf CommitSig to the given pointer. -// It returns an error if the CommitSig is invalid. -func (cs *CommitSig) FromProto(csp tmproto.CommitSig) error { - - cs.BlockIDFlag = BlockIDFlag(csp.BlockIdFlag) - cs.ValidatorAddress = csp.ValidatorAddress - cs.Timestamp = csp.Timestamp - cs.Signature = csp.Signature - - return cs.ValidateBasic() -} - -//------------------------------------- - -// 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 signatures are in order of address to preserve the bonded - // ValidatorSet order. - // Any peer with a block can gossip signatures by index with a peer without - // recalculating the active ValidatorSet. - Height int64 `json:"height"` - Round int32 `json:"round"` - BlockID BlockID `json:"block_id"` - Signatures []CommitSig `json:"signatures"` - - // Memoized in first call to corresponding method. - // NOTE: can't memoize in constructor because constructor isn't used for - // unmarshaling. - hash tmbytes.HexBytes - bitArray *bits.BitArray -} - -// NewCommit returns a new Commit. -func NewCommit(height int64, round int32, blockID BlockID, commitSigs []CommitSig) *Commit { - return &Commit{ - Height: height, - Round: round, - BlockID: blockID, - Signatures: commitSigs, - } -} - -// CommitToVoteSet constructs a VoteSet from the Commit and validator set. -// Panics if signatures from the commit can't be added to the voteset. -// Inverse of VoteSet.MakeCommit(). -func CommitToVoteSet(chainID string, commit *Commit, vals *ValidatorSet) *VoteSet { - voteSet := NewVoteSet(chainID, commit.Height, commit.Round, tmproto.PrecommitType, vals) - for idx, commitSig := range commit.Signatures { - if commitSig.Absent() { - continue // OK, some precommits can be missing. - } - added, err := voteSet.AddVote(commit.GetVote(int32(idx))) - if !added || err != nil { - panic(fmt.Sprintf("Failed to reconstruct LastCommit: %v", err)) - } - } - return voteSet -} - -// GetVote converts the CommitSig for the given valIdx to a Vote. -// Returns nil if the precommit at valIdx is nil. -// Panics if valIdx >= commit.Size(). -func (commit *Commit) GetVote(valIdx int32) *Vote { - commitSig := commit.Signatures[valIdx] - return &Vote{ - Type: tmproto.PrecommitType, - Height: commit.Height, - Round: commit.Round, - BlockID: commitSig.BlockID(commit.BlockID), - Timestamp: commitSig.Timestamp, - ValidatorAddress: commitSig.ValidatorAddress, - ValidatorIndex: valIdx, - Signature: commitSig.Signature, - } -} - -// VoteSignBytes returns the bytes of the Vote corresponding to valIdx for -// signing. -// -// The only unique part is the Timestamp - all other fields signed over are -// otherwise the same for all validators. -// -// Panics if valIdx >= commit.Size(). -// -// See VoteSignBytes -func (commit *Commit) VoteSignBytes(chainID string, valIdx int32) []byte { - v := commit.GetVote(valIdx).ToProto() - return VoteSignBytes(chainID, v) -} - -// Type returns the vote type of the commit, which is always VoteTypePrecommit -// Implements VoteSetReader. -func (commit *Commit) Type() byte { - return byte(tmproto.PrecommitType) -} - -// GetHeight returns height of the commit. -// Implements VoteSetReader. -func (commit *Commit) GetHeight() int64 { - return commit.Height -} - -// GetRound returns height of the commit. -// Implements VoteSetReader. -func (commit *Commit) GetRound() int32 { - return commit.Round -} - -// Size returns the number of signatures in the commit. -// Implements VoteSetReader. -func (commit *Commit) Size() int { - if commit == nil { - return 0 - } - return len(commit.Signatures) -} - -// BitArray returns a BitArray of which validators voted for BlockID or nil in this commit. -// Implements VoteSetReader. -func (commit *Commit) BitArray() *bits.BitArray { - if commit.bitArray == nil { - commit.bitArray = bits.NewBitArray(len(commit.Signatures)) - for i, commitSig := range commit.Signatures { - // TODO: need to check the BlockID otherwise we could be counting conflicts, - // not just the one with +2/3 ! - commit.bitArray.SetIndex(i, !commitSig.Absent()) - } - } - return commit.bitArray -} - -// GetByIndex returns the vote corresponding to a given validator index. -// Panics if `index >= commit.Size()`. -// Implements VoteSetReader. -func (commit *Commit) GetByIndex(valIdx int32) *Vote { - return commit.GetVote(valIdx) -} - -// IsCommit returns true if there is at least one signature. -// Implements VoteSetReader. -func (commit *Commit) IsCommit() bool { - return len(commit.Signatures) != 0 -} - -// ValidateBasic performs basic validation that doesn't involve state data. -// Does not actually check the cryptographic signatures. -func (commit *Commit) ValidateBasic() error { - if commit.Height < 0 { - return errors.New("negative Height") - } - if commit.Round < 0 { - return errors.New("negative Round") - } - - if commit.Height >= 1 { - if commit.BlockID.IsZero() { - return errors.New("commit cannot be for nil block") - } - - if len(commit.Signatures) == 0 { - return errors.New("no signatures in commit") - } - for i, commitSig := range commit.Signatures { - if err := commitSig.ValidateBasic(); err != nil { - return fmt.Errorf("wrong CommitSig #%d: %v", i, err) - } - } - } - return nil -} - -// Hash returns the hash of the commit -func (commit *Commit) Hash() tmbytes.HexBytes { - if commit == nil { - return nil - } - if commit.hash == nil { - bs := make([][]byte, len(commit.Signatures)) - for i, commitSig := range commit.Signatures { - pbcs := commitSig.ToProto() - bz, err := pbcs.Marshal() - if err != nil { - panic(err) - } - - bs[i] = bz - } - commit.hash = merkle.HashFromByteSlices(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" - } - commitSigStrings := make([]string, len(commit.Signatures)) - for i, commitSig := range commit.Signatures { - commitSigStrings[i] = commitSig.String() - } - return fmt.Sprintf(`Commit{ -%s Height: %d -%s Round: %d -%s BlockID: %v -%s Signatures: -%s %v -%s}#%v`, - indent, commit.Height, - indent, commit.Round, - indent, commit.BlockID, - indent, - indent, strings.Join(commitSigStrings, "\n"+indent+" "), - indent, commit.hash) -} - -// ToProto converts Commit to protobuf -func (commit *Commit) ToProto() *tmproto.Commit { - if commit == nil { - return nil - } - - c := new(tmproto.Commit) - sigs := make([]tmproto.CommitSig, len(commit.Signatures)) - for i := range commit.Signatures { - sigs[i] = *commit.Signatures[i].ToProto() - } - c.Signatures = sigs - - c.Height = commit.Height - c.Round = commit.Round - c.BlockID = commit.BlockID.ToProto() - - return c -} - -// FromProto sets a protobuf Commit to the given pointer. -// It returns an error if the commit is invalid. -func CommitFromProto(cp *tmproto.Commit) (*Commit, error) { - if cp == nil { - return nil, errors.New("nil Commit") - } - - var ( - commit = new(Commit) - ) - - bi, err := BlockIDFromProto(&cp.BlockID) - if err != nil { - return nil, err - } - - sigs := make([]CommitSig, len(cp.Signatures)) - for i := range cp.Signatures { - if err := sigs[i].FromProto(cp.Signatures[i]); err != nil { - return nil, err - } - } - commit.Signatures = sigs - - commit.Height = cp.Height - commit.Round = cp.Round - commit.BlockID = *bi - - return commit, commit.ValidateBasic() -} - -//----------------------------------------------------------------------------- - // 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"` + Txs mempool.Txs `json:"txs"` // Volatile hash tmbytes.HexBytes @@ -1012,7 +320,7 @@ type Data struct { // Hash returns the hash of the data func (data *Data) Hash() tmbytes.HexBytes { if data == nil { - return (Txs{}).Hash() + return (mempool.Txs{}).Hash() } if data.hash == nil { data.hash = data.Txs.Hash() // NOTE: leaves of merkle tree are TxIDs @@ -1055,6 +363,11 @@ func (data *Data) ToProto() tmproto.Data { return *tp } +// ClearCache removes the saved hash. This is predominantly used for testing. +func (data *Data) ClearCache() { + data.hash = nil +} + // DataFromProto takes a protobuf representation of Data & // returns the native type. func DataFromProto(dp *tmproto.Data) (Data, error) { @@ -1064,23 +377,31 @@ func DataFromProto(dp *tmproto.Data) (Data, error) { data := new(Data) if len(dp.Txs) > 0 { - txBzs := make(Txs, len(dp.Txs)) + txBzs := make(mempool.Txs, len(dp.Txs)) for i := range dp.Txs { - txBzs[i] = Tx(dp.Txs[i]) + txBzs[i] = mempool.Tx(dp.Txs[i]) } data.Txs = txBzs } else { - data.Txs = Txs{} + data.Txs = mempool.Txs{} } return *data, nil } +// ComputeProtoSizeForTxs wraps the transactions in tmproto.Data{} and calculates the size. +// https://developers.google.com/protocol-buffers/docs/encoding +func ComputeProtoSizeForTxs(txs []mempool.Tx) int64 { + data := Data{Txs: txs} + pdData := data.ToProto() + return int64(pdData.Size()) +} + //----------------------------------------------------------------------------- // EvidenceData contains any evidence of malicious wrong-doing by validators type EvidenceData struct { - Evidence EvidenceList `json:"evidence"` + Evidence evidence.EvidenceList `json:"evidence"` // Volatile. Used as cache hash tmbytes.HexBytes @@ -1136,7 +457,7 @@ func (data *EvidenceData) ToProto() (*tmproto.EvidenceList, error) { evi := new(tmproto.EvidenceList) eviBzs := make([]tmproto.Evidence, len(data.Evidence)) for i := range data.Evidence { - protoEvi, err := EvidenceToProto(data.Evidence[i]) + protoEvi, err := evidence.EvidenceToProto(data.Evidence[i]) if err != nil { return nil, err } @@ -1153,9 +474,9 @@ func (data *EvidenceData) FromProto(eviData *tmproto.EvidenceList) error { return errors.New("nil evidenceData") } - eviBzs := make(EvidenceList, len(eviData.Evidence)) + eviBzs := make(evidence.EvidenceList, len(eviData.Evidence)) for i := range eviData.Evidence { - evi, err := EvidenceFromProto(&eviData.Evidence[i]) + evi, err := evidence.EvidenceFromProto(&eviData.Evidence[i]) if err != nil { return err } @@ -1168,92 +489,3 @@ func (data *EvidenceData) FromProto(eviData *tmproto.EvidenceList) error { } //-------------------------------------------------------------------------------- - -// BlockID -type BlockID struct { - Hash tmbytes.HexBytes `json:"hash"` - PartSetHeader PartSetHeader `json:"parts"` -} - -// 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.PartSetHeader.Equals(other.PartSetHeader) -} - -// Key returns a machine-readable string representation of the BlockID -func (blockID BlockID) Key() string { - pbph := blockID.PartSetHeader.ToProto() - bz, err := pbph.Marshal() - if err != nil { - panic(err) - } - - return fmt.Sprint(string(blockID.Hash), string(bz)) -} - -// ValidateBasic performs basic validation. -func (blockID BlockID) ValidateBasic() error { - // Hash can be empty in case of POLBlockID in Proposal. - if err := ValidateHash(blockID.Hash); err != nil { - return fmt.Errorf("wrong Hash: %w", err) - } - if err := blockID.PartSetHeader.ValidateBasic(); err != nil { - return fmt.Errorf("wrong PartSetHeader: %w", err) - } - return nil -} - -// IsZero returns true if this is the BlockID of a nil block. -func (blockID BlockID) IsZero() bool { - return len(blockID.Hash) == 0 && - blockID.PartSetHeader.IsZero() -} - -// IsComplete returns true if this is a valid BlockID of a non-nil block. -func (blockID BlockID) IsComplete() bool { - return len(blockID.Hash) == tmhash.Size && - blockID.PartSetHeader.Total > 0 && - len(blockID.PartSetHeader.Hash) == tmhash.Size -} - -// String returns a human readable string representation of the BlockID. -// -// 1. hash -// 2. part set header -// -// See PartSetHeader#String -func (blockID BlockID) String() string { - return fmt.Sprintf(`%v:%v`, blockID.Hash, blockID.PartSetHeader) -} - -// ToProto converts BlockID to protobuf -func (blockID *BlockID) ToProto() tmproto.BlockID { - if blockID == nil { - return tmproto.BlockID{} - } - - return tmproto.BlockID{ - Hash: blockID.Hash, - PartSetHeader: blockID.PartSetHeader.ToProto(), - } -} - -// FromProto sets a protobuf BlockID to the given pointer. -// It returns an error if the block id is invalid. -func BlockIDFromProto(bID *tmproto.BlockID) (*BlockID, error) { - if bID == nil { - return nil, errors.New("nil BlockID") - } - - blockID := new(BlockID) - ph, err := PartSetHeaderFromProto(&bID.PartSetHeader) - if err != nil { - return nil, err - } - - blockID.PartSetHeader = *ph - blockID.Hash = bID.Hash - - return blockID, blockID.ValidateBasic() -} diff --git a/pkg/block/block_meta.go b/pkg/block/block_meta.go index 31ed5e2fa..0db7d40c3 100644 --- a/pkg/block/block_meta.go +++ b/pkg/block/block_meta.go @@ -5,21 +5,22 @@ import ( "errors" "fmt" + "github.com/tendermint/tendermint/pkg/meta" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" ) // BlockMeta contains meta information. type BlockMeta struct { - BlockID BlockID `json:"block_id"` - BlockSize int `json:"block_size"` - Header Header `json:"header"` - NumTxs int `json:"num_txs"` + BlockID meta.BlockID `json:"block_id"` + BlockSize int `json:"block_size"` + Header meta.Header `json:"header"` + NumTxs int `json:"num_txs"` } // NewBlockMeta returns a new BlockMeta. -func NewBlockMeta(block *Block, blockParts *PartSet) *BlockMeta { +func NewBlockMeta(block *Block, blockParts *meta.PartSet) *BlockMeta { return &BlockMeta{ - BlockID: BlockID{block.Hash(), blockParts.Header()}, + BlockID: meta.BlockID{block.Hash(), blockParts.Header()}, BlockSize: block.Size(), Header: block.Header, NumTxs: len(block.Data.Txs), @@ -47,12 +48,12 @@ func BlockMetaFromProto(pb *tmproto.BlockMeta) (*BlockMeta, error) { bm := new(BlockMeta) - bi, err := BlockIDFromProto(&pb.BlockID) + bi, err := meta.BlockIDFromProto(&pb.BlockID) if err != nil { return nil, err } - h, err := HeaderFromProto(&pb.Header) + h, err := meta.HeaderFromProto(&pb.Header) if err != nil { return nil, err } diff --git a/pkg/block/block_meta_test.go b/pkg/block/block_meta_test.go index 921e96164..8837b7f57 100644 --- a/pkg/block/block_meta_test.go +++ b/pkg/block/block_meta_test.go @@ -1,4 +1,4 @@ -package block +package block_test import ( "testing" @@ -6,23 +6,26 @@ import ( "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/crypto/tmhash" + test "github.com/tendermint/tendermint/internal/test/factory" tmrand "github.com/tendermint/tendermint/libs/rand" + "github.com/tendermint/tendermint/pkg/block" + "github.com/tendermint/tendermint/pkg/meta" ) func TestBlockMeta_ToProto(t *testing.T) { - h := MakeRandHeader() - bi := BlockID{Hash: h.Hash(), PartSetHeader: PartSetHeader{Total: 123, Hash: tmrand.Bytes(tmhash.Size)}} + h := test.MakeRandomHeader() + bi := meta.BlockID{Hash: h.Hash(), PartSetHeader: meta.PartSetHeader{Total: 123, Hash: tmrand.Bytes(tmhash.Size)}} - bm := &BlockMeta{ + bm := &block.BlockMeta{ BlockID: bi, BlockSize: 200, - Header: h, + Header: *h, NumTxs: 0, } tests := []struct { testName string - bm *BlockMeta + bm *block.BlockMeta expErr bool }{ {"success", bm, false}, @@ -34,7 +37,7 @@ func TestBlockMeta_ToProto(t *testing.T) { t.Run(tt.testName, func(t *testing.T) { pb := tt.bm.ToProto() - bm, err := BlockMetaFromProto(pb) + bm, err := block.BlockMetaFromProto(pb) if !tt.expErr { require.NoError(t, err, tt.testName) @@ -47,37 +50,37 @@ func TestBlockMeta_ToProto(t *testing.T) { } func TestBlockMeta_ValidateBasic(t *testing.T) { - h := MakeRandHeader() - bi := BlockID{Hash: h.Hash(), PartSetHeader: PartSetHeader{Total: 123, Hash: tmrand.Bytes(tmhash.Size)}} - bi2 := BlockID{Hash: tmrand.Bytes(tmhash.Size), - PartSetHeader: PartSetHeader{Total: 123, Hash: tmrand.Bytes(tmhash.Size)}} - bi3 := BlockID{Hash: []byte("incorrect hash"), - PartSetHeader: PartSetHeader{Total: 123, Hash: []byte("incorrect hash")}} + h := test.MakeRandomHeader() + bi := meta.BlockID{Hash: h.Hash(), PartSetHeader: meta.PartSetHeader{Total: 123, Hash: tmrand.Bytes(tmhash.Size)}} + bi2 := meta.BlockID{Hash: tmrand.Bytes(tmhash.Size), + PartSetHeader: meta.PartSetHeader{Total: 123, Hash: tmrand.Bytes(tmhash.Size)}} + bi3 := meta.BlockID{Hash: []byte("incorrect hash"), + PartSetHeader: meta.PartSetHeader{Total: 123, Hash: []byte("incorrect hash")}} - bm := &BlockMeta{ + bm := &block.BlockMeta{ BlockID: bi, BlockSize: 200, - Header: h, + Header: *h, NumTxs: 0, } - bm2 := &BlockMeta{ + bm2 := &block.BlockMeta{ BlockID: bi2, BlockSize: 200, - Header: h, + Header: *h, NumTxs: 0, } - bm3 := &BlockMeta{ + bm3 := &block.BlockMeta{ BlockID: bi3, BlockSize: 200, - Header: h, + Header: *h, NumTxs: 0, } tests := []struct { name string - bm *BlockMeta + bm *block.BlockMeta wantErr bool }{ {"success", bm, false}, diff --git a/pkg/block/block_test.go b/pkg/block/block_test.go index 49b920ce9..dffabb3f3 100644 --- a/pkg/block/block_test.go +++ b/pkg/block/block_test.go @@ -1,32 +1,25 @@ -package block +package block_test import ( - // it is ok to use math/rand here: we do not need a cryptographically secure random - // number generator here and we can run the tests a bit faster - "context" - "crypto/rand" "encoding/hex" "math" mrand "math/rand" "os" - "reflect" "testing" "time" - gogotypes "github.com/gogo/protobuf/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/crypto" - "github.com/tendermint/tendermint/crypto/merkle" - "github.com/tendermint/tendermint/crypto/tmhash" - "github.com/tendermint/tendermint/libs/bits" + test "github.com/tendermint/tendermint/internal/test/factory" "github.com/tendermint/tendermint/libs/bytes" tmrand "github.com/tendermint/tendermint/libs/rand" - tmtime "github.com/tendermint/tendermint/libs/time" + "github.com/tendermint/tendermint/pkg/block" + "github.com/tendermint/tendermint/pkg/evidence" + "github.com/tendermint/tendermint/pkg/mempool" + "github.com/tendermint/tendermint/pkg/meta" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" - tmversion "github.com/tendermint/tendermint/proto/tendermint/version" - "github.com/tendermint/tendermint/version" ) func TestMain(m *testing.M) { @@ -35,79 +28,79 @@ func TestMain(m *testing.M) { } func TestBlockAddEvidence(t *testing.T) { - txs := []Tx{Tx("foo"), Tx("bar")} - lastID := makeBlockIDRandom() + txs := []mempool.Tx{mempool.Tx("foo"), mempool.Tx("bar")} + lastID := test.MakeBlockID() h := int64(3) - voteSet, _, vals := randVoteSet(h-1, 1, tmproto.PrecommitType, 10, 1) - commit, err := makeCommit(lastID, h-1, 1, voteSet, vals, time.Now()) + voteSet, _, vals := test.RandVoteSet(h-1, 1, tmproto.PrecommitType, 10, 1) + commit, err := test.MakeCommit(lastID, h-1, 1, voteSet, vals, time.Now()) require.NoError(t, err) - ev := NewMockDuplicateVoteEvidenceWithValidator(h, time.Now(), vals[0], "block-test-chain") - evList := []Evidence{ev} + ev := evidence.NewMockDuplicateVoteEvidenceWithValidator(h, time.Now(), vals[0], "block-test-chain") + evList := []evidence.Evidence{ev} - block := MakeBlock(h, txs, commit, evList) - require.NotNil(t, block) - require.Equal(t, 1, len(block.Evidence.Evidence)) - require.NotNil(t, block.EvidenceHash) + b := block.MakeBlock(h, txs, commit, evList) + require.NotNil(t, b) + require.Equal(t, 1, len(b.Evidence.Evidence)) + require.NotNil(t, b.EvidenceHash) } func TestBlockValidateBasic(t *testing.T) { - require.Error(t, (*Block)(nil).ValidateBasic()) + require.Error(t, (*block.Block)(nil).ValidateBasic()) - txs := []Tx{Tx("foo"), Tx("bar")} - lastID := makeBlockIDRandom() + txs := []mempool.Tx{mempool.Tx("foo"), mempool.Tx("bar")} + lastID := test.MakeBlockID() h := int64(3) - voteSet, valSet, vals := randVoteSet(h-1, 1, tmproto.PrecommitType, 10, 1) - commit, err := makeCommit(lastID, h-1, 1, voteSet, vals, time.Now()) + voteSet, valSet, vals := test.RandVoteSet(h-1, 1, tmproto.PrecommitType, 10, 1) + commit, err := test.MakeCommit(lastID, h-1, 1, voteSet, vals, time.Now()) require.NoError(t, err) - ev := NewMockDuplicateVoteEvidenceWithValidator(h, time.Now(), vals[0], "block-test-chain") - evList := []Evidence{ev} + ev := evidence.NewMockDuplicateVoteEvidenceWithValidator(h, time.Now(), vals[0], "block-test-chain") + evList := []evidence.Evidence{ev} testCases := []struct { testName string - malleateBlock func(*Block) + malleateBlock func(*block.Block) expErr bool }{ - {"Make Block", func(blk *Block) {}, false}, - {"Make Block w/ proposer Addr", func(blk *Block) { blk.ProposerAddress = valSet.GetProposer().Address }, false}, - {"Negative Height", func(blk *Block) { blk.Height = -1 }, true}, - {"Remove 1/2 the commits", func(blk *Block) { + {"Make Block", func(blk *block.Block) {}, false}, + {"Make Block w/ proposer Addr", func(blk *block.Block) { blk.ProposerAddress = valSet.GetProposer().Address }, false}, + {"Negative Height", func(blk *block.Block) { blk.Height = -1 }, true}, + {"Remove 1/2 the commits", func(blk *block.Block) { blk.LastCommit.Signatures = commit.Signatures[:commit.Size()/2] - blk.LastCommit.hash = nil // clear hash or change wont be noticed + blk.LastCommit.ClearCache() // clear hash or change wont be noticed }, true}, - {"Remove LastCommitHash", func(blk *Block) { blk.LastCommitHash = []byte("something else") }, true}, - {"Tampered Data", func(blk *Block) { - blk.Data.Txs[0] = Tx("something else") - blk.Data.hash = nil // clear hash or change wont be noticed + {"Remove LastCommitHash", func(blk *block.Block) { blk.LastCommitHash = []byte("something else") }, true}, + {"Tampered Data", func(blk *block.Block) { + blk.Data.Txs[0] = mempool.Tx("something else") + blk.Data.ClearCache() // clear hash or change wont be noticed }, true}, - {"Tampered DataHash", func(blk *Block) { + {"Tampered DataHash", func(blk *block.Block) { blk.DataHash = tmrand.Bytes(len(blk.DataHash)) }, true}, - {"Tampered EvidenceHash", func(blk *Block) { + {"Tampered EvidenceHash", func(blk *block.Block) { blk.EvidenceHash = tmrand.Bytes(len(blk.EvidenceHash)) }, true}, - {"Incorrect block protocol version", func(blk *Block) { + {"Incorrect block protocol version", func(blk *block.Block) { blk.Version.Block = 1 }, true}, - {"Missing LastCommit", func(blk *Block) { + {"Missing LastCommit", func(blk *block.Block) { blk.LastCommit = nil }, true}, - {"Invalid LastCommit", func(blk *Block) { - blk.LastCommit = NewCommit(-1, 0, *voteSet.maj23, nil) + {"Invalid LastCommit", func(blk *block.Block) { + blk.LastCommit = meta.NewCommit(-1, 0, test.MakeBlockID(), nil) }, true}, - {"Invalid Evidence", func(blk *Block) { - emptyEv := &DuplicateVoteEvidence{} - blk.Evidence = EvidenceData{Evidence: []Evidence{emptyEv}} + {"Invalid Evidence", func(blk *block.Block) { + emptyEv := &evidence.DuplicateVoteEvidence{} + blk.Evidence = block.EvidenceData{Evidence: []evidence.Evidence{emptyEv}} }, true}, } for i, tc := range testCases { tc := tc i := i t.Run(tc.testName, func(t *testing.T) { - block := MakeBlock(h, txs, commit, evList) + block := block.MakeBlock(h, txs, commit, evList) block.ProposerAddress = valSet.GetProposer().Address tc.malleateBlock(block) err = block.ValidateBasic() @@ -118,49 +111,49 @@ func TestBlockValidateBasic(t *testing.T) { } func TestBlockHash(t *testing.T) { - assert.Nil(t, (*Block)(nil).Hash()) - assert.Nil(t, MakeBlock(int64(3), []Tx{Tx("Hello World")}, nil, nil).Hash()) + assert.Nil(t, (*block.Block)(nil).Hash()) + assert.Nil(t, block.MakeBlock(int64(3), []mempool.Tx{mempool.Tx("Hello World")}, nil, nil).Hash()) } func TestBlockMakePartSet(t *testing.T) { - assert.Nil(t, (*Block)(nil).MakePartSet(2)) + assert.Nil(t, (*block.Block)(nil).MakePartSet(2)) - partSet := MakeBlock(int64(3), []Tx{Tx("Hello World")}, nil, nil).MakePartSet(1024) + partSet := block.MakeBlock(int64(3), []mempool.Tx{mempool.Tx("Hello World")}, nil, nil).MakePartSet(1024) assert.NotNil(t, partSet) assert.EqualValues(t, 1, partSet.Total()) } func TestBlockMakePartSetWithEvidence(t *testing.T) { - assert.Nil(t, (*Block)(nil).MakePartSet(2)) + assert.Nil(t, (*block.Block)(nil).MakePartSet(2)) - lastID := makeBlockIDRandom() + lastID := test.MakeBlockID() h := int64(3) - voteSet, _, vals := randVoteSet(h-1, 1, tmproto.PrecommitType, 10, 1) - commit, err := makeCommit(lastID, h-1, 1, voteSet, vals, time.Now()) + voteSet, _, vals := test.RandVoteSet(h-1, 1, tmproto.PrecommitType, 10, 1) + commit, err := test.MakeCommit(lastID, h-1, 1, voteSet, vals, time.Now()) require.NoError(t, err) - ev := NewMockDuplicateVoteEvidenceWithValidator(h, time.Now(), vals[0], "block-test-chain") - evList := []Evidence{ev} + ev := evidence.NewMockDuplicateVoteEvidenceWithValidator(h, time.Now(), vals[0], "block-test-chain") + evList := []evidence.Evidence{ev} - partSet := MakeBlock(h, []Tx{Tx("Hello World")}, commit, evList).MakePartSet(512) + partSet := block.MakeBlock(h, []mempool.Tx{mempool.Tx("Hello World")}, commit, evList).MakePartSet(512) assert.NotNil(t, partSet) assert.EqualValues(t, 4, partSet.Total()) } func TestBlockHashesTo(t *testing.T) { - assert.False(t, (*Block)(nil).HashesTo(nil)) + assert.False(t, (*block.Block)(nil).HashesTo(nil)) - lastID := makeBlockIDRandom() + lastID := test.MakeBlockID() h := int64(3) - voteSet, valSet, vals := randVoteSet(h-1, 1, tmproto.PrecommitType, 10, 1) - commit, err := makeCommit(lastID, h-1, 1, voteSet, vals, time.Now()) + voteSet, valSet, vals := test.RandVoteSet(h-1, 1, tmproto.PrecommitType, 10, 1) + commit, err := test.MakeCommit(lastID, h-1, 1, voteSet, vals, time.Now()) require.NoError(t, err) - ev := NewMockDuplicateVoteEvidenceWithValidator(h, time.Now(), vals[0], "block-test-chain") - evList := []Evidence{ev} + ev := evidence.NewMockDuplicateVoteEvidenceWithValidator(h, time.Now(), vals[0], "block-test-chain") + evList := []evidence.Evidence{ev} - block := MakeBlock(h, []Tx{Tx("Hello World")}, commit, evList) + block := block.MakeBlock(h, []mempool.Tx{mempool.Tx("Hello World")}, commit, evList) block.ValidatorsHash = valSet.Hash() assert.False(t, block.HashesTo([]byte{})) assert.False(t, block.HashesTo([]byte("something else"))) @@ -168,289 +161,23 @@ func TestBlockHashesTo(t *testing.T) { } func TestBlockSize(t *testing.T) { - size := MakeBlock(int64(3), []Tx{Tx("Hello World")}, nil, nil).Size() + size := block.MakeBlock(int64(3), []mempool.Tx{mempool.Tx("Hello World")}, nil, nil).Size() if size <= 0 { t.Fatal("Size of the block is zero or negative") } } func TestBlockString(t *testing.T) { - assert.Equal(t, "nil-Block", (*Block)(nil).String()) - assert.Equal(t, "nil-Block", (*Block)(nil).StringIndented("")) - assert.Equal(t, "nil-Block", (*Block)(nil).StringShort()) + assert.Equal(t, "nil-Block", (*block.Block)(nil).String()) + assert.Equal(t, "nil-Block", (*block.Block)(nil).StringIndented("")) + assert.Equal(t, "nil-Block", (*block.Block)(nil).StringShort()) - block := MakeBlock(int64(3), []Tx{Tx("Hello World")}, nil, nil) + block := block.MakeBlock(int64(3), []mempool.Tx{mempool.Tx("Hello World")}, nil, nil) assert.NotEqual(t, "nil-Block", block.String()) assert.NotEqual(t, "nil-Block", block.StringIndented("")) assert.NotEqual(t, "nil-Block", block.StringShort()) } -func makeBlockIDRandom() BlockID { - var ( - blockHash = make([]byte, tmhash.Size) - partSetHash = make([]byte, tmhash.Size) - ) - rand.Read(blockHash) //nolint: errcheck // ignore errcheck for read - rand.Read(partSetHash) //nolint: errcheck // ignore errcheck for read - return BlockID{blockHash, PartSetHeader{123, partSetHash}} -} - -func makeBlockID(hash []byte, partSetSize uint32, partSetHash []byte) BlockID { - var ( - h = make([]byte, tmhash.Size) - psH = make([]byte, tmhash.Size) - ) - copy(h, hash) - copy(psH, partSetHash) - return BlockID{ - Hash: h, - PartSetHeader: PartSetHeader{ - Total: partSetSize, - Hash: psH, - }, - } -} - -var nilBytes []byte - -// This follows RFC-6962, i.e. `echo -n '' | sha256sum` -var emptyBytes = []byte{0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, 0xc8, - 0x99, 0x6f, 0xb9, 0x24, 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, - 0x78, 0x52, 0xb8, 0x55} - -func TestNilHeaderHashDoesntCrash(t *testing.T) { - assert.Equal(t, nilBytes, []byte((*Header)(nil).Hash())) - assert.Equal(t, nilBytes, []byte((new(Header)).Hash())) -} - -func TestNilDataHashDoesntCrash(t *testing.T) { - assert.Equal(t, emptyBytes, []byte((*Data)(nil).Hash())) - assert.Equal(t, emptyBytes, []byte(new(Data).Hash())) -} - -func TestCommit(t *testing.T) { - lastID := makeBlockIDRandom() - h := int64(3) - voteSet, _, vals := randVoteSet(h-1, 1, tmproto.PrecommitType, 10, 1) - commit, err := makeCommit(lastID, h-1, 1, voteSet, vals, time.Now()) - require.NoError(t, err) - - assert.Equal(t, h-1, commit.Height) - assert.EqualValues(t, 1, commit.Round) - assert.Equal(t, tmproto.PrecommitType, tmproto.SignedMsgType(commit.Type())) - if commit.Size() <= 0 { - t.Fatalf("commit %v has a zero or negative size: %d", commit, commit.Size()) - } - - require.NotNil(t, commit.BitArray()) - assert.Equal(t, bits.NewBitArray(10).Size(), commit.BitArray().Size()) - - assert.Equal(t, voteSet.GetByIndex(0), commit.GetByIndex(0)) - assert.True(t, commit.IsCommit()) -} - -func TestCommitValidateBasic(t *testing.T) { - testCases := []struct { - testName string - malleateCommit func(*Commit) - expectErr bool - }{ - {"Random Commit", func(com *Commit) {}, false}, - {"Incorrect signature", func(com *Commit) { com.Signatures[0].Signature = []byte{0} }, false}, - {"Incorrect height", func(com *Commit) { com.Height = int64(-100) }, true}, - {"Incorrect round", func(com *Commit) { com.Round = -100 }, true}, - } - for _, tc := range testCases { - tc := tc - t.Run(tc.testName, func(t *testing.T) { - com := randCommit(time.Now()) - tc.malleateCommit(com) - assert.Equal(t, tc.expectErr, com.ValidateBasic() != nil, "Validate Basic had an unexpected result") - }) - } -} - -func TestMaxCommitBytes(t *testing.T) { - // time is varint encoded so need to pick the max. - // year int, month Month, day, hour, min, sec, nsec int, loc *Location - timestamp := time.Date(math.MaxInt64, 0, 0, 0, 0, 0, math.MaxInt64, time.UTC) - - cs := CommitSig{ - BlockIDFlag: BlockIDFlagNil, - ValidatorAddress: crypto.AddressHash([]byte("validator_address")), - Timestamp: timestamp, - Signature: crypto.CRandBytes(MaxSignatureSize), - } - - pbSig := cs.ToProto() - // test that a single commit sig doesn't exceed max commit sig bytes - assert.EqualValues(t, MaxCommitSigBytes, pbSig.Size()) - - // check size with a single commit - commit := &Commit{ - Height: math.MaxInt64, - Round: math.MaxInt32, - BlockID: BlockID{ - Hash: tmhash.Sum([]byte("blockID_hash")), - PartSetHeader: PartSetHeader{ - Total: math.MaxInt32, - Hash: tmhash.Sum([]byte("blockID_part_set_header_hash")), - }, - }, - Signatures: []CommitSig{cs}, - } - - pb := commit.ToProto() - - assert.EqualValues(t, MaxCommitBytes(1), int64(pb.Size())) - - // check the upper bound of the commit size - for i := 1; i < MaxVotesCount; i++ { - commit.Signatures = append(commit.Signatures, cs) - } - - pb = commit.ToProto() - - assert.EqualValues(t, MaxCommitBytes(MaxVotesCount), int64(pb.Size())) - -} - -func TestHeaderHash(t *testing.T) { - testCases := []struct { - desc string - header *Header - expectHash bytes.HexBytes - }{ - {"Generates expected hash", &Header{ - Version: version.Consensus{Block: 1, App: 2}, - ChainID: "chainId", - Height: 3, - Time: time.Date(2019, 10, 13, 16, 14, 44, 0, time.UTC), - LastBlockID: makeBlockID(make([]byte, tmhash.Size), 6, make([]byte, tmhash.Size)), - LastCommitHash: tmhash.Sum([]byte("last_commit_hash")), - DataHash: tmhash.Sum([]byte("data_hash")), - ValidatorsHash: tmhash.Sum([]byte("validators_hash")), - NextValidatorsHash: tmhash.Sum([]byte("next_validators_hash")), - ConsensusHash: tmhash.Sum([]byte("consensus_hash")), - AppHash: tmhash.Sum([]byte("app_hash")), - LastResultsHash: tmhash.Sum([]byte("last_results_hash")), - EvidenceHash: tmhash.Sum([]byte("evidence_hash")), - ProposerAddress: crypto.AddressHash([]byte("proposer_address")), - }, hexBytesFromString("F740121F553B5418C3EFBD343C2DBFE9E007BB67B0D020A0741374BAB65242A4")}, - {"nil header yields nil", nil, nil}, - {"nil ValidatorsHash yields nil", &Header{ - Version: version.Consensus{Block: 1, App: 2}, - ChainID: "chainId", - Height: 3, - Time: time.Date(2019, 10, 13, 16, 14, 44, 0, time.UTC), - LastBlockID: makeBlockID(make([]byte, tmhash.Size), 6, make([]byte, tmhash.Size)), - LastCommitHash: tmhash.Sum([]byte("last_commit_hash")), - DataHash: tmhash.Sum([]byte("data_hash")), - ValidatorsHash: nil, - NextValidatorsHash: tmhash.Sum([]byte("next_validators_hash")), - ConsensusHash: tmhash.Sum([]byte("consensus_hash")), - AppHash: tmhash.Sum([]byte("app_hash")), - LastResultsHash: tmhash.Sum([]byte("last_results_hash")), - EvidenceHash: tmhash.Sum([]byte("evidence_hash")), - ProposerAddress: crypto.AddressHash([]byte("proposer_address")), - }, nil}, - } - for _, tc := range testCases { - tc := tc - t.Run(tc.desc, func(t *testing.T) { - assert.Equal(t, tc.expectHash, tc.header.Hash()) - - // We also make sure that all fields are hashed in struct order, and that all - // fields in the test struct are non-zero. - if tc.header != nil && tc.expectHash != nil { - byteSlices := [][]byte{} - - s := reflect.ValueOf(*tc.header) - for i := 0; i < s.NumField(); i++ { - f := s.Field(i) - - assert.False(t, f.IsZero(), "Found zero-valued field %v", - s.Type().Field(i).Name) - - switch f := f.Interface().(type) { - case int64, bytes.HexBytes, string: - byteSlices = append(byteSlices, cdcEncode(f)) - case time.Time: - bz, err := gogotypes.StdTimeMarshal(f) - require.NoError(t, err) - byteSlices = append(byteSlices, bz) - case version.Consensus: - pbc := tmversion.Consensus{ - Block: f.Block, - App: f.App, - } - bz, err := pbc.Marshal() - require.NoError(t, err) - byteSlices = append(byteSlices, bz) - case BlockID: - pbbi := f.ToProto() - bz, err := pbbi.Marshal() - require.NoError(t, err) - byteSlices = append(byteSlices, bz) - default: - t.Errorf("unknown type %T", f) - } - } - assert.Equal(t, - bytes.HexBytes(merkle.HashFromByteSlices(byteSlices)), tc.header.Hash()) - } - }) - } -} - -func TestMaxHeaderBytes(t *testing.T) { - // Construct a UTF-8 string of MaxChainIDLen length using the supplementary - // characters. - // Each supplementary character takes 4 bytes. - // http://www.i18nguy.com/unicode/supplementary-test.html - maxChainID := "" - for i := 0; i < MaxChainIDLen; i++ { - maxChainID += "𠜎" - } - - // time is varint encoded so need to pick the max. - // year int, month Month, day, hour, min, sec, nsec int, loc *Location - timestamp := time.Date(math.MaxInt64, 0, 0, 0, 0, 0, math.MaxInt64, time.UTC) - - h := Header{ - Version: version.Consensus{Block: math.MaxInt64, App: math.MaxInt64}, - ChainID: maxChainID, - Height: math.MaxInt64, - Time: timestamp, - LastBlockID: makeBlockID(make([]byte, tmhash.Size), math.MaxInt32, make([]byte, tmhash.Size)), - LastCommitHash: tmhash.Sum([]byte("last_commit_hash")), - DataHash: tmhash.Sum([]byte("data_hash")), - ValidatorsHash: tmhash.Sum([]byte("validators_hash")), - NextValidatorsHash: tmhash.Sum([]byte("next_validators_hash")), - ConsensusHash: tmhash.Sum([]byte("consensus_hash")), - AppHash: tmhash.Sum([]byte("app_hash")), - LastResultsHash: tmhash.Sum([]byte("last_results_hash")), - EvidenceHash: tmhash.Sum([]byte("evidence_hash")), - ProposerAddress: crypto.AddressHash([]byte("proposer_address")), - } - - bz, err := h.ToProto().Marshal() - require.NoError(t, err) - - assert.EqualValues(t, MaxHeaderBytes, int64(len(bz))) -} - -func randCommit(now time.Time) *Commit { - lastID := makeBlockIDRandom() - h := int64(3) - voteSet, _, vals := randVoteSet(h-1, 1, tmproto.PrecommitType, 10, 1) - commit, err := makeCommit(lastID, h-1, 1, voteSet, vals, now) - if err != nil { - panic(err) - } - return commit -} - func hexBytesFromString(s string) bytes.HexBytes { b, err := hex.DecodeString(s) if err != nil { @@ -480,12 +207,12 @@ func TestBlockMaxDataBytes(t *testing.T) { tc := tc if tc.panics { assert.Panics(t, func() { - MaxDataBytes(tc.maxBytes, tc.evidenceBytes, tc.valsCount) + block.MaxDataBytes(tc.maxBytes, tc.evidenceBytes, tc.valsCount) }, "#%v", i) } else { assert.Equal(t, tc.result, - MaxDataBytes(tc.maxBytes, tc.evidenceBytes, tc.valsCount), + block.MaxDataBytes(tc.maxBytes, tc.evidenceBytes, tc.valsCount), "#%v", i) } } @@ -509,158 +236,35 @@ func TestBlockMaxDataBytesNoEvidence(t *testing.T) { tc := tc if tc.panics { assert.Panics(t, func() { - MaxDataBytesNoEvidence(tc.maxBytes, tc.valsCount) + block.MaxDataBytesNoEvidence(tc.maxBytes, tc.valsCount) }, "#%v", i) } else { assert.Equal(t, tc.result, - MaxDataBytesNoEvidence(tc.maxBytes, tc.valsCount), + block.MaxDataBytesNoEvidence(tc.maxBytes, tc.valsCount), "#%v", i) } } } -func TestCommitToVoteSet(t *testing.T) { - lastID := makeBlockIDRandom() - h := int64(3) - - voteSet, valSet, vals := randVoteSet(h-1, 1, tmproto.PrecommitType, 10, 1) - commit, err := makeCommit(lastID, h-1, 1, voteSet, vals, time.Now()) - assert.NoError(t, err) - - chainID := voteSet.ChainID() - voteSet2 := CommitToVoteSet(chainID, commit, valSet) - - for i := int32(0); int(i) < len(vals); i++ { - vote1 := voteSet.GetByIndex(i) - vote2 := voteSet2.GetByIndex(i) - vote3 := commit.GetVote(i) - - vote1bz, err := vote1.ToProto().Marshal() - require.NoError(t, err) - vote2bz, err := vote2.ToProto().Marshal() - require.NoError(t, err) - vote3bz, err := vote3.ToProto().Marshal() - require.NoError(t, err) - assert.Equal(t, vote1bz, vote2bz) - assert.Equal(t, vote1bz, vote3bz) - } -} - -func TestCommitToVoteSetWithVotesForNilBlock(t *testing.T) { - blockID := makeBlockID([]byte("blockhash"), 1000, []byte("partshash")) - - const ( - height = int64(3) - round = 0 - ) - - type commitVoteTest struct { - blockIDs []BlockID - numVotes []int // must sum to numValidators - numValidators int - valid bool - } - - testCases := []commitVoteTest{ - {[]BlockID{blockID, {}}, []int{67, 33}, 100, true}, - } - - for _, tc := range testCases { - voteSet, valSet, vals := randVoteSet(height-1, round, tmproto.PrecommitType, tc.numValidators, 1) - - vi := int32(0) - for n := range tc.blockIDs { - for i := 0; i < tc.numVotes[n]; i++ { - pubKey, err := vals[vi].GetPubKey(context.Background()) - require.NoError(t, err) - vote := &Vote{ - ValidatorAddress: pubKey.Address(), - ValidatorIndex: vi, - Height: height - 1, - Round: round, - Type: tmproto.PrecommitType, - BlockID: tc.blockIDs[n], - Timestamp: tmtime.Now(), - } - - added, err := signAddVote(vals[vi], vote, voteSet) - assert.NoError(t, err) - assert.True(t, added) - - vi++ - } - } - - if tc.valid { - commit := voteSet.MakeCommit() // panics without > 2/3 valid votes - assert.NotNil(t, commit) - err := valSet.VerifyCommit(voteSet.ChainID(), blockID, height-1, commit) - assert.Nil(t, err) - } else { - assert.Panics(t, func() { voteSet.MakeCommit() }) - } - } -} - -func TestBlockIDValidateBasic(t *testing.T) { - validBlockID := BlockID{ - Hash: bytes.HexBytes{}, - PartSetHeader: PartSetHeader{ - Total: 1, - Hash: bytes.HexBytes{}, - }, - } - - invalidBlockID := BlockID{ - Hash: []byte{0}, - PartSetHeader: PartSetHeader{ - Total: 1, - Hash: []byte{0}, - }, - } - - testCases := []struct { - testName string - blockIDHash bytes.HexBytes - blockIDPartSetHeader PartSetHeader - expectErr bool - }{ - {"Valid BlockID", validBlockID.Hash, validBlockID.PartSetHeader, false}, - {"Invalid BlockID", invalidBlockID.Hash, validBlockID.PartSetHeader, true}, - {"Invalid BlockID", validBlockID.Hash, invalidBlockID.PartSetHeader, true}, - } - - for _, tc := range testCases { - tc := tc - t.Run(tc.testName, func(t *testing.T) { - blockID := BlockID{ - Hash: tc.blockIDHash, - PartSetHeader: tc.blockIDPartSetHeader, - } - assert.Equal(t, tc.expectErr, blockID.ValidateBasic() != nil, "Validate Basic had an unexpected result") - }) - } -} - func TestBlockProtoBuf(t *testing.T) { h := mrand.Int63() - c1 := randCommit(time.Now()) - b1 := MakeBlock(h, []Tx{Tx([]byte{1})}, &Commit{Signatures: []CommitSig{}}, []Evidence{}) + c1 := test.MakeRandomCommit(time.Now()) + b1 := block.MakeBlock(h, []mempool.Tx{mempool.Tx([]byte{1})}, &meta.Commit{Signatures: []meta.CommitSig{}}, []evidence.Evidence{}) b1.ProposerAddress = tmrand.Bytes(crypto.AddressSize) - b2 := MakeBlock(h, []Tx{Tx([]byte{1})}, c1, []Evidence{}) + b2 := block.MakeBlock(h, []mempool.Tx{mempool.Tx([]byte{1})}, c1, []evidence.Evidence{}) b2.ProposerAddress = tmrand.Bytes(crypto.AddressSize) evidenceTime := time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC) - evi := NewMockDuplicateVoteEvidence(h, evidenceTime, "block-test-chain") - b2.Evidence = EvidenceData{Evidence: EvidenceList{evi}} + evi := evidence.NewMockDuplicateVoteEvidence(h, evidenceTime, "block-test-chain") + b2.Evidence = block.EvidenceData{Evidence: evidence.EvidenceList{evi}} b2.EvidenceHash = b2.Evidence.Hash() - b3 := MakeBlock(h, []Tx{}, c1, []Evidence{}) + b3 := block.MakeBlock(h, []mempool.Tx{}, c1, []evidence.Evidence{}) b3.ProposerAddress = tmrand.Bytes(crypto.AddressSize) testCases := []struct { msg string - b1 *Block + b1 *block.Block expPass bool expPass2 bool }{ @@ -677,7 +281,7 @@ func TestBlockProtoBuf(t *testing.T) { require.Error(t, err, tc.msg) } - block, err := BlockFromProto(pb) + block, err := block.BlockFromProto(pb) if tc.expPass2 { require.NoError(t, err, tc.msg) require.EqualValues(t, tc.b1.Header, block.Header, tc.msg) @@ -691,11 +295,11 @@ func TestBlockProtoBuf(t *testing.T) { } func TestDataProtoBuf(t *testing.T) { - data := &Data{Txs: Txs{Tx([]byte{1}), Tx([]byte{2}), Tx([]byte{3})}} - data2 := &Data{Txs: Txs{}} + data := &block.Data{Txs: mempool.Txs{mempool.Tx([]byte{1}), mempool.Tx([]byte{2}), mempool.Tx([]byte{3})}} + data2 := &block.Data{Txs: mempool.Txs{}} testCases := []struct { msg string - data1 *Data + data1 *block.Data expPass bool }{ {"success", data, true}, @@ -703,7 +307,7 @@ func TestDataProtoBuf(t *testing.T) { } for _, tc := range testCases { protoData := tc.data1.ToProto() - d, err := DataFromProto(&protoData) + d, err := block.DataFromProto(&protoData) if tc.expPass { require.NoError(t, err, tc.msg) require.EqualValues(t, tc.data1, &d, tc.msg) @@ -716,17 +320,17 @@ func TestDataProtoBuf(t *testing.T) { // TestEvidenceDataProtoBuf ensures parity in converting to and from proto. func TestEvidenceDataProtoBuf(t *testing.T) { const chainID = "mychain" - ev := NewMockDuplicateVoteEvidence(math.MaxInt64, time.Now(), chainID) - data := &EvidenceData{Evidence: EvidenceList{ev}} + ev := evidence.NewMockDuplicateVoteEvidence(math.MaxInt64, time.Now(), chainID) + data := &block.EvidenceData{Evidence: evidence.EvidenceList{ev}} _ = data.ByteSize() testCases := []struct { msg string - data1 *EvidenceData + data1 *block.EvidenceData expPass1 bool expPass2 bool }{ {"success", data, true, true}, - {"empty evidenceData", &EvidenceData{Evidence: EvidenceList{}}, true, true}, + {"empty evidenceData", &block.EvidenceData{Evidence: evidence.EvidenceList{}}, true, true}, {"fail nil Data", nil, false, false}, } @@ -738,7 +342,7 @@ func TestEvidenceDataProtoBuf(t *testing.T) { require.Error(t, err, tc.msg) } - eviD := new(EvidenceData) + eviD := new(block.EvidenceData) err = eviD.FromProto(protoData) if tc.expPass2 { require.NoError(t, err, tc.msg) @@ -749,601 +353,12 @@ func TestEvidenceDataProtoBuf(t *testing.T) { } } -// exposed for testing -func MakeRandHeader() Header { - chainID := "test" - t := time.Now() - height := mrand.Int63() - randBytes := tmrand.Bytes(tmhash.Size) - randAddress := tmrand.Bytes(crypto.AddressSize) - h := Header{ - Version: version.Consensus{Block: version.BlockProtocol, App: 1}, - ChainID: chainID, - Height: height, - Time: t, - LastBlockID: BlockID{}, - LastCommitHash: randBytes, - DataHash: randBytes, - ValidatorsHash: randBytes, - NextValidatorsHash: randBytes, - ConsensusHash: randBytes, - AppHash: randBytes, +// This follows RFC-6962, i.e. `echo -n '' | sha256sum` +var emptyBytes = []byte{0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, 0xc8, + 0x99, 0x6f, 0xb9, 0x24, 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, + 0x78, 0x52, 0xb8, 0x55} - LastResultsHash: randBytes, - - EvidenceHash: randBytes, - ProposerAddress: randAddress, - } - - return h -} - -func TestHeaderProto(t *testing.T) { - h1 := MakeRandHeader() - tc := []struct { - msg string - h1 *Header - expPass bool - }{ - {"success", &h1, true}, - {"failure empty Header", &Header{}, false}, - } - - for _, tt := range tc { - tt := tt - t.Run(tt.msg, func(t *testing.T) { - pb := tt.h1.ToProto() - h, err := HeaderFromProto(pb) - if tt.expPass { - require.NoError(t, err, tt.msg) - require.Equal(t, tt.h1, &h, tt.msg) - } else { - require.Error(t, err, tt.msg) - } - - }) - } -} - -func TestBlockIDProtoBuf(t *testing.T) { - blockID := makeBlockID([]byte("hash"), 2, []byte("part_set_hash")) - testCases := []struct { - msg string - bid1 *BlockID - expPass bool - }{ - {"success", &blockID, true}, - {"success empty", &BlockID{}, true}, - {"failure BlockID nil", nil, false}, - } - for _, tc := range testCases { - protoBlockID := tc.bid1.ToProto() - - bi, err := BlockIDFromProto(&protoBlockID) - if tc.expPass { - require.NoError(t, err) - require.Equal(t, tc.bid1, bi, tc.msg) - } else { - require.NotEqual(t, tc.bid1, bi, tc.msg) - } - } -} - -func TestSignedHeaderProtoBuf(t *testing.T) { - commit := randCommit(time.Now()) - h := MakeRandHeader() - - sh := SignedHeader{Header: &h, Commit: commit} - - testCases := []struct { - msg string - sh1 *SignedHeader - expPass bool - }{ - {"empty SignedHeader 2", &SignedHeader{}, true}, - {"success", &sh, true}, - {"failure nil", nil, false}, - } - for _, tc := range testCases { - protoSignedHeader := tc.sh1.ToProto() - - sh, err := SignedHeaderFromProto(protoSignedHeader) - - if tc.expPass { - require.NoError(t, err, tc.msg) - require.Equal(t, tc.sh1, sh, tc.msg) - } else { - require.Error(t, err, tc.msg) - } - } -} - -func TestBlockIDEquals(t *testing.T) { - var ( - blockID = makeBlockID([]byte("hash"), 2, []byte("part_set_hash")) - blockIDDuplicate = makeBlockID([]byte("hash"), 2, []byte("part_set_hash")) - blockIDDifferent = makeBlockID([]byte("different_hash"), 2, []byte("part_set_hash")) - blockIDEmpty = BlockID{} - ) - - assert.True(t, blockID.Equals(blockIDDuplicate)) - assert.False(t, blockID.Equals(blockIDDifferent)) - assert.False(t, blockID.Equals(blockIDEmpty)) - assert.True(t, blockIDEmpty.Equals(blockIDEmpty)) - assert.False(t, blockIDEmpty.Equals(blockIDDifferent)) -} - -func TestCommitSig_ValidateBasic(t *testing.T) { - testCases := []struct { - name string - cs CommitSig - expectErr bool - errString string - }{ - { - "invalid ID flag", - CommitSig{BlockIDFlag: BlockIDFlag(0xFF)}, - true, "unknown BlockIDFlag", - }, - { - "BlockIDFlagAbsent validator address present", - CommitSig{BlockIDFlag: BlockIDFlagAbsent, ValidatorAddress: crypto.Address("testaddr")}, - true, "validator address is present", - }, - { - "BlockIDFlagAbsent timestamp present", - CommitSig{BlockIDFlag: BlockIDFlagAbsent, Timestamp: time.Now().UTC()}, - true, "time is present", - }, - { - "BlockIDFlagAbsent signatures present", - CommitSig{BlockIDFlag: BlockIDFlagAbsent, Signature: []byte{0xAA}}, - true, "signature is present", - }, - { - "BlockIDFlagAbsent valid BlockIDFlagAbsent", - CommitSig{BlockIDFlag: BlockIDFlagAbsent}, - false, "", - }, - { - "non-BlockIDFlagAbsent invalid validator address", - CommitSig{BlockIDFlag: BlockIDFlagCommit, ValidatorAddress: make([]byte, 1)}, - true, "expected ValidatorAddress size", - }, - { - "non-BlockIDFlagAbsent invalid signature (zero)", - CommitSig{ - BlockIDFlag: BlockIDFlagCommit, - ValidatorAddress: make([]byte, crypto.AddressSize), - Signature: make([]byte, 0), - }, - true, "signature is missing", - }, - { - "non-BlockIDFlagAbsent invalid signature (too large)", - CommitSig{ - BlockIDFlag: BlockIDFlagCommit, - ValidatorAddress: make([]byte, crypto.AddressSize), - Signature: make([]byte, MaxSignatureSize+1), - }, - true, "signature is too big", - }, - { - "non-BlockIDFlagAbsent valid", - CommitSig{ - BlockIDFlag: BlockIDFlagCommit, - ValidatorAddress: make([]byte, crypto.AddressSize), - Signature: make([]byte, MaxSignatureSize), - }, - false, "", - }, - } - - for _, tc := range testCases { - tc := tc - - t.Run(tc.name, func(t *testing.T) { - err := tc.cs.ValidateBasic() - if tc.expectErr { - require.Error(t, err) - require.Contains(t, err.Error(), tc.errString) - } else { - require.NoError(t, err) - } - }) - } -} - -func TestHeader_ValidateBasic(t *testing.T) { - testCases := []struct { - name string - header Header - expectErr bool - errString string - }{ - { - "invalid version block", - Header{Version: version.Consensus{Block: version.BlockProtocol + 1}}, - true, "block protocol is incorrect", - }, - { - "invalid chain ID length", - Header{ - Version: version.Consensus{Block: version.BlockProtocol}, - ChainID: string(make([]byte, MaxChainIDLen+1)), - }, - true, "chainID is too long", - }, - { - "invalid height (negative)", - Header{ - Version: version.Consensus{Block: version.BlockProtocol}, - ChainID: string(make([]byte, MaxChainIDLen)), - Height: -1, - }, - true, "negative Height", - }, - { - "invalid height (zero)", - Header{ - Version: version.Consensus{Block: version.BlockProtocol}, - ChainID: string(make([]byte, MaxChainIDLen)), - Height: 0, - }, - true, "zero Height", - }, - { - "invalid block ID hash", - Header{ - Version: version.Consensus{Block: version.BlockProtocol}, - ChainID: string(make([]byte, MaxChainIDLen)), - Height: 1, - LastBlockID: BlockID{ - Hash: make([]byte, tmhash.Size+1), - }, - }, - true, "wrong Hash", - }, - { - "invalid block ID parts header hash", - Header{ - Version: version.Consensus{Block: version.BlockProtocol}, - ChainID: string(make([]byte, MaxChainIDLen)), - Height: 1, - LastBlockID: BlockID{ - Hash: make([]byte, tmhash.Size), - PartSetHeader: PartSetHeader{ - Hash: make([]byte, tmhash.Size+1), - }, - }, - }, - true, "wrong PartSetHeader", - }, - { - "invalid last commit hash", - Header{ - Version: version.Consensus{Block: version.BlockProtocol}, - ChainID: string(make([]byte, MaxChainIDLen)), - Height: 1, - LastBlockID: BlockID{ - Hash: make([]byte, tmhash.Size), - PartSetHeader: PartSetHeader{ - Hash: make([]byte, tmhash.Size), - }, - }, - LastCommitHash: make([]byte, tmhash.Size+1), - }, - true, "wrong LastCommitHash", - }, - { - "invalid data hash", - Header{ - Version: version.Consensus{Block: version.BlockProtocol}, - ChainID: string(make([]byte, MaxChainIDLen)), - Height: 1, - LastBlockID: BlockID{ - Hash: make([]byte, tmhash.Size), - PartSetHeader: PartSetHeader{ - Hash: make([]byte, tmhash.Size), - }, - }, - LastCommitHash: make([]byte, tmhash.Size), - DataHash: make([]byte, tmhash.Size+1), - }, - true, "wrong DataHash", - }, - { - "invalid evidence hash", - Header{ - Version: version.Consensus{Block: version.BlockProtocol}, - ChainID: string(make([]byte, MaxChainIDLen)), - Height: 1, - LastBlockID: BlockID{ - Hash: make([]byte, tmhash.Size), - PartSetHeader: PartSetHeader{ - Hash: make([]byte, tmhash.Size), - }, - }, - LastCommitHash: make([]byte, tmhash.Size), - DataHash: make([]byte, tmhash.Size), - EvidenceHash: make([]byte, tmhash.Size+1), - }, - true, "wrong EvidenceHash", - }, - { - "invalid proposer address", - Header{ - Version: version.Consensus{Block: version.BlockProtocol}, - ChainID: string(make([]byte, MaxChainIDLen)), - Height: 1, - LastBlockID: BlockID{ - Hash: make([]byte, tmhash.Size), - PartSetHeader: PartSetHeader{ - Hash: make([]byte, tmhash.Size), - }, - }, - LastCommitHash: make([]byte, tmhash.Size), - DataHash: make([]byte, tmhash.Size), - EvidenceHash: make([]byte, tmhash.Size), - ProposerAddress: make([]byte, crypto.AddressSize+1), - }, - true, "invalid ProposerAddress length", - }, - { - "invalid validator hash", - Header{ - Version: version.Consensus{Block: version.BlockProtocol}, - ChainID: string(make([]byte, MaxChainIDLen)), - Height: 1, - LastBlockID: BlockID{ - Hash: make([]byte, tmhash.Size), - PartSetHeader: PartSetHeader{ - Hash: make([]byte, tmhash.Size), - }, - }, - LastCommitHash: make([]byte, tmhash.Size), - DataHash: make([]byte, tmhash.Size), - EvidenceHash: make([]byte, tmhash.Size), - ProposerAddress: make([]byte, crypto.AddressSize), - ValidatorsHash: make([]byte, tmhash.Size+1), - }, - true, "wrong ValidatorsHash", - }, - { - "invalid next validator hash", - Header{ - Version: version.Consensus{Block: version.BlockProtocol}, - ChainID: string(make([]byte, MaxChainIDLen)), - Height: 1, - LastBlockID: BlockID{ - Hash: make([]byte, tmhash.Size), - PartSetHeader: PartSetHeader{ - Hash: make([]byte, tmhash.Size), - }, - }, - LastCommitHash: make([]byte, tmhash.Size), - DataHash: make([]byte, tmhash.Size), - EvidenceHash: make([]byte, tmhash.Size), - ProposerAddress: make([]byte, crypto.AddressSize), - ValidatorsHash: make([]byte, tmhash.Size), - NextValidatorsHash: make([]byte, tmhash.Size+1), - }, - true, "wrong NextValidatorsHash", - }, - { - "invalid consensus hash", - Header{ - Version: version.Consensus{Block: version.BlockProtocol}, - ChainID: string(make([]byte, MaxChainIDLen)), - Height: 1, - LastBlockID: BlockID{ - Hash: make([]byte, tmhash.Size), - PartSetHeader: PartSetHeader{ - Hash: make([]byte, tmhash.Size), - }, - }, - LastCommitHash: make([]byte, tmhash.Size), - DataHash: make([]byte, tmhash.Size), - EvidenceHash: make([]byte, tmhash.Size), - ProposerAddress: make([]byte, crypto.AddressSize), - ValidatorsHash: make([]byte, tmhash.Size), - NextValidatorsHash: make([]byte, tmhash.Size), - ConsensusHash: make([]byte, tmhash.Size+1), - }, - true, "wrong ConsensusHash", - }, - { - "invalid last results hash", - Header{ - Version: version.Consensus{Block: version.BlockProtocol}, - ChainID: string(make([]byte, MaxChainIDLen)), - Height: 1, - LastBlockID: BlockID{ - Hash: make([]byte, tmhash.Size), - PartSetHeader: PartSetHeader{ - Hash: make([]byte, tmhash.Size), - }, - }, - LastCommitHash: make([]byte, tmhash.Size), - DataHash: make([]byte, tmhash.Size), - EvidenceHash: make([]byte, tmhash.Size), - ProposerAddress: make([]byte, crypto.AddressSize), - ValidatorsHash: make([]byte, tmhash.Size), - NextValidatorsHash: make([]byte, tmhash.Size), - ConsensusHash: make([]byte, tmhash.Size), - LastResultsHash: make([]byte, tmhash.Size+1), - }, - true, "wrong LastResultsHash", - }, - { - "valid header", - Header{ - Version: version.Consensus{Block: version.BlockProtocol}, - ChainID: string(make([]byte, MaxChainIDLen)), - Height: 1, - LastBlockID: BlockID{ - Hash: make([]byte, tmhash.Size), - PartSetHeader: PartSetHeader{ - Hash: make([]byte, tmhash.Size), - }, - }, - LastCommitHash: make([]byte, tmhash.Size), - DataHash: make([]byte, tmhash.Size), - EvidenceHash: make([]byte, tmhash.Size), - ProposerAddress: make([]byte, crypto.AddressSize), - ValidatorsHash: make([]byte, tmhash.Size), - NextValidatorsHash: make([]byte, tmhash.Size), - ConsensusHash: make([]byte, tmhash.Size), - LastResultsHash: make([]byte, tmhash.Size), - }, - false, "", - }, - } - - for _, tc := range testCases { - tc := tc - - t.Run(tc.name, func(t *testing.T) { - err := tc.header.ValidateBasic() - if tc.expectErr { - require.Error(t, err) - require.Contains(t, err.Error(), tc.errString) - } else { - require.NoError(t, err) - } - }) - } -} - -func TestCommit_ValidateBasic(t *testing.T) { - testCases := []struct { - name string - commit *Commit - expectErr bool - errString string - }{ - { - "invalid height", - &Commit{Height: -1}, - true, "negative Height", - }, - { - "invalid round", - &Commit{Height: 1, Round: -1}, - true, "negative Round", - }, - { - "invalid block ID", - &Commit{ - Height: 1, - Round: 1, - BlockID: BlockID{}, - }, - true, "commit cannot be for nil block", - }, - { - "no signatures", - &Commit{ - Height: 1, - Round: 1, - BlockID: BlockID{ - Hash: make([]byte, tmhash.Size), - PartSetHeader: PartSetHeader{ - Hash: make([]byte, tmhash.Size), - }, - }, - }, - true, "no signatures in commit", - }, - { - "invalid signature", - &Commit{ - Height: 1, - Round: 1, - BlockID: BlockID{ - Hash: make([]byte, tmhash.Size), - PartSetHeader: PartSetHeader{ - Hash: make([]byte, tmhash.Size), - }, - }, - Signatures: []CommitSig{ - { - BlockIDFlag: BlockIDFlagCommit, - ValidatorAddress: make([]byte, crypto.AddressSize), - Signature: make([]byte, MaxSignatureSize+1), - }, - }, - }, - true, "wrong CommitSig", - }, - { - "valid commit", - &Commit{ - Height: 1, - Round: 1, - BlockID: BlockID{ - Hash: make([]byte, tmhash.Size), - PartSetHeader: PartSetHeader{ - Hash: make([]byte, tmhash.Size), - }, - }, - Signatures: []CommitSig{ - { - BlockIDFlag: BlockIDFlagCommit, - ValidatorAddress: make([]byte, crypto.AddressSize), - Signature: make([]byte, MaxSignatureSize), - }, - }, - }, - false, "", - }, - } - - for _, tc := range testCases { - tc := tc - - t.Run(tc.name, func(t *testing.T) { - err := tc.commit.ValidateBasic() - if tc.expectErr { - require.Error(t, err) - require.Contains(t, err.Error(), tc.errString) - } else { - require.NoError(t, err) - } - }) - } -} - -func TestHeaderHashVector(t *testing.T) { - chainID := "test" - h := Header{ - Version: version.Consensus{Block: 1, App: 1}, - ChainID: chainID, - Height: 50, - Time: time.Date(math.MaxInt64, 0, 0, 0, 0, 0, math.MaxInt64, time.UTC), - LastBlockID: BlockID{}, - LastCommitHash: []byte("f2564c78071e26643ae9b3e2a19fa0dc10d4d9e873aa0be808660123f11a1e78"), - DataHash: []byte("f2564c78071e26643ae9b3e2a19fa0dc10d4d9e873aa0be808660123f11a1e78"), - ValidatorsHash: []byte("f2564c78071e26643ae9b3e2a19fa0dc10d4d9e873aa0be808660123f11a1e78"), - NextValidatorsHash: []byte("f2564c78071e26643ae9b3e2a19fa0dc10d4d9e873aa0be808660123f11a1e78"), - ConsensusHash: []byte("f2564c78071e26643ae9b3e2a19fa0dc10d4d9e873aa0be808660123f11a1e78"), - AppHash: []byte("f2564c78071e26643ae9b3e2a19fa0dc10d4d9e873aa0be808660123f11a1e78"), - - LastResultsHash: []byte("f2564c78071e26643ae9b3e2a19fa0dc10d4d9e873aa0be808660123f11a1e78"), - - EvidenceHash: []byte("f2564c78071e26643ae9b3e2a19fa0dc10d4d9e873aa0be808660123f11a1e78"), - ProposerAddress: []byte("2915b7b15f979e48ebc61774bb1d86ba3136b7eb"), - } - - testCases := []struct { - header Header - expBytes string - }{ - {header: h, expBytes: "87b6117ac7f827d656f178a3d6d30b24b205db2b6a3a053bae8baf4618570bfc"}, - } - - for _, tc := range testCases { - hash := tc.header.Hash() - require.Equal(t, tc.expBytes, hex.EncodeToString(hash)) - } +func TestNilDataHashDoesntCrash(t *testing.T) { + assert.Equal(t, emptyBytes, []byte((*block.Data)(nil).Hash())) + assert.Equal(t, emptyBytes, []byte(new(block.Data).Hash())) } diff --git a/pkg/block/encoding_helper.go b/pkg/block/encoding_helper.go deleted file mode 100644 index 483614ff4..000000000 --- a/pkg/block/encoding_helper.go +++ /dev/null @@ -1,47 +0,0 @@ -package block - -import ( - gogotypes "github.com/gogo/protobuf/types" - - "github.com/tendermint/tendermint/libs/bytes" -) - -// cdcEncode returns nil if the input is nil, otherwise returns -// proto.Marshal(Value{Value: item}) -func cdcEncode(item interface{}) []byte { - if item != nil && !isTypedNil(item) && !isEmpty(item) { - switch item := item.(type) { - case string: - i := gogotypes.StringValue{ - Value: item, - } - bz, err := i.Marshal() - if err != nil { - return nil - } - return bz - case int64: - i := gogotypes.Int64Value{ - Value: item, - } - bz, err := i.Marshal() - if err != nil { - return nil - } - return bz - case bytes.HexBytes: - i := gogotypes.BytesValue{ - Value: item, - } - bz, err := i.Marshal() - if err != nil { - return nil - } - return bz - default: - return nil - } - } - - return nil -} diff --git a/pkg/block/signable.go b/pkg/block/signable.go deleted file mode 100644 index ad54eb1b6..000000000 --- a/pkg/block/signable.go +++ /dev/null @@ -1,23 +0,0 @@ -package block - -import ( - "github.com/tendermint/tendermint/crypto/ed25519" - tmmath "github.com/tendermint/tendermint/libs/math" -) - -var ( - // MaxSignatureSize is a maximum allowed signature size for the Proposal - // and Vote. - // XXX: secp256k1 does not have Size nor MaxSize defined. - MaxSignatureSize = tmmath.MaxInt(ed25519.SignatureSize, 64) -) - -// 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 marshaling. -type Signable interface { - SignBytes(chainID string) []byte -} diff --git a/pkg/block/signed_msg_type.go b/pkg/block/signed_msg_type.go deleted file mode 100644 index 9360cf7b0..000000000 --- a/pkg/block/signed_msg_type.go +++ /dev/null @@ -1,13 +0,0 @@ -package block - -import tmproto "github.com/tendermint/tendermint/proto/tendermint/types" - -// IsVoteTypeValid returns true if t is a valid vote type. -func IsVoteTypeValid(t tmproto.SignedMsgType) bool { - switch t { - case tmproto.PrevoteType, tmproto.PrecommitType: - return true - default: - return false - } -} diff --git a/pkg/block/test_util.go b/pkg/block/test_util.go deleted file mode 100644 index f2f6dc181..000000000 --- a/pkg/block/test_util.go +++ /dev/null @@ -1,47 +0,0 @@ -package block - -import ( - "context" - "fmt" - "time" - - tmproto "github.com/tendermint/tendermint/proto/tendermint/types" -) - -func makeCommit(blockID BlockID, height int64, round int32, - voteSet *VoteSet, validators []PrivValidator, now time.Time) (*Commit, error) { - - // all sign - for i := 0; i < len(validators); i++ { - pubKey, err := validators[i].GetPubKey(context.Background()) - if err != nil { - return nil, fmt.Errorf("can't get pubkey: %w", err) - } - vote := &Vote{ - ValidatorAddress: pubKey.Address(), - ValidatorIndex: int32(i), - Height: height, - Round: round, - Type: tmproto.PrecommitType, - BlockID: blockID, - Timestamp: now, - } - - _, 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) { - v := vote.ToProto() - err = privVal.SignVote(context.Background(), voteSet.ChainID(), v) - if err != nil { - return false, err - } - vote.Signature = v.Signature - return voteSet.AddVote(vote) -} diff --git a/pkg/block/utils.go b/pkg/block/utils.go deleted file mode 100644 index 9a2720a12..000000000 --- a/pkg/block/utils.go +++ /dev/null @@ -1,29 +0,0 @@ -package block - -import "reflect" - -// Go lacks a simple and safe way to see if something is a typed nil. -// See: -// - https://dave.cheney.net/2017/08/09/typed-nils-in-go-2 -// - https://groups.google.com/forum/#!topic/golang-nuts/wnH302gBa4I/discussion -// - https://github.com/golang/go/issues/21538 -func isTypedNil(o interface{}) bool { - rv := reflect.ValueOf(o) - switch rv.Kind() { - case reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr, reflect.Slice: - return rv.IsNil() - default: - return false - } -} - -// Returns true if it has zero length. -func isEmpty(o interface{}) bool { - rv := reflect.ValueOf(o) - switch rv.Kind() { - case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice, reflect.String: - return rv.Len() == 0 - default: - return false - } -} diff --git a/pkg/consensus/canonical.go b/pkg/consensus/canonical.go new file mode 100644 index 000000000..7df8f2b85 --- /dev/null +++ b/pkg/consensus/canonical.go @@ -0,0 +1,32 @@ +package consensus + +import ( + "github.com/tendermint/tendermint/pkg/meta" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" +) + +// CanonicalizeVote transforms the given Proposal to a CanonicalProposal. +func CanonicalizeProposal(chainID string, proposal *tmproto.Proposal) tmproto.CanonicalProposal { + return tmproto.CanonicalProposal{ + Type: tmproto.ProposalType, + Height: proposal.Height, // encoded as sfixed64 + Round: int64(proposal.Round), // encoded as sfixed64 + POLRound: int64(proposal.PolRound), + BlockID: meta.CanonicalizeBlockID(proposal.BlockID), + Timestamp: proposal.Timestamp, + ChainID: chainID, + } +} + +// CanonicalizeVote transforms the given Vote to a CanonicalVote, which does +// not contain ValidatorIndex and ValidatorAddress fields. +func CanonicalizeVote(chainID string, vote *tmproto.Vote) tmproto.CanonicalVote { + return tmproto.CanonicalVote{ + Type: vote.Type, + Height: vote.Height, // encoded as sfixed64 + Round: int64(vote.Round), // encoded as sfixed64 + BlockID: meta.CanonicalizeBlockID(vote.BlockID), + Timestamp: vote.Timestamp, + ChainID: chainID, + } +} diff --git a/pkg/block/errors.go b/pkg/consensus/errors.go similarity index 98% rename from pkg/block/errors.go rename to pkg/consensus/errors.go index c46c1082f..3fb1c1873 100644 --- a/pkg/block/errors.go +++ b/pkg/consensus/errors.go @@ -1,4 +1,4 @@ -package block +package consensus import "fmt" diff --git a/pkg/block/genesis.go b/pkg/consensus/genesis.go similarity index 96% rename from pkg/block/genesis.go rename to pkg/consensus/genesis.go index 7ffe3808e..9b7e77caa 100644 --- a/pkg/block/genesis.go +++ b/pkg/consensus/genesis.go @@ -1,4 +1,4 @@ -package block +package consensus import ( "bytes" @@ -12,11 +12,7 @@ import ( tmbytes "github.com/tendermint/tendermint/libs/bytes" tmjson "github.com/tendermint/tendermint/libs/json" tmtime "github.com/tendermint/tendermint/libs/time" -) - -const ( - // MaxChainIDLen is a maximum length of the chain ID. - MaxChainIDLen = 50 + "github.com/tendermint/tendermint/pkg/meta" ) //------------------------------------------------------------ @@ -70,8 +66,8 @@ func (genDoc *GenesisDoc) ValidateAndComplete() error { if genDoc.ChainID == "" { return errors.New("genesis doc must include non-empty chain_id") } - if len(genDoc.ChainID) > MaxChainIDLen { - return fmt.Errorf("chain_id in genesis doc is too long (max: %d)", MaxChainIDLen) + if len(genDoc.ChainID) > meta.MaxChainIDLen { + return fmt.Errorf("chain_id in genesis doc is too long (max: %d)", meta.MaxChainIDLen) } if genDoc.InitialHeight < 0 { return fmt.Errorf("initial_height cannot be negative (got %v)", genDoc.InitialHeight) diff --git a/pkg/block/genesis_test.go b/pkg/consensus/genesis_test.go similarity index 85% rename from pkg/block/genesis_test.go rename to pkg/consensus/genesis_test.go index 63902741f..3a9128dee 100644 --- a/pkg/block/genesis_test.go +++ b/pkg/consensus/genesis_test.go @@ -1,4 +1,4 @@ -package block +package consensus_test import ( "io/ioutil" @@ -11,6 +11,7 @@ import ( "github.com/tendermint/tendermint/crypto/ed25519" tmjson "github.com/tendermint/tendermint/libs/json" tmtime "github.com/tendermint/tendermint/libs/time" + "github.com/tendermint/tendermint/pkg/consensus" ) func TestGenesisBad(t *testing.T) { @@ -52,7 +53,7 @@ func TestGenesisBad(t *testing.T) { } for _, testCase := range testCases { - _, err := GenesisDocFromJSON(testCase) + _, err := consensus.GenesisDocFromJSON(testCase) assert.Error(t, err, "expected error for empty genDoc json") } } @@ -74,20 +75,20 @@ func TestGenesisGood(t *testing.T) { "app_state":{"account_owner": "Bob"} }`, ) - _, err := GenesisDocFromJSON(genDocBytes) + _, err := consensus.GenesisDocFromJSON(genDocBytes) assert.NoError(t, err, "expected no error for good genDoc json") pubkey := ed25519.GenPrivKey().PubKey() // create a base gendoc from struct - baseGenDoc := &GenesisDoc{ + baseGenDoc := &consensus.GenesisDoc{ ChainID: "abc", - Validators: []GenesisValidator{{pubkey.Address(), pubkey, 10, "myval"}}, + Validators: []consensus.GenesisValidator{{pubkey.Address(), pubkey, 10, "myval"}}, } genDocBytes, err = tmjson.Marshal(baseGenDoc) assert.NoError(t, err, "error marshaling genDoc") // test base gendoc and check consensus params were filled - genDoc, err := GenesisDocFromJSON(genDocBytes) + genDoc, err := consensus.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") @@ -97,14 +98,14 @@ func TestGenesisGood(t *testing.T) { // create json with consensus params filled genDocBytes, err = tmjson.Marshal(genDoc) assert.NoError(t, err, "error marshaling genDoc") - genDoc, err = GenesisDocFromJSON(genDocBytes) + genDoc, err = consensus.GenesisDocFromJSON(genDocBytes) assert.NoError(t, err, "expected no error for valid genDoc json") // test with invalid consensus params genDoc.ConsensusParams.Block.MaxBytes = 0 genDocBytes, err = tmjson.Marshal(genDoc) assert.NoError(t, err, "error marshaling genDoc") - _, err = GenesisDocFromJSON(genDocBytes) + _, err = consensus.GenesisDocFromJSON(genDocBytes) assert.Error(t, err, "expected error for genDoc json with block size of 0") // Genesis doc from raw json @@ -116,7 +117,7 @@ func TestGenesisGood(t *testing.T) { } for _, tc := range missingValidatorsTestCases { - _, err := GenesisDocFromJSON(tc) + _, err := consensus.GenesisDocFromJSON(tc) assert.NoError(t, err) } } @@ -141,7 +142,7 @@ func TestGenesisSaveAs(t *testing.T) { require.NoError(t, err) // load - genDoc2, err := GenesisDocFromFile(tmpfile.Name()) + genDoc2, err := consensus.GenesisDocFromFile(tmpfile.Name()) require.NoError(t, err) assert.EqualValues(t, genDoc2, genDoc) assert.Equal(t, genDoc2.Validators, genDoc.Validators) @@ -152,14 +153,14 @@ func TestGenesisValidatorHash(t *testing.T) { assert.NotEmpty(t, genDoc.ValidatorHash()) } -func randomGenesisDoc() *GenesisDoc { +func randomGenesisDoc() *consensus.GenesisDoc { pubkey := ed25519.GenPrivKey().PubKey() - return &GenesisDoc{ + return &consensus.GenesisDoc{ GenesisTime: tmtime.Now(), ChainID: "abc", InitialHeight: 1000, - Validators: []GenesisValidator{{pubkey.Address(), pubkey, 10, "myval"}}, - ConsensusParams: DefaultConsensusParams(), + Validators: []consensus.GenesisValidator{{pubkey.Address(), pubkey, 10, "myval"}}, + ConsensusParams: consensus.DefaultConsensusParams(), AppHash: []byte{1, 2, 3}, } } diff --git a/types/keys.go b/pkg/consensus/keys.go similarity index 78% rename from types/keys.go rename to pkg/consensus/keys.go index 941e82b65..a28725d6f 100644 --- a/types/keys.go +++ b/pkg/consensus/keys.go @@ -1,4 +1,4 @@ -package types +package consensus // UNSTABLE var ( diff --git a/pkg/block/params.go b/pkg/consensus/params.go similarity index 92% rename from pkg/block/params.go rename to pkg/consensus/params.go index 6eb1b101c..2363c830b 100644 --- a/pkg/block/params.go +++ b/pkg/consensus/params.go @@ -1,4 +1,4 @@ -package block +package consensus import ( "errors" @@ -10,19 +10,11 @@ import ( "github.com/tendermint/tendermint/crypto/sr25519" "github.com/tendermint/tendermint/crypto/tmhash" tmstrings "github.com/tendermint/tendermint/libs/strings" + "github.com/tendermint/tendermint/pkg/meta" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" ) const ( - // MaxBlockSizeBytes is the maximum permitted size of the blocks. - MaxBlockSizeBytes = 104857600 // 100MB - - // BlockPartSizeBytes is the size of one block part. - BlockPartSizeBytes uint32 = 65536 // 64kB - - // MaxBlockPartsCount is the maximum number of block parts. - MaxBlockPartsCount = (MaxBlockSizeBytes / BlockPartSizeBytes) + 1 - ABCIPubKeyTypeEd25519 = ed25519.KeyType ABCIPubKeyTypeSecp256k1 = secp256k1.KeyType ABCIPubKeyTypeSr25519 = sr25519.KeyType @@ -129,16 +121,16 @@ func (val *ValidatorParams) IsValidPubkeyType(pubkeyType string) bool { // allowed limits, and returns an error if they are not. func (params ConsensusParams) ValidateConsensusParams() error { if params.Block.MaxBytes <= 0 { - return fmt.Errorf("block.MaxBytes must be greater than 0. Got %d", + return fmt.Errorf("meta.MaxBytes must be greater than 0. Got %d", params.Block.MaxBytes) } - if params.Block.MaxBytes > MaxBlockSizeBytes { - return fmt.Errorf("block.MaxBytes is too big. %d > %d", - params.Block.MaxBytes, MaxBlockSizeBytes) + if params.Block.MaxBytes > meta.MaxBlockSizeBytes { + return fmt.Errorf("meta.MaxBytes is too big. %d > %d", + params.Block.MaxBytes, meta.MaxBlockSizeBytes) } if params.Block.MaxGas < -1 { - return fmt.Errorf("block.MaxGas must be greater or equal to -1. Got %d", + return fmt.Errorf("meta.MaxGas must be greater or equal to -1. Got %d", params.Block.MaxGas) } diff --git a/pkg/block/params_test.go b/pkg/consensus/params_test.go similarity index 99% rename from pkg/block/params_test.go rename to pkg/consensus/params_test.go index 6ab272b6c..281a7c23c 100644 --- a/pkg/block/params_test.go +++ b/pkg/consensus/params_test.go @@ -1,4 +1,4 @@ -package block +package consensus import ( "bytes" diff --git a/pkg/block/priv_validator.go b/pkg/consensus/priv_validator.go similarity index 99% rename from pkg/block/priv_validator.go rename to pkg/consensus/priv_validator.go index f90319952..a727c5b05 100644 --- a/pkg/block/priv_validator.go +++ b/pkg/consensus/priv_validator.go @@ -1,4 +1,4 @@ -package block +package consensus import ( "bytes" diff --git a/pkg/block/proposal.go b/pkg/consensus/proposal.go similarity index 85% rename from pkg/block/proposal.go rename to pkg/consensus/proposal.go index deee8390d..3d8d0d702 100644 --- a/pkg/block/proposal.go +++ b/pkg/consensus/proposal.go @@ -1,4 +1,4 @@ -package block +package consensus import ( "errors" @@ -8,6 +8,7 @@ import ( "github.com/tendermint/tendermint/internal/libs/protoio" tmbytes "github.com/tendermint/tendermint/libs/bytes" tmtime "github.com/tendermint/tendermint/libs/time" + "github.com/tendermint/tendermint/pkg/meta" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" ) @@ -24,17 +25,17 @@ var ( // If POLRound >= 0, then BlockID corresponds to the block that is locked in POLRound. type Proposal struct { Type tmproto.SignedMsgType - Height int64 `json:"height"` - Round int32 `json:"round"` // there can not be greater than 2_147_483_647 rounds - POLRound int32 `json:"pol_round"` // -1 if null. - BlockID BlockID `json:"block_id"` - Timestamp time.Time `json:"timestamp"` - Signature []byte `json:"signature"` + Height int64 `json:"height"` + Round int32 `json:"round"` // there can not be greater than 2_147_483_647 rounds + POLRound int32 `json:"pol_round"` // -1 if null. + BlockID meta.BlockID `json:"block_id"` + Timestamp time.Time `json:"timestamp"` + Signature []byte `json:"signature"` } // NewProposal returns a new Proposal. // If there is no POLRound, polRound should be -1. -func NewProposal(height int64, round int32, polRound int32, blockID BlockID) *Proposal { +func NewProposal(height int64, round int32, polRound int32, blockID meta.BlockID) *Proposal { return &Proposal{ Type: tmproto.ProposalType, Height: height, @@ -73,8 +74,8 @@ func (p *Proposal) ValidateBasic() error { return errors.New("signature is missing") } - if len(p.Signature) > MaxSignatureSize { - return fmt.Errorf("signature is too big (max: %d)", MaxSignatureSize) + if len(p.Signature) > meta.MaxSignatureSize { + return fmt.Errorf("signature is too big (max: %d)", meta.MaxSignatureSize) } return nil } @@ -96,7 +97,7 @@ func (p *Proposal) String() string { p.BlockID, p.POLRound, tmbytes.Fingerprint(p.Signature), - CanonicalTime(p.Timestamp)) + meta.CanonicalTime(p.Timestamp)) } // ProposalSignBytes returns the proto-encoding of the canonicalized Proposal, @@ -144,7 +145,7 @@ func ProposalFromProto(pp *tmproto.Proposal) (*Proposal, error) { p := new(Proposal) - blockID, err := BlockIDFromProto(&pp.BlockID) + blockID, err := meta.BlockIDFromProto(&pp.BlockID) if err != nil { return nil, err } diff --git a/pkg/block/proposal_test.go b/pkg/consensus/proposal_test.go similarity index 60% rename from pkg/block/proposal_test.go rename to pkg/consensus/proposal_test.go index 6bb7e5e69..bcf35ac6b 100644 --- a/pkg/block/proposal_test.go +++ b/pkg/consensus/proposal_test.go @@ -1,4 +1,4 @@ -package block +package consensus_test import ( "context" @@ -13,24 +13,26 @@ import ( "github.com/tendermint/tendermint/crypto/tmhash" "github.com/tendermint/tendermint/internal/libs/protoio" tmrand "github.com/tendermint/tendermint/libs/rand" + "github.com/tendermint/tendermint/pkg/consensus" + "github.com/tendermint/tendermint/pkg/meta" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" ) var ( - testProposal *Proposal + testProposal *consensus.Proposal pbp *tmproto.Proposal ) func init() { - var stamp, err = time.Parse(TimeFormat, "2018-02-11T07:09:22.765Z") + var stamp, err = time.Parse(meta.TimeFormat, "2018-02-11T07:09:22.765Z") if err != nil { panic(err) } - testProposal = &Proposal{ + testProposal = &consensus.Proposal{ Height: 12345, Round: 23456, - BlockID: BlockID{Hash: []byte("--June_15_2020_amino_was_removed"), - PartSetHeader: PartSetHeader{Total: 111, Hash: []byte("--June_15_2020_amino_was_removed")}}, + BlockID: meta.BlockID{Hash: []byte("--June_15_2020_amino_was_removed"), + PartSetHeader: meta.PartSetHeader{Total: 111, Hash: []byte("--June_15_2020_amino_was_removed")}}, POLRound: -1, Timestamp: stamp, } @@ -39,8 +41,8 @@ func init() { func TestProposalSignable(t *testing.T) { chainID := "test_chain_id" - signBytes := ProposalSignBytes(chainID, pbp) - pb := CanonicalizeProposal(chainID, pbp) + signBytes := consensus.ProposalSignBytes(chainID, pbp) + pb := consensus.CanonicalizeProposal(chainID, pbp) expected, err := protoio.MarshalDelimited(&pb) require.NoError(t, err) @@ -56,15 +58,15 @@ func TestProposalString(t *testing.T) { } func TestProposalVerifySignature(t *testing.T) { - privVal := NewMockPV() + privVal := consensus.NewMockPV() pubKey, err := privVal.GetPubKey(context.Background()) require.NoError(t, err) - prop := NewProposal( + prop := consensus.NewProposal( 4, 2, 2, - BlockID{tmrand.Bytes(tmhash.Size), PartSetHeader{777, tmrand.Bytes(tmhash.Size)}}) + meta.BlockID{tmrand.Bytes(tmhash.Size), meta.PartSetHeader{777, tmrand.Bytes(tmhash.Size)}}) p := prop.ToProto() - signBytes := ProposalSignBytes("test_chain_id", p) + signBytes := consensus.ProposalSignBytes("test_chain_id", p) // sign it err = privVal.SignProposal(context.Background(), "test_chain_id", p) @@ -85,11 +87,11 @@ func TestProposalVerifySignature(t *testing.T) { err = proto.Unmarshal(bs, newProp) require.NoError(t, err) - np, err := ProposalFromProto(newProp) + np, err := consensus.ProposalFromProto(newProp) require.NoError(t, err) // verify the transmitted proposal - newSignBytes := ProposalSignBytes("test_chain_id", pb) + newSignBytes := consensus.ProposalSignBytes("test_chain_id", pb) require.Equal(t, string(signBytes), string(newSignBytes)) valid = pubKey.VerifySignature(newSignBytes, np.Signature) require.True(t, valid) @@ -97,12 +99,12 @@ func TestProposalVerifySignature(t *testing.T) { func BenchmarkProposalWriteSignBytes(b *testing.B) { for i := 0; i < b.N; i++ { - ProposalSignBytes("test_chain_id", pbp) + consensus.ProposalSignBytes("test_chain_id", pbp) } } func BenchmarkProposalSign(b *testing.B) { - privVal := NewMockPV() + privVal := consensus.NewMockPV() for i := 0; i < b.N; i++ { err := privVal.SignProposal(context.Background(), "test_chain_id", pbp) if err != nil { @@ -112,46 +114,45 @@ func BenchmarkProposalSign(b *testing.B) { } func BenchmarkProposalVerifySignature(b *testing.B) { - privVal := NewMockPV() + privVal := consensus.NewMockPV() err := privVal.SignProposal(context.Background(), "test_chain_id", pbp) require.NoError(b, err) pubKey, err := privVal.GetPubKey(context.Background()) require.NoError(b, err) for i := 0; i < b.N; i++ { - pubKey.VerifySignature(ProposalSignBytes("test_chain_id", pbp), testProposal.Signature) + pubKey.VerifySignature(consensus.ProposalSignBytes("test_chain_id", pbp), testProposal.Signature) } } func TestProposalValidateBasic(t *testing.T) { - - privVal := NewMockPV() + privVal := consensus.NewMockPV() testCases := []struct { testName string - malleateProposal func(*Proposal) + malleateProposal func(*consensus.Proposal) expectErr bool }{ - {"Good Proposal", func(p *Proposal) {}, false}, - {"Invalid Type", func(p *Proposal) { p.Type = tmproto.PrecommitType }, true}, - {"Invalid Height", func(p *Proposal) { p.Height = -1 }, true}, - {"Invalid Round", func(p *Proposal) { p.Round = -1 }, true}, - {"Invalid POLRound", func(p *Proposal) { p.POLRound = -2 }, true}, - {"Invalid BlockId", func(p *Proposal) { - p.BlockID = BlockID{[]byte{1, 2, 3}, PartSetHeader{111, []byte("blockparts")}} + {"Good Proposal", func(p *consensus.Proposal) {}, false}, + {"Invalid Type", func(p *consensus.Proposal) { p.Type = tmproto.PrecommitType }, true}, + {"Invalid Height", func(p *consensus.Proposal) { p.Height = -1 }, true}, + {"Invalid Round", func(p *consensus.Proposal) { p.Round = -1 }, true}, + {"Invalid POLRound", func(p *consensus.Proposal) { p.POLRound = -2 }, true}, + {"Invalid BlockId", func(p *consensus.Proposal) { + p.BlockID = meta.BlockID{[]byte{1, 2, 3}, meta.PartSetHeader{111, []byte("blockparts")}} }, true}, - {"Invalid Signature", func(p *Proposal) { + {"Invalid Signature", func(p *consensus.Proposal) { p.Signature = make([]byte, 0) }, true}, - {"Too big Signature", func(p *Proposal) { - p.Signature = make([]byte, MaxSignatureSize+1) + {"Too big Signature", func(p *consensus.Proposal) { + p.Signature = make([]byte, meta.MaxSignatureSize+1) }, true}, } - blockID := makeBlockID(tmhash.Sum([]byte("blockhash")), math.MaxInt32, tmhash.Sum([]byte("partshash"))) + blockID := meta.BlockID{tmhash.Sum([]byte("blockhash")), meta.PartSetHeader{math.MaxInt32, tmhash.Sum([]byte("partshash"))}} for _, tc := range testCases { tc := tc t.Run(tc.testName, func(t *testing.T) { - prop := NewProposal( + prop := consensus.NewProposal( 4, 2, 2, blockID) p := prop.ToProto() @@ -165,24 +166,24 @@ func TestProposalValidateBasic(t *testing.T) { } func TestProposalProtoBuf(t *testing.T) { - proposal := NewProposal(1, 2, 3, makeBlockID([]byte("hash"), 2, []byte("part_set_hash"))) + proposal := consensus.NewProposal(1, 2, 3, meta.BlockID{[]byte("hash"), meta.PartSetHeader{2, []byte("part_set_hash")}}) proposal.Signature = []byte("sig") - proposal2 := NewProposal(1, 2, 3, BlockID{}) + proposal2 := consensus.NewProposal(1, 2, 3, meta.BlockID{}) testCases := []struct { msg string - p1 *Proposal + p1 *consensus.Proposal expPass bool }{ {"success", proposal, true}, {"success", proposal2, false}, // blcokID cannot be empty - {"empty proposal failure validatebasic", &Proposal{}, false}, + {"empty proposal failure validatebasic", &consensus.Proposal{}, false}, {"nil proposal", nil, false}, } for _, tc := range testCases { protoProposal := tc.p1.ToProto() - p, err := ProposalFromProto(protoProposal) + p, err := consensus.ProposalFromProto(protoProposal) if tc.expPass { require.NoError(t, err) require.Equal(t, tc.p1, p, tc.msg) diff --git a/pkg/block/protobuf.go b/pkg/consensus/protobuf.go similarity index 98% rename from pkg/block/protobuf.go rename to pkg/consensus/protobuf.go index 76baa2b97..5aa4c4a82 100644 --- a/pkg/block/protobuf.go +++ b/pkg/consensus/protobuf.go @@ -1,4 +1,4 @@ -package block +package consensus import ( abci "github.com/tendermint/tendermint/abci/types" diff --git a/pkg/block/protobuf_test.go b/pkg/consensus/protobuf_test.go similarity index 66% rename from pkg/block/protobuf_test.go rename to pkg/consensus/protobuf_test.go index 08d2e9afe..29a3bd798 100644 --- a/pkg/block/protobuf_test.go +++ b/pkg/consensus/protobuf_test.go @@ -1,4 +1,4 @@ -package block +package consensus_test import ( "testing" @@ -10,11 +10,12 @@ import ( "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/ed25519" cryptoenc "github.com/tendermint/tendermint/crypto/encoding" + "github.com/tendermint/tendermint/pkg/consensus" ) func TestABCIPubKey(t *testing.T) { pkEd := ed25519.GenPrivKey().PubKey() - err := testABCIPubKey(t, pkEd, ABCIPubKeyTypeEd25519) + err := testABCIPubKey(t, pkEd, consensus.ABCIPubKeyTypeEd25519) assert.NoError(t, err) } @@ -31,23 +32,23 @@ func TestABCIValidators(t *testing.T) { pkEd := ed25519.GenPrivKey().PubKey() // correct validator - tmValExpected := NewValidator(pkEd, 10) + tmValExpected := consensus.NewValidator(pkEd, 10) - tmVal := NewValidator(pkEd, 10) + tmVal := consensus.NewValidator(pkEd, 10) - abciVal := TM2PB.ValidatorUpdate(tmVal) - tmVals, err := PB2TM.ValidatorUpdates([]abci.ValidatorUpdate{abciVal}) + abciVal := consensus.TM2PB.ValidatorUpdate(tmVal) + tmVals, err := consensus.PB2TM.ValidatorUpdates([]abci.ValidatorUpdate{abciVal}) assert.Nil(t, err) assert.Equal(t, tmValExpected, tmVals[0]) - abciVals := TM2PB.ValidatorUpdates(NewValidatorSet(tmVals)) + abciVals := consensus.TM2PB.ValidatorUpdates(consensus.NewValidatorSet(tmVals)) assert.Equal(t, []abci.ValidatorUpdate{abciVal}, abciVals) // val with address tmVal.Address = pkEd.Address() - abciVal = TM2PB.ValidatorUpdate(tmVal) - tmVals, err = PB2TM.ValidatorUpdates([]abci.ValidatorUpdate{abciVal}) + abciVal = consensus.TM2PB.ValidatorUpdate(tmVal) + tmVals, err = consensus.PB2TM.ValidatorUpdates([]abci.ValidatorUpdate{abciVal}) assert.Nil(t, err) assert.Equal(t, tmValExpected, tmVals[0]) } @@ -55,7 +56,7 @@ func TestABCIValidators(t *testing.T) { func TestABCIValidatorWithoutPubKey(t *testing.T) { pkEd := ed25519.GenPrivKey().PubKey() - abciVal := TM2PB.Validator(NewValidator(pkEd, 10)) + abciVal := consensus.TM2PB.Validator(consensus.NewValidator(pkEd, 10)) // pubkey must be nil tmValExpected := abci.Validator{ diff --git a/pkg/block/validation.go b/pkg/consensus/validation.go similarity index 87% rename from pkg/block/validation.go rename to pkg/consensus/validation.go index 1950ff278..12a7e838d 100644 --- a/pkg/block/validation.go +++ b/pkg/consensus/validation.go @@ -1,17 +1,17 @@ -package block +package consensus import ( "errors" "fmt" "github.com/tendermint/tendermint/crypto/batch" - "github.com/tendermint/tendermint/crypto/tmhash" tmmath "github.com/tendermint/tendermint/libs/math" + "github.com/tendermint/tendermint/pkg/meta" ) const batchVerifyThreshold = 2 -func shouldBatchVerify(vals *ValidatorSet, commit *Commit) bool { +func shouldBatchVerify(vals *ValidatorSet, commit *meta.Commit) bool { return len(commit.Signatures) >= batchVerifyThreshold && batch.SupportsBatchVerifier(vals.GetProposer().PubKey) } @@ -22,8 +22,8 @@ func shouldBatchVerify(vals *ValidatorSet, commit *Commit) bool { // application that depends on the LastCommitInfo sent in BeginBlock, which // includes which validators signed. For instance, Gaia incentivizes proposers // with a bonus for including more than +2/3 of the signatures. -func VerifyCommit(chainID string, vals *ValidatorSet, blockID BlockID, - height int64, commit *Commit) error { +func VerifyCommit(chainID string, vals *ValidatorSet, blockID meta.BlockID, + height int64, commit *meta.Commit) error { // run a basic validation of the arguments if err := verifyBasicValsAndCommit(vals, commit, height, blockID); err != nil { return err @@ -34,10 +34,10 @@ func VerifyCommit(chainID string, vals *ValidatorSet, blockID BlockID, votingPowerNeeded := vals.TotalVotingPower() * 2 / 3 // ignore all absent signatures - ignore := func(c CommitSig) bool { return c.Absent() } + ignore := func(c meta.CommitSig) bool { return c.Absent() } // only count the signatures that are for the block - count := func(c CommitSig) bool { return c.ForBlock() } + count := func(c meta.CommitSig) bool { return c.ForBlock() } // attempt to batch verify if shouldBatchVerify(vals, commit) { @@ -56,8 +56,8 @@ func VerifyCommit(chainID string, vals *ValidatorSet, blockID BlockID, // // This method is primarily used by the light client and does not check all the // signatures. -func VerifyCommitLight(chainID string, vals *ValidatorSet, blockID BlockID, - height int64, commit *Commit) error { +func VerifyCommitLight(chainID string, vals *ValidatorSet, blockID meta.BlockID, + height int64, commit *meta.Commit) error { // run a basic validation of the arguments if err := verifyBasicValsAndCommit(vals, commit, height, blockID); err != nil { return err @@ -67,10 +67,10 @@ func VerifyCommitLight(chainID string, vals *ValidatorSet, blockID BlockID, votingPowerNeeded := vals.TotalVotingPower() * 2 / 3 // ignore all commit signatures that are not for the block - ignore := func(c CommitSig) bool { return !c.ForBlock() } + ignore := func(c meta.CommitSig) bool { return !c.ForBlock() } // count all the remaining signatures - count := func(c CommitSig) bool { return true } + count := func(c meta.CommitSig) bool { return true } // attempt to batch verify if shouldBatchVerify(vals, commit) { @@ -91,7 +91,7 @@ func VerifyCommitLight(chainID string, vals *ValidatorSet, blockID BlockID, // // This method is primarily used by the light client and does not check all the // signatures. -func VerifyCommitLightTrusting(chainID string, vals *ValidatorSet, commit *Commit, trustLevel tmmath.Fraction) error { +func VerifyCommitLightTrusting(chainID string, vals *ValidatorSet, commit *meta.Commit, trustLevel tmmath.Fraction) error { // sanity checks if vals == nil { return errors.New("nil validator set") @@ -104,17 +104,17 @@ func VerifyCommitLightTrusting(chainID string, vals *ValidatorSet, commit *Commi } // safely calculate voting power needed. - totalVotingPowerMulByNumerator, overflow := safeMul(vals.TotalVotingPower(), int64(trustLevel.Numerator)) + totalVotingPowerMulByNumerator, overflow := tmmath.SafeMul(vals.TotalVotingPower(), int64(trustLevel.Numerator)) if overflow { return errors.New("int64 overflow while calculating voting power needed. please provide smaller trustLevel numerator") } votingPowerNeeded := totalVotingPowerMulByNumerator / int64(trustLevel.Denominator) // ignore all commit signatures that are not for the block - ignore := func(c CommitSig) bool { return !c.ForBlock() } + ignore := func(c meta.CommitSig) bool { return !c.ForBlock() } // count all the remaining signatures - count := func(c CommitSig) bool { return true } + count := func(c meta.CommitSig) bool { return true } // attempt to batch verify commit. As the validator set doesn't necessarily // correspond with the validator set that signed the block we need to look @@ -129,18 +129,6 @@ func VerifyCommitLightTrusting(chainID string, vals *ValidatorSet, commit *Commi ignore, count, false, false) } -// ValidateHash returns an error if the hash is not empty, but its -// size != tmhash.Size. -func ValidateHash(h []byte) error { - if len(h) > 0 && len(h) != tmhash.Size { - return fmt.Errorf("expected size to be %d bytes, got %d bytes", - tmhash.Size, - len(h), - ) - } - return nil -} - // Batch verification // verifyCommitBatch batch verifies commits. This routine is equivalent @@ -152,10 +140,10 @@ func ValidateHash(h []byte) error { func verifyCommitBatch( chainID string, vals *ValidatorSet, - commit *Commit, + commit *meta.Commit, votingPowerNeeded int64, - ignoreSig func(CommitSig) bool, - countSig func(CommitSig) bool, + ignoreSig func(meta.CommitSig) bool, + countSig func(meta.CommitSig) bool, countAllSignatures bool, lookUpByIndex bool, ) error { @@ -203,7 +191,7 @@ func verifyCommitBatch( } // Validate signature. - voteSignBytes := commit.VoteSignBytes(chainID, int32(idx)) + voteSignBytes := VoteSignBytesFromCommit(commit, chainID, int32(idx)) // add the key, sig and message to the verifier if err := bv.Add(val.PubKey, voteSignBytes, commitSig.Signature); err != nil { @@ -265,10 +253,10 @@ func verifyCommitBatch( func verifyCommitSingle( chainID string, vals *ValidatorSet, - commit *Commit, + commit *meta.Commit, votingPowerNeeded int64, - ignoreSig func(CommitSig) bool, - countSig func(CommitSig) bool, + ignoreSig func(meta.CommitSig) bool, + countSig func(meta.CommitSig) bool, countAllSignatures bool, lookUpByIndex bool, ) error { @@ -306,7 +294,7 @@ func verifyCommitSingle( seenVals[valIdx] = idx } - voteSignBytes = commit.VoteSignBytes(chainID, int32(idx)) + voteSignBytes = VoteSignBytesFromCommit(commit, chainID, int32(idx)) if !val.PubKey.VerifySignature(voteSignBytes, commitSig.Signature) { return fmt.Errorf("wrong signature (#%d): %X", idx, commitSig.Signature) @@ -331,7 +319,7 @@ func verifyCommitSingle( return nil } -func verifyBasicValsAndCommit(vals *ValidatorSet, commit *Commit, height int64, blockID BlockID) error { +func verifyBasicValsAndCommit(vals *ValidatorSet, commit *meta.Commit, height int64, blockID meta.BlockID) error { if vals == nil { return errors.New("nil validator set") } diff --git a/pkg/block/validation_test.go b/pkg/consensus/validation_test.go similarity index 77% rename from pkg/block/validation_test.go rename to pkg/consensus/validation_test.go index 8fe94daf5..712b57b4b 100644 --- a/pkg/block/validation_test.go +++ b/pkg/consensus/validation_test.go @@ -1,4 +1,4 @@ -package block +package consensus_test import ( "context" @@ -8,7 +8,10 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + test "github.com/tendermint/tendermint/internal/test/factory" tmmath "github.com/tendermint/tendermint/libs/math" + "github.com/tendermint/tendermint/pkg/consensus" + "github.com/tendermint/tendermint/pkg/meta" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" ) @@ -19,7 +22,7 @@ func TestValidatorSet_VerifyCommit_All(t *testing.T) { round = int32(0) height = int64(100) - blockID = makeBlockID([]byte("blockhash"), 1000, []byte("partshash")) + blockID = test.MakeBlockIDWithHash([]byte("blockhash")) chainID = "Lalande21185" trustLevel = tmmath.Fraction{Numerator: 2, Denominator: 3} ) @@ -29,7 +32,7 @@ func TestValidatorSet_VerifyCommit_All(t *testing.T) { // vote chainID chainID string // vote blockID - blockID BlockID + blockID meta.BlockID valSize int // height of the commit @@ -46,7 +49,7 @@ func TestValidatorSet_VerifyCommit_All(t *testing.T) { {"good (single verification)", chainID, blockID, 1, height, 1, 0, 0, false}, {"wrong signature (#0)", "EpsilonEridani", blockID, 2, height, 2, 0, 0, true}, - {"wrong block ID", chainID, makeBlockIDRandom(), 2, height, 2, 0, 0, true}, + {"wrong block ID", chainID, test.MakeBlockID(), 2, height, 2, 0, 0, true}, {"wrong height", chainID, blockID, 1, height - 1, 1, 0, 0, true}, {"wrong set size: 4 vs 3", chainID, blockID, 4, height, 3, 0, 0, true}, @@ -60,20 +63,20 @@ func TestValidatorSet_VerifyCommit_All(t *testing.T) { for _, tc := range testCases { tc := tc t.Run(tc.description, func(t *testing.T) { - _, valSet, vals := randVoteSet(tc.height, round, tmproto.PrecommitType, tc.valSize, 10) + _, valSet, vals := test.RandVoteSet(tc.height, round, tmproto.PrecommitType, tc.valSize, 10) totalVotes := tc.blockVotes + tc.absentVotes + tc.nilVotes - sigs := make([]CommitSig, totalVotes) + sigs := make([]meta.CommitSig, totalVotes) vi := 0 // add absent sigs first for i := 0; i < tc.absentVotes; i++ { - sigs[vi] = NewCommitSigAbsent() + sigs[vi] = meta.NewCommitSigAbsent() vi++ } for i := 0; i < tc.blockVotes+tc.nilVotes; i++ { pubKey, err := vals[vi%len(vals)].GetPubKey(context.Background()) require.NoError(t, err) - vote := &Vote{ + vote := &consensus.Vote{ ValidatorAddress: pubKey.Address(), ValidatorIndex: int32(vi), Height: tc.height, @@ -83,7 +86,7 @@ func TestValidatorSet_VerifyCommit_All(t *testing.T) { Timestamp: time.Now(), } if i >= tc.blockVotes { - vote.BlockID = BlockID{} + vote.BlockID = meta.BlockID{} } v := vote.ToProto() @@ -95,7 +98,7 @@ func TestValidatorSet_VerifyCommit_All(t *testing.T) { vi++ } - commit := NewCommit(tc.height, round, tc.blockID, sigs) + commit := meta.NewCommit(tc.height, round, tc.blockID, sigs) err := valSet.VerifyCommit(chainID, blockID, height, commit) if tc.expErr { @@ -135,11 +138,11 @@ func TestValidatorSet_VerifyCommit_CheckAllSignatures(t *testing.T) { var ( chainID = "test_chain_id" h = int64(3) - blockID = makeBlockIDRandom() + blockID = test.MakeBlockID() ) - voteSet, valSet, vals := randVoteSet(h, 0, tmproto.PrecommitType, 4, 10) - commit, err := makeCommit(blockID, h, 0, voteSet, vals, time.Now()) + voteSet, valSet, vals := test.RandVoteSet(h, 0, tmproto.PrecommitType, 4, 10) + commit, err := test.MakeCommit(blockID, h, 0, voteSet, vals, time.Now()) require.NoError(t, err) require.NoError(t, valSet.VerifyCommit(chainID, blockID, h, commit)) @@ -161,11 +164,11 @@ func TestValidatorSet_VerifyCommitLight_ReturnsAsSoonAsMajorityOfVotingPowerSign var ( chainID = "test_chain_id" h = int64(3) - blockID = makeBlockIDRandom() + blockID = test.MakeBlockID() ) - voteSet, valSet, vals := randVoteSet(h, 0, tmproto.PrecommitType, 4, 10) - commit, err := makeCommit(blockID, h, 0, voteSet, vals, time.Now()) + voteSet, valSet, vals := test.RandVoteSet(h, 0, tmproto.PrecommitType, 4, 10) + commit, err := test.MakeCommit(blockID, h, 0, voteSet, vals, time.Now()) require.NoError(t, err) require.NoError(t, valSet.VerifyCommit(chainID, blockID, h, commit)) @@ -185,11 +188,11 @@ func TestValidatorSet_VerifyCommitLightTrusting_ReturnsAsSoonAsTrustLevelOfVotin var ( chainID = "test_chain_id" h = int64(3) - blockID = makeBlockIDRandom() + blockID = test.MakeBlockID() ) - voteSet, valSet, vals := randVoteSet(h, 0, tmproto.PrecommitType, 4, 10) - commit, err := makeCommit(blockID, h, 0, voteSet, vals, time.Now()) + voteSet, valSet, vals := test.RandVoteSet(h, 0, tmproto.PrecommitType, 4, 10) + commit, err := test.MakeCommit(blockID, h, 0, voteSet, vals, time.Now()) require.NoError(t, err) require.NoError(t, valSet.VerifyCommit(chainID, blockID, h, commit)) @@ -207,15 +210,15 @@ func TestValidatorSet_VerifyCommitLightTrusting_ReturnsAsSoonAsTrustLevelOfVotin func TestValidatorSet_VerifyCommitLightTrusting(t *testing.T) { var ( - blockID = makeBlockIDRandom() - voteSet, originalValset, vals = randVoteSet(1, 1, tmproto.PrecommitType, 6, 1) - commit, err = makeCommit(blockID, 1, 1, voteSet, vals, time.Now()) - newValSet, _ = randValidatorPrivValSet(2, 1) + blockID = test.MakeBlockID() + voteSet, originalValset, vals = test.RandVoteSet(1, 1, tmproto.PrecommitType, 6, 1) + commit, err = test.MakeCommit(blockID, 1, 1, voteSet, vals, time.Now()) + newValSet, _ = test.RandValidatorPrivValSet(2, 1) ) require.NoError(t, err) testCases := []struct { - valSet *ValidatorSet + valSet *consensus.ValidatorSet err bool }{ // good @@ -230,7 +233,7 @@ func TestValidatorSet_VerifyCommitLightTrusting(t *testing.T) { }, // good - first two are different but the rest of the same -> >1/3 2: { - valSet: NewValidatorSet(append(newValSet.Validators, originalValset.Validators...)), + valSet: consensus.NewValidatorSet(append(newValSet.Validators, originalValset.Validators...)), err: false, }, } @@ -248,9 +251,9 @@ func TestValidatorSet_VerifyCommitLightTrusting(t *testing.T) { func TestValidatorSet_VerifyCommitLightTrustingErrorsOnOverflow(t *testing.T) { var ( - blockID = makeBlockIDRandom() - voteSet, valSet, vals = randVoteSet(1, 1, tmproto.PrecommitType, 1, MaxTotalVotingPower) - commit, err = makeCommit(blockID, 1, 1, voteSet, vals, time.Now()) + blockID = test.MakeBlockID() + voteSet, valSet, vals = test.RandVoteSet(1, 1, tmproto.PrecommitType, 1, consensus.MaxTotalVotingPower) + commit, err = test.MakeCommit(blockID, 1, 1, voteSet, vals, time.Now()) ) require.NoError(t, err) diff --git a/pkg/block/validator.go b/pkg/consensus/validator.go similarity index 99% rename from pkg/block/validator.go rename to pkg/consensus/validator.go index 6a5539a9c..d6e465bc4 100644 --- a/pkg/block/validator.go +++ b/pkg/consensus/validator.go @@ -1,4 +1,4 @@ -package block +package consensus import ( "bytes" diff --git a/pkg/block/validator_set.go b/pkg/consensus/validator_set.go similarity index 94% rename from pkg/block/validator_set.go rename to pkg/consensus/validator_set.go index c36cf6de5..7ff08094f 100644 --- a/pkg/block/validator_set.go +++ b/pkg/consensus/validator_set.go @@ -1,4 +1,4 @@ -package block +package consensus import ( "bytes" @@ -11,6 +11,7 @@ import ( "github.com/tendermint/tendermint/crypto/merkle" tmmath "github.com/tendermint/tendermint/libs/math" + "github.com/tendermint/tendermint/pkg/meta" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" ) @@ -166,13 +167,13 @@ func (vals *ValidatorSet) RescalePriorities(diffMax int64) { func (vals *ValidatorSet) incrementProposerPriority() *Validator { for _, val := range vals.Validators { // Check for overflow for sum. - newPrio := safeAddClip(val.ProposerPriority, val.VotingPower) + newPrio := tmmath.SafeAddClip(val.ProposerPriority, val.VotingPower) val.ProposerPriority = newPrio } // Decrement the validator with most ProposerPriority. mostest := vals.getValWithMostPriority() // Mind the underflow. - mostest.ProposerPriority = safeSubClip(mostest.ProposerPriority, vals.TotalVotingPower()) + mostest.ProposerPriority = tmmath.SafeSubClip(mostest.ProposerPriority, vals.TotalVotingPower()) return mostest } @@ -229,7 +230,7 @@ func (vals *ValidatorSet) shiftByAvgProposerPriority() { } avgProposerPriority := vals.computeAvgProposerPriority() for _, val := range vals.Validators { - val.ProposerPriority = safeSubClip(val.ProposerPriority, avgProposerPriority) + val.ProposerPriority = tmmath.SafeSubClip(val.ProposerPriority, avgProposerPriority) } } @@ -299,7 +300,7 @@ func (vals *ValidatorSet) updateTotalVotingPower() { sum := int64(0) for _, val := range vals.Validators { // mind overflow - sum = safeAddClip(sum, val.VotingPower) + sum = tmmath.SafeAddClip(sum, val.VotingPower) if sum > MaxTotalVotingPower { panic(fmt.Sprintf( "Total voting power should be guarded to not exceed %v; got: %v", @@ -654,22 +655,22 @@ func (vals *ValidatorSet) UpdateWithChangeSet(changes []*Validator) error { // VerifyCommit verifies +2/3 of the set had signed the given commit and all // other signatures are valid -func (vals *ValidatorSet) VerifyCommit(chainID string, blockID BlockID, - height int64, commit *Commit) error { +func (vals *ValidatorSet) VerifyCommit(chainID string, blockID meta.BlockID, + height int64, commit *meta.Commit) error { return VerifyCommit(chainID, vals, blockID, height, commit) } // LIGHT CLIENT VERIFICATION METHODS // VerifyCommitLight verifies +2/3 of the set had signed the given commit. -func (vals *ValidatorSet) VerifyCommitLight(chainID string, blockID BlockID, - height int64, commit *Commit) error { +func (vals *ValidatorSet) VerifyCommitLight(chainID string, blockID meta.BlockID, + height int64, commit *meta.Commit) error { return VerifyCommitLight(chainID, vals, blockID, height, commit) } // VerifyCommitLightTrusting verifies that trustLevel of the validator set signed // this commit. -func (vals *ValidatorSet) VerifyCommitLightTrusting(chainID string, commit *Commit, trustLevel tmmath.Fraction) error { +func (vals *ValidatorSet) VerifyCommitLightTrusting(chainID string, commit *meta.Commit, trustLevel tmmath.Fraction) error { return VerifyCommitLightTrusting(chainID, vals, commit, trustLevel) } @@ -865,69 +866,3 @@ func ValidatorSetFromExistingValidators(valz []*Validator) (*ValidatorSet, error sort.Sort(ValidatorsByVotingPower(vals.Validators)) return vals, nil } - -//---------------------------------------- - -// safe addition/subtraction/multiplication - -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 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 -} - -func safeMul(a, b int64) (int64, bool) { - if a == 0 || b == 0 { - return 0, false - } - - absOfB := b - if b < 0 { - absOfB = -b - } - - absOfA := a - if a < 0 { - absOfA = -a - } - - if absOfA > math.MaxInt64/absOfB { - return 0, true - } - - return a * b, false -} diff --git a/pkg/consensus/validator_set_benchmark_test.go b/pkg/consensus/validator_set_benchmark_test.go new file mode 100644 index 000000000..155fec644 --- /dev/null +++ b/pkg/consensus/validator_set_benchmark_test.go @@ -0,0 +1,159 @@ +package consensus_test + +import ( + "fmt" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/tendermint/tendermint/crypto/ed25519" + test "github.com/tendermint/tendermint/internal/test/factory" + tmmath "github.com/tendermint/tendermint/libs/math" + "github.com/tendermint/tendermint/pkg/consensus" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" +) + +//------------------------------------- +// Benchmark tests +// +func BenchmarkUpdates(b *testing.B) { + const ( + n = 100 + m = 2000 + ) + // Init with n validators + vs := make([]*consensus.Validator, n) + for j := 0; j < n; j++ { + vs[j] = consensus.NewValidator(ed25519.GenPrivKey().PubKey(), 100) + } + valSet := consensus.NewValidatorSet(vs) + + // Make m new validators + newValList := make([]*consensus.Validator, m) + for j := 0; j < m; j++ { + newValList[j] = consensus.NewValidator(ed25519.GenPrivKey().PubKey(), 1000) + } + b.ResetTimer() + + for i := 0; i < b.N; i++ { + // Add m validators to valSetCopy + valSetCopy := valSet.Copy() + assert.NoError(b, valSetCopy.UpdateWithChangeSet(newValList)) + } +} + +func BenchmarkValidatorSet_VerifyCommit_Ed25519(b *testing.B) { + for _, n := range []int{1, 8, 64, 1024} { + n := n + var ( + chainID = "test_chain_id" + h = int64(3) + blockID = test.MakeBlockID() + ) + b.Run(fmt.Sprintf("valset size %d", n), func(b *testing.B) { + b.ReportAllocs() + // generate n validators + voteSet, valSet, vals := test.RandVoteSet(h, 0, tmproto.PrecommitType, n, int64(n*5)) + // create a commit with n validators + commit, err := test.MakeCommit(blockID, h, 0, voteSet, vals, time.Now()) + require.NoError(b, err) + + for i := 0; i < b.N/n; i++ { + err = valSet.VerifyCommit(chainID, blockID, h, commit) + assert.NoError(b, err) + } + }) + } +} + +func BenchmarkValidatorSet_VerifyCommitLight_Ed25519(b *testing.B) { + for _, n := range []int{1, 8, 64, 1024} { + n := n + var ( + chainID = "test_chain_id" + h = int64(3) + blockID = test.MakeBlockID() + ) + b.Run(fmt.Sprintf("valset size %d", n), func(b *testing.B) { + b.ReportAllocs() + // generate n validators + voteSet, valSet, vals := test.RandVoteSet(h, 0, tmproto.PrecommitType, n, int64(n*5)) + // create a commit with n validators + commit, err := test.MakeCommit(blockID, h, 0, voteSet, vals, time.Now()) + require.NoError(b, err) + + for i := 0; i < b.N/n; i++ { + err = valSet.VerifyCommitLight(chainID, blockID, h, commit) + assert.NoError(b, err) + } + }) + } +} + +func BenchmarkValidatorSet_VerifyCommitLightTrusting_Ed25519(b *testing.B) { + for _, n := range []int{1, 8, 64, 1024} { + n := n + var ( + chainID = "test_chain_id" + h = int64(3) + blockID = test.MakeBlockID() + ) + b.Run(fmt.Sprintf("valset size %d", n), func(b *testing.B) { + b.ReportAllocs() + // generate n validators + voteSet, valSet, vals := test.RandVoteSet(h, 0, tmproto.PrecommitType, n, int64(n*5)) + // create a commit with n validators + commit, err := test.MakeCommit(blockID, h, 0, voteSet, vals, time.Now()) + require.NoError(b, err) + + for i := 0; i < b.N/n; i++ { + err = valSet.VerifyCommitLightTrusting(chainID, commit, tmmath.Fraction{Numerator: 1, Denominator: 3}) + assert.NoError(b, err) + } + }) + } +} + +func TestValidatorSetProtoBuf(t *testing.T) { + valset, _ := test.RandValidatorPrivValSet(10, 100) + valset2, _ := test.RandValidatorPrivValSet(10, 100) + valset2.Validators[0] = &consensus.Validator{} + + valset3, _ := test.RandValidatorPrivValSet(10, 100) + valset3.Proposer = nil + + valset4, _ := test.RandValidatorPrivValSet(10, 100) + valset4.Proposer = &consensus.Validator{} + + testCases := []struct { + msg string + v1 *consensus.ValidatorSet + expPass1 bool + expPass2 bool + }{ + {"success", valset, true, true}, + {"fail valSet2, pubkey empty", valset2, false, false}, + {"fail nil Proposer", valset3, false, false}, + {"fail empty Proposer", valset4, false, false}, + {"fail empty valSet", &consensus.ValidatorSet{}, true, false}, + {"false nil", nil, true, false}, + } + for _, tc := range testCases { + protoValSet, err := tc.v1.ToProto() + if tc.expPass1 { + require.NoError(t, err, tc.msg) + } else { + require.Error(t, err, tc.msg) + } + + valSet, err := consensus.ValidatorSetFromProto(protoValSet) + if tc.expPass2 { + require.NoError(t, err, tc.msg) + require.EqualValues(t, tc.v1, valSet, tc.msg) + } else { + require.Error(t, err, tc.msg) + } + } +} diff --git a/pkg/block/validator_set_test.go b/pkg/consensus/validator_set_test.go similarity index 87% rename from pkg/block/validator_set_test.go rename to pkg/consensus/validator_set_test.go index bb0ed93d2..2a15d7357 100644 --- a/pkg/block/validator_set_test.go +++ b/pkg/consensus/validator_set_test.go @@ -1,4 +1,4 @@ -package block +package consensus import ( "bytes" @@ -9,14 +9,12 @@ import ( "sort" "strings" "testing" - "testing/quick" - "time" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/ed25519" + tmmath "github.com/tendermint/tendermint/libs/math" tmrand "github.com/tendermint/tendermint/libs/rand" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" @@ -654,29 +652,6 @@ func TestAveragingInIncrementProposerPriorityWithVotingPower(t *testing.T) { } } -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 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 TestEmptySet(t *testing.T) { @@ -771,7 +746,7 @@ func valSetTotalProposerPriority(valSet *ValidatorSet) int64 { sum := int64(0) for _, val := range valSet.Validators { // mind overflow - sum = safeAddClip(sum, val.ProposerPriority) + sum = tmmath.SafeAddClip(sum, val.ProposerPriority) } return sum } @@ -1382,74 +1357,6 @@ func TestValSetUpdateOverflowRelated(t *testing.T) { } } -func TestSafeMul(t *testing.T) { - testCases := []struct { - a int64 - b int64 - c int64 - overflow bool - }{ - 0: {0, 0, 0, false}, - 1: {1, 0, 0, false}, - 2: {2, 3, 6, false}, - 3: {2, -3, -6, false}, - 4: {-2, -3, 6, false}, - 5: {-2, 3, -6, false}, - 6: {math.MaxInt64, 1, math.MaxInt64, false}, - 7: {math.MaxInt64 / 2, 2, math.MaxInt64 - 1, false}, - 8: {math.MaxInt64 / 2, 3, 0, true}, - 9: {math.MaxInt64, 2, 0, true}, - } - - for i, tc := range testCases { - c, overflow := safeMul(tc.a, tc.b) - assert.Equal(t, tc.c, c, "#%d", i) - assert.Equal(t, tc.overflow, overflow, "#%d", i) - } -} - -func TestValidatorSetProtoBuf(t *testing.T) { - valset, _ := randValidatorPrivValSet(10, 100) - valset2, _ := randValidatorPrivValSet(10, 100) - valset2.Validators[0] = &Validator{} - - valset3, _ := randValidatorPrivValSet(10, 100) - valset3.Proposer = nil - - valset4, _ := randValidatorPrivValSet(10, 100) - valset4.Proposer = &Validator{} - - testCases := []struct { - msg string - v1 *ValidatorSet - expPass1 bool - expPass2 bool - }{ - {"success", valset, true, true}, - {"fail valSet2, pubkey empty", valset2, false, false}, - {"fail nil Proposer", valset3, false, false}, - {"fail empty Proposer", valset4, false, false}, - {"fail empty valSet", &ValidatorSet{}, true, false}, - {"false nil", nil, true, false}, - } - for _, tc := range testCases { - protoValSet, err := tc.v1.ToProto() - if tc.expPass1 { - require.NoError(t, err, tc.msg) - } else { - require.Error(t, err, tc.msg) - } - - valSet, err := ValidatorSetFromProto(protoValSet) - if tc.expPass2 { - require.NoError(t, err, tc.msg) - require.EqualValues(t, tc.v1, valSet, tc.msg) - } else { - require.Error(t, err, tc.msg) - } - } -} - //--------------------- // Sort validators by priority and address type validatorsByPriority []*Validator @@ -1490,129 +1397,3 @@ func (tvals testValsByVotingPower) Less(i, j int) bool { func (tvals testValsByVotingPower) Swap(i, j int) { tvals[i], tvals[j] = tvals[j], tvals[i] } - -//------------------------------------- -// Benchmark tests -// -func BenchmarkUpdates(b *testing.B) { - const ( - n = 100 - m = 2000 - ) - // Init with n validators - vs := make([]*Validator, n) - for j := 0; j < n; j++ { - vs[j] = newValidator([]byte(fmt.Sprintf("v%d", j)), 100) - } - valSet := NewValidatorSet(vs) - l := len(valSet.Validators) - - // Make m new validators - newValList := make([]*Validator, m) - for j := 0; j < m; j++ { - newValList[j] = newValidator([]byte(fmt.Sprintf("v%d", j+l)), 1000) - } - b.ResetTimer() - - for i := 0; i < b.N; i++ { - // Add m validators to valSetCopy - valSetCopy := valSet.Copy() - assert.NoError(b, valSetCopy.UpdateWithChangeSet(newValList)) - } -} - -func BenchmarkValidatorSet_VerifyCommit_Ed25519(b *testing.B) { - for _, n := range []int{1, 8, 64, 1024} { - n := n - var ( - chainID = "test_chain_id" - h = int64(3) - blockID = makeBlockIDRandom() - ) - b.Run(fmt.Sprintf("valset size %d", n), func(b *testing.B) { - b.ReportAllocs() - // generate n validators - voteSet, valSet, vals := randVoteSet(h, 0, tmproto.PrecommitType, n, int64(n*5)) - // create a commit with n validators - commit, err := makeCommit(blockID, h, 0, voteSet, vals, time.Now()) - require.NoError(b, err) - - for i := 0; i < b.N/n; i++ { - err = valSet.VerifyCommit(chainID, blockID, h, commit) - assert.NoError(b, err) - } - }) - } -} - -func BenchmarkValidatorSet_VerifyCommitLight_Ed25519(b *testing.B) { - for _, n := range []int{1, 8, 64, 1024} { - n := n - var ( - chainID = "test_chain_id" - h = int64(3) - blockID = makeBlockIDRandom() - ) - b.Run(fmt.Sprintf("valset size %d", n), func(b *testing.B) { - b.ReportAllocs() - // generate n validators - voteSet, valSet, vals := randVoteSet(h, 0, tmproto.PrecommitType, n, int64(n*5)) - // create a commit with n validators - commit, err := makeCommit(blockID, h, 0, voteSet, vals, time.Now()) - require.NoError(b, err) - - for i := 0; i < b.N/n; i++ { - err = valSet.VerifyCommitLight(chainID, blockID, h, commit) - assert.NoError(b, err) - } - }) - } -} - -func BenchmarkValidatorSet_VerifyCommitLightTrusting_Ed25519(b *testing.B) { - for _, n := range []int{1, 8, 64, 1024} { - n := n - var ( - chainID = "test_chain_id" - h = int64(3) - blockID = makeBlockIDRandom() - ) - b.Run(fmt.Sprintf("valset size %d", n), func(b *testing.B) { - b.ReportAllocs() - // generate n validators - voteSet, valSet, vals := randVoteSet(h, 0, tmproto.PrecommitType, n, int64(n*5)) - // create a commit with n validators - commit, err := makeCommit(blockID, h, 0, voteSet, vals, time.Now()) - require.NoError(b, err) - - for i := 0; i < b.N/n; i++ { - err = valSet.VerifyCommitLightTrusting(chainID, commit, tmmath.Fraction{Numerator: 1, Denominator: 3}) - assert.NoError(b, err) - } - }) - } -} - -// Testing Utils - -// deterministicValidatorSet returns a deterministic validator set (size: +numValidators+), -// where each validator has a power of 50 -// -// EXPOSED FOR TESTING. -func deterministicValidatorSet() (*ValidatorSet, []PrivValidator) { - var ( - valz = make([]*Validator, 10) - privValidators = make([]PrivValidator, 10) - ) - - for i := 0; i < 10; i++ { - // val, privValidator := DeterministicValidator(ed25519.PrivKey([]byte(deterministicKeys[i]))) - val, privValidator := deterministicValidator(ed25519.GenPrivKeyFromSecret([]byte(fmt.Sprintf("key: %x", i)))) - valz[i] = val - privValidators[i] = privValidator - } - - sort.Sort(PrivValidatorsByAddress(privValidators)) - - return NewValidatorSet(valz), privValidators -} diff --git a/pkg/block/validator_test.go b/pkg/consensus/validator_test.go similarity index 64% rename from pkg/block/validator_test.go rename to pkg/consensus/validator_test.go index a4bdcab0a..bdcea7ce0 100644 --- a/pkg/block/validator_test.go +++ b/pkg/consensus/validator_test.go @@ -1,26 +1,26 @@ -package block +package consensus_test import ( "context" - "fmt" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/tendermint/tendermint/crypto" + test "github.com/tendermint/tendermint/internal/test/factory" + "github.com/tendermint/tendermint/pkg/consensus" ) func TestValidatorProtoBuf(t *testing.T) { - val, _ := randValidator(true, 100) + val, _ := test.RandValidator(true, 100) testCases := []struct { msg string - v1 *Validator + v1 *consensus.Validator expPass1 bool expPass2 bool }{ {"success validator", val, true, true}, - {"failure empty", &Validator{}, false, false}, + {"failure empty", &consensus.Validator{}, false, false}, {"failure nil", nil, false, false}, } for _, tc := range testCases { @@ -32,7 +32,7 @@ func TestValidatorProtoBuf(t *testing.T) { require.Error(t, err, tc.msg) } - val, err := ValidatorFromProto(protoVal) + val, err := consensus.ValidatorFromProto(protoVal) if tc.expPass2 { require.NoError(t, err, tc.msg) require.Equal(t, tc.v1, val, tc.msg) @@ -43,15 +43,15 @@ func TestValidatorProtoBuf(t *testing.T) { } func TestValidatorValidateBasic(t *testing.T) { - priv := NewMockPV() + priv := consensus.NewMockPV() pubKey, _ := priv.GetPubKey(context.Background()) testCases := []struct { - val *Validator + val *consensus.Validator err bool msg string }{ { - val: NewValidator(pubKey, 1), + val: consensus.NewValidator(pubKey, 1), err: false, msg: "", }, @@ -61,19 +61,19 @@ func TestValidatorValidateBasic(t *testing.T) { msg: "nil validator", }, { - val: &Validator{ + val: &consensus.Validator{ PubKey: nil, }, err: true, msg: "validator does not have a public key", }, { - val: NewValidator(pubKey, -1), + val: consensus.NewValidator(pubKey, -1), err: true, msg: "validator has negative voting power", }, { - val: &Validator{ + val: &consensus.Validator{ PubKey: pubKey, Address: nil, }, @@ -81,7 +81,7 @@ func TestValidatorValidateBasic(t *testing.T) { msg: "validator address is the wrong size: ", }, { - val: &Validator{ + val: &consensus.Validator{ PubKey: pubKey, Address: []byte{'a'}, }, @@ -101,19 +101,3 @@ func TestValidatorValidateBasic(t *testing.T) { } } } - -// Testing util functions - -// deterministicValidator returns a deterministic validator, useful for testing. -// UNSTABLE -func deterministicValidator(key crypto.PrivKey) (*Validator, PrivValidator) { - privVal := NewMockPV() - privVal.PrivKey = key - var votePower int64 = 50 - pubKey, err := privVal.GetPubKey(context.TODO()) - if err != nil { - panic(fmt.Errorf("could not retrieve pubkey %w", err)) - } - val := NewValidator(pubKey, votePower) - return val, privVal -} diff --git a/pkg/block/vote.go b/pkg/consensus/vote.go similarity index 77% rename from pkg/block/vote.go rename to pkg/consensus/vote.go index 165018109..4d9961ebb 100644 --- a/pkg/block/vote.go +++ b/pkg/consensus/vote.go @@ -1,4 +1,4 @@ -package block +package consensus import ( "bytes" @@ -9,6 +9,7 @@ import ( "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/internal/libs/protoio" tmbytes "github.com/tendermint/tendermint/libs/bytes" + "github.com/tendermint/tendermint/pkg/meta" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" ) @@ -51,7 +52,7 @@ type Vote struct { Type tmproto.SignedMsgType `json:"type"` Height int64 `json:"height"` Round int32 `json:"round"` // assume there will not be greater than 2_147_483_647 rounds - BlockID BlockID `json:"block_id"` // zero if vote is nil. + BlockID meta.BlockID `json:"block_id"` // zero if vote is nil. Timestamp time.Time `json:"timestamp"` ValidatorAddress Address `json:"validator_address"` ValidatorIndex int32 `json:"validator_index"` @@ -59,22 +60,22 @@ type Vote struct { } // CommitSig converts the Vote to a CommitSig. -func (vote *Vote) CommitSig() CommitSig { +func (vote *Vote) CommitSig() meta.CommitSig { if vote == nil { - return NewCommitSigAbsent() + return meta.NewCommitSigAbsent() } - var blockIDFlag BlockIDFlag + var blockIDFlag meta.BlockIDFlag switch { case vote.BlockID.IsComplete(): - blockIDFlag = BlockIDFlagCommit + blockIDFlag = meta.BlockIDFlagCommit case vote.BlockID.IsZero(): - blockIDFlag = BlockIDFlagNil + blockIDFlag = meta.BlockIDFlagNil default: panic(fmt.Sprintf("Invalid vote %v - expected BlockID to be either empty or complete", vote)) } - return CommitSig{ + return meta.CommitSig{ BlockIDFlag: blockIDFlag, ValidatorAddress: vote.ValidatorAddress, Timestamp: vote.Timestamp, @@ -82,6 +83,23 @@ func (vote *Vote) CommitSig() CommitSig { } } +// GetVote converts the CommitSig for the given valIdx to a Vote. +// Returns nil if the precommit at valIdx is nil. +// Panics if valIdx >= commit.Size(). +func GetVoteFromCommit(commit *meta.Commit, valIdx int32) *Vote { + commitSig := commit.Signatures[valIdx] + return &Vote{ + Type: tmproto.PrecommitType, + Height: commit.Height, + Round: commit.Round, + BlockID: commitSig.BlockID(commit.BlockID), + Timestamp: commitSig.Timestamp, + ValidatorAddress: commitSig.ValidatorAddress, + ValidatorIndex: valIdx, + Signature: commitSig.Signature, + } +} + // VoteSignBytes returns the proto-encoding of the canonicalized Vote, for // signing. Panics is the marshaling fails. // @@ -105,6 +123,20 @@ func (vote *Vote) Copy() *Vote { return &voteCopy } +// VoteSignBytes returns the bytes of the Vote corresponding to valIdx for +// signing. +// +// The only unique part is the Timestamp - all other fields signed over are +// otherwise the same for all validators. +// +// Panics if valIdx >= commit.Size(). +// +// See VoteSignBytes +func VoteSignBytesFromCommit(commit *meta.Commit, chainID string, valIdx int32) []byte { + v := GetVoteFromCommit(commit, valIdx).ToProto() + return VoteSignBytes(chainID, v) +} + // String returns a string representation of Vote. // // 1. validator index @@ -140,7 +172,7 @@ func (vote *Vote) String() string { typeString, tmbytes.Fingerprint(vote.BlockID.Hash), tmbytes.Fingerprint(vote.Signature), - CanonicalTime(vote.Timestamp), + meta.CanonicalTime(vote.Timestamp), ) } @@ -194,8 +226,8 @@ func (vote *Vote) ValidateBasic() error { return errors.New("signature is missing") } - if len(vote.Signature) > MaxSignatureSize { - return fmt.Errorf("signature is too big (max: %d)", MaxSignatureSize) + if len(vote.Signature) > meta.MaxSignatureSize { + return fmt.Errorf("signature is too big (max: %d)", meta.MaxSignatureSize) } return nil @@ -227,7 +259,7 @@ func VoteFromProto(pv *tmproto.Vote) (*Vote, error) { return nil, errors.New("nil vote") } - blockID, err := BlockIDFromProto(&pv.BlockID) + blockID, err := meta.BlockIDFromProto(&pv.BlockID) if err != nil { return nil, err } @@ -244,3 +276,13 @@ func VoteFromProto(pv *tmproto.Vote) (*Vote, error) { return vote, vote.ValidateBasic() } + +// IsVoteTypeValid returns true if t is a valid vote type. +func IsVoteTypeValid(t tmproto.SignedMsgType) bool { + switch t { + case tmproto.PrevoteType, tmproto.PrecommitType: + return true + default: + return false + } +} diff --git a/pkg/block/vote_set.go b/pkg/consensus/vote_set.go similarity index 91% rename from pkg/block/vote_set.go rename to pkg/consensus/vote_set.go index 5f8216334..cd5f19783 100644 --- a/pkg/block/vote_set.go +++ b/pkg/consensus/vote_set.go @@ -1,4 +1,4 @@ -package block +package consensus import ( "bytes" @@ -8,6 +8,7 @@ import ( tmsync "github.com/tendermint/tendermint/internal/libs/sync" "github.com/tendermint/tendermint/libs/bits" tmjson "github.com/tendermint/tendermint/libs/json" + "github.com/tendermint/tendermint/pkg/meta" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" ) @@ -41,10 +42,10 @@ type P2PID string 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 + `.votesByBlock` keeps track of a list of votes for a particular meta. 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. + 1. the first vote seen by a validator was for the particular meta. + 2. a peer claims to have seen 2/3 majority for the particular meta. Since the first vote from a validator will always get added in `.votesByBlock` , all votes in `.votes` will have a corresponding entry in `.votesByBlock`. @@ -69,9 +70,9 @@ type VoteSet struct { votesBitArray *bits.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 + maj23 *meta.BlockID // First 2/3 majority seen votesByBlock map[string]*blockVotes // string(blockHash|blockParts) -> blockVotes - peerMaj23s map[P2PID]BlockID // Maj23 for each peer + peerMaj23s map[P2PID]meta.BlockID // Maj23 for each peer } // Constructs a new VoteSet struct used to accumulate votes for given height/round. @@ -91,10 +92,27 @@ func NewVoteSet(chainID string, height int64, round int32, sum: 0, maj23: nil, votesByBlock: make(map[string]*blockVotes, valSet.Size()), - peerMaj23s: make(map[P2PID]BlockID), + peerMaj23s: make(map[P2PID]meta.BlockID), } } +// CommitToVoteSet constructs a VoteSet from the Commit and validator set. +// Panics if signatures from the commit can't be added to the voteset. +// Inverse of VoteSet.MakeCommit(). +func VoteSetFromCommit(chainID string, commit *meta.Commit, vals *ValidatorSet) *VoteSet { + voteSet := NewVoteSet(chainID, commit.Height, commit.Round, tmproto.PrecommitType, vals) + for idx, commitSig := range commit.Signatures { + if commitSig.Absent() { + continue // OK, some precommits can be missing. + } + added, err := voteSet.AddVote(GetVoteFromCommit(commit, int32(idx))) + if !added || err != nil { + panic(fmt.Sprintf("Failed to reconstruct LastCommit: %v", err)) + } + } + return voteSet +} + func (voteSet *VoteSet) ChainID() string { return voteSet.chainID } @@ -306,7 +324,7 @@ func (voteSet *VoteSet) addVerifiedVote( // 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 { +func (voteSet *VoteSet) SetPeerMaj23(peerID P2PID, blockID meta.BlockID) error { if voteSet == nil { panic("SetPeerMaj23() on nil VoteSet") } @@ -351,7 +369,7 @@ func (voteSet *VoteSet) BitArray() *bits.BitArray { return voteSet.votesBitArray.Copy() } -func (voteSet *VoteSet) BitArrayByBlockID(blockID BlockID) *bits.BitArray { +func (voteSet *VoteSet) BitArrayByBlockID(blockID meta.BlockID) *bits.BitArray { if voteSet == nil { return nil } @@ -427,16 +445,16 @@ func (voteSet *VoteSet) HasAll() bool { // 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) { +func (voteSet *VoteSet) TwoThirdsMajority() (blockID meta.BlockID, ok bool) { if voteSet == nil { - return BlockID{}, false + return meta.BlockID{}, false } voteSet.mtx.Lock() defer voteSet.mtx.Unlock() if voteSet.maj23 != nil { return *voteSet.maj23, true } - return BlockID{}, false + return meta.BlockID{}, false } //-------------------------------------------------------------------------------- @@ -503,9 +521,9 @@ func (voteSet *VoteSet) MarshalJSON() ([]byte, error) { // NOTE: insufficient for unmarshaling 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"` + Votes []string `json:"votes"` + VotesBitArray string `json:"votes_bit_array"` + PeerMaj23s map[P2PID]meta.BlockID `json:"peer_maj_23s"` } // Return the bit-array of votes including @@ -589,8 +607,8 @@ func (voteSet *VoteSet) sumTotalFrac() (int64, int64, float64) { // for the block, which has 2/3+ majority, and nil. // // Panics if the vote type is not PrecommitType or if there's no +2/3 votes for -// a single block. -func (voteSet *VoteSet) MakeCommit() *Commit { +// a single meta. +func (voteSet *VoteSet) MakeCommit() *meta.Commit { if voteSet.signedMsgType != tmproto.PrecommitType { panic("Cannot MakeCommit() unless VoteSet.Type is PrecommitType") } @@ -603,17 +621,17 @@ func (voteSet *VoteSet) MakeCommit() *Commit { } // For every validator, get the precommit - commitSigs := make([]CommitSig, len(voteSet.votes)) + commitSigs := make([]meta.CommitSig, len(voteSet.votes)) for i, v := range voteSet.votes { commitSig := v.CommitSig() // if block ID exists but doesn't match, exclude sig if commitSig.ForBlock() && !v.BlockID.Equals(*voteSet.maj23) { - commitSig = NewCommitSigAbsent() + commitSig = meta.NewCommitSigAbsent() } commitSigs[i] = commitSig } - return NewCommit(voteSet.GetHeight(), voteSet.GetRound(), *voteSet.maj23, commitSigs) + return meta.NewCommit(voteSet.GetHeight(), voteSet.GetRound(), *voteSet.maj23, commitSigs) } //-------------------------------------------------------------------------------- diff --git a/pkg/block/vote_set_test.go b/pkg/consensus/vote_set_test.go similarity index 63% rename from pkg/block/vote_set_test.go rename to pkg/consensus/vote_set_test.go index 8f32aea55..f7566ba76 100644 --- a/pkg/block/vote_set_test.go +++ b/pkg/consensus/vote_set_test.go @@ -1,23 +1,26 @@ -package block +package consensus_test import ( "bytes" "context" - "sort" "testing" + "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/crypto" + test "github.com/tendermint/tendermint/internal/test/factory" tmrand "github.com/tendermint/tendermint/libs/rand" tmtime "github.com/tendermint/tendermint/libs/time" + "github.com/tendermint/tendermint/pkg/consensus" + "github.com/tendermint/tendermint/pkg/meta" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" ) func TestVoteSet_AddVote_Good(t *testing.T) { height, round := int64(1), int32(0) - voteSet, _, privValidators := randVoteSet(height, round, tmproto.PrevoteType, 10, 1) + voteSet, _, privValidators := test.RandVoteSet(height, round, tmproto.PrevoteType, 10, 1) val0 := privValidators[0] val0p, err := val0.GetPubKey(context.Background()) @@ -29,16 +32,16 @@ func TestVoteSet_AddVote_Good(t *testing.T) { blockID, ok := voteSet.TwoThirdsMajority() assert.False(t, ok || !blockID.IsZero(), "there should be no 2/3 majority") - vote := &Vote{ + vote := &consensus.Vote{ ValidatorAddress: val0Addr, ValidatorIndex: 0, // since privValidators are in order Height: height, Round: round, Type: tmproto.PrevoteType, Timestamp: tmtime.Now(), - BlockID: BlockID{nil, PartSetHeader{}}, + BlockID: meta.BlockID{nil, meta.PartSetHeader{}}, } - _, err = signAddVote(val0, vote, voteSet) + _, err = test.SignAddVote(val0, vote, voteSet) require.NoError(t, err) assert.NotNil(t, voteSet.GetByAddress(val0Addr)) @@ -49,16 +52,16 @@ func TestVoteSet_AddVote_Good(t *testing.T) { func TestVoteSet_AddVote_Bad(t *testing.T) { height, round := int64(1), int32(0) - voteSet, _, privValidators := randVoteSet(height, round, tmproto.PrevoteType, 10, 1) + voteSet, _, privValidators := test.RandVoteSet(height, round, tmproto.PrevoteType, 10, 1) - voteProto := &Vote{ + voteProto := &consensus.Vote{ ValidatorAddress: nil, ValidatorIndex: -1, Height: height, Round: round, Timestamp: tmtime.Now(), Type: tmproto.PrevoteType, - BlockID: BlockID{nil, PartSetHeader{}}, + BlockID: meta.BlockID{nil, meta.PartSetHeader{}}, } // val0 votes for nil. @@ -67,19 +70,19 @@ func TestVoteSet_AddVote_Bad(t *testing.T) { require.NoError(t, err) addr := pubKey.Address() vote := withValidator(voteProto, addr, 0) - added, err := signAddVote(privValidators[0], vote, voteSet) + added, err := test.SignAddVote(privValidators[0], vote, voteSet) if !added || err != nil { t.Errorf("expected VoteSet.Add to succeed") } } - // val0 votes again for some block. + // val0 votes again for some meta. { pubKey, err := privValidators[0].GetPubKey(context.Background()) require.NoError(t, err) addr := pubKey.Address() vote := withValidator(voteProto, addr, 0) - added, err := signAddVote(privValidators[0], withBlockHash(vote, tmrand.Bytes(32)), voteSet) + added, err := test.SignAddVote(privValidators[0], withBlockHash(vote, tmrand.Bytes(32)), voteSet) if added || err == nil { t.Errorf("expected VoteSet.Add to fail, conflicting vote.") } @@ -91,7 +94,7 @@ func TestVoteSet_AddVote_Bad(t *testing.T) { require.NoError(t, err) addr := pubKey.Address() vote := withValidator(voteProto, addr, 1) - added, err := signAddVote(privValidators[1], withHeight(vote, height+1), voteSet) + added, err := test.SignAddVote(privValidators[1], withHeight(vote, height+1), voteSet) if added || err == nil { t.Errorf("expected VoteSet.Add to fail, wrong height") } @@ -103,7 +106,7 @@ func TestVoteSet_AddVote_Bad(t *testing.T) { require.NoError(t, err) addr := pubKey.Address() vote := withValidator(voteProto, addr, 2) - added, err := signAddVote(privValidators[2], withRound(vote, round+1), voteSet) + added, err := test.SignAddVote(privValidators[2], withRound(vote, round+1), voteSet) if added || err == nil { t.Errorf("expected VoteSet.Add to fail, wrong round") } @@ -115,7 +118,7 @@ func TestVoteSet_AddVote_Bad(t *testing.T) { require.NoError(t, err) addr := pubKey.Address() vote := withValidator(voteProto, addr, 3) - added, err := signAddVote(privValidators[3], withType(vote, byte(tmproto.PrecommitType)), voteSet) + added, err := test.SignAddVote(privValidators[3], withType(vote, byte(tmproto.PrecommitType)), voteSet) if added || err == nil { t.Errorf("expected VoteSet.Add to fail, wrong type") } @@ -124,16 +127,16 @@ func TestVoteSet_AddVote_Bad(t *testing.T) { func TestVoteSet_2_3Majority(t *testing.T) { height, round := int64(1), int32(0) - voteSet, _, privValidators := randVoteSet(height, round, tmproto.PrevoteType, 10, 1) + voteSet, _, privValidators := test.RandVoteSet(height, round, tmproto.PrevoteType, 10, 1) - voteProto := &Vote{ + voteProto := &consensus.Vote{ ValidatorAddress: nil, // NOTE: must fill in ValidatorIndex: -1, // NOTE: must fill in Height: height, Round: round, Type: tmproto.PrevoteType, Timestamp: tmtime.Now(), - BlockID: BlockID{nil, PartSetHeader{}}, + BlockID: meta.BlockID{nil, meta.PartSetHeader{}}, } // 6 out of 10 voted for nil. for i := int32(0); i < 6; i++ { @@ -141,7 +144,7 @@ func TestVoteSet_2_3Majority(t *testing.T) { require.NoError(t, err) addr := pubKey.Address() vote := withValidator(voteProto, addr, i) - _, err = signAddVote(privValidators[i], vote, voteSet) + _, err = test.SignAddVote(privValidators[i], vote, voteSet) require.NoError(t, err) } blockID, ok := voteSet.TwoThirdsMajority() @@ -153,7 +156,7 @@ func TestVoteSet_2_3Majority(t *testing.T) { require.NoError(t, err) addr := pubKey.Address() vote := withValidator(voteProto, addr, 6) - _, err = signAddVote(privValidators[6], withBlockHash(vote, tmrand.Bytes(32)), voteSet) + _, err = test.SignAddVote(privValidators[6], withBlockHash(vote, tmrand.Bytes(32)), voteSet) require.NoError(t, err) blockID, ok = voteSet.TwoThirdsMajority() assert.False(t, ok || !blockID.IsZero(), "there should be no 2/3 majority") @@ -165,7 +168,7 @@ func TestVoteSet_2_3Majority(t *testing.T) { require.NoError(t, err) addr := pubKey.Address() vote := withValidator(voteProto, addr, 7) - _, err = signAddVote(privValidators[7], vote, voteSet) + _, err = test.SignAddVote(privValidators[7], vote, voteSet) require.NoError(t, err) blockID, ok = voteSet.TwoThirdsMajority() assert.True(t, ok || blockID.IsZero(), "there should be 2/3 majority for nil") @@ -174,20 +177,20 @@ func TestVoteSet_2_3Majority(t *testing.T) { func TestVoteSet_2_3MajorityRedux(t *testing.T) { height, round := int64(1), int32(0) - voteSet, _, privValidators := randVoteSet(height, round, tmproto.PrevoteType, 100, 1) + voteSet, _, privValidators := test.RandVoteSet(height, round, tmproto.PrevoteType, 100, 1) blockHash := crypto.CRandBytes(32) blockPartsTotal := uint32(123) - blockPartSetHeader := PartSetHeader{blockPartsTotal, crypto.CRandBytes(32)} + blockPartSetHeader := meta.PartSetHeader{blockPartsTotal, crypto.CRandBytes(32)} - voteProto := &Vote{ + voteProto := &consensus.Vote{ ValidatorAddress: nil, // NOTE: must fill in ValidatorIndex: -1, // NOTE: must fill in Height: height, Round: round, Timestamp: tmtime.Now(), Type: tmproto.PrevoteType, - BlockID: BlockID{blockHash, blockPartSetHeader}, + BlockID: meta.BlockID{blockHash, blockPartSetHeader}, } // 66 out of 100 voted for nil. @@ -196,7 +199,7 @@ func TestVoteSet_2_3MajorityRedux(t *testing.T) { require.NoError(t, err) addr := pubKey.Address() vote := withValidator(voteProto, addr, i) - _, err = signAddVote(privValidators[i], vote, voteSet) + _, err = test.SignAddVote(privValidators[i], vote, voteSet) require.NoError(t, err) } blockID, ok := voteSet.TwoThirdsMajority() @@ -209,7 +212,7 @@ func TestVoteSet_2_3MajorityRedux(t *testing.T) { require.NoError(t, err) adrr := pubKey.Address() vote := withValidator(voteProto, adrr, 66) - _, err = signAddVote(privValidators[66], withBlockHash(vote, nil), voteSet) + _, err = test.SignAddVote(privValidators[66], withBlockHash(vote, nil), voteSet) require.NoError(t, err) blockID, ok = voteSet.TwoThirdsMajority() assert.False(t, ok || !blockID.IsZero(), @@ -222,8 +225,8 @@ func TestVoteSet_2_3MajorityRedux(t *testing.T) { require.NoError(t, err) addr := pubKey.Address() vote := withValidator(voteProto, addr, 67) - blockPartsHeader := PartSetHeader{blockPartsTotal, crypto.CRandBytes(32)} - _, err = signAddVote(privValidators[67], withBlockPartSetHeader(vote, blockPartsHeader), voteSet) + blockPartsHeader := meta.PartSetHeader{blockPartsTotal, crypto.CRandBytes(32)} + _, err = test.SignAddVote(privValidators[67], withBlockPartSetHeader(vote, blockPartsHeader), voteSet) require.NoError(t, err) blockID, ok = voteSet.TwoThirdsMajority() assert.False(t, ok || !blockID.IsZero(), @@ -236,8 +239,8 @@ func TestVoteSet_2_3MajorityRedux(t *testing.T) { require.NoError(t, err) addr := pubKey.Address() vote := withValidator(voteProto, addr, 68) - blockPartsHeader := PartSetHeader{blockPartsTotal + 1, blockPartSetHeader.Hash} - _, err = signAddVote(privValidators[68], withBlockPartSetHeader(vote, blockPartsHeader), voteSet) + blockPartsHeader := meta.PartSetHeader{blockPartsTotal + 1, blockPartSetHeader.Hash} + _, err = test.SignAddVote(privValidators[68], withBlockPartSetHeader(vote, blockPartsHeader), voteSet) require.NoError(t, err) blockID, ok = voteSet.TwoThirdsMajority() assert.False(t, ok || !blockID.IsZero(), @@ -250,7 +253,7 @@ func TestVoteSet_2_3MajorityRedux(t *testing.T) { require.NoError(t, err) addr := pubKey.Address() vote := withValidator(voteProto, addr, 69) - _, err = signAddVote(privValidators[69], withBlockHash(vote, tmrand.Bytes(32)), voteSet) + _, err = test.SignAddVote(privValidators[69], withBlockHash(vote, tmrand.Bytes(32)), voteSet) require.NoError(t, err) blockID, ok = voteSet.TwoThirdsMajority() assert.False(t, ok || !blockID.IsZero(), @@ -263,28 +266,28 @@ func TestVoteSet_2_3MajorityRedux(t *testing.T) { require.NoError(t, err) addr := pubKey.Address() vote := withValidator(voteProto, addr, 70) - _, err = signAddVote(privValidators[70], vote, voteSet) + _, err = test.SignAddVote(privValidators[70], vote, voteSet) require.NoError(t, err) blockID, ok = voteSet.TwoThirdsMajority() - assert.True(t, ok && blockID.Equals(BlockID{blockHash, blockPartSetHeader}), + assert.True(t, ok && blockID.Equals(meta.BlockID{blockHash, blockPartSetHeader}), "there should be 2/3 majority") } } func TestVoteSet_Conflicts(t *testing.T) { height, round := int64(1), int32(0) - voteSet, _, privValidators := randVoteSet(height, round, tmproto.PrevoteType, 4, 1) + voteSet, _, privValidators := test.RandVoteSet(height, round, tmproto.PrevoteType, 4, 1) blockHash1 := tmrand.Bytes(32) blockHash2 := tmrand.Bytes(32) - voteProto := &Vote{ + voteProto := &consensus.Vote{ ValidatorAddress: nil, ValidatorIndex: -1, Height: height, Round: round, Timestamp: tmtime.Now(), Type: tmproto.PrevoteType, - BlockID: BlockID{nil, PartSetHeader{}}, + BlockID: meta.BlockID{nil, meta.PartSetHeader{}}, } val0, err := privValidators[0].GetPubKey(context.Background()) @@ -294,7 +297,7 @@ func TestVoteSet_Conflicts(t *testing.T) { // val0 votes for nil. { vote := withValidator(voteProto, val0Addr, 0) - added, err := signAddVote(privValidators[0], vote, voteSet) + added, err := test.SignAddVote(privValidators[0], vote, voteSet) if !added || err != nil { t.Errorf("expected VoteSet.Add to succeed") } @@ -303,31 +306,31 @@ func TestVoteSet_Conflicts(t *testing.T) { // val0 votes again for blockHash1. { vote := withValidator(voteProto, val0Addr, 0) - added, err := signAddVote(privValidators[0], withBlockHash(vote, blockHash1), voteSet) + added, err := test.SignAddVote(privValidators[0], withBlockHash(vote, blockHash1), voteSet) assert.False(t, added, "conflicting vote") assert.Error(t, err, "conflicting vote") } // start tracking blockHash1 - err = voteSet.SetPeerMaj23("peerA", BlockID{blockHash1, PartSetHeader{}}) + err = voteSet.SetPeerMaj23("peerA", meta.BlockID{blockHash1, meta.PartSetHeader{}}) require.NoError(t, err) // val0 votes again for blockHash1. { vote := withValidator(voteProto, val0Addr, 0) - added, err := signAddVote(privValidators[0], withBlockHash(vote, blockHash1), voteSet) + added, err := test.SignAddVote(privValidators[0], withBlockHash(vote, blockHash1), voteSet) assert.True(t, added, "called SetPeerMaj23()") assert.Error(t, err, "conflicting vote") } // attempt tracking blockHash2, should fail because already set for peerA. - err = voteSet.SetPeerMaj23("peerA", BlockID{blockHash2, PartSetHeader{}}) + err = voteSet.SetPeerMaj23("peerA", meta.BlockID{blockHash2, meta.PartSetHeader{}}) require.Error(t, err) // val0 votes again for blockHash1. { vote := withValidator(voteProto, val0Addr, 0) - added, err := signAddVote(privValidators[0], withBlockHash(vote, blockHash2), voteSet) + added, err := test.SignAddVote(privValidators[0], withBlockHash(vote, blockHash2), voteSet) assert.False(t, added, "duplicate SetPeerMaj23() from peerA") assert.Error(t, err, "conflicting vote") } @@ -338,7 +341,7 @@ func TestVoteSet_Conflicts(t *testing.T) { assert.NoError(t, err) addr := pv.Address() vote := withValidator(voteProto, addr, 1) - added, err := signAddVote(privValidators[1], withBlockHash(vote, blockHash1), voteSet) + added, err := test.SignAddVote(privValidators[1], withBlockHash(vote, blockHash1), voteSet) if !added || err != nil { t.Errorf("expected VoteSet.Add to succeed") } @@ -358,7 +361,7 @@ func TestVoteSet_Conflicts(t *testing.T) { assert.NoError(t, err) addr := pv.Address() vote := withValidator(voteProto, addr, 2) - added, err := signAddVote(privValidators[2], withBlockHash(vote, blockHash2), voteSet) + added, err := test.SignAddVote(privValidators[2], withBlockHash(vote, blockHash2), voteSet) if !added || err != nil { t.Errorf("expected VoteSet.Add to succeed") } @@ -373,7 +376,7 @@ func TestVoteSet_Conflicts(t *testing.T) { } // now attempt tracking blockHash1 - err = voteSet.SetPeerMaj23("peerB", BlockID{blockHash1, PartSetHeader{}}) + err = voteSet.SetPeerMaj23("peerB", meta.BlockID{blockHash1, meta.PartSetHeader{}}) require.NoError(t, err) // val2 votes for blockHash1. @@ -382,7 +385,7 @@ func TestVoteSet_Conflicts(t *testing.T) { assert.NoError(t, err) addr := pv.Address() vote := withValidator(voteProto, addr, 2) - added, err := signAddVote(privValidators[2], withBlockHash(vote, blockHash1), voteSet) + added, err := test.SignAddVote(privValidators[2], withBlockHash(vote, blockHash1), voteSet) assert.True(t, added) assert.Error(t, err, "conflicting vote") } @@ -402,26 +405,26 @@ func TestVoteSet_Conflicts(t *testing.T) { func TestVoteSet_MakeCommit(t *testing.T) { height, round := int64(1), int32(0) - voteSet, _, privValidators := randVoteSet(height, round, tmproto.PrecommitType, 10, 1) - blockHash, blockPartSetHeader := crypto.CRandBytes(32), PartSetHeader{123, crypto.CRandBytes(32)} + voteSet, _, privValidators := test.RandVoteSet(height, round, tmproto.PrecommitType, 10, 1) + blockHash, blockPartSetHeader := crypto.CRandBytes(32), meta.PartSetHeader{123, crypto.CRandBytes(32)} - voteProto := &Vote{ + voteProto := &consensus.Vote{ ValidatorAddress: nil, ValidatorIndex: -1, Height: height, Round: round, Timestamp: tmtime.Now(), Type: tmproto.PrecommitType, - BlockID: BlockID{blockHash, blockPartSetHeader}, + BlockID: meta.BlockID{blockHash, blockPartSetHeader}, } - // 6 out of 10 voted for some block. + // 6 out of 10 voted for some meta. for i := int32(0); i < 6; i++ { pv, err := privValidators[i].GetPubKey(context.Background()) assert.NoError(t, err) addr := pv.Address() vote := withValidator(voteProto, addr, i) - _, err = signAddVote(privValidators[i], vote, voteSet) + _, err = test.SignAddVote(privValidators[i], vote, voteSet) if err != nil { t.Error(err) } @@ -430,16 +433,16 @@ func TestVoteSet_MakeCommit(t *testing.T) { // MakeCommit should fail. assert.Panics(t, func() { voteSet.MakeCommit() }, "Doesn't have +2/3 majority") - // 7th voted for some other block. + // 7th voted for some other meta. { pv, err := privValidators[6].GetPubKey(context.Background()) assert.NoError(t, err) addr := pv.Address() vote := withValidator(voteProto, addr, 6) vote = withBlockHash(vote, tmrand.Bytes(32)) - vote = withBlockPartSetHeader(vote, PartSetHeader{123, tmrand.Bytes(32)}) + vote = withBlockPartSetHeader(vote, meta.PartSetHeader{123, tmrand.Bytes(32)}) - _, err = signAddVote(privValidators[6], vote, voteSet) + _, err = test.SignAddVote(privValidators[6], vote, voteSet) require.NoError(t, err) } @@ -449,7 +452,7 @@ func TestVoteSet_MakeCommit(t *testing.T) { assert.NoError(t, err) addr := pv.Address() vote := withValidator(voteProto, addr, 7) - _, err = signAddVote(privValidators[7], vote, voteSet) + _, err = test.SignAddVote(privValidators[7], vote, voteSet) require.NoError(t, err) } @@ -459,9 +462,9 @@ func TestVoteSet_MakeCommit(t *testing.T) { assert.NoError(t, err) addr := pv.Address() vote := withValidator(voteProto, addr, 8) - vote.BlockID = BlockID{} + vote.BlockID = meta.BlockID{} - _, err = signAddVote(privValidators[8], vote, voteSet) + _, err = test.SignAddVote(privValidators[8], vote, voteSet) require.NoError(t, err) } @@ -476,47 +479,91 @@ func TestVoteSet_MakeCommit(t *testing.T) { } } -// NOTE: privValidators are in order -func randVoteSet( - height int64, - round int32, - signedMsgType tmproto.SignedMsgType, - numValidators int, - votingPower int64, -) (*VoteSet, *ValidatorSet, []PrivValidator) { - valSet, privValidators := randValidatorPrivValSet(numValidators, votingPower) - return NewVoteSet("test_chain_id", height, round, signedMsgType, valSet), valSet, privValidators +func TestCommitToVoteSet(t *testing.T) { + lastID := test.MakeBlockID() + h := int64(3) + + voteSet, valSet, vals := test.RandVoteSet(h-1, 1, tmproto.PrecommitType, 10, 1) + commit, err := test.MakeCommit(lastID, h-1, 1, voteSet, vals, time.Now()) + assert.NoError(t, err) + + chainID := voteSet.ChainID() + voteSet2 := consensus.VoteSetFromCommit(chainID, commit, valSet) + + for i := int32(0); int(i) < len(vals); i++ { + vote1 := voteSet.GetByIndex(i) + vote2 := voteSet2.GetByIndex(i) + vote3 := consensus.GetVoteFromCommit(commit, i) + + vote1bz, err := vote1.ToProto().Marshal() + require.NoError(t, err) + vote2bz, err := vote2.ToProto().Marshal() + require.NoError(t, err) + vote3bz, err := vote3.ToProto().Marshal() + require.NoError(t, err) + assert.Equal(t, vote1bz, vote2bz) + assert.Equal(t, vote1bz, vote3bz) + } } -func deterministicVoteSet( - height int64, - round int32, - signedMsgType tmproto.SignedMsgType, - votingPower int64, -) (*VoteSet, *ValidatorSet, []PrivValidator) { - valSet, privValidators := deterministicValidatorSet() - return NewVoteSet("test_chain_id", height, round, signedMsgType, valSet), valSet, privValidators -} +func TestCommitToVoteSetWithVotesForNilBlock(t *testing.T) { + blockID := test.MakeBlockIDWithHash([]byte("blockhash")) -func randValidatorPrivValSet(numValidators int, votingPower int64) (*ValidatorSet, []PrivValidator) { - var ( - valz = make([]*Validator, numValidators) - privValidators = make([]PrivValidator, numValidators) + const ( + height = int64(3) + round = 0 ) - for i := 0; i < numValidators; i++ { - val, privValidator := randValidator(false, votingPower) - valz[i] = val - privValidators[i] = privValidator + type commitVoteTest struct { + blockIDs []meta.BlockID + numVotes []int // must sum to numValidators + numValidators int + valid bool } - sort.Sort(PrivValidatorsByAddress(privValidators)) + testCases := []commitVoteTest{ + {[]meta.BlockID{blockID, {}}, []int{67, 33}, 100, true}, + } - return NewValidatorSet(valz), privValidators + for _, tc := range testCases { + voteSet, valSet, vals := test.RandVoteSet(height-1, round, tmproto.PrecommitType, tc.numValidators, 1) + + vi := int32(0) + for n := range tc.blockIDs { + for i := 0; i < tc.numVotes[n]; i++ { + pubKey, err := vals[vi].GetPubKey(context.Background()) + require.NoError(t, err) + vote := &consensus.Vote{ + ValidatorAddress: pubKey.Address(), + ValidatorIndex: vi, + Height: height - 1, + Round: round, + Type: tmproto.PrecommitType, + BlockID: tc.blockIDs[n], + Timestamp: tmtime.Now(), + } + + added, err := test.SignAddVote(vals[vi], vote, voteSet) + assert.NoError(t, err) + assert.True(t, added) + + vi++ + } + } + + if tc.valid { + commit := voteSet.MakeCommit() // panics without > 2/3 valid votes + assert.NotNil(t, commit) + err := valSet.VerifyCommit(voteSet.ChainID(), blockID, height-1, commit) + assert.Nil(t, err) + } else { + assert.Panics(t, func() { voteSet.MakeCommit() }) + } + } } // Convenience: Return new vote with different validator address/index -func withValidator(vote *Vote, addr []byte, idx int32) *Vote { +func withValidator(vote *consensus.Vote, addr []byte, idx int32) *consensus.Vote { vote = vote.Copy() vote.ValidatorAddress = addr vote.ValidatorIndex = idx @@ -524,35 +571,35 @@ func withValidator(vote *Vote, addr []byte, idx int32) *Vote { } // Convenience: Return new vote with different height -func withHeight(vote *Vote, height int64) *Vote { +func withHeight(vote *consensus.Vote, height int64) *consensus.Vote { vote = vote.Copy() vote.Height = height return vote } // Convenience: Return new vote with different round -func withRound(vote *Vote, round int32) *Vote { +func withRound(vote *consensus.Vote, round int32) *consensus.Vote { vote = vote.Copy() vote.Round = round return vote } // Convenience: Return new vote with different type -func withType(vote *Vote, signedMsgType byte) *Vote { +func withType(vote *consensus.Vote, signedMsgType byte) *consensus.Vote { vote = vote.Copy() vote.Type = tmproto.SignedMsgType(signedMsgType) return vote } // Convenience: Return new vote with different blockHash -func withBlockHash(vote *Vote, blockHash []byte) *Vote { +func withBlockHash(vote *consensus.Vote, blockHash []byte) *consensus.Vote { vote = vote.Copy() vote.BlockID.Hash = blockHash return vote } // Convenience: Return new vote with different blockParts -func withBlockPartSetHeader(vote *Vote, blockPartsHeader PartSetHeader) *Vote { +func withBlockPartSetHeader(vote *consensus.Vote, blockPartsHeader meta.PartSetHeader) *consensus.Vote { vote = vote.Copy() vote.BlockID.PartSetHeader = blockPartsHeader return vote diff --git a/pkg/block/vote_test.go b/pkg/consensus/vote_test.go similarity index 96% rename from pkg/block/vote_test.go rename to pkg/consensus/vote_test.go index 1161843ef..75207e43a 100644 --- a/pkg/block/vote_test.go +++ b/pkg/consensus/vote_test.go @@ -1,4 +1,4 @@ -package block +package consensus import ( "context" @@ -13,6 +13,7 @@ import ( "github.com/tendermint/tendermint/crypto/ed25519" "github.com/tendermint/tendermint/crypto/tmhash" "github.com/tendermint/tendermint/internal/libs/protoio" + "github.com/tendermint/tendermint/pkg/meta" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" ) @@ -25,7 +26,7 @@ func examplePrecommit() *Vote { } func exampleVote(t byte) *Vote { - var stamp, err = time.Parse(TimeFormat, "2017-12-25T03:00:01.234Z") + var stamp, err = time.Parse(meta.TimeFormat, "2017-12-25T03:00:01.234Z") if err != nil { panic(err) } @@ -35,9 +36,9 @@ func exampleVote(t byte) *Vote { Height: 12345, Round: 2, Timestamp: stamp, - BlockID: BlockID{ + BlockID: meta.BlockID{ Hash: tmhash.Sum([]byte("blockID_hash")), - PartSetHeader: PartSetHeader{ + PartSetHeader: meta.PartSetHeader{ Total: 1000000, Hash: tmhash.Sum([]byte("blockID_part_set_header_hash")), }, @@ -244,12 +245,12 @@ func TestVoteValidateBasic(t *testing.T) { {"Negative Height", func(v *Vote) { v.Height = -1 }, true}, {"Negative Round", func(v *Vote) { v.Round = -1 }, true}, {"Invalid BlockID", func(v *Vote) { - v.BlockID = BlockID{[]byte{1, 2, 3}, PartSetHeader{111, []byte("blockparts")}} + v.BlockID = meta.BlockID{[]byte{1, 2, 3}, meta.PartSetHeader{111, []byte("blockparts")}} }, true}, {"Invalid Address", func(v *Vote) { v.ValidatorAddress = make([]byte, 1) }, true}, {"Invalid ValidatorIndex", func(v *Vote) { v.ValidatorIndex = -1 }, true}, {"Invalid Signature", func(v *Vote) { v.Signature = nil }, true}, - {"Too big Signature", func(v *Vote) { v.Signature = make([]byte, MaxSignatureSize+1) }, true}, + {"Too big Signature", func(v *Vote) { v.Signature = make([]byte, meta.MaxSignatureSize+1) }, true}, } for _, tc := range testCases { tc := tc diff --git a/pkg/block/event_bus.go b/pkg/events/event_bus.go similarity index 98% rename from pkg/block/event_bus.go rename to pkg/events/event_bus.go index f9867aa9e..7333bec56 100644 --- a/pkg/block/event_bus.go +++ b/pkg/events/event_bus.go @@ -1,4 +1,4 @@ -package block +package events import ( "context" @@ -9,6 +9,7 @@ import ( "github.com/tendermint/tendermint/libs/log" tmpubsub "github.com/tendermint/tendermint/libs/pubsub" "github.com/tendermint/tendermint/libs/service" + "github.com/tendermint/tendermint/pkg/mempool" ) const defaultCapacity = 0 @@ -178,7 +179,7 @@ func (b *EventBus) PublishEventTx(data EventDataTx) error { Attributes: []types.EventAttribute{ { Key: tokens[1], - Value: fmt.Sprintf("%X", Tx(data.Tx).Hash()), + Value: fmt.Sprintf("%X", mempool.Tx(data.Tx).Hash()), }, }, }) diff --git a/pkg/block/event_bus_test.go b/pkg/events/event_bus_test.go similarity index 72% rename from pkg/block/event_bus_test.go rename to pkg/events/event_bus_test.go index 58d7e7dc3..75539e9eb 100644 --- a/pkg/block/event_bus_test.go +++ b/pkg/events/event_bus_test.go @@ -1,4 +1,4 @@ -package block +package events_test import ( "context" @@ -13,10 +13,15 @@ import ( abci "github.com/tendermint/tendermint/abci/types" tmpubsub "github.com/tendermint/tendermint/libs/pubsub" tmquery "github.com/tendermint/tendermint/libs/pubsub/query" + "github.com/tendermint/tendermint/pkg/block" + "github.com/tendermint/tendermint/pkg/events" + "github.com/tendermint/tendermint/pkg/evidence" + "github.com/tendermint/tendermint/pkg/mempool" + "github.com/tendermint/tendermint/pkg/meta" ) func TestEventBusPublishEventTx(t *testing.T) { - eventBus := NewEventBus() + eventBus := events.NewEventBus() err := eventBus.Start() require.NoError(t, err) t.Cleanup(func() { @@ -25,7 +30,7 @@ func TestEventBusPublishEventTx(t *testing.T) { } }) - tx := Tx("foo") + tx := mempool.Tx("foo") result := abci.ResponseDeliverTx{ Data: []byte("bar"), Events: []abci.Event{ @@ -41,7 +46,7 @@ func TestEventBusPublishEventTx(t *testing.T) { done := make(chan struct{}) go func() { msg := <-txsSub.Out() - edt := msg.Data().(EventDataTx) + edt := msg.Data().(events.EventDataTx) assert.Equal(t, int64(1), edt.Height) assert.Equal(t, uint32(0), edt.Index) assert.EqualValues(t, tx, edt.Tx) @@ -49,7 +54,7 @@ func TestEventBusPublishEventTx(t *testing.T) { close(done) }() - err = eventBus.PublishEventTx(EventDataTx{abci.TxResult{ + err = eventBus.PublishEventTx(events.EventDataTx{abci.TxResult{ Height: 1, Index: 0, Tx: tx, @@ -65,7 +70,7 @@ func TestEventBusPublishEventTx(t *testing.T) { } func TestEventBusPublishEventNewBlock(t *testing.T) { - eventBus := NewEventBus() + eventBus := events.NewEventBus() err := eventBus.Start() require.NoError(t, err) t.Cleanup(func() { @@ -74,8 +79,8 @@ func TestEventBusPublishEventNewBlock(t *testing.T) { } }) - block := MakeBlock(0, []Tx{}, nil, []Evidence{}) - blockID := BlockID{Hash: block.Hash(), PartSetHeader: block.MakePartSet(BlockPartSizeBytes).Header()} + block := block.MakeBlock(0, []mempool.Tx{}, nil, []evidence.Evidence{}) + blockID := meta.BlockID{Hash: block.Hash(), PartSetHeader: block.MakePartSet(meta.BlockPartSizeBytes).Header()} resultBeginBlock := abci.ResponseBeginBlock{ Events: []abci.Event{ {Type: "testType", Attributes: []abci.EventAttribute{{Key: "baz", Value: "1"}}}, @@ -95,7 +100,7 @@ func TestEventBusPublishEventNewBlock(t *testing.T) { done := make(chan struct{}) go func() { msg := <-blocksSub.Out() - edt := msg.Data().(EventDataNewBlock) + edt := msg.Data().(events.EventDataNewBlock) assert.Equal(t, block, edt.Block) assert.Equal(t, blockID, edt.BlockID) assert.Equal(t, resultBeginBlock, edt.ResultBeginBlock) @@ -103,7 +108,7 @@ func TestEventBusPublishEventNewBlock(t *testing.T) { close(done) }() - err = eventBus.PublishEventNewBlock(EventDataNewBlock{ + err = eventBus.PublishEventNewBlock(events.EventDataNewBlock{ Block: block, BlockID: blockID, ResultBeginBlock: resultBeginBlock, @@ -119,7 +124,7 @@ func TestEventBusPublishEventNewBlock(t *testing.T) { } func TestEventBusPublishEventTxDuplicateKeys(t *testing.T) { - eventBus := NewEventBus() + eventBus := events.NewEventBus() err := eventBus.Start() require.NoError(t, err) t.Cleanup(func() { @@ -128,7 +133,7 @@ func TestEventBusPublishEventTxDuplicateKeys(t *testing.T) { } }) - tx := Tx("foo") + tx := mempool.Tx("foo") result := abci.ResponseDeliverTx{ Data: []byte("bar"), Events: []abci.Event{ @@ -194,7 +199,7 @@ func TestEventBusPublishEventTxDuplicateKeys(t *testing.T) { go func() { select { case msg := <-sub.Out(): - data := msg.Data().(EventDataTx) + data := msg.Data().(events.EventDataTx) assert.Equal(t, int64(1), data.Height) assert.Equal(t, uint32(0), data.Index) assert.EqualValues(t, tx, data.Tx) @@ -205,7 +210,7 @@ func TestEventBusPublishEventTxDuplicateKeys(t *testing.T) { } }() - err = eventBus.PublishEventTx(EventDataTx{abci.TxResult{ + err = eventBus.PublishEventTx(events.EventDataTx{abci.TxResult{ Height: 1, Index: 0, Tx: tx, @@ -227,7 +232,7 @@ func TestEventBusPublishEventTxDuplicateKeys(t *testing.T) { } func TestEventBusPublishEventNewBlockHeader(t *testing.T) { - eventBus := NewEventBus() + eventBus := events.NewEventBus() err := eventBus.Start() require.NoError(t, err) t.Cleanup(func() { @@ -236,7 +241,7 @@ func TestEventBusPublishEventNewBlockHeader(t *testing.T) { } }) - block := MakeBlock(0, []Tx{}, nil, []Evidence{}) + block := block.MakeBlock(0, []mempool.Tx{}, nil, []evidence.Evidence{}) resultBeginBlock := abci.ResponseBeginBlock{ Events: []abci.Event{ {Type: "testType", Attributes: []abci.EventAttribute{{Key: "baz", Value: "1"}}}, @@ -256,14 +261,14 @@ func TestEventBusPublishEventNewBlockHeader(t *testing.T) { done := make(chan struct{}) go func() { msg := <-headersSub.Out() - edt := msg.Data().(EventDataNewBlockHeader) + edt := msg.Data().(events.EventDataNewBlockHeader) assert.Equal(t, block.Header, edt.Header) assert.Equal(t, resultBeginBlock, edt.ResultBeginBlock) assert.Equal(t, resultEndBlock, edt.ResultEndBlock) close(done) }() - err = eventBus.PublishEventNewBlockHeader(EventDataNewBlockHeader{ + err = eventBus.PublishEventNewBlockHeader(events.EventDataNewBlockHeader{ Header: block.Header, ResultBeginBlock: resultBeginBlock, ResultEndBlock: resultEndBlock, @@ -278,7 +283,7 @@ func TestEventBusPublishEventNewBlockHeader(t *testing.T) { } func TestEventBusPublishEventNewEvidence(t *testing.T) { - eventBus := NewEventBus() + eventBus := events.NewEventBus() err := eventBus.Start() require.NoError(t, err) t.Cleanup(func() { @@ -287,7 +292,7 @@ func TestEventBusPublishEventNewEvidence(t *testing.T) { } }) - ev := NewMockDuplicateVoteEvidence(1, time.Now(), "test-chain-id") + ev := evidence.NewMockDuplicateVoteEvidence(1, time.Now(), "test-chain-id") query := "tm.event='NewEvidence'" evSub, err := eventBus.Subscribe(context.Background(), "test", tmquery.MustParse(query)) @@ -296,13 +301,13 @@ func TestEventBusPublishEventNewEvidence(t *testing.T) { done := make(chan struct{}) go func() { msg := <-evSub.Out() - edt := msg.Data().(EventDataNewEvidence) + edt := msg.Data().(events.EventDataNewEvidence) assert.Equal(t, ev, edt.Evidence) assert.Equal(t, int64(4), edt.Height) close(done) }() - err = eventBus.PublishEventNewEvidence(EventDataNewEvidence{ + err = eventBus.PublishEventNewEvidence(events.EventDataNewEvidence{ Evidence: ev, Height: 4, }) @@ -316,7 +321,7 @@ func TestEventBusPublishEventNewEvidence(t *testing.T) { } func TestEventBusPublish(t *testing.T) { - eventBus := NewEventBus() + eventBus := events.NewEventBus() err := eventBus.Start() require.NoError(t, err) t.Cleanup(func() { @@ -342,37 +347,37 @@ func TestEventBusPublish(t *testing.T) { } }() - err = eventBus.Publish(EventNewBlockHeaderValue, EventDataNewBlockHeader{}) + err = eventBus.Publish(events.EventNewBlockHeaderValue, events.EventDataNewBlockHeader{}) require.NoError(t, err) - err = eventBus.PublishEventNewBlock(EventDataNewBlock{}) + err = eventBus.PublishEventNewBlock(events.EventDataNewBlock{}) require.NoError(t, err) - err = eventBus.PublishEventNewBlockHeader(EventDataNewBlockHeader{}) + err = eventBus.PublishEventNewBlockHeader(events.EventDataNewBlockHeader{}) require.NoError(t, err) - err = eventBus.PublishEventVote(EventDataVote{}) + err = eventBus.PublishEventVote(events.EventDataVote{}) require.NoError(t, err) - err = eventBus.PublishEventNewRoundStep(EventDataRoundState{}) + err = eventBus.PublishEventNewRoundStep(events.EventDataRoundState{}) require.NoError(t, err) - err = eventBus.PublishEventTimeoutPropose(EventDataRoundState{}) + err = eventBus.PublishEventTimeoutPropose(events.EventDataRoundState{}) require.NoError(t, err) - err = eventBus.PublishEventTimeoutWait(EventDataRoundState{}) + err = eventBus.PublishEventTimeoutWait(events.EventDataRoundState{}) require.NoError(t, err) - err = eventBus.PublishEventNewRound(EventDataNewRound{}) + err = eventBus.PublishEventNewRound(events.EventDataNewRound{}) require.NoError(t, err) - err = eventBus.PublishEventCompleteProposal(EventDataCompleteProposal{}) + err = eventBus.PublishEventCompleteProposal(events.EventDataCompleteProposal{}) require.NoError(t, err) - err = eventBus.PublishEventPolka(EventDataRoundState{}) + err = eventBus.PublishEventPolka(events.EventDataRoundState{}) require.NoError(t, err) - err = eventBus.PublishEventUnlock(EventDataRoundState{}) + err = eventBus.PublishEventUnlock(events.EventDataRoundState{}) require.NoError(t, err) - err = eventBus.PublishEventRelock(EventDataRoundState{}) + err = eventBus.PublishEventRelock(events.EventDataRoundState{}) require.NoError(t, err) - err = eventBus.PublishEventLock(EventDataRoundState{}) + err = eventBus.PublishEventLock(events.EventDataRoundState{}) require.NoError(t, err) - err = eventBus.PublishEventValidatorSetUpdates(EventDataValidatorSetUpdates{}) + err = eventBus.PublishEventValidatorSetUpdates(events.EventDataValidatorSetUpdates{}) require.NoError(t, err) - err = eventBus.PublishEventBlockSyncStatus(EventDataBlockSyncStatus{}) + err = eventBus.PublishEventBlockSyncStatus(events.EventDataBlockSyncStatus{}) require.NoError(t, err) - err = eventBus.PublishEventStateSyncStatus(EventDataStateSyncStatus{}) + err = eventBus.PublishEventStateSyncStatus(events.EventDataStateSyncStatus{}) require.NoError(t, err) select { @@ -418,7 +423,7 @@ func benchmarkEventBus(numClients int, randQueries bool, randEvents bool, b *tes // for random* functions mrand.Seed(time.Now().Unix()) - eventBus := NewEventBusWithBufferCapacity(0) // set buffer capacity to 0 so we are not testing cache + eventBus := events.NewEventBusWithBufferCapacity(0) // set buffer capacity to 0 so we are not testing cache err := eventBus.Start() if err != nil { b.Error(err) @@ -430,7 +435,7 @@ func benchmarkEventBus(numClients int, randQueries bool, randEvents bool, b *tes }) ctx := context.Background() - q := EventQueryNewBlock + q := events.EventQueryNewBlock for i := 0; i < numClients; i++ { if randQueries { @@ -451,7 +456,7 @@ func benchmarkEventBus(numClients int, randQueries bool, randEvents bool, b *tes }() } - eventValue := EventNewBlockValue + eventValue := events.EventNewBlockValue b.ReportAllocs() b.ResetTimer() @@ -460,50 +465,50 @@ func benchmarkEventBus(numClients int, randQueries bool, randEvents bool, b *tes eventValue = randEventValue() } - err := eventBus.Publish(eventValue, EventDataString("Gamora")) + err := eventBus.Publish(eventValue, events.EventDataString("Gamora")) if err != nil { b.Error(err) } } } -var events = []string{ - EventNewBlockValue, - EventNewBlockHeaderValue, - EventNewRoundValue, - EventNewRoundStepValue, - EventTimeoutProposeValue, - EventCompleteProposalValue, - EventPolkaValue, - EventUnlockValue, - EventLockValue, - EventRelockValue, - EventTimeoutWaitValue, - EventVoteValue, - EventBlockSyncStatusValue, - EventStateSyncStatusValue, +var allEvents = []string{ + events.EventNewBlockValue, + events.EventNewBlockHeaderValue, + events.EventNewRoundValue, + events.EventNewRoundStepValue, + events.EventTimeoutProposeValue, + events.EventCompleteProposalValue, + events.EventPolkaValue, + events.EventUnlockValue, + events.EventLockValue, + events.EventRelockValue, + events.EventTimeoutWaitValue, + events.EventVoteValue, + events.EventBlockSyncStatusValue, + events.EventStateSyncStatusValue, } func randEventValue() string { - return events[mrand.Intn(len(events))] + return allEvents[mrand.Intn(len(allEvents))] } var queries = []tmpubsub.Query{ - EventQueryNewBlock, - EventQueryNewBlockHeader, - EventQueryNewRound, - EventQueryNewRoundStep, - EventQueryTimeoutPropose, - EventQueryCompleteProposal, - EventQueryPolka, - EventQueryUnlock, - EventQueryLock, - EventQueryRelock, - EventQueryTimeoutWait, - EventQueryVote, - EventQueryBlockSyncStatus, - EventQueryStateSyncStatus, + events.EventQueryNewBlock, + events.EventQueryNewBlockHeader, + events.EventQueryNewRound, + events.EventQueryNewRoundStep, + events.EventQueryTimeoutPropose, + events.EventQueryCompleteProposal, + events.EventQueryPolka, + events.EventQueryUnlock, + events.EventQueryLock, + events.EventQueryRelock, + events.EventQueryTimeoutWait, + events.EventQueryVote, + events.EventQueryBlockSyncStatus, + events.EventQueryStateSyncStatus, } func randQuery() tmpubsub.Query { diff --git a/pkg/block/events.go b/pkg/events/events.go similarity index 92% rename from pkg/block/events.go rename to pkg/events/events.go index f6dbb799c..96c52936b 100644 --- a/pkg/block/events.go +++ b/pkg/events/events.go @@ -1,4 +1,4 @@ -package block +package events import ( "fmt" @@ -8,6 +8,12 @@ import ( tmjson "github.com/tendermint/tendermint/libs/json" tmpubsub "github.com/tendermint/tendermint/libs/pubsub" tmquery "github.com/tendermint/tendermint/libs/pubsub/query" + + "github.com/tendermint/tendermint/pkg/block" + "github.com/tendermint/tendermint/pkg/consensus" + "github.com/tendermint/tendermint/pkg/evidence" + "github.com/tendermint/tendermint/pkg/mempool" + "github.com/tendermint/tendermint/pkg/meta" ) // Reserved event types (alphabetically sorted). @@ -112,15 +118,15 @@ func init() { // but some (an input to a call tx or a receive) are more exotic type EventDataNewBlock struct { - Block *Block `json:"block"` - BlockID BlockID `json:"block_id"` + Block *block.Block `json:"block"` + BlockID meta.BlockID `json:"block_id"` ResultBeginBlock abci.ResponseBeginBlock `json:"result_begin_block"` ResultEndBlock abci.ResponseEndBlock `json:"result_end_block"` } type EventDataNewBlockHeader struct { - Header Header `json:"header"` + Header meta.Header `json:"header"` NumTxs int64 `json:"num_txs"` // Number of txs in a block ResultBeginBlock abci.ResponseBeginBlock `json:"result_begin_block"` @@ -128,7 +134,7 @@ type EventDataNewBlockHeader struct { } type EventDataNewEvidence struct { - Evidence Evidence `json:"evidence"` + Evidence evidence.Evidence `json:"evidence"` Height int64 `json:"height"` } @@ -146,8 +152,8 @@ type EventDataRoundState struct { } type ValidatorInfo struct { - Address Address `json:"address"` - Index int32 `json:"index"` + Address consensus.Address `json:"address"` + Index int32 `json:"index"` } type EventDataNewRound struct { @@ -163,17 +169,17 @@ type EventDataCompleteProposal struct { Round int32 `json:"round"` Step string `json:"step"` - BlockID BlockID `json:"block_id"` + BlockID meta.BlockID `json:"block_id"` } type EventDataVote struct { - Vote *Vote + Vote *consensus.Vote } type EventDataString string type EventDataValidatorSetUpdates struct { - ValidatorUpdates []*Validator `json:"validator_updates"` + ValidatorUpdates []*consensus.Validator `json:"validator_updates"` } // EventDataBlockSyncStatus shows the fastsync status and the @@ -231,7 +237,7 @@ var ( EventQueryStateSyncStatus = QueryForEvent(EventStateSyncStatusValue) ) -func EventQueryTxFor(tx Tx) tmpubsub.Query { +func EventQueryTxFor(tx mempool.Tx) tmpubsub.Query { return tmquery.MustParse(fmt.Sprintf("%s='%s' AND %s='%X'", EventTypeKey, EventTxValue, TxHashKey, tx.Hash())) } diff --git a/pkg/block/events_test.go b/pkg/events/events_test.go similarity index 83% rename from pkg/block/events_test.go rename to pkg/events/events_test.go index fb1e26232..97aa292df 100644 --- a/pkg/block/events_test.go +++ b/pkg/events/events_test.go @@ -1,14 +1,16 @@ -package block +package events import ( "fmt" "testing" "github.com/stretchr/testify/assert" + + "github.com/tendermint/tendermint/pkg/mempool" ) func TestQueryTxFor(t *testing.T) { - tx := Tx("foo") + tx := mempool.Tx("foo") assert.Equal(t, fmt.Sprintf("tm.event='Tx' AND tx.hash='%X'", tx.Hash()), EventQueryTxFor(tx).String(), diff --git a/pkg/block/evidence.go b/pkg/evidence/evidence.go similarity index 91% rename from pkg/block/evidence.go rename to pkg/evidence/evidence.go index bcfa56263..bc93ad2a2 100644 --- a/pkg/block/evidence.go +++ b/pkg/evidence/evidence.go @@ -1,4 +1,4 @@ -package block +package evidence import ( "bytes" @@ -15,6 +15,9 @@ import ( "github.com/tendermint/tendermint/crypto/tmhash" tmjson "github.com/tendermint/tendermint/libs/json" tmrand "github.com/tendermint/tendermint/libs/rand" + "github.com/tendermint/tendermint/pkg/consensus" + "github.com/tendermint/tendermint/pkg/light" + "github.com/tendermint/tendermint/pkg/meta" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" ) @@ -30,12 +33,49 @@ type Evidence interface { ValidateBasic() error // basic consistency check } +//----------------------------------------------------------------------------- + +// 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 { + // These allocations are required because Evidence is not of type Bytes, and + // golang slices can't be typed cast. This shouldn't be a performance problem since + // the Evidence size is capped. + evidenceBzs := make([][]byte, len(evl)) + for i := 0; i < len(evl); i++ { + // TODO: We should change this to the hash. Using bytes contains some unexported data that + // may cause different hashes + evidenceBzs[i] = evl[i].Bytes() + } + return merkle.HashFromByteSlices(evidenceBzs) +} + +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 bytes.Equal(evidence.Hash(), ev.Hash()) { + return true + } + } + return false +} + //-------------------------------------------------------------------------------------- // DuplicateVoteEvidence contains evidence of a single validator signing two conflicting votes. type DuplicateVoteEvidence struct { - VoteA *Vote `json:"vote_a"` - VoteB *Vote `json:"vote_b"` + VoteA *consensus.Vote `json:"vote_a"` + VoteB *consensus.Vote `json:"vote_b"` // abci specific information TotalVotingPower int64 @@ -47,8 +87,8 @@ var _ Evidence = &DuplicateVoteEvidence{} // NewDuplicateVoteEvidence creates DuplicateVoteEvidence with right ordering given // two conflicting votes. If one of the votes is nil, evidence returned is nil as well -func NewDuplicateVoteEvidence(vote1, vote2 *Vote, blockTime time.Time, valSet *ValidatorSet) *DuplicateVoteEvidence { - var voteA, voteB *Vote +func NewDuplicateVoteEvidence(vote1, vote2 *consensus.Vote, blockTime time.Time, valSet *consensus.ValidatorSet) *DuplicateVoteEvidence { + var voteA, voteB *consensus.Vote if vote1 == nil || vote2 == nil || valSet == nil { return nil } @@ -143,8 +183,8 @@ func (dve *DuplicateVoteEvidence) ValidateBasic() error { // ValidateABCI validates the ABCI component of the evidence by checking the // timestamp, validator power and total voting power. func (dve *DuplicateVoteEvidence) ValidateABCI( - val *Validator, - valSet *ValidatorSet, + val *consensus.Validator, + valSet *consensus.ValidatorSet, evidenceTime time.Time, ) error { @@ -169,8 +209,8 @@ func (dve *DuplicateVoteEvidence) ValidateABCI( // GenerateABCI populates the ABCI component of the evidence. This includes the // validator power, timestamp and total voting power. func (dve *DuplicateVoteEvidence) GenerateABCI( - val *Validator, - valSet *ValidatorSet, + val *consensus.Validator, + valSet *consensus.ValidatorSet, evidenceTime time.Time, ) { dve.ValidatorPower = val.VotingPower @@ -198,12 +238,12 @@ func DuplicateVoteEvidenceFromProto(pb *tmproto.DuplicateVoteEvidence) (*Duplica return nil, errors.New("nil duplicate vote evidence") } - vA, err := VoteFromProto(pb.VoteA) + vA, err := consensus.VoteFromProto(pb.VoteA) if err != nil { return nil, err } - vB, err := VoteFromProto(pb.VoteB) + vB, err := consensus.VoteFromProto(pb.VoteB) if err != nil { return nil, err } @@ -230,13 +270,13 @@ func DuplicateVoteEvidenceFromProto(pb *tmproto.DuplicateVoteEvidence) (*Duplica // CommonHeight is used to indicate the type of attack. If the height is different to the conflicting block // height, then nodes will treat this as of the Lunatic form, else it is of the Equivocation form. type LightClientAttackEvidence struct { - ConflictingBlock *LightBlock + ConflictingBlock *light.LightBlock CommonHeight int64 // abci specific information - ByzantineValidators []*Validator // validators in the validator set that misbehaved in creating the conflicting block - TotalVotingPower int64 // total voting power of the validator set at the common height - Timestamp time.Time // timestamp of the block at the common height + ByzantineValidators []*consensus.Validator // validators in the validator set that misbehaved in creating the conflicting block + TotalVotingPower int64 // total voting power of the validator set at the common height + Timestamp time.Time // timestamp of the block at the common height } var _ Evidence = &LightClientAttackEvidence{} @@ -247,7 +287,7 @@ func (l *LightClientAttackEvidence) ABCI() []abci.Evidence { for idx, val := range l.ByzantineValidators { abciEv[idx] = abci.Evidence{ Type: abci.EvidenceType_LIGHT_CLIENT_ATTACK, - Validator: TM2PB.Validator(val), + Validator: consensus.TM2PB.Validator(val), Height: l.Height(), Time: l.Timestamp, TotalVotingPower: l.TotalVotingPower, @@ -272,9 +312,9 @@ func (l *LightClientAttackEvidence) Bytes() []byte { // GetByzantineValidators finds out what style of attack LightClientAttackEvidence was and then works out who // the malicious validators were and returns them. This is used both for forming the ByzantineValidators // field and for validating that it is correct. Validators are ordered based on validator power -func (l *LightClientAttackEvidence) GetByzantineValidators(commonVals *ValidatorSet, - trusted *SignedHeader) []*Validator { - var validators []*Validator +func (l *LightClientAttackEvidence) GetByzantineValidators(commonVals *consensus.ValidatorSet, + trusted *light.SignedHeader) []*consensus.Validator { + var validators []*consensus.Validator // First check if the header is invalid. This means that it is a lunatic attack and therefore we take the // validators who are in the commonVals and voted for the lunatic header if l.ConflictingHeaderIsInvalid(trusted.Header) { @@ -290,7 +330,7 @@ func (l *LightClientAttackEvidence) GetByzantineValidators(commonVals *Validator } validators = append(validators, val) } - sort.Sort(ValidatorsByVotingPower(validators)) + sort.Sort(consensus.ValidatorsByVotingPower(validators)) return validators } else if trusted.Commit.Round == l.ConflictingBlock.Commit.Round { // This is an equivocation attack as both commits are in the same round. We then find the validators @@ -311,7 +351,7 @@ func (l *LightClientAttackEvidence) GetByzantineValidators(commonVals *Validator _, val := l.ConflictingBlock.ValidatorSet.GetByAddress(sigA.ValidatorAddress) validators = append(validators, val) } - sort.Sort(ValidatorsByVotingPower(validators)) + sort.Sort(consensus.ValidatorsByVotingPower(validators)) return validators } // if the rounds are different then this is an amnesia attack. Unfortunately, given the nature of the attack, @@ -324,7 +364,7 @@ func (l *LightClientAttackEvidence) GetByzantineValidators(commonVals *Validator // to determine whether the conflicting header was the product of a valid state transition // or not. If it is then all the deterministic fields of the header should be the same. // If not, it is an invalid header and constitutes a lunatic attack. -func (l *LightClientAttackEvidence) ConflictingHeaderIsInvalid(trustedHeader *Header) bool { +func (l *LightClientAttackEvidence) ConflictingHeaderIsInvalid(trustedHeader *meta.Header) bool { return !bytes.Equal(trustedHeader.ValidatorsHash, l.ConflictingBlock.ValidatorsHash) || !bytes.Equal(trustedHeader.NextValidatorsHash, l.ConflictingBlock.NextValidatorsHash) || !bytes.Equal(trustedHeader.ConsensusHash, l.ConflictingBlock.ConsensusHash) || @@ -413,8 +453,8 @@ func (l *LightClientAttackEvidence) ValidateBasic() error { // components are validated separately because they can be re generated if // invalid. func (l *LightClientAttackEvidence) ValidateABCI( - commonVals *ValidatorSet, - trustedHeader *SignedHeader, + commonVals *consensus.ValidatorSet, + trustedHeader *light.SignedHeader, evidenceTime time.Time, ) error { @@ -469,8 +509,8 @@ func (l *LightClientAttackEvidence) ValidateABCI( // GenerateABCI populates the ABCI component of the evidence: the timestamp, // total voting power and byantine validators func (l *LightClientAttackEvidence) GenerateABCI( - commonVals *ValidatorSet, - trustedHeader *SignedHeader, + commonVals *consensus.ValidatorSet, + trustedHeader *light.SignedHeader, evidenceTime time.Time, ) { l.Timestamp = evidenceTime @@ -509,14 +549,14 @@ func LightClientAttackEvidenceFromProto(lpb *tmproto.LightClientAttackEvidence) return nil, errors.New("empty light client attack evidence") } - conflictingBlock, err := LightBlockFromProto(lpb.ConflictingBlock) + conflictingBlock, err := light.LightBlockFromProto(lpb.ConflictingBlock) if err != nil { return nil, err } - byzVals := make([]*Validator, len(lpb.ByzantineValidators)) + byzVals := make([]*consensus.Validator, len(lpb.ByzantineValidators)) for idx, valpb := range lpb.ByzantineValidators { - val, err := ValidatorFromProto(valpb) + val, err := consensus.ValidatorFromProto(valpb) if err != nil { return nil, err } @@ -534,43 +574,6 @@ func LightClientAttackEvidenceFromProto(lpb *tmproto.LightClientAttackEvidence) return l, l.ValidateBasic() } -//------------------------------------------------------------------------------------------ - -// 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 { - // These allocations are required because Evidence is not of type Bytes, and - // golang slices can't be typed cast. This shouldn't be a performance problem since - // the Evidence size is capped. - evidenceBzs := make([][]byte, len(evl)) - for i := 0; i < len(evl); i++ { - // TODO: We should change this to the hash. Using bytes contains some unexported data that - // may cause different hashes - evidenceBzs[i] = evl[i].Bytes() - } - return merkle.HashFromByteSlices(evidenceBzs) -} - -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 bytes.Equal(evidence.Hash(), ev.Hash()) { - return true - } - } - return false -} - //------------------------------------------ PROTO -------------------------------------- // EvidenceToProto is a generalized function for encoding evidence that conforms to the @@ -667,15 +670,15 @@ func (err *ErrEvidenceOverflow) Error() string { // assumes the round to be 0 and the validator index to be 0 func NewMockDuplicateVoteEvidence(height int64, time time.Time, chainID string) *DuplicateVoteEvidence { - val := NewMockPV() + val := consensus.NewMockPV() return NewMockDuplicateVoteEvidenceWithValidator(height, time, val, chainID) } // assumes voting power to be 10 and validator to be the only one in the set func NewMockDuplicateVoteEvidenceWithValidator(height int64, time time.Time, - pv PrivValidator, chainID string) *DuplicateVoteEvidence { + pv consensus.PrivValidator, chainID string) *DuplicateVoteEvidence { pubKey, _ := pv.GetPubKey(context.Background()) - val := NewValidator(pubKey, 10) + val := consensus.NewValidator(pubKey, 10) voteA := makeMockVote(height, 0, 0, pubKey.Address(), randBlockID(), time) vA := voteA.ToProto() _ = pv.SignVote(context.Background(), chainID, vA) @@ -684,12 +687,12 @@ func NewMockDuplicateVoteEvidenceWithValidator(height int64, time time.Time, vB := voteB.ToProto() _ = pv.SignVote(context.Background(), chainID, vB) voteB.Signature = vB.Signature - return NewDuplicateVoteEvidence(voteA, voteB, time, NewValidatorSet([]*Validator{val})) + return NewDuplicateVoteEvidence(voteA, voteB, time, consensus.NewValidatorSet([]*consensus.Validator{val})) } -func makeMockVote(height int64, round, index int32, addr Address, - blockID BlockID, time time.Time) *Vote { - return &Vote{ +func makeMockVote(height int64, round, index int32, addr consensus.Address, + blockID meta.BlockID, time time.Time) *consensus.Vote { + return &consensus.Vote{ Type: tmproto.SignedMsgType(2), Height: height, Round: round, @@ -700,10 +703,10 @@ func makeMockVote(height int64, round, index int32, addr Address, } } -func randBlockID() BlockID { - return BlockID{ +func randBlockID() meta.BlockID { + return meta.BlockID{ Hash: tmrand.Bytes(tmhash.Size), - PartSetHeader: PartSetHeader{ + PartSetHeader: meta.PartSetHeader{ Total: 1, Hash: tmrand.Bytes(tmhash.Size), }, diff --git a/pkg/block/evidence_test.go b/pkg/evidence/evidence_test.go similarity index 61% rename from pkg/block/evidence_test.go rename to pkg/evidence/evidence_test.go index 5d0aa7cb8..53df56862 100644 --- a/pkg/block/evidence_test.go +++ b/pkg/evidence/evidence_test.go @@ -1,4 +1,4 @@ -package block +package evidence_test import ( "context" @@ -14,7 +14,12 @@ import ( "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/ed25519" "github.com/tendermint/tendermint/crypto/tmhash" + test "github.com/tendermint/tendermint/internal/test/factory" tmrand "github.com/tendermint/tendermint/libs/rand" + "github.com/tendermint/tendermint/pkg/consensus" + "github.com/tendermint/tendermint/pkg/evidence" + "github.com/tendermint/tendermint/pkg/light" + "github.com/tendermint/tendermint/pkg/meta" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" "github.com/tendermint/tendermint/version" ) @@ -23,19 +28,19 @@ var defaultVoteTime = time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC) func TestEvidenceList(t *testing.T) { ev := randomDuplicateVoteEvidence(t) - evl := EvidenceList([]Evidence{ev}) + evl := evidence.EvidenceList([]evidence.Evidence{ev}) assert.NotNil(t, evl.Hash()) assert.True(t, evl.Has(ev)) - assert.False(t, evl.Has(&DuplicateVoteEvidence{})) + assert.False(t, evl.Has(&evidence.DuplicateVoteEvidence{})) } -func randomDuplicateVoteEvidence(t *testing.T) *DuplicateVoteEvidence { - val := NewMockPV() - blockID := makeBlockID([]byte("blockhash"), 1000, []byte("partshash")) - blockID2 := makeBlockID([]byte("blockhash2"), 1000, []byte("partshash")) +func randomDuplicateVoteEvidence(t *testing.T) *evidence.DuplicateVoteEvidence { + val := consensus.NewMockPV() + blockID := test.MakeBlockIDWithHash([]byte("blockhash")) + blockID2 := test.MakeBlockIDWithHash([]byte("blockhash2")) const chainID = "mychain" - return &DuplicateVoteEvidence{ + return &evidence.DuplicateVoteEvidence{ VoteA: makeVote(t, val, chainID, 0, 10, 2, 1, blockID, defaultVoteTime), VoteB: makeVote(t, val, chainID, 0, 10, 2, 1, blockID2, defaultVoteTime.Add(1*time.Minute)), TotalVotingPower: 30, @@ -46,34 +51,34 @@ func randomDuplicateVoteEvidence(t *testing.T) *DuplicateVoteEvidence { func TestDuplicateVoteEvidence(t *testing.T) { const height = int64(13) - ev := NewMockDuplicateVoteEvidence(height, time.Now(), "mock-chain-id") + ev := evidence.NewMockDuplicateVoteEvidence(height, time.Now(), "mock-chain-id") assert.Equal(t, ev.Hash(), tmhash.Sum(ev.Bytes())) assert.NotNil(t, ev.String()) assert.Equal(t, ev.Height(), height) } func TestDuplicateVoteEvidenceValidation(t *testing.T) { - val := NewMockPV() - blockID := makeBlockID(tmhash.Sum([]byte("blockhash")), math.MaxInt32, tmhash.Sum([]byte("partshash"))) - blockID2 := makeBlockID(tmhash.Sum([]byte("blockhash2")), math.MaxInt32, tmhash.Sum([]byte("partshash"))) + val := consensus.NewMockPV() + blockID := test.MakeBlockIDWithHash(tmhash.Sum([]byte("blockhash"))) + blockID2 := test.MakeBlockIDWithHash(tmhash.Sum([]byte("blockhash2"))) const chainID = "mychain" testCases := []struct { testName string - malleateEvidence func(*DuplicateVoteEvidence) + malleateEvidence func(*evidence.DuplicateVoteEvidence) expectErr bool }{ - {"Good DuplicateVoteEvidence", func(ev *DuplicateVoteEvidence) {}, false}, - {"Nil vote A", func(ev *DuplicateVoteEvidence) { ev.VoteA = nil }, true}, - {"Nil vote B", func(ev *DuplicateVoteEvidence) { ev.VoteB = nil }, true}, - {"Nil votes", func(ev *DuplicateVoteEvidence) { + {"Good DuplicateVoteEvidence", func(ev *evidence.DuplicateVoteEvidence) {}, false}, + {"Nil vote A", func(ev *evidence.DuplicateVoteEvidence) { ev.VoteA = nil }, true}, + {"Nil vote B", func(ev *evidence.DuplicateVoteEvidence) { ev.VoteB = nil }, true}, + {"Nil votes", func(ev *evidence.DuplicateVoteEvidence) { ev.VoteA = nil ev.VoteB = nil }, true}, - {"Invalid vote type", func(ev *DuplicateVoteEvidence) { + {"Invalid vote type", func(ev *evidence.DuplicateVoteEvidence) { ev.VoteA = makeVote(t, val, chainID, math.MaxInt32, math.MaxInt64, math.MaxInt32, 0, blockID2, defaultVoteTime) }, true}, - {"Invalid vote order", func(ev *DuplicateVoteEvidence) { + {"Invalid vote order", func(ev *evidence.DuplicateVoteEvidence) { swap := ev.VoteA.Copy() ev.VoteA = ev.VoteB.Copy() ev.VoteB = swap @@ -84,8 +89,8 @@ func TestDuplicateVoteEvidenceValidation(t *testing.T) { t.Run(tc.testName, func(t *testing.T) { vote1 := makeVote(t, val, chainID, math.MaxInt32, math.MaxInt64, math.MaxInt32, 0x02, blockID, defaultVoteTime) vote2 := makeVote(t, val, chainID, math.MaxInt32, math.MaxInt64, math.MaxInt32, 0x02, blockID2, defaultVoteTime) - valSet := NewValidatorSet([]*Validator{val.ExtractIntoValidator(10)}) - ev := NewDuplicateVoteEvidence(vote1, vote2, defaultVoteTime, valSet) + valSet := consensus.NewValidatorSet([]*consensus.Validator{val.ExtractIntoValidator(10)}) + ev := evidence.NewDuplicateVoteEvidence(vote1, vote2, defaultVoteTime, valSet) tc.malleateEvidence(ev) assert.Equal(t, tc.expectErr, ev.ValidateBasic() != nil, "Validate Basic had an unexpected result") }) @@ -96,15 +101,15 @@ func TestLightClientAttackEvidenceBasic(t *testing.T) { height := int64(5) commonHeight := height - 1 nValidators := 10 - voteSet, valSet, privVals := randVoteSet(height, 1, tmproto.PrecommitType, nValidators, 1) + voteSet, valSet, privVals := test.RandVoteSet(height, 1, tmproto.PrecommitType, nValidators, 1) header := makeHeaderRandom() header.Height = height - blockID := makeBlockID(tmhash.Sum([]byte("blockhash")), math.MaxInt32, tmhash.Sum([]byte("partshash"))) - commit, err := makeCommit(blockID, height, 1, voteSet, privVals, defaultVoteTime) + blockID := test.MakeBlockIDWithHash(tmhash.Sum([]byte("blockhash"))) + commit, err := test.MakeCommit(blockID, height, 1, voteSet, privVals, defaultVoteTime) require.NoError(t, err) - lcae := &LightClientAttackEvidence{ - ConflictingBlock: &LightBlock{ - SignedHeader: &SignedHeader{ + lcae := &evidence.LightClientAttackEvidence{ + ConflictingBlock: &light.LightBlock{ + SignedHeader: &light.SignedHeader{ Header: header, Commit: commit, }, @@ -123,18 +128,18 @@ func TestLightClientAttackEvidenceBasic(t *testing.T) { // maleate evidence to test hash uniqueness testCases := []struct { testName string - malleateEvidence func(*LightClientAttackEvidence) + malleateEvidence func(*evidence.LightClientAttackEvidence) }{ - {"Different header", func(ev *LightClientAttackEvidence) { ev.ConflictingBlock.Header = makeHeaderRandom() }}, - {"Different common height", func(ev *LightClientAttackEvidence) { + {"Different header", func(ev *evidence.LightClientAttackEvidence) { ev.ConflictingBlock.Header = makeHeaderRandom() }}, + {"Different common height", func(ev *evidence.LightClientAttackEvidence) { ev.CommonHeight = height + 1 }}, } for _, tc := range testCases { - lcae := &LightClientAttackEvidence{ - ConflictingBlock: &LightBlock{ - SignedHeader: &SignedHeader{ + lcae := &evidence.LightClientAttackEvidence{ + ConflictingBlock: &light.LightBlock{ + SignedHeader: &light.SignedHeader{ Header: header, Commit: commit, }, @@ -155,16 +160,16 @@ func TestLightClientAttackEvidenceValidation(t *testing.T) { height := int64(5) commonHeight := height - 1 nValidators := 10 - voteSet, valSet, privVals := randVoteSet(height, 1, tmproto.PrecommitType, nValidators, 1) + voteSet, valSet, privVals := test.RandVoteSet(height, 1, tmproto.PrecommitType, nValidators, 1) header := makeHeaderRandom() header.Height = height header.ValidatorsHash = valSet.Hash() - blockID := makeBlockID(header.Hash(), math.MaxInt32, tmhash.Sum([]byte("partshash"))) - commit, err := makeCommit(blockID, height, 1, voteSet, privVals, time.Now()) + blockID := test.MakeBlockIDWithHash(header.Hash()) + commit, err := test.MakeCommit(blockID, height, 1, voteSet, privVals, time.Now()) require.NoError(t, err) - lcae := &LightClientAttackEvidence{ - ConflictingBlock: &LightBlock{ - SignedHeader: &SignedHeader{ + lcae := &evidence.LightClientAttackEvidence{ + ConflictingBlock: &light.LightBlock{ + SignedHeader: &light.SignedHeader{ Header: header, Commit: commit, }, @@ -179,32 +184,32 @@ func TestLightClientAttackEvidenceValidation(t *testing.T) { testCases := []struct { testName string - malleateEvidence func(*LightClientAttackEvidence) + malleateEvidence func(*evidence.LightClientAttackEvidence) expectErr bool }{ - {"Good LightClientAttackEvidence", func(ev *LightClientAttackEvidence) {}, false}, - {"Negative height", func(ev *LightClientAttackEvidence) { ev.CommonHeight = -10 }, true}, - {"Height is greater than divergent block", func(ev *LightClientAttackEvidence) { + {"Good LightClientAttackEvidence", func(ev *evidence.LightClientAttackEvidence) {}, false}, + {"Negative height", func(ev *evidence.LightClientAttackEvidence) { ev.CommonHeight = -10 }, true}, + {"Height is greater than divergent block", func(ev *evidence.LightClientAttackEvidence) { ev.CommonHeight = height + 1 }, true}, - {"Height is equal to the divergent block", func(ev *LightClientAttackEvidence) { + {"Height is equal to the divergent block", func(ev *evidence.LightClientAttackEvidence) { ev.CommonHeight = height }, false}, - {"Nil conflicting header", func(ev *LightClientAttackEvidence) { ev.ConflictingBlock.Header = nil }, true}, - {"Nil conflicting blocl", func(ev *LightClientAttackEvidence) { ev.ConflictingBlock = nil }, true}, - {"Nil validator set", func(ev *LightClientAttackEvidence) { - ev.ConflictingBlock.ValidatorSet = &ValidatorSet{} + {"Nil conflicting header", func(ev *evidence.LightClientAttackEvidence) { ev.ConflictingBlock.Header = nil }, true}, + {"Nil conflicting blocl", func(ev *evidence.LightClientAttackEvidence) { ev.ConflictingBlock = nil }, true}, + {"Nil validator set", func(ev *evidence.LightClientAttackEvidence) { + ev.ConflictingBlock.ValidatorSet = &consensus.ValidatorSet{} }, true}, - {"Negative total voting power", func(ev *LightClientAttackEvidence) { + {"Negative total voting power", func(ev *evidence.LightClientAttackEvidence) { ev.TotalVotingPower = -1 }, true}, } for _, tc := range testCases { tc := tc t.Run(tc.testName, func(t *testing.T) { - lcae := &LightClientAttackEvidence{ - ConflictingBlock: &LightBlock{ - SignedHeader: &SignedHeader{ + lcae := &evidence.LightClientAttackEvidence{ + ConflictingBlock: &light.LightBlock{ + SignedHeader: &light.SignedHeader{ Header: header, Commit: commit, }, @@ -227,16 +232,16 @@ func TestLightClientAttackEvidenceValidation(t *testing.T) { } func TestMockEvidenceValidateBasic(t *testing.T) { - goodEvidence := NewMockDuplicateVoteEvidence(int64(1), time.Now(), "mock-chain-id") + goodEvidence := evidence.NewMockDuplicateVoteEvidence(int64(1), time.Now(), "mock-chain-id") assert.Nil(t, goodEvidence.ValidateBasic()) } func makeVote( - t *testing.T, val PrivValidator, chainID string, valIndex int32, height int64, round int32, step int, blockID BlockID, - time time.Time) *Vote { + t *testing.T, val consensus.PrivValidator, chainID string, valIndex int32, height int64, round int32, step int, blockID meta.BlockID, + time time.Time) *consensus.Vote { pubKey, err := val.GetPubKey(context.Background()) require.NoError(t, err) - v := &Vote{ + v := &consensus.Vote{ ValidatorAddress: pubKey.Address(), ValidatorIndex: valIndex, Height: height, @@ -255,13 +260,13 @@ func makeVote( return v } -func makeHeaderRandom() *Header { - return &Header{ +func makeHeaderRandom() *meta.Header { + return &meta.Header{ Version: version.Consensus{Block: version.BlockProtocol, App: 1}, ChainID: tmrand.Str(12), Height: int64(mrand.Uint32() + 1), Time: time.Now(), - LastBlockID: makeBlockIDRandom(), + LastBlockID: test.MakeBlockID(), LastCommitHash: crypto.CRandBytes(tmhash.Size), DataHash: crypto.CRandBytes(tmhash.Size), ValidatorsHash: crypto.CRandBytes(tmhash.Size), @@ -276,36 +281,36 @@ func makeHeaderRandom() *Header { func TestEvidenceProto(t *testing.T) { // -------- Votes -------- - val := NewMockPV() - blockID := makeBlockID(tmhash.Sum([]byte("blockhash")), math.MaxInt32, tmhash.Sum([]byte("partshash"))) - blockID2 := makeBlockID(tmhash.Sum([]byte("blockhash2")), math.MaxInt32, tmhash.Sum([]byte("partshash"))) + val := consensus.NewMockPV() + blockID := test.MakeBlockIDWithHash(tmhash.Sum([]byte("blockhash"))) + blockID2 := test.MakeBlockIDWithHash(tmhash.Sum([]byte("blockhash2"))) const chainID = "mychain" v := makeVote(t, val, chainID, math.MaxInt32, math.MaxInt64, 1, 0x01, blockID, defaultVoteTime) v2 := makeVote(t, val, chainID, math.MaxInt32, math.MaxInt64, 2, 0x01, blockID2, defaultVoteTime) tests := []struct { testName string - evidence Evidence + evidence evidence.Evidence toProtoErr bool fromProtoErr bool }{ {"nil fail", nil, true, true}, - {"DuplicateVoteEvidence empty fail", &DuplicateVoteEvidence{}, false, true}, - {"DuplicateVoteEvidence nil voteB", &DuplicateVoteEvidence{VoteA: v, VoteB: nil}, false, true}, - {"DuplicateVoteEvidence nil voteA", &DuplicateVoteEvidence{VoteA: nil, VoteB: v}, false, true}, - {"DuplicateVoteEvidence success", &DuplicateVoteEvidence{VoteA: v2, VoteB: v}, false, false}, + {"DuplicateVoteEvidence empty fail", &evidence.DuplicateVoteEvidence{}, false, true}, + {"DuplicateVoteEvidence nil voteB", &evidence.DuplicateVoteEvidence{VoteA: v, VoteB: nil}, false, true}, + {"DuplicateVoteEvidence nil voteA", &evidence.DuplicateVoteEvidence{VoteA: nil, VoteB: v}, false, true}, + {"DuplicateVoteEvidence success", &evidence.DuplicateVoteEvidence{VoteA: v2, VoteB: v}, false, false}, } for _, tt := range tests { tt := tt t.Run(tt.testName, func(t *testing.T) { - pb, err := EvidenceToProto(tt.evidence) + pb, err := evidence.EvidenceToProto(tt.evidence) if tt.toProtoErr { assert.Error(t, err, tt.testName) return } assert.NoError(t, err, tt.testName) - evi, err := EvidenceFromProto(pb) + evi, err := evidence.EvidenceFromProto(pb) if tt.fromProtoErr { assert.Error(t, err, tt.testName) return @@ -317,10 +322,10 @@ func TestEvidenceProto(t *testing.T) { func TestEvidenceVectors(t *testing.T) { // Votes for duplicateEvidence - val := NewMockPV() + val := consensus.NewMockPV() val.PrivKey = ed25519.GenPrivKeyFromSecret([]byte("it's a secret")) // deterministic key - blockID := makeBlockID(tmhash.Sum([]byte("blockhash")), math.MaxInt32, tmhash.Sum([]byte("partshash"))) - blockID2 := makeBlockID(tmhash.Sum([]byte("blockhash2")), math.MaxInt32, tmhash.Sum([]byte("partshash"))) + blockID := test.MakeBlockIDWithHash(tmhash.Sum([]byte("blockhash"))) + blockID2 := test.MakeBlockIDWithHash(tmhash.Sum([]byte("blockhash2"))) const chainID = "mychain" v := makeVote(t, val, chainID, math.MaxInt32, math.MaxInt64, 1, 0x01, blockID, defaultVoteTime) v2 := makeVote(t, val, chainID, math.MaxInt32, math.MaxInt64, 2, 0x01, blockID2, defaultVoteTime) @@ -329,13 +334,13 @@ func TestEvidenceVectors(t *testing.T) { height := int64(5) commonHeight := height - 1 nValidators := 10 - voteSet, valSet, privVals := deterministicVoteSet(height, 1, tmproto.PrecommitType, 1) - header := &Header{ + voteSet, valSet, privVals := test.DeterministicVoteSet(height, 1, tmproto.PrecommitType, 1) + header := &meta.Header{ Version: version.Consensus{Block: 1, App: 1}, ChainID: chainID, Height: height, Time: time.Date(math.MaxInt64, 0, 0, 0, 0, 0, math.MaxInt64, time.UTC), - LastBlockID: BlockID{}, + LastBlockID: meta.BlockID{}, LastCommitHash: []byte("f2564c78071e26643ae9b3e2a19fa0dc10d4d9e873aa0be808660123f11a1e78"), DataHash: []byte("f2564c78071e26643ae9b3e2a19fa0dc10d4d9e873aa0be808660123f11a1e78"), ValidatorsHash: valSet.Hash(), @@ -348,12 +353,12 @@ func TestEvidenceVectors(t *testing.T) { EvidenceHash: []byte("f2564c78071e26643ae9b3e2a19fa0dc10d4d9e873aa0be808660123f11a1e78"), ProposerAddress: []byte("2915b7b15f979e48ebc61774bb1d86ba3136b7eb"), } - blockID3 := makeBlockID(header.Hash(), math.MaxInt32, tmhash.Sum([]byte("partshash"))) - commit, err := makeCommit(blockID3, height, 1, voteSet, privVals, defaultVoteTime) + blockID3 := test.MakeBlockIDWithHash(header.Hash()) + commit, err := test.MakeCommit(blockID3, height, 1, voteSet, privVals, defaultVoteTime) require.NoError(t, err) - lcae := &LightClientAttackEvidence{ - ConflictingBlock: &LightBlock{ - SignedHeader: &SignedHeader{ + lcae := &evidence.LightClientAttackEvidence{ + ConflictingBlock: &light.LightBlock{ + SignedHeader: &light.SignedHeader{ Header: header, Commit: commit, }, @@ -368,19 +373,19 @@ func TestEvidenceVectors(t *testing.T) { testCases := []struct { testName string - evList EvidenceList + evList evidence.EvidenceList expBytes string }{ {"duplicateVoteEvidence", - EvidenceList{&DuplicateVoteEvidence{VoteA: v2, VoteB: v}}, + evidence.EvidenceList{&evidence.DuplicateVoteEvidence{VoteA: v2, VoteB: v}}, "a9ce28d13bb31001fc3e5b7927051baf98f86abdbd64377643a304164c826923", }, {"LightClientAttackEvidence", - EvidenceList{lcae}, + evidence.EvidenceList{lcae}, "2f8782163c3905b26e65823ababc977fe54e97b94e60c0360b1e4726b668bb8e", }, {"LightClientAttackEvidence & DuplicateVoteEvidence", - EvidenceList{&DuplicateVoteEvidence{VoteA: v2, VoteB: v}, lcae}, + evidence.EvidenceList{&evidence.DuplicateVoteEvidence{VoteA: v2, VoteB: v}, lcae}, "eedb4b47d6dbc9d43f53da8aa50bb826e8d9fc7d897da777c8af6a04aa74163e", }, } diff --git a/pkg/block/light.go b/pkg/light/light.go similarity index 93% rename from pkg/block/light.go rename to pkg/light/light.go index 139b63a2a..52207390d 100644 --- a/pkg/block/light.go +++ b/pkg/light/light.go @@ -1,10 +1,12 @@ -package block +package light import ( "bytes" "errors" "fmt" + "github.com/tendermint/tendermint/pkg/consensus" + "github.com/tendermint/tendermint/pkg/meta" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" ) @@ -12,7 +14,7 @@ import ( // It is the basis of the light client type LightBlock struct { *SignedHeader `json:"signed_header"` - ValidatorSet *ValidatorSet `json:"validator_set"` + ValidatorSet *consensus.ValidatorSet `json:"validator_set"` } // ValidateBasic checks that the data is correct and consistent @@ -101,7 +103,7 @@ func LightBlockFromProto(pb *tmproto.LightBlock) (*LightBlock, error) { } if pb.ValidatorSet != nil { - vals, err := ValidatorSetFromProto(pb.ValidatorSet) + vals, err := consensus.ValidatorSetFromProto(pb.ValidatorSet) if err != nil { return nil, err } @@ -115,9 +117,9 @@ func LightBlockFromProto(pb *tmproto.LightBlock) (*LightBlock, error) { // SignedHeader is a header along with the commits that prove it. type SignedHeader struct { - *Header `json:"header"` + *meta.Header `json:"header"` - Commit *Commit `json:"commit"` + Commit *meta.Commit `json:"commit"` } // ValidateBasic does basic consistency checks and makes sure the header @@ -202,7 +204,7 @@ func SignedHeaderFromProto(shp *tmproto.SignedHeader) (*SignedHeader, error) { sh := new(SignedHeader) if shp.Header != nil { - h, err := HeaderFromProto(shp.Header) + h, err := meta.HeaderFromProto(shp.Header) if err != nil { return nil, err } @@ -210,7 +212,7 @@ func SignedHeaderFromProto(shp *tmproto.SignedHeader) (*SignedHeader, error) { } if shp.Commit != nil { - c, err := CommitFromProto(shp.Commit) + c, err := meta.CommitFromProto(shp.Commit) if err != nil { return nil, err } diff --git a/pkg/block/light_test.go b/pkg/light/light_test.go similarity index 58% rename from pkg/block/light_test.go rename to pkg/light/light_test.go index 61b85e035..271604287 100644 --- a/pkg/block/light_test.go +++ b/pkg/light/light_test.go @@ -1,4 +1,4 @@ -package block +package light_test import ( "math" @@ -6,43 +6,48 @@ import ( "time" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/crypto" + test "github.com/tendermint/tendermint/internal/test/factory" + "github.com/tendermint/tendermint/pkg/consensus" + "github.com/tendermint/tendermint/pkg/light" + "github.com/tendermint/tendermint/pkg/meta" "github.com/tendermint/tendermint/version" ) func TestLightBlockValidateBasic(t *testing.T) { - header := MakeRandHeader() - commit := randCommit(time.Now()) - vals, _ := randValidatorPrivValSet(5, 1) + header := test.MakeRandomHeader() + commit := test.MakeRandomCommit(time.Now()) + vals, _ := test.RandValidatorPrivValSet(5, 1) header.Height = commit.Height header.LastBlockID = commit.BlockID header.ValidatorsHash = vals.Hash() header.Version.Block = version.BlockProtocol - vals2, _ := randValidatorPrivValSet(3, 1) + vals2, _ := test.RandValidatorPrivValSet(3, 1) vals3 := vals.Copy() - vals3.Proposer = &Validator{} + vals3.Proposer = &consensus.Validator{} commit.BlockID.Hash = header.Hash() - sh := &SignedHeader{ - Header: &header, + sh := &light.SignedHeader{ + Header: header, Commit: commit, } testCases := []struct { name string - sh *SignedHeader - vals *ValidatorSet + sh *light.SignedHeader + vals *consensus.ValidatorSet expectErr bool }{ {"valid light block", sh, vals, false}, {"hashes don't match", sh, vals2, true}, {"invalid validator set", sh, vals3, true}, - {"invalid signed header", &SignedHeader{Header: &header, Commit: randCommit(time.Now())}, vals, true}, + {"invalid signed header", &light.SignedHeader{Header: header, Commit: test.MakeRandomCommit(time.Now())}, vals, true}, } for _, tc := range testCases { - lightBlock := LightBlock{ + lightBlock := light.LightBlock{ SignedHeader: tc.sh, ValidatorSet: tc.vals, } @@ -57,37 +62,37 @@ func TestLightBlockValidateBasic(t *testing.T) { } func TestLightBlockProtobuf(t *testing.T) { - header := MakeRandHeader() - commit := randCommit(time.Now()) - vals, _ := randValidatorPrivValSet(5, 1) + header := test.MakeRandomHeader() + commit := test.MakeRandomCommit(time.Now()) + vals, _ := test.RandValidatorPrivValSet(5, 1) header.Height = commit.Height header.LastBlockID = commit.BlockID header.Version.Block = version.BlockProtocol header.ValidatorsHash = vals.Hash() vals3 := vals.Copy() - vals3.Proposer = &Validator{} + vals3.Proposer = &consensus.Validator{} commit.BlockID.Hash = header.Hash() - sh := &SignedHeader{ - Header: &header, + sh := &light.SignedHeader{ + Header: header, Commit: commit, } testCases := []struct { name string - sh *SignedHeader - vals *ValidatorSet + sh *light.SignedHeader + vals *consensus.ValidatorSet toProtoErr bool toBlockErr bool }{ {"valid light block", sh, vals, false, false}, - {"empty signed header", &SignedHeader{}, vals, false, false}, - {"empty validator set", sh, &ValidatorSet{}, false, true}, - {"empty light block", &SignedHeader{}, &ValidatorSet{}, false, true}, + {"empty signed header", &light.SignedHeader{}, vals, false, false}, + {"empty validator set", sh, &consensus.ValidatorSet{}, false, true}, + {"empty light block", &light.SignedHeader{}, &consensus.ValidatorSet{}, false, true}, } for _, tc := range testCases { - lightBlock := &LightBlock{ + lightBlock := &light.LightBlock{ SignedHeader: tc.sh, ValidatorSet: tc.vals, } @@ -98,7 +103,7 @@ func TestLightBlockProtobuf(t *testing.T) { assert.NoError(t, err, tc.name) } - lb, err := LightBlockFromProto(lbp) + lb, err := light.LightBlockFromProto(lbp) if tc.toBlockErr { assert.Error(t, err, tc.name) } else { @@ -110,10 +115,10 @@ func TestLightBlockProtobuf(t *testing.T) { } func TestSignedHeaderValidateBasic(t *testing.T) { - commit := randCommit(time.Now()) + commit := test.MakeRandomCommit(time.Now()) chainID := "𠜎" timestamp := time.Date(math.MaxInt64, 0, 0, 0, 0, 0, math.MaxInt64, time.UTC) - h := Header{ + h := meta.Header{ Version: version.Consensus{Block: version.BlockProtocol, App: math.MaxInt64}, ChainID: chainID, Height: commit.Height, @@ -130,14 +135,14 @@ func TestSignedHeaderValidateBasic(t *testing.T) { ProposerAddress: crypto.AddressHash([]byte("proposer_address")), } - validSignedHeader := SignedHeader{Header: &h, Commit: commit} + validSignedHeader := light.SignedHeader{Header: &h, Commit: commit} validSignedHeader.Commit.BlockID.Hash = validSignedHeader.Hash() - invalidSignedHeader := SignedHeader{} + invalidSignedHeader := light.SignedHeader{} testCases := []struct { testName string - shHeader *Header - shCommit *Commit + shHeader *meta.Header + shCommit *meta.Commit expectErr bool }{ {"Valid Signed Header", validSignedHeader.Header, validSignedHeader.Commit, false}, @@ -148,7 +153,7 @@ func TestSignedHeaderValidateBasic(t *testing.T) { for _, tc := range testCases { tc := tc t.Run(tc.testName, func(t *testing.T) { - sh := SignedHeader{ + sh := light.SignedHeader{ Header: tc.shHeader, Commit: tc.shCommit, } @@ -163,3 +168,32 @@ func TestSignedHeaderValidateBasic(t *testing.T) { }) } } + +func TestSignedHeaderProtoBuf(t *testing.T) { + commit := test.MakeRandomCommit(time.Now()) + h := test.MakeRandomHeader() + + sh := light.SignedHeader{Header: h, Commit: commit} + + testCases := []struct { + msg string + sh1 *light.SignedHeader + expPass bool + }{ + {"empty SignedHeader 2", &light.SignedHeader{}, true}, + {"success", &sh, true}, + {"failure nil", nil, false}, + } + for _, tc := range testCases { + protoSignedHeader := tc.sh1.ToProto() + + sh, err := light.SignedHeaderFromProto(protoSignedHeader) + + if tc.expPass { + require.NoError(t, err, tc.msg) + require.Equal(t, tc.sh1, sh, tc.msg) + } else { + require.Error(t, err, tc.msg) + } + } +} diff --git a/pkg/block/tx.go b/pkg/mempool/tx.go similarity index 92% rename from pkg/block/tx.go rename to pkg/mempool/tx.go index a9a55c579..fdcfc4b8a 100644 --- a/pkg/block/tx.go +++ b/pkg/mempool/tx.go @@ -1,4 +1,4 @@ -package block +package mempool import ( "bytes" @@ -137,11 +137,3 @@ func TxProofFromProto(pb tmproto.TxProof) (TxProof, error) { return pbtp, nil } - -// ComputeProtoSizeForTxs wraps the transactions in tmproto.Data{} and calculates the size. -// https://developers.google.com/protocol-buffers/docs/encoding -func ComputeProtoSizeForTxs(txs []Tx) int64 { - data := Data{Txs: txs} - pdData := data.ToProto() - return int64(pdData.Size()) -} diff --git a/pkg/block/tx_test.go b/pkg/mempool/tx_test.go similarity index 99% rename from pkg/block/tx_test.go rename to pkg/mempool/tx_test.go index 8e51f8549..e12840802 100644 --- a/pkg/block/tx_test.go +++ b/pkg/mempool/tx_test.go @@ -1,4 +1,4 @@ -package block +package mempool import ( "bytes" diff --git a/pkg/block/canonical.go b/pkg/meta/canonical.go similarity index 56% rename from pkg/block/canonical.go rename to pkg/meta/canonical.go index 4002580f0..7b0f648ef 100644 --- a/pkg/block/canonical.go +++ b/pkg/meta/canonical.go @@ -1,4 +1,4 @@ -package block +package meta import ( "time" @@ -38,32 +38,6 @@ func CanonicalizePartSetHeader(psh tmproto.PartSetHeader) tmproto.CanonicalPartS return tmproto.CanonicalPartSetHeader(psh) } -// CanonicalizeVote transforms the given Proposal to a CanonicalProposal. -func CanonicalizeProposal(chainID string, proposal *tmproto.Proposal) tmproto.CanonicalProposal { - return tmproto.CanonicalProposal{ - Type: tmproto.ProposalType, - Height: proposal.Height, // encoded as sfixed64 - Round: int64(proposal.Round), // encoded as sfixed64 - POLRound: int64(proposal.PolRound), - BlockID: CanonicalizeBlockID(proposal.BlockID), - Timestamp: proposal.Timestamp, - ChainID: chainID, - } -} - -// CanonicalizeVote transforms the given Vote to a CanonicalVote, which does -// not contain ValidatorIndex and ValidatorAddress fields. -func CanonicalizeVote(chainID string, vote *tmproto.Vote) tmproto.CanonicalVote { - return tmproto.CanonicalVote{ - Type: vote.Type, - Height: vote.Height, // encoded as sfixed64 - Round: int64(vote.Round), // encoded as sfixed64 - BlockID: CanonicalizeBlockID(vote.BlockID), - Timestamp: vote.Timestamp, - ChainID: chainID, - } -} - // CanonicalTime can be used to stringify time in a canonical way. func CanonicalTime(t time.Time) string { // Note that sending time over amino resets it to diff --git a/pkg/block/canonical_test.go b/pkg/meta/canonical_test.go similarity index 88% rename from pkg/block/canonical_test.go rename to pkg/meta/canonical_test.go index bb5324118..08499eb86 100644 --- a/pkg/block/canonical_test.go +++ b/pkg/meta/canonical_test.go @@ -1,4 +1,4 @@ -package block +package meta_test import ( "reflect" @@ -6,6 +6,7 @@ import ( "github.com/tendermint/tendermint/crypto/tmhash" tmrand "github.com/tendermint/tendermint/libs/rand" + "github.com/tendermint/tendermint/pkg/meta" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" ) @@ -31,7 +32,7 @@ func TestCanonicalizeBlockID(t *testing.T) { for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { - if got := CanonicalizeBlockID(tt.args); !reflect.DeepEqual(got, tt.want) { + if got := meta.CanonicalizeBlockID(tt.args); !reflect.DeepEqual(got, tt.want) { t.Errorf("CanonicalizeBlockID() = %v, want %v", got, tt.want) } }) diff --git a/pkg/meta/commit.go b/pkg/meta/commit.go new file mode 100644 index 000000000..d14986c3c --- /dev/null +++ b/pkg/meta/commit.go @@ -0,0 +1,379 @@ +package meta + +import ( + "crypto/ed25519" + "errors" + "fmt" + "strings" + "time" + + "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/crypto/merkle" + "github.com/tendermint/tendermint/libs/bits" + tmbytes "github.com/tendermint/tendermint/libs/bytes" + tmmath "github.com/tendermint/tendermint/libs/math" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" +) + +var ( + // MaxSignatureSize is a maximum allowed signature size for the Proposal + // and Vote. + // XXX: secp256k1 does not have Size nor MaxSize defined. + MaxSignatureSize = tmmath.MaxInt(ed25519.SignatureSize, 64) +) + +//------------------------------------- + +const ( + // Max size of commit without any commitSigs -> 82 for BlockID, 8 for Height, 4 for Round. + MaxCommitOverheadBytes int64 = 94 + // Commit sig size is made up of 64 bytes for the signature, 20 bytes for the address, + // 1 byte for the flag and 14 bytes for the timestamp + MaxCommitSigBytes int64 = 109 +) + +// CommitSig is a part of the Vote included in a Commit. +type CommitSig struct { + BlockIDFlag BlockIDFlag `json:"block_id_flag"` + ValidatorAddress crypto.Address `json:"validator_address"` + Timestamp time.Time `json:"timestamp"` + Signature []byte `json:"signature"` +} + +// NewCommitSigForBlock returns new CommitSig with BlockIDFlagCommit. +func NewCommitSigForBlock(signature []byte, valAddr crypto.Address, ts time.Time) CommitSig { + return CommitSig{ + BlockIDFlag: BlockIDFlagCommit, + ValidatorAddress: valAddr, + Timestamp: ts, + Signature: signature, + } +} + +func MaxCommitBytes(valCount int) int64 { + // From the repeated commit sig field + var protoEncodingOverhead int64 = 2 + return MaxCommitOverheadBytes + ((MaxCommitSigBytes + protoEncodingOverhead) * int64(valCount)) +} + +// NewCommitSigAbsent returns new CommitSig with BlockIDFlagAbsent. Other +// fields are all empty. +func NewCommitSigAbsent() CommitSig { + return CommitSig{ + BlockIDFlag: BlockIDFlagAbsent, + } +} + +// ForBlock returns true if CommitSig is for the block. +func (cs CommitSig) ForBlock() bool { + return cs.BlockIDFlag == BlockIDFlagCommit +} + +// Absent returns true if CommitSig is absent. +func (cs CommitSig) Absent() bool { + return cs.BlockIDFlag == BlockIDFlagAbsent +} + +// CommitSig returns a string representation of CommitSig. +// +// 1. first 6 bytes of signature +// 2. first 6 bytes of validator address +// 3. block ID flag +// 4. timestamp +func (cs CommitSig) String() string { + return fmt.Sprintf("CommitSig{%X by %X on %v @ %s}", + tmbytes.Fingerprint(cs.Signature), + tmbytes.Fingerprint(cs.ValidatorAddress), + cs.BlockIDFlag, + CanonicalTime(cs.Timestamp)) +} + +// BlockID returns the Commit's BlockID if CommitSig indicates signing, +// otherwise - empty BlockID. +func (cs CommitSig) BlockID(commitBlockID BlockID) BlockID { + var blockID BlockID + switch cs.BlockIDFlag { + case BlockIDFlagAbsent: + blockID = BlockID{} + case BlockIDFlagCommit: + blockID = commitBlockID + case BlockIDFlagNil: + blockID = BlockID{} + default: + panic(fmt.Sprintf("Unknown BlockIDFlag: %v", cs.BlockIDFlag)) + } + return blockID +} + +// ValidateBasic performs basic validation. +func (cs CommitSig) ValidateBasic() error { + switch cs.BlockIDFlag { + case BlockIDFlagAbsent: + case BlockIDFlagCommit: + case BlockIDFlagNil: + default: + return fmt.Errorf("unknown BlockIDFlag: %v", cs.BlockIDFlag) + } + + switch cs.BlockIDFlag { + case BlockIDFlagAbsent: + if len(cs.ValidatorAddress) != 0 { + return errors.New("validator address is present") + } + if !cs.Timestamp.IsZero() { + return errors.New("time is present") + } + if len(cs.Signature) != 0 { + return errors.New("signature is present") + } + default: + if len(cs.ValidatorAddress) != crypto.AddressSize { + return fmt.Errorf("expected ValidatorAddress size to be %d bytes, got %d bytes", + crypto.AddressSize, + len(cs.ValidatorAddress), + ) + } + // NOTE: Timestamp validation is subtle and handled elsewhere. + if len(cs.Signature) == 0 { + return errors.New("signature is missing") + } + if len(cs.Signature) > MaxSignatureSize { + return fmt.Errorf("signature is too big (max: %d)", MaxSignatureSize) + } + } + + return nil +} + +// ToProto converts CommitSig to protobuf +func (cs *CommitSig) ToProto() *tmproto.CommitSig { + if cs == nil { + return nil + } + + return &tmproto.CommitSig{ + BlockIdFlag: tmproto.BlockIDFlag(cs.BlockIDFlag), + ValidatorAddress: cs.ValidatorAddress, + Timestamp: cs.Timestamp, + Signature: cs.Signature, + } +} + +// FromProto sets a protobuf CommitSig to the given pointer. +// It returns an error if the CommitSig is invalid. +func (cs *CommitSig) FromProto(csp tmproto.CommitSig) error { + + cs.BlockIDFlag = BlockIDFlag(csp.BlockIdFlag) + cs.ValidatorAddress = csp.ValidatorAddress + cs.Timestamp = csp.Timestamp + cs.Signature = csp.Signature + + return cs.ValidateBasic() +} + +//------------------------------------- + +// 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 signatures are in order of address to preserve the bonded + // ValidatorSet order. + // Any peer with a block can gossip signatures by index with a peer without + // recalculating the active ValidatorSet. + Height int64 `json:"height"` + Round int32 `json:"round"` + BlockID BlockID `json:"block_id"` + Signatures []CommitSig `json:"signatures"` + + // Memoized in first call to corresponding method. + // NOTE: can't memoize in constructor because constructor isn't used for + // unmarshaling. + hash tmbytes.HexBytes + bitArray *bits.BitArray +} + +// NewCommit returns a new Commit. +func NewCommit(height int64, round int32, blockID BlockID, commitSigs []CommitSig) *Commit { + return &Commit{ + Height: height, + Round: round, + BlockID: blockID, + Signatures: commitSigs, + } +} + +// Type returns the vote type of the commit, which is always VoteTypePrecommit +// Implements VoteSetReader. +func (commit *Commit) Type() byte { + return byte(tmproto.PrecommitType) +} + +// GetHeight returns height of the commit. +// Implements VoteSetReader. +func (commit *Commit) GetHeight() int64 { + return commit.Height +} + +// GetRound returns height of the commit. +// Implements VoteSetReader. +func (commit *Commit) GetRound() int32 { + return commit.Round +} + +// Size returns the number of signatures in the commit. +// Implements VoteSetReader. +func (commit *Commit) Size() int { + if commit == nil { + return 0 + } + return len(commit.Signatures) +} + +// ClearCache removes the saved hash. This is predominantly used for testing. +func (commit *Commit) ClearCache() { + commit.hash = nil + commit.bitArray = nil +} + +// BitArray returns a BitArray of which validators voted for BlockID or nil in this commit. +// Implements VoteSetReader. +func (commit *Commit) BitArray() *bits.BitArray { + if commit.bitArray == nil { + commit.bitArray = bits.NewBitArray(len(commit.Signatures)) + for i, commitSig := range commit.Signatures { + // TODO: need to check the BlockID otherwise we could be counting conflicts, + // not just the one with +2/3 ! + commit.bitArray.SetIndex(i, !commitSig.Absent()) + } + } + return commit.bitArray +} + +// IsCommit returns true if there is at least one signature. +// Implements VoteSetReader. +func (commit *Commit) IsCommit() bool { + return len(commit.Signatures) != 0 +} + +// ValidateBasic performs basic validation that doesn't involve state data. +// Does not actually check the cryptographic signatures. +func (commit *Commit) ValidateBasic() error { + if commit.Height < 0 { + return errors.New("negative Height") + } + if commit.Round < 0 { + return errors.New("negative Round") + } + + if commit.Height >= 1 { + if commit.BlockID.IsZero() { + return errors.New("commit cannot be for nil block") + } + + if len(commit.Signatures) == 0 { + return errors.New("no signatures in commit") + } + for i, commitSig := range commit.Signatures { + if err := commitSig.ValidateBasic(); err != nil { + return fmt.Errorf("wrong CommitSig #%d: %v", i, err) + } + } + } + return nil +} + +// Hash returns the hash of the commit +func (commit *Commit) Hash() tmbytes.HexBytes { + if commit == nil { + return nil + } + if commit.hash == nil { + bs := make([][]byte, len(commit.Signatures)) + for i, commitSig := range commit.Signatures { + pbcs := commitSig.ToProto() + bz, err := pbcs.Marshal() + if err != nil { + panic(err) + } + + bs[i] = bz + } + commit.hash = merkle.HashFromByteSlices(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" + } + commitSigStrings := make([]string, len(commit.Signatures)) + for i, commitSig := range commit.Signatures { + commitSigStrings[i] = commitSig.String() + } + return fmt.Sprintf(`Commit{ +%s Height: %d +%s Round: %d +%s BlockID: %v +%s Signatures: +%s %v +%s}#%v`, + indent, commit.Height, + indent, commit.Round, + indent, commit.BlockID, + indent, + indent, strings.Join(commitSigStrings, "\n"+indent+" "), + indent, commit.hash) +} + +// ToProto converts Commit to protobuf +func (commit *Commit) ToProto() *tmproto.Commit { + if commit == nil { + return nil + } + + c := new(tmproto.Commit) + sigs := make([]tmproto.CommitSig, len(commit.Signatures)) + for i := range commit.Signatures { + sigs[i] = *commit.Signatures[i].ToProto() + } + c.Signatures = sigs + + c.Height = commit.Height + c.Round = commit.Round + c.BlockID = commit.BlockID.ToProto() + + return c +} + +// FromProto sets a protobuf Commit to the given pointer. +// It returns an error if the commit is invalid. +func CommitFromProto(cp *tmproto.Commit) (*Commit, error) { + if cp == nil { + return nil, errors.New("nil Commit") + } + + var ( + commit = new(Commit) + ) + + bi, err := BlockIDFromProto(&cp.BlockID) + if err != nil { + return nil, err + } + + sigs := make([]CommitSig, len(cp.Signatures)) + for i := range cp.Signatures { + if err := sigs[i].FromProto(cp.Signatures[i]); err != nil { + return nil, err + } + } + commit.Signatures = sigs + + commit.Height = cp.Height + commit.Round = cp.Round + commit.BlockID = *bi + + return commit, commit.ValidateBasic() +} diff --git a/pkg/meta/commit_test.go b/pkg/meta/commit_test.go new file mode 100644 index 000000000..dc6fb14f4 --- /dev/null +++ b/pkg/meta/commit_test.go @@ -0,0 +1,284 @@ +package meta_test + +import ( + "math" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/crypto/tmhash" + test "github.com/tendermint/tendermint/internal/test/factory" + "github.com/tendermint/tendermint/libs/bits" + "github.com/tendermint/tendermint/pkg/consensus" + "github.com/tendermint/tendermint/pkg/meta" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" +) + +func TestCommit(t *testing.T) { + lastID := test.MakeBlockID() + h := int64(3) + voteSet, _, vals := test.RandVoteSet(h-1, 1, tmproto.PrecommitType, 10, 1) + commit, err := test.MakeCommit(lastID, h-1, 1, voteSet, vals, time.Now()) + require.NoError(t, err) + + assert.Equal(t, h-1, commit.Height) + assert.EqualValues(t, 1, commit.Round) + assert.Equal(t, tmproto.PrecommitType, tmproto.SignedMsgType(commit.Type())) + if commit.Size() <= 0 { + t.Fatalf("commit %v has a zero or negative size: %d", commit, commit.Size()) + } + + require.NotNil(t, commit.BitArray()) + assert.Equal(t, bits.NewBitArray(10).Size(), commit.BitArray().Size()) + + assert.Equal(t, voteSet.GetByIndex(0), consensus.GetVoteFromCommit(commit, 0)) + assert.True(t, commit.IsCommit()) +} + +func TestCommitValidateBasic(t *testing.T) { + testCases := []struct { + testName string + malleateCommit func(*meta.Commit) + expectErr bool + }{ + {"Random Commit", func(com *meta.Commit) {}, false}, + {"Incorrect signature", func(com *meta.Commit) { com.Signatures[0].Signature = []byte{0} }, false}, + {"Incorrect height", func(com *meta.Commit) { com.Height = int64(-100) }, true}, + {"Incorrect round", func(com *meta.Commit) { com.Round = -100 }, true}, + } + for _, tc := range testCases { + tc := tc + t.Run(tc.testName, func(t *testing.T) { + com := test.MakeRandomCommit(time.Now()) + tc.malleateCommit(com) + assert.Equal(t, tc.expectErr, com.ValidateBasic() != nil, "Validate Basic had an unexpected result") + }) + } +} + +func TestCommit_ValidateBasic(t *testing.T) { + testCases := []struct { + name string + commit *meta.Commit + expectErr bool + errString string + }{ + { + "invalid height", + &meta.Commit{Height: -1}, + true, "negative Height", + }, + { + "invalid round", + &meta.Commit{Height: 1, Round: -1}, + true, "negative Round", + }, + { + "invalid block ID", + &meta.Commit{ + Height: 1, + Round: 1, + BlockID: meta.BlockID{}, + }, + true, "commit cannot be for nil block", + }, + { + "no signatures", + &meta.Commit{ + Height: 1, + Round: 1, + BlockID: meta.BlockID{ + Hash: make([]byte, tmhash.Size), + PartSetHeader: meta.PartSetHeader{ + Hash: make([]byte, tmhash.Size), + }, + }, + }, + true, "no signatures in commit", + }, + { + "invalid signature", + &meta.Commit{ + Height: 1, + Round: 1, + BlockID: meta.BlockID{ + Hash: make([]byte, tmhash.Size), + PartSetHeader: meta.PartSetHeader{ + Hash: make([]byte, tmhash.Size), + }, + }, + Signatures: []meta.CommitSig{ + { + BlockIDFlag: meta.BlockIDFlagCommit, + ValidatorAddress: make([]byte, crypto.AddressSize), + Signature: make([]byte, meta.MaxSignatureSize+1), + }, + }, + }, + true, "wrong CommitSig", + }, + { + "valid commit", + &meta.Commit{ + Height: 1, + Round: 1, + BlockID: meta.BlockID{ + Hash: make([]byte, tmhash.Size), + PartSetHeader: meta.PartSetHeader{ + Hash: make([]byte, tmhash.Size), + }, + }, + Signatures: []meta.CommitSig{ + { + BlockIDFlag: meta.BlockIDFlagCommit, + ValidatorAddress: make([]byte, crypto.AddressSize), + Signature: make([]byte, meta.MaxSignatureSize), + }, + }, + }, + false, "", + }, + } + + for _, tc := range testCases { + tc := tc + + t.Run(tc.name, func(t *testing.T) { + err := tc.commit.ValidateBasic() + if tc.expectErr { + require.Error(t, err) + require.Contains(t, err.Error(), tc.errString) + } else { + require.NoError(t, err) + } + }) + } +} + +func TestMaxCommitBytes(t *testing.T) { + // time is varint encoded so need to pick the max. + // year int, month Month, day, hour, min, sec, nsec int, loc *Location + timestamp := time.Date(math.MaxInt64, 0, 0, 0, 0, 0, math.MaxInt64, time.UTC) + + cs := meta.CommitSig{ + BlockIDFlag: meta.BlockIDFlagNil, + ValidatorAddress: crypto.AddressHash([]byte("validator_address")), + Timestamp: timestamp, + Signature: crypto.CRandBytes(meta.MaxSignatureSize), + } + + pbSig := cs.ToProto() + // test that a single commit sig doesn't exceed max commit sig bytes + assert.EqualValues(t, meta.MaxCommitSigBytes, pbSig.Size()) + + // check size with a single commit + commit := &meta.Commit{ + Height: math.MaxInt64, + Round: math.MaxInt32, + BlockID: meta.BlockID{ + Hash: tmhash.Sum([]byte("blockID_hash")), + PartSetHeader: meta.PartSetHeader{ + Total: math.MaxInt32, + Hash: tmhash.Sum([]byte("blockID_part_set_header_hash")), + }, + }, + Signatures: []meta.CommitSig{cs}, + } + + pb := commit.ToProto() + + assert.EqualValues(t, meta.MaxCommitBytes(1), int64(pb.Size())) + + // check the upper bound of the commit size + for i := 1; i < consensus.MaxVotesCount; i++ { + commit.Signatures = append(commit.Signatures, cs) + } + + pb = commit.ToProto() + + assert.EqualValues(t, meta.MaxCommitBytes(consensus.MaxVotesCount), int64(pb.Size())) +} + +func TestCommitSig_ValidateBasic(t *testing.T) { + testCases := []struct { + name string + cs meta.CommitSig + expectErr bool + errString string + }{ + { + "invalid ID flag", + meta.CommitSig{BlockIDFlag: meta.BlockIDFlag(0xFF)}, + true, "unknown BlockIDFlag", + }, + { + "BlockIDFlagAbsent validator address present", + meta.CommitSig{BlockIDFlag: meta.BlockIDFlagAbsent, ValidatorAddress: crypto.Address("testaddr")}, + true, "validator address is present", + }, + { + "BlockIDFlagAbsent timestamp present", + meta.CommitSig{BlockIDFlag: meta.BlockIDFlagAbsent, Timestamp: time.Now().UTC()}, + true, "time is present", + }, + { + "BlockIDFlagAbsent signatures present", + meta.CommitSig{BlockIDFlag: meta.BlockIDFlagAbsent, Signature: []byte{0xAA}}, + true, "signature is present", + }, + { + "BlockIDFlagAbsent valid BlockIDFlagAbsent", + meta.CommitSig{BlockIDFlag: meta.BlockIDFlagAbsent}, + false, "", + }, + { + "non-BlockIDFlagAbsent invalid validator address", + meta.CommitSig{BlockIDFlag: meta.BlockIDFlagCommit, ValidatorAddress: make([]byte, 1)}, + true, "expected ValidatorAddress size", + }, + { + "non-BlockIDFlagAbsent invalid signature (zero)", + meta.CommitSig{ + BlockIDFlag: meta.BlockIDFlagCommit, + ValidatorAddress: make([]byte, crypto.AddressSize), + Signature: make([]byte, 0), + }, + true, "signature is missing", + }, + { + "non-BlockIDFlagAbsent invalid signature (too large)", + meta.CommitSig{ + BlockIDFlag: meta.BlockIDFlagCommit, + ValidatorAddress: make([]byte, crypto.AddressSize), + Signature: make([]byte, meta.MaxSignatureSize+1), + }, + true, "signature is too big", + }, + { + "non-BlockIDFlagAbsent valid", + meta.CommitSig{ + BlockIDFlag: meta.BlockIDFlagCommit, + ValidatorAddress: make([]byte, crypto.AddressSize), + Signature: make([]byte, meta.MaxSignatureSize), + }, + false, "", + }, + } + + for _, tc := range testCases { + tc := tc + + t.Run(tc.name, func(t *testing.T) { + err := tc.cs.ValidateBasic() + if tc.expectErr { + require.Error(t, err) + require.Contains(t, err.Error(), tc.errString) + } else { + require.NoError(t, err) + } + }) + } +} diff --git a/pkg/meta/header.go b/pkg/meta/header.go new file mode 100644 index 000000000..35fe4833e --- /dev/null +++ b/pkg/meta/header.go @@ -0,0 +1,293 @@ +package meta + +import ( + "errors" + "fmt" + "time" + + gogotypes "github.com/gogo/protobuf/types" + + "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/crypto/merkle" + tmbytes "github.com/tendermint/tendermint/libs/bytes" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + "github.com/tendermint/tendermint/version" +) + +const ( + // MaxHeaderBytes is a maximum header size. + // NOTE: Because app hash can be of arbitrary size, the header is therefore not + // capped in size and thus this number should be seen as a soft max + MaxHeaderBytes int64 = 626 + + // MaxOverheadForBlock - maximum overhead to encode a block (up to + // MaxBlockSizeBytes in size) not including it's parts except Data. + // This means it also excludes the overhead for individual transactions. + // + // Uvarint length of MaxBlockSizeBytes: 4 bytes + // 2 fields (2 embedded): 2 bytes + // Uvarint length of Data.Txs: 4 bytes + // Data.Txs field: 1 byte + MaxOverheadForBlock int64 = 11 + + // MaxChainIDLen is a maximum length of the chain ID. + MaxChainIDLen = 50 + + // MaxBlockSizeBytes is the maximum permitted size of the blocks. + MaxBlockSizeBytes = 104857600 // 100MB +) + +// Header defines the structure of a Tendermint block header. +// NOTE: changes to the Header should be duplicated in: +// - header.Hash() +// - abci.Header +// - https://github.com/tendermint/spec/blob/master/spec/blockchain/blockchain.md +type Header struct { + // basic block info + Version version.Consensus `json:"version"` + ChainID string `json:"chain_id"` + Height int64 `json:"height"` + Time time.Time `json:"time"` + + // prev block info + LastBlockID BlockID `json:"last_block_id"` + + // hashes of block data + LastCommitHash tmbytes.HexBytes `json:"last_commit_hash"` // commit from validators from the last block + DataHash tmbytes.HexBytes `json:"data_hash"` // transactions + + // hashes from the app output from the prev block + ValidatorsHash tmbytes.HexBytes `json:"validators_hash"` // validators for the current block + NextValidatorsHash tmbytes.HexBytes `json:"next_validators_hash"` // validators for the next block + ConsensusHash tmbytes.HexBytes `json:"consensus_hash"` // consensus params for current block + AppHash tmbytes.HexBytes `json:"app_hash"` // state after txs from the previous block + // root hash of all results from the txs from the previous block + // see `deterministicResponseDeliverTx` to understand which parts of a tx is hashed into here + LastResultsHash tmbytes.HexBytes `json:"last_results_hash"` + + // consensus info + EvidenceHash tmbytes.HexBytes `json:"evidence_hash"` // evidence included in the block + ProposerAddress crypto.Address `json:"proposer_address"` // original proposer of the block +} + +// Populate the Header with state-derived data. +// Call this after MakeBlock to complete the Header. +func (h *Header) Populate( + version version.Consensus, chainID string, + timestamp time.Time, lastBlockID BlockID, + valHash, nextValHash []byte, + consensusHash, appHash, lastResultsHash []byte, + proposerAddress crypto.Address, +) { + h.Version = version + h.ChainID = chainID + h.Time = timestamp + h.LastBlockID = lastBlockID + h.ValidatorsHash = valHash + h.NextValidatorsHash = nextValHash + h.ConsensusHash = consensusHash + h.AppHash = appHash + h.LastResultsHash = lastResultsHash + h.ProposerAddress = proposerAddress +} + +// ValidateBasic performs stateless validation on a Header returning an error +// if any validation fails. +// +// NOTE: Timestamp validation is subtle and handled elsewhere. +func (h Header) ValidateBasic() error { + if h.Version.Block != version.BlockProtocol { + return fmt.Errorf("block protocol is incorrect: got: %d, want: %d ", h.Version.Block, version.BlockProtocol) + } + if len(h.ChainID) > MaxChainIDLen { + return fmt.Errorf("chainID is too long; got: %d, max: %d", len(h.ChainID), MaxChainIDLen) + } + + if h.Height < 0 { + return errors.New("negative Height") + } else if h.Height == 0 { + return errors.New("zero Height") + } + + if err := h.LastBlockID.ValidateBasic(); err != nil { + return fmt.Errorf("wrong LastBlockID: %w", err) + } + + if err := ValidateHash(h.LastCommitHash); err != nil { + return fmt.Errorf("wrong LastCommitHash: %v", err) + } + + if err := ValidateHash(h.DataHash); err != nil { + return fmt.Errorf("wrong DataHash: %v", err) + } + + if err := ValidateHash(h.EvidenceHash); err != nil { + return fmt.Errorf("wrong EvidenceHash: %v", err) + } + + if len(h.ProposerAddress) != crypto.AddressSize { + return fmt.Errorf( + "invalid ProposerAddress length; got: %d, expected: %d", + len(h.ProposerAddress), crypto.AddressSize, + ) + } + + // Basic validation of hashes related to application data. + // Will validate fully against state in state#ValidateBlock. + if err := ValidateHash(h.ValidatorsHash); err != nil { + return fmt.Errorf("wrong ValidatorsHash: %v", err) + } + if err := ValidateHash(h.NextValidatorsHash); err != nil { + return fmt.Errorf("wrong NextValidatorsHash: %v", err) + } + if err := ValidateHash(h.ConsensusHash); err != nil { + return fmt.Errorf("wrong ConsensusHash: %v", err) + } + // NOTE: AppHash is arbitrary length + if err := ValidateHash(h.LastResultsHash); err != nil { + return fmt.Errorf("wrong LastResultsHash: %v", err) + } + + return nil +} + +// Hash returns the hash of the header. +// It computes a Merkle tree from the header fields +// ordered as they appear in the Header. +// Returns nil if ValidatorHash is missing, +// since a Header is not valid unless there is +// a ValidatorsHash (corresponding to the validator set). +func (h *Header) Hash() tmbytes.HexBytes { + if h == nil || len(h.ValidatorsHash) == 0 { + return nil + } + hpb := h.Version.ToProto() + hbz, err := hpb.Marshal() + if err != nil { + return nil + } + + pbt, err := gogotypes.StdTimeMarshal(h.Time) + if err != nil { + return nil + } + + pbbi := h.LastBlockID.ToProto() + bzbi, err := pbbi.Marshal() + if err != nil { + return nil + } + return merkle.HashFromByteSlices([][]byte{ + hbz, + CdcEncode(h.ChainID), + CdcEncode(h.Height), + pbt, + bzbi, + CdcEncode(h.LastCommitHash), + CdcEncode(h.DataHash), + CdcEncode(h.ValidatorsHash), + CdcEncode(h.NextValidatorsHash), + CdcEncode(h.ConsensusHash), + CdcEncode(h.AppHash), + CdcEncode(h.LastResultsHash), + CdcEncode(h.EvidenceHash), + CdcEncode(h.ProposerAddress), + }) +} + +// StringIndented returns an indented string representation of the header. +func (h *Header) StringIndented(indent string) string { + if h == nil { + return "nil-Header" + } + return fmt.Sprintf(`Header{ +%s Version: %v +%s ChainID: %v +%s Height: %v +%s Time: %v +%s LastBlockID: %v +%s LastCommit: %v +%s Data: %v +%s Validators: %v +%s NextValidators: %v +%s App: %v +%s Consensus: %v +%s Results: %v +%s Evidence: %v +%s Proposer: %v +%s}#%v`, + indent, h.Version, + indent, h.ChainID, + indent, h.Height, + indent, h.Time, + indent, h.LastBlockID, + indent, h.LastCommitHash, + indent, h.DataHash, + indent, h.ValidatorsHash, + indent, h.NextValidatorsHash, + indent, h.AppHash, + indent, h.ConsensusHash, + indent, h.LastResultsHash, + indent, h.EvidenceHash, + indent, h.ProposerAddress, + indent, h.Hash()) +} + +// ToProto converts Header to protobuf +func (h *Header) ToProto() *tmproto.Header { + if h == nil { + return nil + } + + return &tmproto.Header{ + Version: h.Version.ToProto(), + ChainID: h.ChainID, + Height: h.Height, + Time: h.Time, + LastBlockId: h.LastBlockID.ToProto(), + ValidatorsHash: h.ValidatorsHash, + NextValidatorsHash: h.NextValidatorsHash, + ConsensusHash: h.ConsensusHash, + AppHash: h.AppHash, + DataHash: h.DataHash, + EvidenceHash: h.EvidenceHash, + LastResultsHash: h.LastResultsHash, + LastCommitHash: h.LastCommitHash, + ProposerAddress: h.ProposerAddress, + } +} + +// FromProto sets a protobuf Header to the given pointer. +// It returns an error if the header is invalid. +func HeaderFromProto(ph *tmproto.Header) (Header, error) { + if ph == nil { + return Header{}, errors.New("nil Header") + } + + h := new(Header) + + bi, err := BlockIDFromProto(&ph.LastBlockId) + if err != nil { + return Header{}, err + } + + h.Version = version.Consensus{Block: ph.Version.Block, App: ph.Version.App} + h.ChainID = ph.ChainID + h.Height = ph.Height + h.Time = ph.Time + h.Height = ph.Height + h.LastBlockID = *bi + h.ValidatorsHash = ph.ValidatorsHash + h.NextValidatorsHash = ph.NextValidatorsHash + h.ConsensusHash = ph.ConsensusHash + h.AppHash = ph.AppHash + h.DataHash = ph.DataHash + h.EvidenceHash = ph.EvidenceHash + h.LastResultsHash = ph.LastResultsHash + h.LastCommitHash = ph.LastCommitHash + h.ProposerAddress = ph.ProposerAddress + + return *h, h.ValidateBasic() +} + +//------------------------------------- diff --git a/pkg/meta/header_test.go b/pkg/meta/header_test.go new file mode 100644 index 000000000..987dc8e89 --- /dev/null +++ b/pkg/meta/header_test.go @@ -0,0 +1,496 @@ +package meta_test + +import ( + "encoding/hex" + "math" + "reflect" + "testing" + "time" + + gogotypes "github.com/gogo/protobuf/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/crypto/merkle" + "github.com/tendermint/tendermint/crypto/tmhash" + test "github.com/tendermint/tendermint/internal/test/factory" + "github.com/tendermint/tendermint/libs/bytes" + "github.com/tendermint/tendermint/pkg/meta" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + tmversion "github.com/tendermint/tendermint/proto/tendermint/version" + "github.com/tendermint/tendermint/version" +) + +var nilBytes []byte + +func TestNilHeaderHashDoesntCrash(t *testing.T) { + assert.Equal(t, nilBytes, []byte((*meta.Header)(nil).Hash())) + assert.Equal(t, nilBytes, []byte((new(meta.Header)).Hash())) +} + +func TestHeaderHash(t *testing.T) { + testCases := []struct { + desc string + header *meta.Header + expectHash bytes.HexBytes + }{ + {"Generates expected hash", &meta.Header{ + Version: version.Consensus{Block: 1, App: 2}, + ChainID: "chainId", + Height: 3, + Time: time.Date(2019, 10, 13, 16, 14, 44, 0, time.UTC), + LastBlockID: test.MakeBlockIDWithHash(make([]byte, tmhash.Size)), + LastCommitHash: tmhash.Sum([]byte("last_commit_hash")), + DataHash: tmhash.Sum([]byte("data_hash")), + ValidatorsHash: tmhash.Sum([]byte("validators_hash")), + NextValidatorsHash: tmhash.Sum([]byte("next_validators_hash")), + ConsensusHash: tmhash.Sum([]byte("consensus_hash")), + AppHash: tmhash.Sum([]byte("app_hash")), + LastResultsHash: tmhash.Sum([]byte("last_results_hash")), + EvidenceHash: tmhash.Sum([]byte("evidence_hash")), + ProposerAddress: crypto.AddressHash([]byte("proposer_address")), + }, hexBytesFromString("F740121F553B5418C3EFBD343C2DBFE9E007BB67B0D020A0741374BAB65242A4")}, + {"nil header yields nil", nil, nil}, + {"nil ValidatorsHash yields nil", &meta.Header{ + Version: version.Consensus{Block: 1, App: 2}, + ChainID: "chainId", + Height: 3, + Time: time.Date(2019, 10, 13, 16, 14, 44, 0, time.UTC), + LastBlockID: test.MakeBlockIDWithHash(make([]byte, tmhash.Size)), + LastCommitHash: tmhash.Sum([]byte("last_commit_hash")), + DataHash: tmhash.Sum([]byte("data_hash")), + ValidatorsHash: nil, + NextValidatorsHash: tmhash.Sum([]byte("next_validators_hash")), + ConsensusHash: tmhash.Sum([]byte("consensus_hash")), + AppHash: tmhash.Sum([]byte("app_hash")), + LastResultsHash: tmhash.Sum([]byte("last_results_hash")), + EvidenceHash: tmhash.Sum([]byte("evidence_hash")), + ProposerAddress: crypto.AddressHash([]byte("proposer_address")), + }, nil}, + } + for _, tc := range testCases { + tc := tc + t.Run(tc.desc, func(t *testing.T) { + assert.Equal(t, tc.expectHash, tc.header.Hash()) + + // We also make sure that all fields are hashed in struct order, and that all + // fields in the test struct are non-zero. + if tc.header != nil && tc.expectHash != nil { + byteSlices := [][]byte{} + + s := reflect.ValueOf(*tc.header) + for i := 0; i < s.NumField(); i++ { + f := s.Field(i) + + assert.False(t, f.IsZero(), "Found zero-valued field %v", + s.Type().Field(i).Name) + + switch f := f.Interface().(type) { + case int64, bytes.HexBytes, string: + byteSlices = append(byteSlices, meta.CdcEncode(f)) + case time.Time: + bz, err := gogotypes.StdTimeMarshal(f) + require.NoError(t, err) + byteSlices = append(byteSlices, bz) + case version.Consensus: + pbc := tmversion.Consensus{ + Block: f.Block, + App: f.App, + } + bz, err := pbc.Marshal() + require.NoError(t, err) + byteSlices = append(byteSlices, bz) + case meta.BlockID: + pbbi := f.ToProto() + bz, err := pbbi.Marshal() + require.NoError(t, err) + byteSlices = append(byteSlices, bz) + default: + t.Errorf("unknown type %T", f) + } + } + assert.Equal(t, + bytes.HexBytes(merkle.HashFromByteSlices(byteSlices)), tc.header.Hash()) + } + }) + } +} + +func TestMaxHeaderBytes(t *testing.T) { + // Construct a UTF-8 string of MaxChainIDLen length using the supplementary + // characters. + // Each supplementary character takes 4 bytes. + // http://www.i18nguy.com/unicode/supplementary-test.html + maxChainID := "" + for i := 0; i < meta.MaxChainIDLen; i++ { + maxChainID += "𠜎" + } + + // time is varint encoded so need to pick the max. + // year int, month Month, day, hour, min, sec, nsec int, loc *Location + timestamp := time.Date(math.MaxInt64, 0, 0, 0, 0, 0, math.MaxInt64, time.UTC) + + h := meta.Header{ + Version: version.Consensus{Block: math.MaxInt64, App: math.MaxInt64}, + ChainID: maxChainID, + Height: math.MaxInt64, + Time: timestamp, + LastBlockID: meta.BlockID{make([]byte, tmhash.Size), meta.PartSetHeader{math.MaxInt32, make([]byte, tmhash.Size)}}, + LastCommitHash: tmhash.Sum([]byte("last_commit_hash")), + DataHash: tmhash.Sum([]byte("data_hash")), + ValidatorsHash: tmhash.Sum([]byte("validators_hash")), + NextValidatorsHash: tmhash.Sum([]byte("next_validators_hash")), + ConsensusHash: tmhash.Sum([]byte("consensus_hash")), + AppHash: tmhash.Sum([]byte("app_hash")), + LastResultsHash: tmhash.Sum([]byte("last_results_hash")), + EvidenceHash: tmhash.Sum([]byte("evidence_hash")), + ProposerAddress: crypto.AddressHash([]byte("proposer_address")), + } + + bz, err := h.ToProto().Marshal() + require.NoError(t, err) + + assert.EqualValues(t, meta.MaxHeaderBytes, int64(len(bz))) +} + +func randCommit(now time.Time) *meta.Commit { + lastID := test.MakeBlockID() + h := int64(3) + voteSet, _, vals := test.RandVoteSet(h-1, 1, tmproto.PrecommitType, 10, 1) + commit, err := test.MakeCommit(lastID, h-1, 1, voteSet, vals, now) + if err != nil { + panic(err) + } + return commit +} + +func hexBytesFromString(s string) bytes.HexBytes { + b, err := hex.DecodeString(s) + if err != nil { + panic(err) + } + return bytes.HexBytes(b) +} + +func TestHeader_ValidateBasic(t *testing.T) { + testCases := []struct { + name string + header meta.Header + expectErr bool + errString string + }{ + { + "invalid version block", + meta.Header{Version: version.Consensus{Block: version.BlockProtocol + 1}}, + true, "block protocol is incorrect", + }, + { + "invalid chain ID length", + meta.Header{ + Version: version.Consensus{Block: version.BlockProtocol}, + ChainID: string(make([]byte, meta.MaxChainIDLen+1)), + }, + true, "chainID is too long", + }, + { + "invalid height (negative)", + meta.Header{ + Version: version.Consensus{Block: version.BlockProtocol}, + ChainID: string(make([]byte, meta.MaxChainIDLen)), + Height: -1, + }, + true, "negative Height", + }, + { + "invalid height (zero)", + meta.Header{ + Version: version.Consensus{Block: version.BlockProtocol}, + ChainID: string(make([]byte, meta.MaxChainIDLen)), + Height: 0, + }, + true, "zero Height", + }, + { + "invalid block ID hash", + meta.Header{ + Version: version.Consensus{Block: version.BlockProtocol}, + ChainID: string(make([]byte, meta.MaxChainIDLen)), + Height: 1, + LastBlockID: meta.BlockID{ + Hash: make([]byte, tmhash.Size+1), + }, + }, + true, "wrong Hash", + }, + { + "invalid block ID parts header hash", + meta.Header{ + Version: version.Consensus{Block: version.BlockProtocol}, + ChainID: string(make([]byte, meta.MaxChainIDLen)), + Height: 1, + LastBlockID: meta.BlockID{ + Hash: make([]byte, tmhash.Size), + PartSetHeader: meta.PartSetHeader{ + Hash: make([]byte, tmhash.Size+1), + }, + }, + }, + true, "wrong PartSetHeader", + }, + { + "invalid last commit hash", + meta.Header{ + Version: version.Consensus{Block: version.BlockProtocol}, + ChainID: string(make([]byte, meta.MaxChainIDLen)), + Height: 1, + LastBlockID: meta.BlockID{ + Hash: make([]byte, tmhash.Size), + PartSetHeader: meta.PartSetHeader{ + Hash: make([]byte, tmhash.Size), + }, + }, + LastCommitHash: make([]byte, tmhash.Size+1), + }, + true, "wrong LastCommitHash", + }, + { + "invalid data hash", + meta.Header{ + Version: version.Consensus{Block: version.BlockProtocol}, + ChainID: string(make([]byte, meta.MaxChainIDLen)), + Height: 1, + LastBlockID: meta.BlockID{ + Hash: make([]byte, tmhash.Size), + PartSetHeader: meta.PartSetHeader{ + Hash: make([]byte, tmhash.Size), + }, + }, + LastCommitHash: make([]byte, tmhash.Size), + DataHash: make([]byte, tmhash.Size+1), + }, + true, "wrong DataHash", + }, + { + "invalid evidence hash", + meta.Header{ + Version: version.Consensus{Block: version.BlockProtocol}, + ChainID: string(make([]byte, meta.MaxChainIDLen)), + Height: 1, + LastBlockID: meta.BlockID{ + Hash: make([]byte, tmhash.Size), + PartSetHeader: meta.PartSetHeader{ + Hash: make([]byte, tmhash.Size), + }, + }, + LastCommitHash: make([]byte, tmhash.Size), + DataHash: make([]byte, tmhash.Size), + EvidenceHash: make([]byte, tmhash.Size+1), + }, + true, "wrong EvidenceHash", + }, + { + "invalid proposer address", + meta.Header{ + Version: version.Consensus{Block: version.BlockProtocol}, + ChainID: string(make([]byte, meta.MaxChainIDLen)), + Height: 1, + LastBlockID: meta.BlockID{ + Hash: make([]byte, tmhash.Size), + PartSetHeader: meta.PartSetHeader{ + Hash: make([]byte, tmhash.Size), + }, + }, + LastCommitHash: make([]byte, tmhash.Size), + DataHash: make([]byte, tmhash.Size), + EvidenceHash: make([]byte, tmhash.Size), + ProposerAddress: make([]byte, crypto.AddressSize+1), + }, + true, "invalid ProposerAddress length", + }, + { + "invalid validator hash", + meta.Header{ + Version: version.Consensus{Block: version.BlockProtocol}, + ChainID: string(make([]byte, meta.MaxChainIDLen)), + Height: 1, + LastBlockID: meta.BlockID{ + Hash: make([]byte, tmhash.Size), + PartSetHeader: meta.PartSetHeader{ + Hash: make([]byte, tmhash.Size), + }, + }, + LastCommitHash: make([]byte, tmhash.Size), + DataHash: make([]byte, tmhash.Size), + EvidenceHash: make([]byte, tmhash.Size), + ProposerAddress: make([]byte, crypto.AddressSize), + ValidatorsHash: make([]byte, tmhash.Size+1), + }, + true, "wrong ValidatorsHash", + }, + { + "invalid next validator hash", + meta.Header{ + Version: version.Consensus{Block: version.BlockProtocol}, + ChainID: string(make([]byte, meta.MaxChainIDLen)), + Height: 1, + LastBlockID: meta.BlockID{ + Hash: make([]byte, tmhash.Size), + PartSetHeader: meta.PartSetHeader{ + Hash: make([]byte, tmhash.Size), + }, + }, + LastCommitHash: make([]byte, tmhash.Size), + DataHash: make([]byte, tmhash.Size), + EvidenceHash: make([]byte, tmhash.Size), + ProposerAddress: make([]byte, crypto.AddressSize), + ValidatorsHash: make([]byte, tmhash.Size), + NextValidatorsHash: make([]byte, tmhash.Size+1), + }, + true, "wrong NextValidatorsHash", + }, + { + "invalid consensus hash", + meta.Header{ + Version: version.Consensus{Block: version.BlockProtocol}, + ChainID: string(make([]byte, meta.MaxChainIDLen)), + Height: 1, + LastBlockID: meta.BlockID{ + Hash: make([]byte, tmhash.Size), + PartSetHeader: meta.PartSetHeader{ + Hash: make([]byte, tmhash.Size), + }, + }, + LastCommitHash: make([]byte, tmhash.Size), + DataHash: make([]byte, tmhash.Size), + EvidenceHash: make([]byte, tmhash.Size), + ProposerAddress: make([]byte, crypto.AddressSize), + ValidatorsHash: make([]byte, tmhash.Size), + NextValidatorsHash: make([]byte, tmhash.Size), + ConsensusHash: make([]byte, tmhash.Size+1), + }, + true, "wrong ConsensusHash", + }, + { + "invalid last results hash", + meta.Header{ + Version: version.Consensus{Block: version.BlockProtocol}, + ChainID: string(make([]byte, meta.MaxChainIDLen)), + Height: 1, + LastBlockID: meta.BlockID{ + Hash: make([]byte, tmhash.Size), + PartSetHeader: meta.PartSetHeader{ + Hash: make([]byte, tmhash.Size), + }, + }, + LastCommitHash: make([]byte, tmhash.Size), + DataHash: make([]byte, tmhash.Size), + EvidenceHash: make([]byte, tmhash.Size), + ProposerAddress: make([]byte, crypto.AddressSize), + ValidatorsHash: make([]byte, tmhash.Size), + NextValidatorsHash: make([]byte, tmhash.Size), + ConsensusHash: make([]byte, tmhash.Size), + LastResultsHash: make([]byte, tmhash.Size+1), + }, + true, "wrong LastResultsHash", + }, + { + "valid header", + meta.Header{ + Version: version.Consensus{Block: version.BlockProtocol}, + ChainID: string(make([]byte, meta.MaxChainIDLen)), + Height: 1, + LastBlockID: meta.BlockID{ + Hash: make([]byte, tmhash.Size), + PartSetHeader: meta.PartSetHeader{ + Hash: make([]byte, tmhash.Size), + }, + }, + LastCommitHash: make([]byte, tmhash.Size), + DataHash: make([]byte, tmhash.Size), + EvidenceHash: make([]byte, tmhash.Size), + ProposerAddress: make([]byte, crypto.AddressSize), + ValidatorsHash: make([]byte, tmhash.Size), + NextValidatorsHash: make([]byte, tmhash.Size), + ConsensusHash: make([]byte, tmhash.Size), + LastResultsHash: make([]byte, tmhash.Size), + }, + false, "", + }, + } + + for _, tc := range testCases { + tc := tc + + t.Run(tc.name, func(t *testing.T) { + err := tc.header.ValidateBasic() + if tc.expectErr { + require.Error(t, err) + require.Contains(t, err.Error(), tc.errString) + } else { + require.NoError(t, err) + } + }) + } +} + +func TestHeaderProto(t *testing.T) { + h1 := test.MakeRandomHeader() + tc := []struct { + msg string + h1 *meta.Header + expPass bool + }{ + {"success", h1, true}, + {"failure empty Header", &meta.Header{}, false}, + } + + for _, tt := range tc { + tt := tt + t.Run(tt.msg, func(t *testing.T) { + pb := tt.h1.ToProto() + h, err := meta.HeaderFromProto(pb) + if tt.expPass { + require.NoError(t, err, tt.msg) + require.Equal(t, tt.h1, &h, tt.msg) + } else { + require.Error(t, err, tt.msg) + } + + }) + } +} + +func TestHeaderHashVector(t *testing.T) { + chainID := "test" + h := meta.Header{ + Version: version.Consensus{Block: 1, App: 1}, + ChainID: chainID, + Height: 50, + Time: time.Date(math.MaxInt64, 0, 0, 0, 0, 0, math.MaxInt64, time.UTC), + LastBlockID: meta.BlockID{}, + LastCommitHash: []byte("f2564c78071e26643ae9b3e2a19fa0dc10d4d9e873aa0be808660123f11a1e78"), + DataHash: []byte("f2564c78071e26643ae9b3e2a19fa0dc10d4d9e873aa0be808660123f11a1e78"), + ValidatorsHash: []byte("f2564c78071e26643ae9b3e2a19fa0dc10d4d9e873aa0be808660123f11a1e78"), + NextValidatorsHash: []byte("f2564c78071e26643ae9b3e2a19fa0dc10d4d9e873aa0be808660123f11a1e78"), + ConsensusHash: []byte("f2564c78071e26643ae9b3e2a19fa0dc10d4d9e873aa0be808660123f11a1e78"), + AppHash: []byte("f2564c78071e26643ae9b3e2a19fa0dc10d4d9e873aa0be808660123f11a1e78"), + + LastResultsHash: []byte("f2564c78071e26643ae9b3e2a19fa0dc10d4d9e873aa0be808660123f11a1e78"), + + EvidenceHash: []byte("f2564c78071e26643ae9b3e2a19fa0dc10d4d9e873aa0be808660123f11a1e78"), + ProposerAddress: []byte("2915b7b15f979e48ebc61774bb1d86ba3136b7eb"), + } + + testCases := []struct { + header meta.Header + expBytes string + }{ + {header: h, expBytes: "87b6117ac7f827d656f178a3d6d30b24b205db2b6a3a053bae8baf4618570bfc"}, + } + + for _, tc := range testCases { + hash := tc.header.Hash() + require.Equal(t, tc.expBytes, hex.EncodeToString(hash)) + } +} diff --git a/pkg/meta/id.go b/pkg/meta/id.go new file mode 100644 index 000000000..fab38a30f --- /dev/null +++ b/pkg/meta/id.go @@ -0,0 +1,124 @@ +package meta + +import ( + "bytes" + "errors" + "fmt" + + "github.com/tendermint/tendermint/crypto/tmhash" + tmbytes "github.com/tendermint/tendermint/libs/bytes" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" +) + +// BlockID +type BlockID struct { + Hash tmbytes.HexBytes `json:"hash"` + PartSetHeader PartSetHeader `json:"parts"` +} + +// BlockIDFlag indicates which BlockID the signature is for. +type BlockIDFlag byte + +const ( + // BlockIDFlagAbsent - no vote was received from a validator. + BlockIDFlagAbsent BlockIDFlag = iota + 1 + // BlockIDFlagCommit - voted for the Commit.BlockID. + BlockIDFlagCommit + // BlockIDFlagNil - voted for nil. + BlockIDFlagNil +) + +// 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.PartSetHeader.Equals(other.PartSetHeader) +} + +// Key returns a machine-readable string representation of the BlockID +func (blockID BlockID) Key() string { + pbph := blockID.PartSetHeader.ToProto() + bz, err := pbph.Marshal() + if err != nil { + panic(err) + } + + return fmt.Sprint(string(blockID.Hash), string(bz)) +} + +// ValidateBasic performs basic validation. +func (blockID BlockID) ValidateBasic() error { + // Hash can be empty in case of POLBlockID in Proposal. + if err := ValidateHash(blockID.Hash); err != nil { + return fmt.Errorf("wrong Hash: %w", err) + } + if err := blockID.PartSetHeader.ValidateBasic(); err != nil { + return fmt.Errorf("wrong PartSetHeader: %w", err) + } + return nil +} + +// IsZero returns true if this is the BlockID of a nil block. +func (blockID BlockID) IsZero() bool { + return len(blockID.Hash) == 0 && + blockID.PartSetHeader.IsZero() +} + +// IsComplete returns true if this is a valid BlockID of a non-nil block. +func (blockID BlockID) IsComplete() bool { + return len(blockID.Hash) == tmhash.Size && + blockID.PartSetHeader.Total > 0 && + len(blockID.PartSetHeader.Hash) == tmhash.Size +} + +// String returns a human readable string representation of the BlockID. +// +// 1. hash +// 2. part set header +// +// See PartSetHeader#String +func (blockID BlockID) String() string { + return fmt.Sprintf(`%v:%v`, blockID.Hash, blockID.PartSetHeader) +} + +// ToProto converts BlockID to protobuf +func (blockID *BlockID) ToProto() tmproto.BlockID { + if blockID == nil { + return tmproto.BlockID{} + } + + return tmproto.BlockID{ + Hash: blockID.Hash, + PartSetHeader: blockID.PartSetHeader.ToProto(), + } +} + +// FromProto sets a protobuf BlockID to the given pointer. +// It returns an error if the block id is invalid. +func BlockIDFromProto(bID *tmproto.BlockID) (*BlockID, error) { + if bID == nil { + return nil, errors.New("nil BlockID") + } + + blockID := new(BlockID) + ph, err := PartSetHeaderFromProto(&bID.PartSetHeader) + if err != nil { + return nil, err + } + + blockID.PartSetHeader = *ph + blockID.Hash = bID.Hash + + return blockID, blockID.ValidateBasic() +} + +// ValidateHash returns an error if the hash is not empty, but its +// size != tmhash.Size. +func ValidateHash(h []byte) error { + if len(h) > 0 && len(h) != tmhash.Size { + return fmt.Errorf("expected size to be %d bytes, got %d bytes", + tmhash.Size, + len(h), + ) + } + return nil +} diff --git a/pkg/meta/id_test.go b/pkg/meta/id_test.go new file mode 100644 index 000000000..6220e6053 --- /dev/null +++ b/pkg/meta/id_test.go @@ -0,0 +1,91 @@ +package meta_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + test "github.com/tendermint/tendermint/internal/test/factory" + "github.com/tendermint/tendermint/libs/bytes" + "github.com/tendermint/tendermint/pkg/meta" +) + +func TestBlockIDEquals(t *testing.T) { + var ( + blockID = meta.BlockID{[]byte("hash"), meta.PartSetHeader{2, []byte("part_set_hash")}} + blockIDDuplicate = meta.BlockID{[]byte("hash"), meta.PartSetHeader{2, []byte("part_set_hash")}} + blockIDDifferent = meta.BlockID{[]byte("different_hash"), meta.PartSetHeader{2, []byte("part_set_hash")}} + blockIDEmpty = meta.BlockID{} + ) + + assert.True(t, blockID.Equals(blockIDDuplicate)) + assert.False(t, blockID.Equals(blockIDDifferent)) + assert.False(t, blockID.Equals(blockIDEmpty)) + assert.True(t, blockIDEmpty.Equals(blockIDEmpty)) + assert.False(t, blockIDEmpty.Equals(blockIDDifferent)) +} + +func TestBlockIDValidateBasic(t *testing.T) { + validBlockID := meta.BlockID{ + Hash: bytes.HexBytes{}, + PartSetHeader: meta.PartSetHeader{ + Total: 1, + Hash: bytes.HexBytes{}, + }, + } + + invalidBlockID := meta.BlockID{ + Hash: []byte{0}, + PartSetHeader: meta.PartSetHeader{ + Total: 1, + Hash: []byte{0}, + }, + } + + testCases := []struct { + testName string + blockIDHash bytes.HexBytes + blockIDPartSetHeader meta.PartSetHeader + expectErr bool + }{ + {"Valid BlockID", validBlockID.Hash, validBlockID.PartSetHeader, false}, + {"Invalid BlockID", invalidBlockID.Hash, validBlockID.PartSetHeader, true}, + {"Invalid BlockID", validBlockID.Hash, invalidBlockID.PartSetHeader, true}, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.testName, func(t *testing.T) { + blockID := meta.BlockID{ + Hash: tc.blockIDHash, + PartSetHeader: tc.blockIDPartSetHeader, + } + assert.Equal(t, tc.expectErr, blockID.ValidateBasic() != nil, "Validate Basic had an unexpected result") + }) + } +} + +func TestBlockIDProtoBuf(t *testing.T) { + blockID := test.MakeBlockIDWithHash([]byte("hash")) + testCases := []struct { + msg string + bid1 *meta.BlockID + expPass bool + }{ + {"success", &blockID, true}, + {"success empty", &meta.BlockID{}, true}, + {"failure BlockID nil", nil, false}, + } + for _, tc := range testCases { + protoBlockID := tc.bid1.ToProto() + + bi, err := meta.BlockIDFromProto(&protoBlockID) + if tc.expPass { + require.NoError(t, err) + require.Equal(t, tc.bid1, bi, tc.msg) + } else { + require.NotEqual(t, tc.bid1, bi, tc.msg) + } + } +} diff --git a/pkg/block/part_set.go b/pkg/meta/part_set.go similarity index 97% rename from pkg/block/part_set.go rename to pkg/meta/part_set.go index 0c4b08382..9bca18844 100644 --- a/pkg/block/part_set.go +++ b/pkg/meta/part_set.go @@ -1,4 +1,4 @@ -package block +package meta import ( "bytes" @@ -20,6 +20,14 @@ var ( ErrPartSetInvalidProof = errors.New("error part set invalid proof") ) +const ( + // BlockPartSizeBytes is the size of one block part. + BlockPartSizeBytes uint32 = 65536 // 64kB + + // MaxBlockPartsCount is the maximum number of block parts. + MaxBlockPartsCount = (MaxBlockSizeBytes / BlockPartSizeBytes) + 1 +) + type Part struct { Index uint32 `json:"index"` Bytes tmbytes.HexBytes `json:"bytes"` diff --git a/pkg/block/part_set_test.go b/pkg/meta/part_set_test.go similarity index 99% rename from pkg/block/part_set_test.go rename to pkg/meta/part_set_test.go index 8357c5e28..d081396d3 100644 --- a/pkg/block/part_set_test.go +++ b/pkg/meta/part_set_test.go @@ -1,4 +1,4 @@ -package block +package meta import ( "io/ioutil" diff --git a/pkg/meta/utils.go b/pkg/meta/utils.go new file mode 100644 index 000000000..dd0fabbe2 --- /dev/null +++ b/pkg/meta/utils.go @@ -0,0 +1,75 @@ +package meta + +import ( + "reflect" + + gogotypes "github.com/gogo/protobuf/types" + + tmbytes "github.com/tendermint/tendermint/libs/bytes" +) + +// Go lacks a simple and safe way to see if something is a typed nil. +// See: +// - https://dave.cheney.net/2017/08/09/typed-nils-in-go-2 +// - https://groups.google.com/forum/#!topic/golang-nuts/wnH302gBa4I/discussion +// - https://github.com/golang/go/issues/21538 +func isTypedNil(o interface{}) bool { + rv := reflect.ValueOf(o) + switch rv.Kind() { + case reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr, reflect.Slice: + return rv.IsNil() + default: + return false + } +} + +// Returns true if it has zero length. +func isEmpty(o interface{}) bool { + rv := reflect.ValueOf(o) + switch rv.Kind() { + case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice, reflect.String: + return rv.Len() == 0 + default: + return false + } +} + +// CdcEncode returns nil if the input is nil, otherwise returns +// proto.Marshal(Value{Value: item}) +func CdcEncode(item interface{}) []byte { + if item != nil && !isTypedNil(item) && !isEmpty(item) { + switch item := item.(type) { + case string: + i := gogotypes.StringValue{ + Value: item, + } + bz, err := i.Marshal() + if err != nil { + return nil + } + return bz + case int64: + i := gogotypes.Int64Value{ + Value: item, + } + bz, err := i.Marshal() + if err != nil { + return nil + } + return bz + case tmbytes.HexBytes: + i := gogotypes.BytesValue{ + Value: item, + } + bz, err := i.Marshal() + if err != nil { + return nil + } + return bz + default: + return nil + } + } + + return nil +} diff --git a/types/errors_p2p.go b/pkg/p2p/errors.go similarity index 97% rename from types/errors_p2p.go rename to pkg/p2p/errors.go index ab166d7d3..fbdac7c54 100644 --- a/types/errors_p2p.go +++ b/pkg/p2p/errors.go @@ -1,4 +1,4 @@ -package types +package p2p import ( "fmt" diff --git a/types/netaddress.go b/pkg/p2p/netaddress.go similarity index 99% rename from types/netaddress.go rename to pkg/p2p/netaddress.go index bc074dca6..3c9239c75 100644 --- a/types/netaddress.go +++ b/pkg/p2p/netaddress.go @@ -2,7 +2,7 @@ // Originally Copyright (c) 2013-2014 Conformal Systems LLC. // https://github.com/conformal/btcd/blob/master/LICENSE -package types +package p2p import ( "errors" diff --git a/types/netaddress_test.go b/pkg/p2p/netaddress_test.go similarity index 99% rename from types/netaddress_test.go rename to pkg/p2p/netaddress_test.go index 393d70e0b..7949f7faf 100644 --- a/types/netaddress_test.go +++ b/pkg/p2p/netaddress_test.go @@ -1,4 +1,4 @@ -package types +package p2p import ( "net" diff --git a/types/node_id.go b/pkg/p2p/node_id.go similarity index 99% rename from types/node_id.go rename to pkg/p2p/node_id.go index c260aa117..89900780b 100644 --- a/types/node_id.go +++ b/pkg/p2p/node_id.go @@ -1,4 +1,4 @@ -package types +package p2p import ( "encoding/hex" diff --git a/types/node_info.go b/pkg/p2p/node_info.go similarity index 99% rename from types/node_info.go rename to pkg/p2p/node_info.go index 9dbdbf70d..6e5979a4d 100644 --- a/types/node_info.go +++ b/pkg/p2p/node_info.go @@ -1,4 +1,4 @@ -package types +package p2p import ( "errors" diff --git a/types/node_info_test.go b/pkg/p2p/node_info_test.go similarity index 99% rename from types/node_info_test.go rename to pkg/p2p/node_info_test.go index 812cec184..b7e098525 100644 --- a/types/node_info_test.go +++ b/pkg/p2p/node_info_test.go @@ -1,4 +1,4 @@ -package types +package p2p import ( "fmt" diff --git a/types/node_key.go b/pkg/p2p/node_key.go similarity index 99% rename from types/node_key.go rename to pkg/p2p/node_key.go index 547fa1696..84c9f25c3 100644 --- a/types/node_key.go +++ b/pkg/p2p/node_key.go @@ -1,4 +1,4 @@ -package types +package p2p import ( "io/ioutil" diff --git a/types/node_key_test.go b/pkg/p2p/node_key_test.go similarity index 71% rename from types/node_key_test.go rename to pkg/p2p/node_key_test.go index 7c0b945c3..62c915a70 100644 --- a/types/node_key_test.go +++ b/pkg/p2p/node_key_test.go @@ -1,4 +1,4 @@ -package types_test +package p2p_test import ( "os" @@ -8,16 +8,16 @@ import ( "github.com/stretchr/testify/require" tmrand "github.com/tendermint/tendermint/libs/rand" - "github.com/tendermint/tendermint/types" + "github.com/tendermint/tendermint/pkg/p2p" ) func TestLoadOrGenNodeKey(t *testing.T) { filePath := filepath.Join(os.TempDir(), tmrand.Str(12)+"_peer_id.json") - nodeKey, err := types.LoadOrGenNodeKey(filePath) + nodeKey, err := p2p.LoadOrGenNodeKey(filePath) require.Nil(t, err) - nodeKey2, err := types.LoadOrGenNodeKey(filePath) + nodeKey2, err := p2p.LoadOrGenNodeKey(filePath) require.Nil(t, err) require.Equal(t, nodeKey, nodeKey2) } @@ -25,13 +25,13 @@ func TestLoadOrGenNodeKey(t *testing.T) { func TestLoadNodeKey(t *testing.T) { filePath := filepath.Join(os.TempDir(), tmrand.Str(12)+"_peer_id.json") - _, err := types.LoadNodeKey(filePath) + _, err := p2p.LoadNodeKey(filePath) require.True(t, os.IsNotExist(err)) - _, err = types.LoadOrGenNodeKey(filePath) + _, err = p2p.LoadOrGenNodeKey(filePath) require.NoError(t, err) - nodeKey, err := types.LoadNodeKey(filePath) + nodeKey, err := p2p.LoadNodeKey(filePath) require.NoError(t, err) require.NotNil(t, nodeKey) } @@ -40,7 +40,7 @@ func TestNodeKeySaveAs(t *testing.T) { filePath := filepath.Join(os.TempDir(), tmrand.Str(12)+"_peer_id.json") require.NoFileExists(t, filePath) - nodeKey := types.GenNodeKey() + nodeKey := p2p.GenNodeKey() require.NoError(t, nodeKey.SaveAs(filePath)) require.FileExists(t, filePath) }