rename to metadata

This commit is contained in:
Callum Waters
2021-08-20 16:41:02 +02:00
parent 508b7f9758
commit beafe7d4f1
37 changed files with 342 additions and 342 deletions
+47
View File
@@ -0,0 +1,47 @@
package metadata
import (
"time"
tmtime "github.com/tendermint/tendermint/libs/time"
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
)
// Canonical* wraps the structs in types for amino encoding them for use in SignBytes / the Signable interface.
// TimeFormat is used for generating the sigs
const TimeFormat = time.RFC3339Nano
//-----------------------------------
// Canonicalize the structs
func CanonicalizeBlockID(bid tmproto.BlockID) *tmproto.CanonicalBlockID {
rbid, err := BlockIDFromProto(&bid)
if err != nil {
panic(err)
}
var cbid *tmproto.CanonicalBlockID
if rbid == nil || rbid.IsZero() {
cbid = nil
} else {
cbid = &tmproto.CanonicalBlockID{
Hash: bid.Hash,
PartSetHeader: CanonicalizePartSetHeader(bid.PartSetHeader),
}
}
return cbid
}
// CanonicalizeVote transforms the given PartSetHeader to a CanonicalPartSetHeader.
func CanonicalizePartSetHeader(psh tmproto.PartSetHeader) tmproto.CanonicalPartSetHeader {
return tmproto.CanonicalPartSetHeader(psh)
}
// 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
// local time, we need to force UTC here, so the
// signatures match
return tmtime.Canonical(t).Format(TimeFormat)
}
+40
View File
@@ -0,0 +1,40 @@
package metadata_test
import (
"reflect"
"testing"
"github.com/tendermint/tendermint/crypto/tmhash"
tmrand "github.com/tendermint/tendermint/libs/rand"
"github.com/tendermint/tendermint/pkg/metadata"
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
)
func TestCanonicalizeBlockID(t *testing.T) {
randhash := tmrand.Bytes(tmhash.Size)
block1 := tmproto.BlockID{Hash: randhash,
PartSetHeader: tmproto.PartSetHeader{Total: 5, Hash: randhash}}
block2 := tmproto.BlockID{Hash: randhash,
PartSetHeader: tmproto.PartSetHeader{Total: 10, Hash: randhash}}
cblock1 := tmproto.CanonicalBlockID{Hash: randhash,
PartSetHeader: tmproto.CanonicalPartSetHeader{Total: 5, Hash: randhash}}
cblock2 := tmproto.CanonicalBlockID{Hash: randhash,
PartSetHeader: tmproto.CanonicalPartSetHeader{Total: 10, Hash: randhash}}
tests := []struct {
name string
args tmproto.BlockID
want *tmproto.CanonicalBlockID
}{
{"first", block1, &cblock1},
{"second", block2, &cblock2},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
if got := metadata.CanonicalizeBlockID(tt.args); !reflect.DeepEqual(got, tt.want) {
t.Errorf("CanonicalizeBlockID() = %v, want %v", got, tt.want)
}
})
}
}
+379
View File
@@ -0,0 +1,379 @@
package metadata
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()
}
+284
View File
@@ -0,0 +1,284 @@
package metadata_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/metadata"
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(*metadata.Commit)
expectErr bool
}{
{"Random Commit", func(com *metadata.Commit) {}, false},
{"Incorrect signature", func(com *metadata.Commit) { com.Signatures[0].Signature = []byte{0} }, false},
{"Incorrect height", func(com *metadata.Commit) { com.Height = int64(-100) }, true},
{"Incorrect round", func(com *metadata.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 *metadata.Commit
expectErr bool
errString string
}{
{
"invalid height",
&metadata.Commit{Height: -1},
true, "negative Height",
},
{
"invalid round",
&metadata.Commit{Height: 1, Round: -1},
true, "negative Round",
},
{
"invalid block ID",
&metadata.Commit{
Height: 1,
Round: 1,
BlockID: metadata.BlockID{},
},
true, "commit cannot be for nil block",
},
{
"no signatures",
&metadata.Commit{
Height: 1,
Round: 1,
BlockID: metadata.BlockID{
Hash: make([]byte, tmhash.Size),
PartSetHeader: metadata.PartSetHeader{
Hash: make([]byte, tmhash.Size),
},
},
},
true, "no signatures in commit",
},
{
"invalid signature",
&metadata.Commit{
Height: 1,
Round: 1,
BlockID: metadata.BlockID{
Hash: make([]byte, tmhash.Size),
PartSetHeader: metadata.PartSetHeader{
Hash: make([]byte, tmhash.Size),
},
},
Signatures: []metadata.CommitSig{
{
BlockIDFlag: metadata.BlockIDFlagCommit,
ValidatorAddress: make([]byte, crypto.AddressSize),
Signature: make([]byte, metadata.MaxSignatureSize+1),
},
},
},
true, "wrong CommitSig",
},
{
"valid commit",
&metadata.Commit{
Height: 1,
Round: 1,
BlockID: metadata.BlockID{
Hash: make([]byte, tmhash.Size),
PartSetHeader: metadata.PartSetHeader{
Hash: make([]byte, tmhash.Size),
},
},
Signatures: []metadata.CommitSig{
{
BlockIDFlag: metadata.BlockIDFlagCommit,
ValidatorAddress: make([]byte, crypto.AddressSize),
Signature: make([]byte, metadata.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 := metadata.CommitSig{
BlockIDFlag: metadata.BlockIDFlagNil,
ValidatorAddress: crypto.AddressHash([]byte("validator_address")),
Timestamp: timestamp,
Signature: crypto.CRandBytes(metadata.MaxSignatureSize),
}
pbSig := cs.ToProto()
// test that a single commit sig doesn't exceed max commit sig bytes
assert.EqualValues(t, metadata.MaxCommitSigBytes, pbSig.Size())
// check size with a single commit
commit := &metadata.Commit{
Height: math.MaxInt64,
Round: math.MaxInt32,
BlockID: metadata.BlockID{
Hash: tmhash.Sum([]byte("blockID_hash")),
PartSetHeader: metadata.PartSetHeader{
Total: math.MaxInt32,
Hash: tmhash.Sum([]byte("blockID_part_set_header_hash")),
},
},
Signatures: []metadata.CommitSig{cs},
}
pb := commit.ToProto()
assert.EqualValues(t, metadata.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, metadata.MaxCommitBytes(consensus.MaxVotesCount), int64(pb.Size()))
}
func TestCommitSig_ValidateBasic(t *testing.T) {
testCases := []struct {
name string
cs metadata.CommitSig
expectErr bool
errString string
}{
{
"invalid ID flag",
metadata.CommitSig{BlockIDFlag: metadata.BlockIDFlag(0xFF)},
true, "unknown BlockIDFlag",
},
{
"BlockIDFlagAbsent validator address present",
metadata.CommitSig{BlockIDFlag: metadata.BlockIDFlagAbsent, ValidatorAddress: crypto.Address("testaddr")},
true, "validator address is present",
},
{
"BlockIDFlagAbsent timestamp present",
metadata.CommitSig{BlockIDFlag: metadata.BlockIDFlagAbsent, Timestamp: time.Now().UTC()},
true, "time is present",
},
{
"BlockIDFlagAbsent signatures present",
metadata.CommitSig{BlockIDFlag: metadata.BlockIDFlagAbsent, Signature: []byte{0xAA}},
true, "signature is present",
},
{
"BlockIDFlagAbsent valid BlockIDFlagAbsent",
metadata.CommitSig{BlockIDFlag: metadata.BlockIDFlagAbsent},
false, "",
},
{
"non-BlockIDFlagAbsent invalid validator address",
metadata.CommitSig{BlockIDFlag: metadata.BlockIDFlagCommit, ValidatorAddress: make([]byte, 1)},
true, "expected ValidatorAddress size",
},
{
"non-BlockIDFlagAbsent invalid signature (zero)",
metadata.CommitSig{
BlockIDFlag: metadata.BlockIDFlagCommit,
ValidatorAddress: make([]byte, crypto.AddressSize),
Signature: make([]byte, 0),
},
true, "signature is missing",
},
{
"non-BlockIDFlagAbsent invalid signature (too large)",
metadata.CommitSig{
BlockIDFlag: metadata.BlockIDFlagCommit,
ValidatorAddress: make([]byte, crypto.AddressSize),
Signature: make([]byte, metadata.MaxSignatureSize+1),
},
true, "signature is too big",
},
{
"non-BlockIDFlagAbsent valid",
metadata.CommitSig{
BlockIDFlag: metadata.BlockIDFlagCommit,
ValidatorAddress: make([]byte, crypto.AddressSize),
Signature: make([]byte, metadata.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)
}
})
}
}
+293
View File
@@ -0,0 +1,293 @@
package metadata
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()
}
//-------------------------------------
+496
View File
@@ -0,0 +1,496 @@
package metadata_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/metadata"
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((*metadata.Header)(nil).Hash()))
assert.Equal(t, nilBytes, []byte((new(metadata.Header)).Hash()))
}
func TestHeaderHash(t *testing.T) {
testCases := []struct {
desc string
header *metadata.Header
expectHash bytes.HexBytes
}{
{"Generates expected hash", &metadata.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", &metadata.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, metadata.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 metadata.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 < metadata.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 := metadata.Header{
Version: version.Consensus{Block: math.MaxInt64, App: math.MaxInt64},
ChainID: maxChainID,
Height: math.MaxInt64,
Time: timestamp,
LastBlockID: metadata.BlockID{make([]byte, tmhash.Size), metadata.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, metadata.MaxHeaderBytes, int64(len(bz)))
}
func randCommit(now time.Time) *metadata.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 metadata.Header
expectErr bool
errString string
}{
{
"invalid version block",
metadata.Header{Version: version.Consensus{Block: version.BlockProtocol + 1}},
true, "block protocol is incorrect",
},
{
"invalid chain ID length",
metadata.Header{
Version: version.Consensus{Block: version.BlockProtocol},
ChainID: string(make([]byte, metadata.MaxChainIDLen+1)),
},
true, "chainID is too long",
},
{
"invalid height (negative)",
metadata.Header{
Version: version.Consensus{Block: version.BlockProtocol},
ChainID: string(make([]byte, metadata.MaxChainIDLen)),
Height: -1,
},
true, "negative Height",
},
{
"invalid height (zero)",
metadata.Header{
Version: version.Consensus{Block: version.BlockProtocol},
ChainID: string(make([]byte, metadata.MaxChainIDLen)),
Height: 0,
},
true, "zero Height",
},
{
"invalid block ID hash",
metadata.Header{
Version: version.Consensus{Block: version.BlockProtocol},
ChainID: string(make([]byte, metadata.MaxChainIDLen)),
Height: 1,
LastBlockID: metadata.BlockID{
Hash: make([]byte, tmhash.Size+1),
},
},
true, "wrong Hash",
},
{
"invalid block ID parts header hash",
metadata.Header{
Version: version.Consensus{Block: version.BlockProtocol},
ChainID: string(make([]byte, metadata.MaxChainIDLen)),
Height: 1,
LastBlockID: metadata.BlockID{
Hash: make([]byte, tmhash.Size),
PartSetHeader: metadata.PartSetHeader{
Hash: make([]byte, tmhash.Size+1),
},
},
},
true, "wrong PartSetHeader",
},
{
"invalid last commit hash",
metadata.Header{
Version: version.Consensus{Block: version.BlockProtocol},
ChainID: string(make([]byte, metadata.MaxChainIDLen)),
Height: 1,
LastBlockID: metadata.BlockID{
Hash: make([]byte, tmhash.Size),
PartSetHeader: metadata.PartSetHeader{
Hash: make([]byte, tmhash.Size),
},
},
LastCommitHash: make([]byte, tmhash.Size+1),
},
true, "wrong LastCommitHash",
},
{
"invalid data hash",
metadata.Header{
Version: version.Consensus{Block: version.BlockProtocol},
ChainID: string(make([]byte, metadata.MaxChainIDLen)),
Height: 1,
LastBlockID: metadata.BlockID{
Hash: make([]byte, tmhash.Size),
PartSetHeader: metadata.PartSetHeader{
Hash: make([]byte, tmhash.Size),
},
},
LastCommitHash: make([]byte, tmhash.Size),
DataHash: make([]byte, tmhash.Size+1),
},
true, "wrong DataHash",
},
{
"invalid evidence hash",
metadata.Header{
Version: version.Consensus{Block: version.BlockProtocol},
ChainID: string(make([]byte, metadata.MaxChainIDLen)),
Height: 1,
LastBlockID: metadata.BlockID{
Hash: make([]byte, tmhash.Size),
PartSetHeader: metadata.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",
metadata.Header{
Version: version.Consensus{Block: version.BlockProtocol},
ChainID: string(make([]byte, metadata.MaxChainIDLen)),
Height: 1,
LastBlockID: metadata.BlockID{
Hash: make([]byte, tmhash.Size),
PartSetHeader: metadata.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",
metadata.Header{
Version: version.Consensus{Block: version.BlockProtocol},
ChainID: string(make([]byte, metadata.MaxChainIDLen)),
Height: 1,
LastBlockID: metadata.BlockID{
Hash: make([]byte, tmhash.Size),
PartSetHeader: metadata.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",
metadata.Header{
Version: version.Consensus{Block: version.BlockProtocol},
ChainID: string(make([]byte, metadata.MaxChainIDLen)),
Height: 1,
LastBlockID: metadata.BlockID{
Hash: make([]byte, tmhash.Size),
PartSetHeader: metadata.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",
metadata.Header{
Version: version.Consensus{Block: version.BlockProtocol},
ChainID: string(make([]byte, metadata.MaxChainIDLen)),
Height: 1,
LastBlockID: metadata.BlockID{
Hash: make([]byte, tmhash.Size),
PartSetHeader: metadata.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",
metadata.Header{
Version: version.Consensus{Block: version.BlockProtocol},
ChainID: string(make([]byte, metadata.MaxChainIDLen)),
Height: 1,
LastBlockID: metadata.BlockID{
Hash: make([]byte, tmhash.Size),
PartSetHeader: metadata.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",
metadata.Header{
Version: version.Consensus{Block: version.BlockProtocol},
ChainID: string(make([]byte, metadata.MaxChainIDLen)),
Height: 1,
LastBlockID: metadata.BlockID{
Hash: make([]byte, tmhash.Size),
PartSetHeader: metadata.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 *metadata.Header
expPass bool
}{
{"success", h1, true},
{"failure empty Header", &metadata.Header{}, false},
}
for _, tt := range tc {
tt := tt
t.Run(tt.msg, func(t *testing.T) {
pb := tt.h1.ToProto()
h, err := metadata.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 := metadata.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: metadata.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 metadata.Header
expBytes string
}{
{header: h, expBytes: "87b6117ac7f827d656f178a3d6d30b24b205db2b6a3a053bae8baf4618570bfc"},
}
for _, tc := range testCases {
hash := tc.header.Hash()
require.Equal(t, tc.expBytes, hex.EncodeToString(hash))
}
}
+124
View File
@@ -0,0 +1,124 @@
package metadata
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
}
+91
View File
@@ -0,0 +1,91 @@
package metadata_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/metadata"
)
func TestBlockIDEquals(t *testing.T) {
var (
blockID = metadata.BlockID{[]byte("hash"), metadata.PartSetHeader{2, []byte("part_set_hash")}}
blockIDDuplicate = metadata.BlockID{[]byte("hash"), metadata.PartSetHeader{2, []byte("part_set_hash")}}
blockIDDifferent = metadata.BlockID{[]byte("different_hash"), metadata.PartSetHeader{2, []byte("part_set_hash")}}
blockIDEmpty = metadata.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 := metadata.BlockID{
Hash: bytes.HexBytes{},
PartSetHeader: metadata.PartSetHeader{
Total: 1,
Hash: bytes.HexBytes{},
},
}
invalidBlockID := metadata.BlockID{
Hash: []byte{0},
PartSetHeader: metadata.PartSetHeader{
Total: 1,
Hash: []byte{0},
},
}
testCases := []struct {
testName string
blockIDHash bytes.HexBytes
blockIDPartSetHeader metadata.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 := metadata.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 *metadata.BlockID
expPass bool
}{
{"success", &blockID, true},
{"success empty", &metadata.BlockID{}, true},
{"failure BlockID nil", nil, false},
}
for _, tc := range testCases {
protoBlockID := tc.bid1.ToProto()
bi, err := metadata.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)
}
}
}
+383
View File
@@ -0,0 +1,383 @@
package metadata
import (
"bytes"
"errors"
"fmt"
"io"
"github.com/tendermint/tendermint/crypto/merkle"
tmsync "github.com/tendermint/tendermint/internal/libs/sync"
"github.com/tendermint/tendermint/libs/bits"
tmbytes "github.com/tendermint/tendermint/libs/bytes"
tmjson "github.com/tendermint/tendermint/libs/json"
tmmath "github.com/tendermint/tendermint/libs/math"
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
)
var (
ErrPartSetUnexpectedIndex = errors.New("error part set unexpected index")
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"`
Proof merkle.Proof `json:"proof"`
}
// ValidateBasic performs basic validation.
func (part *Part) ValidateBasic() error {
if len(part.Bytes) > int(BlockPartSizeBytes) {
return fmt.Errorf("too big: %d bytes, max: %d", len(part.Bytes), BlockPartSizeBytes)
}
if err := part.Proof.ValidateBasic(); err != nil {
return fmt.Errorf("wrong Proof: %w", err)
}
return nil
}
// String returns a string representation of Part.
//
// See StringIndented.
func (part *Part) String() string {
return part.StringIndented("")
}
// StringIndented returns an indented Part.
//
// See merkle.Proof#StringIndented
func (part *Part) StringIndented(indent string) string {
return fmt.Sprintf(`Part{#%v
%s Bytes: %X...
%s Proof: %v
%s}`,
part.Index,
indent, tmbytes.Fingerprint(part.Bytes),
indent, part.Proof.StringIndented(indent+" "),
indent)
}
func (part *Part) ToProto() (*tmproto.Part, error) {
if part == nil {
return nil, errors.New("nil part")
}
pb := new(tmproto.Part)
proof := part.Proof.ToProto()
pb.Index = part.Index
pb.Bytes = part.Bytes
pb.Proof = *proof
return pb, nil
}
func PartFromProto(pb *tmproto.Part) (*Part, error) {
if pb == nil {
return nil, errors.New("nil part")
}
part := new(Part)
proof, err := merkle.ProofFromProto(&pb.Proof)
if err != nil {
return nil, err
}
part.Index = pb.Index
part.Bytes = pb.Bytes
part.Proof = *proof
return part, part.ValidateBasic()
}
//-------------------------------------
type PartSetHeader struct {
Total uint32 `json:"total"`
Hash tmbytes.HexBytes `json:"hash"`
}
// String returns a string representation of PartSetHeader.
//
// 1. total number of parts
// 2. first 6 bytes of the hash
func (psh PartSetHeader) String() string {
return fmt.Sprintf("%v:%X", psh.Total, tmbytes.Fingerprint(psh.Hash))
}
func (psh PartSetHeader) IsZero() bool {
return psh.Total == 0 && len(psh.Hash) == 0
}
func (psh PartSetHeader) Equals(other PartSetHeader) bool {
return psh.Total == other.Total && bytes.Equal(psh.Hash, other.Hash)
}
// ValidateBasic performs basic validation.
func (psh PartSetHeader) ValidateBasic() error {
// Hash can be empty in case of POLBlockID.PartSetHeader in Proposal.
if err := ValidateHash(psh.Hash); err != nil {
return fmt.Errorf("wrong Hash: %w", err)
}
return nil
}
// ToProto converts PartSetHeader to protobuf
func (psh *PartSetHeader) ToProto() tmproto.PartSetHeader {
if psh == nil {
return tmproto.PartSetHeader{}
}
return tmproto.PartSetHeader{
Total: psh.Total,
Hash: psh.Hash,
}
}
// FromProto sets a protobuf PartSetHeader to the given pointer
func PartSetHeaderFromProto(ppsh *tmproto.PartSetHeader) (*PartSetHeader, error) {
if ppsh == nil {
return nil, errors.New("nil PartSetHeader")
}
psh := new(PartSetHeader)
psh.Total = ppsh.Total
psh.Hash = ppsh.Hash
return psh, psh.ValidateBasic()
}
//-------------------------------------
type PartSet struct {
total uint32
hash []byte
mtx tmsync.Mutex
parts []*Part
partsBitArray *bits.BitArray
count uint32
// a count of the total size (in bytes). Used to ensure that the
// part set doesn't exceed the maximum block bytes
byteSize int64
}
// Returns an immutable, full PartSet from the data bytes.
// The data bytes are split into "partSize" chunks, and merkle tree computed.
// CONTRACT: partSize is greater than zero.
func NewPartSetFromData(data []byte, partSize uint32) *PartSet {
// divide data into 4kb parts.
total := (uint32(len(data)) + partSize - 1) / partSize
parts := make([]*Part, total)
partsBytes := make([][]byte, total)
partsBitArray := bits.NewBitArray(int(total))
for i := uint32(0); i < total; i++ {
part := &Part{
Index: i,
Bytes: data[i*partSize : tmmath.MinInt(len(data), int((i+1)*partSize))],
}
parts[i] = part
partsBytes[i] = part.Bytes
partsBitArray.SetIndex(int(i), true)
}
// Compute merkle proofs
root, proofs := merkle.ProofsFromByteSlices(partsBytes)
for i := uint32(0); i < total; i++ {
parts[i].Proof = *proofs[i]
}
return &PartSet{
total: total,
hash: root,
parts: parts,
partsBitArray: partsBitArray,
count: total,
byteSize: int64(len(data)),
}
}
// Returns an empty PartSet ready to be populated.
func NewPartSetFromHeader(header PartSetHeader) *PartSet {
return &PartSet{
total: header.Total,
hash: header.Hash,
parts: make([]*Part, header.Total),
partsBitArray: bits.NewBitArray(int(header.Total)),
count: 0,
byteSize: 0,
}
}
func (ps *PartSet) Header() PartSetHeader {
if ps == nil {
return PartSetHeader{}
}
return PartSetHeader{
Total: ps.total,
Hash: ps.hash,
}
}
func (ps *PartSet) HasHeader(header PartSetHeader) bool {
if ps == nil {
return false
}
return ps.Header().Equals(header)
}
func (ps *PartSet) BitArray() *bits.BitArray {
ps.mtx.Lock()
defer ps.mtx.Unlock()
return ps.partsBitArray.Copy()
}
func (ps *PartSet) Hash() []byte {
if ps == nil {
return merkle.HashFromByteSlices(nil)
}
return ps.hash
}
func (ps *PartSet) HashesTo(hash []byte) bool {
if ps == nil {
return false
}
return bytes.Equal(ps.hash, hash)
}
func (ps *PartSet) Count() uint32 {
if ps == nil {
return 0
}
return ps.count
}
func (ps *PartSet) ByteSize() int64 {
if ps == nil {
return 0
}
return ps.byteSize
}
func (ps *PartSet) Total() uint32 {
if ps == nil {
return 0
}
return ps.total
}
func (ps *PartSet) AddPart(part *Part) (bool, error) {
if ps == nil {
return false, nil
}
ps.mtx.Lock()
defer ps.mtx.Unlock()
// Invalid part index
if part.Index >= ps.total {
return false, ErrPartSetUnexpectedIndex
}
// If part already exists, return false.
if ps.parts[part.Index] != nil {
return false, nil
}
// Check hash proof
if part.Proof.Verify(ps.Hash(), part.Bytes) != nil {
return false, ErrPartSetInvalidProof
}
// Add part
ps.parts[part.Index] = part
ps.partsBitArray.SetIndex(int(part.Index), true)
ps.count++
ps.byteSize += int64(len(part.Bytes))
return true, nil
}
func (ps *PartSet) GetPart(index int) *Part {
ps.mtx.Lock()
defer ps.mtx.Unlock()
return ps.parts[index]
}
func (ps *PartSet) IsComplete() bool {
return ps.count == ps.total
}
func (ps *PartSet) GetReader() io.Reader {
if !ps.IsComplete() {
panic("Cannot GetReader() on incomplete PartSet")
}
return NewPartSetReader(ps.parts)
}
type PartSetReader struct {
i int
parts []*Part
reader *bytes.Reader
}
func NewPartSetReader(parts []*Part) *PartSetReader {
return &PartSetReader{
i: 0,
parts: parts,
reader: bytes.NewReader(parts[0].Bytes),
}
}
func (psr *PartSetReader) Read(p []byte) (n int, err error) {
readerLen := psr.reader.Len()
if readerLen >= len(p) {
return psr.reader.Read(p)
} else if readerLen > 0 {
n1, err := psr.Read(p[:readerLen])
if err != nil {
return n1, err
}
n2, err := psr.Read(p[readerLen:])
return n1 + n2, err
}
psr.i++
if psr.i >= len(psr.parts) {
return 0, io.EOF
}
psr.reader = bytes.NewReader(psr.parts[psr.i].Bytes)
return psr.Read(p)
}
// StringShort returns a short version of String.
//
// (Count of Total)
func (ps *PartSet) StringShort() string {
if ps == nil {
return "nil-PartSet"
}
ps.mtx.Lock()
defer ps.mtx.Unlock()
return fmt.Sprintf("(%v of %v)", ps.Count(), ps.Total())
}
func (ps *PartSet) MarshalJSON() ([]byte, error) {
if ps == nil {
return []byte("{}"), nil
}
ps.mtx.Lock()
defer ps.mtx.Unlock()
return tmjson.Marshal(struct {
CountTotal string `json:"count/total"`
PartsBitArray *bits.BitArray `json:"parts_bit_array"`
}{
fmt.Sprintf("%d/%d", ps.Count(), ps.Total()),
ps.partsBitArray,
})
}
+194
View File
@@ -0,0 +1,194 @@
package metadata
import (
"io/ioutil"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/tendermint/tendermint/crypto/merkle"
tmrand "github.com/tendermint/tendermint/libs/rand"
)
const (
testPartSize = 65536 // 64KB ... 4096 // 4KB
)
func TestBasicPartSet(t *testing.T) {
// Construct random data of size partSize * 100
nParts := 100
data := tmrand.Bytes(testPartSize * nParts)
partSet := NewPartSetFromData(data, testPartSize)
assert.NotEmpty(t, partSet.Hash())
assert.EqualValues(t, nParts, partSet.Total())
assert.Equal(t, nParts, partSet.BitArray().Size())
assert.True(t, partSet.HashesTo(partSet.Hash()))
assert.True(t, partSet.IsComplete())
assert.EqualValues(t, nParts, partSet.Count())
assert.EqualValues(t, testPartSize*nParts, partSet.ByteSize())
// Test adding parts to a new partSet.
partSet2 := NewPartSetFromHeader(partSet.Header())
assert.True(t, partSet2.HasHeader(partSet.Header()))
for i := 0; i < int(partSet.Total()); i++ {
part := partSet.GetPart(i)
// t.Logf("\n%v", part)
added, err := partSet2.AddPart(part)
if !added || err != nil {
t.Errorf("failed to add part %v, error: %v", i, err)
}
}
// adding part with invalid index
added, err := partSet2.AddPart(&Part{Index: 10000})
assert.False(t, added)
assert.Error(t, err)
// adding existing part
added, err = partSet2.AddPart(partSet2.GetPart(0))
assert.False(t, added)
assert.Nil(t, err)
assert.Equal(t, partSet.Hash(), partSet2.Hash())
assert.EqualValues(t, nParts, partSet2.Total())
assert.EqualValues(t, nParts*testPartSize, partSet.ByteSize())
assert.True(t, partSet2.IsComplete())
// Reconstruct data, assert that they are equal.
data2Reader := partSet2.GetReader()
data2, err := ioutil.ReadAll(data2Reader)
require.NoError(t, err)
assert.Equal(t, data, data2)
}
func TestWrongProof(t *testing.T) {
// Construct random data of size partSize * 100
data := tmrand.Bytes(testPartSize * 100)
partSet := NewPartSetFromData(data, testPartSize)
// Test adding a part with wrong data.
partSet2 := NewPartSetFromHeader(partSet.Header())
// Test adding a part with wrong trail.
part := partSet.GetPart(0)
part.Proof.Aunts[0][0] += byte(0x01)
added, err := partSet2.AddPart(part)
if added || err == nil {
t.Errorf("expected to fail adding a part with bad trail.")
}
// Test adding a part with wrong bytes.
part = partSet.GetPart(1)
part.Bytes[0] += byte(0x01)
added, err = partSet2.AddPart(part)
if added || err == nil {
t.Errorf("expected to fail adding a part with bad bytes.")
}
}
func TestPartSetHeaderValidateBasic(t *testing.T) {
testCases := []struct {
testName string
malleatePartSetHeader func(*PartSetHeader)
expectErr bool
}{
{"Good PartSet", func(psHeader *PartSetHeader) {}, false},
{"Invalid Hash", func(psHeader *PartSetHeader) { psHeader.Hash = make([]byte, 1) }, true},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.testName, func(t *testing.T) {
data := tmrand.Bytes(testPartSize * 100)
ps := NewPartSetFromData(data, testPartSize)
psHeader := ps.Header()
tc.malleatePartSetHeader(&psHeader)
assert.Equal(t, tc.expectErr, psHeader.ValidateBasic() != nil, "Validate Basic had an unexpected result")
})
}
}
func TestPartValidateBasic(t *testing.T) {
testCases := []struct {
testName string
malleatePart func(*Part)
expectErr bool
}{
{"Good Part", func(pt *Part) {}, false},
{"Too big part", func(pt *Part) { pt.Bytes = make([]byte, BlockPartSizeBytes+1) }, true},
{"Too big proof", func(pt *Part) {
pt.Proof = merkle.Proof{
Total: 1,
Index: 1,
LeafHash: make([]byte, 1024*1024),
}
}, true},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.testName, func(t *testing.T) {
data := tmrand.Bytes(testPartSize * 100)
ps := NewPartSetFromData(data, testPartSize)
part := ps.GetPart(0)
tc.malleatePart(part)
assert.Equal(t, tc.expectErr, part.ValidateBasic() != nil, "Validate Basic had an unexpected result")
})
}
}
func TestParSetHeaderProtoBuf(t *testing.T) {
testCases := []struct {
msg string
ps1 *PartSetHeader
expPass bool
}{
{"success empty", &PartSetHeader{}, true},
{"success",
&PartSetHeader{Total: 1, Hash: []byte("hash")}, true},
}
for _, tc := range testCases {
protoBlockID := tc.ps1.ToProto()
psh, err := PartSetHeaderFromProto(&protoBlockID)
if tc.expPass {
require.Equal(t, tc.ps1, psh, tc.msg)
} else {
require.Error(t, err, tc.msg)
}
}
}
func TestPartProtoBuf(t *testing.T) {
proof := merkle.Proof{
Total: 1,
Index: 1,
LeafHash: tmrand.Bytes(32),
}
testCases := []struct {
msg string
ps1 *Part
expPass bool
}{
{"failure empty", &Part{}, false},
{"failure nil", nil, false},
{"success",
&Part{Index: 1, Bytes: tmrand.Bytes(32), Proof: proof}, true},
}
for _, tc := range testCases {
proto, err := tc.ps1.ToProto()
if tc.expPass {
require.NoError(t, err, tc.msg)
}
p, err := PartFromProto(proto)
if tc.expPass {
require.NoError(t, err)
require.Equal(t, tc.ps1, p, tc.msg)
}
}
}
+75
View File
@@ -0,0 +1,75 @@
package metadata
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(<type>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
}