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:
Anton Kaliaev
2018-11-01 02:07:18 -04:00
committed by Ethan Buchman
parent a22c962e28
commit fb91ef7462
41 changed files with 614 additions and 337 deletions
+97 -20
View File
@@ -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
View File
@@ -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)
})
}
}
+21
View File
@@ -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_)
}
+31
View File
@@ -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
View File
@@ -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 {
+29
View File
@@ -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}",
+12
View File
@@ -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
+4 -5
View File
@@ -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
+40
View File
@@ -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
View File
@@ -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
}