mirror of
https://github.com/tendermint/tendermint.git
synced 2026-09-19 14:34:17 +00:00
validate reactor messages (#2711)
* validate reactor messages Refs #2683 * validate blockchain messages Refs #2683 * validate evidence messages Refs #2683 * todo * check ProposalPOL and signature sizes * add a changelog entry * check addr is valid when we add it to the addrbook * validate incoming netAddr (not just nil check!) * fixes after Bucky's review * check timestamps * beef up block#ValidateBasic * move some checks into bcBlockResponseMessage * update Gopkg.lock Fix ``` grouped write of manifest, lock and vendor: failed to export github.com/tendermint/go-amino: fatal: failed to unpack tree object 6dcc6ddc143e116455c94b25c1004c99e0d0ca12 ``` by running `dep ensure -update` * bump year since now we check it * generate test/p2p/data on the fly using tendermint testnet * allow sync chains older than 1 year * use full path when creating a testnet * move testnet gen to test/docker/Dockerfile * relax LastCommitRound check Refs #2737 * fix conflicts after merge * add small comment * some ValidateBasic updates * fixes * AppHash length is not fixed
This commit is contained in:
committed by
Ethan Buchman
parent
a22c962e28
commit
fb91ef7462
+97
-20
@@ -2,12 +2,14 @@ package types
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
"github.com/tendermint/tendermint/crypto/merkle"
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
"github.com/tendermint/tendermint/version"
|
||||
@@ -57,54 +59,117 @@ func MakeBlock(height int64, txs []Tx, lastCommit *Commit, evidence []Evidence)
|
||||
|
||||
// ValidateBasic performs basic validation that doesn't involve state data.
|
||||
// It checks the internal consistency of the block.
|
||||
// Further validation is done using state#ValidateBlock.
|
||||
func (b *Block) ValidateBasic() error {
|
||||
if b == nil {
|
||||
return errors.New("Nil blocks are invalid")
|
||||
return errors.New("nil block")
|
||||
}
|
||||
b.mtx.Lock()
|
||||
defer b.mtx.Unlock()
|
||||
|
||||
if b.Height < 0 {
|
||||
return fmt.Errorf(
|
||||
"Negative Block.Header.Height: %v",
|
||||
b.Height,
|
||||
)
|
||||
if len(b.ChainID) > MaxChainIDLen {
|
||||
return fmt.Errorf("ChainID is too long. Max is %d, got %d", MaxChainIDLen, len(b.ChainID))
|
||||
}
|
||||
|
||||
if b.Height < 0 {
|
||||
return errors.New("Negative Header.Height")
|
||||
} else if b.Height == 0 {
|
||||
return errors.New("Zero Header.Height")
|
||||
}
|
||||
|
||||
// NOTE: Timestamp validation is subtle and handled elsewhere.
|
||||
|
||||
newTxs := int64(len(b.Data.Txs))
|
||||
if b.NumTxs != newTxs {
|
||||
return fmt.Errorf(
|
||||
"Wrong Block.Header.NumTxs. Expected %v, got %v",
|
||||
return fmt.Errorf("Wrong Header.NumTxs. Expected %v, got %v",
|
||||
newTxs,
|
||||
b.NumTxs,
|
||||
)
|
||||
}
|
||||
|
||||
// TODO: fix tests so we can do this
|
||||
/*if b.TotalTxs < b.NumTxs {
|
||||
return fmt.Errorf("Header.TotalTxs (%d) is less than Header.NumTxs (%d)", b.TotalTxs, b.NumTxs)
|
||||
}*/
|
||||
if b.TotalTxs < 0 {
|
||||
return errors.New("Negative Header.TotalTxs")
|
||||
}
|
||||
|
||||
if err := b.LastBlockID.ValidateBasic(); err != nil {
|
||||
return fmt.Errorf("Wrong Header.LastBlockID: %v", err)
|
||||
}
|
||||
|
||||
// Validate the last commit and its hash.
|
||||
if b.Header.Height > 1 {
|
||||
if b.LastCommit == nil {
|
||||
return errors.New("nil LastCommit")
|
||||
}
|
||||
if err := b.LastCommit.ValidateBasic(); err != nil {
|
||||
return fmt.Errorf("Wrong LastCommit")
|
||||
}
|
||||
}
|
||||
if err := ValidateHash(b.LastCommitHash); err != nil {
|
||||
return fmt.Errorf("Wrong Header.LastCommitHash: %v", err)
|
||||
}
|
||||
if !bytes.Equal(b.LastCommitHash, b.LastCommit.Hash()) {
|
||||
return fmt.Errorf(
|
||||
"Wrong Block.Header.LastCommitHash. Expected %v, got %v",
|
||||
b.LastCommitHash,
|
||||
return fmt.Errorf("Wrong Header.LastCommitHash. Expected %v, got %v",
|
||||
b.LastCommit.Hash(),
|
||||
b.LastCommitHash,
|
||||
)
|
||||
}
|
||||
if b.Header.Height != 1 {
|
||||
if err := b.LastCommit.ValidateBasic(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Validate the hash of the transactions.
|
||||
// NOTE: b.Data.Txs may be nil, but b.Data.Hash()
|
||||
// still works fine
|
||||
if err := ValidateHash(b.DataHash); err != nil {
|
||||
return fmt.Errorf("Wrong Header.DataHash: %v", err)
|
||||
}
|
||||
if !bytes.Equal(b.DataHash, b.Data.Hash()) {
|
||||
return fmt.Errorf(
|
||||
"Wrong Block.Header.DataHash. Expected %v, got %v",
|
||||
b.DataHash,
|
||||
"Wrong Header.DataHash. Expected %v, got %v",
|
||||
b.Data.Hash(),
|
||||
b.DataHash,
|
||||
)
|
||||
}
|
||||
|
||||
// Basic validation of hashes related to application data.
|
||||
// Will validate fully against state in state#ValidateBlock.
|
||||
if err := ValidateHash(b.ValidatorsHash); err != nil {
|
||||
return fmt.Errorf("Wrong Header.ValidatorsHash: %v", err)
|
||||
}
|
||||
if err := ValidateHash(b.NextValidatorsHash); err != nil {
|
||||
return fmt.Errorf("Wrong Header.NextValidatorsHash: %v", err)
|
||||
}
|
||||
if err := ValidateHash(b.ConsensusHash); err != nil {
|
||||
return fmt.Errorf("Wrong Header.ConsensusHash: %v", err)
|
||||
}
|
||||
// NOTE: AppHash is arbitrary length
|
||||
if err := ValidateHash(b.LastResultsHash); err != nil {
|
||||
return fmt.Errorf("Wrong Header.LastResultsHash: %v", err)
|
||||
}
|
||||
|
||||
// Validate evidence and its hash.
|
||||
if err := ValidateHash(b.EvidenceHash); err != nil {
|
||||
return fmt.Errorf("Wrong Header.EvidenceHash: %v", err)
|
||||
}
|
||||
// NOTE: b.Evidence.Evidence may be nil, but we're just looping.
|
||||
for i, ev := range b.Evidence.Evidence {
|
||||
if err := ev.ValidateBasic(); err != nil {
|
||||
return fmt.Errorf("Invalid evidence (#%d): %v", i, err)
|
||||
}
|
||||
}
|
||||
if !bytes.Equal(b.EvidenceHash, b.Evidence.Hash()) {
|
||||
return fmt.Errorf(
|
||||
"Wrong Block.Header.EvidenceHash. Expected %v, got %v",
|
||||
return fmt.Errorf("Wrong Header.EvidenceHash. Expected %v, got %v",
|
||||
b.EvidenceHash,
|
||||
b.Evidence.Hash(),
|
||||
)
|
||||
}
|
||||
|
||||
if len(b.ProposerAddress) != crypto.AddressSize {
|
||||
return fmt.Errorf("Expected len(Header.ProposerAddress) to be %d, got %d",
|
||||
crypto.AddressSize, len(b.ProposerAddress))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -719,6 +784,18 @@ func (blockID BlockID) Key() string {
|
||||
return 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")
|
||||
}
|
||||
if err := blockID.PartsHeader.ValidateBasic(); err != nil {
|
||||
return fmt.Errorf("Wrong PartsHeader: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// String returns a human readable string representation of the BlockID
|
||||
func (blockID BlockID) String() string {
|
||||
return fmt.Sprintf(`%v:%v`, blockID.Hash, blockID.PartsHeader)
|
||||
|
||||
+4
-2
@@ -80,11 +80,13 @@ func TestBlockValidateBasic(t *testing.T) {
|
||||
blk.EvidenceHash = []byte("something else")
|
||||
}, true},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
for i, tc := range testCases {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
block := MakeBlock(h, txs, commit, evList)
|
||||
block.ProposerAddress = valSet.GetProposer().Address
|
||||
tc.malleateBlock(block)
|
||||
assert.Equal(t, tc.expErr, block.ValidateBasic() != nil, "ValidateBasic had an unexpected result")
|
||||
err = block.ValidateBasic()
|
||||
assert.Equal(t, tc.expErr, err != nil, "#%d: %v", i, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/tendermint/tendermint/crypto/tmhash"
|
||||
|
||||
amino "github.com/tendermint/go-amino"
|
||||
@@ -60,6 +61,7 @@ type Evidence interface {
|
||||
Verify(chainID string, pubKey crypto.PubKey) error // verify the evidence
|
||||
Equal(Evidence) bool // check equality of evidence
|
||||
|
||||
ValidateBasic() error
|
||||
String() string
|
||||
}
|
||||
|
||||
@@ -172,6 +174,23 @@ func (dve *DuplicateVoteEvidence) Equal(ev Evidence) bool {
|
||||
return bytes.Equal(dveHash, evHash)
|
||||
}
|
||||
|
||||
// ValidateBasic performs basic validation.
|
||||
func (dve *DuplicateVoteEvidence) ValidateBasic() error {
|
||||
if len(dve.PubKey.Bytes()) == 0 {
|
||||
return errors.New("Empty PubKey")
|
||||
}
|
||||
if dve.VoteA == nil || dve.VoteB == nil {
|
||||
return fmt.Errorf("One or both of the votes are empty %v, %v", dve.VoteA, dve.VoteB)
|
||||
}
|
||||
if err := dve.VoteA.ValidateBasic(); err != nil {
|
||||
return fmt.Errorf("Invalid VoteA: %v", err)
|
||||
}
|
||||
if err := dve.VoteB.ValidateBasic(); err != nil {
|
||||
return fmt.Errorf("Invalid VoteB: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------
|
||||
|
||||
// UNSTABLE
|
||||
@@ -201,6 +220,7 @@ func (e MockGoodEvidence) Equal(ev Evidence) bool {
|
||||
return e.Height_ == e2.Height_ &&
|
||||
bytes.Equal(e.Address_, e2.Address_)
|
||||
}
|
||||
func (e MockGoodEvidence) ValidateBasic() error { return nil }
|
||||
func (e MockGoodEvidence) String() string {
|
||||
return fmt.Sprintf("GoodEvidence: %d/%s", e.Height_, e.Address_)
|
||||
}
|
||||
@@ -218,6 +238,7 @@ func (e MockBadEvidence) Equal(ev Evidence) bool {
|
||||
return e.Height_ == e2.Height_ &&
|
||||
bytes.Equal(e.Address_, e2.Address_)
|
||||
}
|
||||
func (e MockBadEvidence) ValidateBasic() error { return nil }
|
||||
func (e MockBadEvidence) String() string {
|
||||
return fmt.Sprintf("BadEvidence: %d/%s", e.Height_, e.Address_)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ package types
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
)
|
||||
|
||||
@@ -50,3 +52,32 @@ func (heartbeat *Heartbeat) String() string {
|
||||
heartbeat.Height, heartbeat.Round, heartbeat.Sequence,
|
||||
fmt.Sprintf("/%X.../", cmn.Fingerprint(heartbeat.Signature[:])))
|
||||
}
|
||||
|
||||
// ValidateBasic performs basic validation.
|
||||
func (heartbeat *Heartbeat) ValidateBasic() error {
|
||||
if len(heartbeat.ValidatorAddress) != crypto.AddressSize {
|
||||
return fmt.Errorf("Expected ValidatorAddress size to be %d bytes, got %d bytes",
|
||||
crypto.AddressSize,
|
||||
len(heartbeat.ValidatorAddress),
|
||||
)
|
||||
}
|
||||
if heartbeat.ValidatorIndex < 0 {
|
||||
return errors.New("Negative ValidatorIndex")
|
||||
}
|
||||
if heartbeat.Height < 0 {
|
||||
return errors.New("Negative Height")
|
||||
}
|
||||
if heartbeat.Round < 0 {
|
||||
return errors.New("Negative Round")
|
||||
}
|
||||
if heartbeat.Sequence < 0 {
|
||||
return errors.New("Negative Sequence")
|
||||
}
|
||||
if len(heartbeat.Signature) == 0 {
|
||||
return errors.New("Signature is missing")
|
||||
}
|
||||
if len(heartbeat.Signature) > MaxSignatureSize {
|
||||
return fmt.Errorf("Signature is too big (max: %d)", MaxSignatureSize)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
+25
-1
@@ -2,11 +2,12 @@ package types
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/tendermint/tendermint/crypto/merkle"
|
||||
"github.com/tendermint/tendermint/crypto/tmhash"
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
@@ -36,6 +37,17 @@ func (part *Part) Hash() []byte {
|
||||
return part.hash
|
||||
}
|
||||
|
||||
// ValidateBasic performs basic validation.
|
||||
func (part *Part) ValidateBasic() error {
|
||||
if part.Index < 0 {
|
||||
return errors.New("Negative Index")
|
||||
}
|
||||
if len(part.Bytes) > BlockPartSizeBytes {
|
||||
return fmt.Errorf("Too big (max: %d)", BlockPartSizeBytes)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (part *Part) String() string {
|
||||
return part.StringIndented("")
|
||||
}
|
||||
@@ -70,6 +82,18 @@ 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 {
|
||||
if psh.Total < 0 {
|
||||
return errors.New("Negative Total")
|
||||
}
|
||||
// Hash can be empty in case of POLBlockID.PartsHeader in Proposal.
|
||||
if err := ValidateHash(psh.Hash); err != nil {
|
||||
return errors.Wrap(err, "Wrong Hash")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//-------------------------------------
|
||||
|
||||
type PartSet struct {
|
||||
|
||||
@@ -43,6 +43,35 @@ func NewProposal(height int64, round int, polRound int, blockID BlockID) *Propos
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateBasic performs basic validation.
|
||||
func (p *Proposal) ValidateBasic() error {
|
||||
if p.Type != ProposalType {
|
||||
return errors.New("Invalid Type")
|
||||
}
|
||||
if p.Height < 0 {
|
||||
return errors.New("Negative Height")
|
||||
}
|
||||
if p.Round < 0 {
|
||||
return errors.New("Negative Round")
|
||||
}
|
||||
if p.POLRound < -1 {
|
||||
return errors.New("Negative POLRound (exception: -1)")
|
||||
}
|
||||
if err := p.BlockID.ValidateBasic(); err != nil {
|
||||
return fmt.Errorf("Wrong BlockID: %v", err)
|
||||
}
|
||||
|
||||
// NOTE: Timestamp validation is subtle and handled elsewhere.
|
||||
|
||||
if len(p.Signature) == 0 {
|
||||
return errors.New("Signature is missing")
|
||||
}
|
||||
if len(p.Signature) > MaxSignatureSize {
|
||||
return fmt.Errorf("Signature is too big (max: %d)", MaxSignatureSize)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// String returns a string representation of the Proposal.
|
||||
func (p *Proposal) String() string {
|
||||
return fmt.Sprintf("Proposal{%v/%v (%v, %v) %X @ %s}",
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"github.com/tendermint/tendermint/crypto/ed25519"
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
)
|
||||
|
||||
var (
|
||||
// MaxSignatureSize is a maximum allowed signature size for the Heartbeat,
|
||||
// Proposal and Vote.
|
||||
// XXX: secp256k1 does not have Size nor MaxSize defined.
|
||||
MaxSignatureSize = cmn.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
|
||||
|
||||
@@ -15,11 +15,10 @@ const (
|
||||
HeartbeatType SignedMsgType = 0x30
|
||||
)
|
||||
|
||||
func IsVoteTypeValid(type_ SignedMsgType) bool {
|
||||
switch type_ {
|
||||
case PrevoteType:
|
||||
return true
|
||||
case PrecommitType:
|
||||
// IsVoteTypeValid returns true if t is a valid vote type.
|
||||
func IsVoteTypeValid(t SignedMsgType) bool {
|
||||
switch t {
|
||||
case PrevoteType, PrecommitType:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/tendermint/tendermint/crypto/tmhash"
|
||||
tmtime "github.com/tendermint/tendermint/types/time"
|
||||
)
|
||||
|
||||
// ValidateTime does a basic time validation ensuring time does not drift too
|
||||
// much: +/- one year.
|
||||
// TODO: reduce this to eg 1 day
|
||||
// NOTE: DO NOT USE in ValidateBasic methods in this package. This function
|
||||
// can only be used for real time validation, like on proposals and votes
|
||||
// in the consensus. If consensus is stuck, and rounds increase for more than a day,
|
||||
// having only a 1-day band here could break things...
|
||||
// Can't use for validating blocks because we may be syncing years worth of history.
|
||||
func ValidateTime(t time.Time) error {
|
||||
var (
|
||||
now = tmtime.Now()
|
||||
oneYear = 8766 * time.Hour
|
||||
)
|
||||
if t.Before(now.Add(-oneYear)) || t.After(now.Add(oneYear)) {
|
||||
return fmt.Errorf("Time drifted too much. Expected: -1 < %v < 1 year", now)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
+37
-1
@@ -46,7 +46,8 @@ func NewConflictingVoteError(val *Validator, voteA, voteB *Vote) *ErrVoteConflic
|
||||
// Address is hex bytes.
|
||||
type Address = crypto.Address
|
||||
|
||||
// Represents a prevote, precommit, or commit vote from validators for consensus.
|
||||
// Vote represents a prevote, precommit, or commit vote from validators for
|
||||
// consensus.
|
||||
type Vote struct {
|
||||
Type SignedMsgType `json:"type"`
|
||||
Height int64 `json:"height"`
|
||||
@@ -108,3 +109,38 @@ func (vote *Vote) Verify(chainID string, pubKey crypto.PubKey) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateBasic performs basic validation.
|
||||
func (vote *Vote) ValidateBasic() error {
|
||||
if !IsVoteTypeValid(vote.Type) {
|
||||
return errors.New("Invalid Type")
|
||||
}
|
||||
if vote.Height < 0 {
|
||||
return errors.New("Negative Height")
|
||||
}
|
||||
if vote.Round < 0 {
|
||||
return errors.New("Negative Round")
|
||||
}
|
||||
|
||||
// NOTE: Timestamp validation is subtle and handled elsewhere.
|
||||
|
||||
if err := vote.BlockID.ValidateBasic(); err != nil {
|
||||
return fmt.Errorf("Wrong BlockID: %v", err)
|
||||
}
|
||||
if len(vote.ValidatorAddress) != crypto.AddressSize {
|
||||
return fmt.Errorf("Expected ValidatorAddress size to be %d bytes, got %d bytes",
|
||||
crypto.AddressSize,
|
||||
len(vote.ValidatorAddress),
|
||||
)
|
||||
}
|
||||
if vote.ValidatorIndex < 0 {
|
||||
return errors.New("Negative ValidatorIndex")
|
||||
}
|
||||
if len(vote.Signature) == 0 {
|
||||
return errors.New("Signature is missing")
|
||||
}
|
||||
if len(vote.Signature) > MaxSignatureSize {
|
||||
return fmt.Errorf("Signature is too big (max: %d)", MaxSignatureSize)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user