package state import ( "bytes" "errors" "fmt" "github.com/tendermint/tendermint/crypto/tmhash" dbm "github.com/tendermint/tendermint/libs/db" "github.com/tendermint/tendermint/types" ) //----------------------------------------------------- // Validate block func validateBlock(stateDB dbm.DB, state State, block *types.Block) error { // Validate internal consistency. if err := block.ValidateBasic(); err != nil { return err } // Validate basic info. if block.ChainID != state.ChainID { return fmt.Errorf( "Wrong Block.Header.ChainID. Expected %v, got %v", state.ChainID, block.ChainID, ) } if block.Height != state.LastBlockHeight+1 { return fmt.Errorf( "Wrong Block.Header.Height. Expected %v, got %v", state.LastBlockHeight+1, block.Height, ) } // Validate prev block info. if !block.LastBlockID.Equals(state.LastBlockID) { return fmt.Errorf( "Wrong Block.Header.LastBlockID. Expected %v, got %v", state.LastBlockID, block.LastBlockID, ) } newTxs := int64(len(block.Data.Txs)) if block.TotalTxs != state.LastBlockTotalTx+newTxs { return fmt.Errorf( "Wrong Block.Header.TotalTxs. Expected %v, got %v", state.LastBlockTotalTx+newTxs, block.TotalTxs, ) } // Validate app info if !bytes.Equal(block.AppHash, state.AppHash) { return fmt.Errorf( "Wrong Block.Header.AppHash. Expected %X, got %v", state.AppHash, block.AppHash, ) } if !bytes.Equal(block.ConsensusHash, state.ConsensusParams.Hash()) { return fmt.Errorf( "Wrong Block.Header.ConsensusHash. Expected %X, got %v", state.ConsensusParams.Hash(), block.ConsensusHash, ) } if !bytes.Equal(block.LastResultsHash, state.LastResultsHash) { return fmt.Errorf( "Wrong Block.Header.LastResultsHash. Expected %X, got %v", state.LastResultsHash, block.LastResultsHash, ) } if !bytes.Equal(block.ValidatorsHash, state.Validators.Hash()) { return fmt.Errorf( "Wrong Block.Header.ValidatorsHash. Expected %X, got %v", state.Validators.Hash(), block.ValidatorsHash, ) } if !bytes.Equal(block.NextValidatorsHash, state.NextValidators.Hash()) { return fmt.Errorf("Wrong Block.Header.NextValidatorsHash. Expected %X, got %v", state.NextValidators.Hash(), block.NextValidatorsHash) } // Validate block LastCommit. if block.Height == 1 { if len(block.LastCommit.Precommits) != 0 { return errors.New("Block at height 1 (first block) should have no LastCommit precommits") } } else { if len(block.LastCommit.Precommits) != state.LastValidators.Size() { return fmt.Errorf( "Invalid block commit size. Expected %v, got %v", state.LastValidators.Size(), len(block.LastCommit.Precommits), ) } err := state.LastValidators.VerifyCommit( state.ChainID, state.LastBlockID, block.Height-1, block.LastCommit) if err != nil { return err } } // Validate block Time if block.Height > 1 { if !block.Time.After(state.LastBlockTime) { return fmt.Errorf( "Block time %v not greater than last block time %v", block.Time, state.LastBlockTime, ) } medianTime := MedianTime(block.LastCommit, state.LastValidators) if !block.Time.Equal(medianTime) { return fmt.Errorf( "Invalid block time. Expected %v, got %v", medianTime, block.Time, ) } } // Validate all evidence. // TODO: Each check requires loading an old validator set. // We should cap the amount of evidence per block // to prevent potential proposer DoS. for _, ev := range block.Evidence.Evidence { if err := VerifyEvidence(stateDB, state, ev); err != nil { return types.NewEvidenceInvalidErr(ev, err) } } // NOTE: We can't actually verify it's the right proposer because we dont // know what round the block was first proposed. So just check that it's // a legit address and a known validator. if len(block.ProposerAddress) != tmhash.Size || !state.Validators.HasAddress(block.ProposerAddress) { return fmt.Errorf( "Block.Header.ProposerAddress, %X, is not a validator", block.ProposerAddress, ) } return nil } // VerifyEvidence verifies the evidence fully by checking: // - it is sufficiently recent (MaxAge) // - it is from a key who was a validator at the given height // - it is internally consistent // - it was properly signed by the alleged equivocator func VerifyEvidence(stateDB dbm.DB, state State, evidence types.Evidence) error { height := state.LastBlockHeight evidenceAge := height - evidence.Height() maxAge := state.ConsensusParams.EvidenceParams.MaxAge if evidenceAge > maxAge { return fmt.Errorf("Evidence from height %d is too old. Min height is %d", evidence.Height(), height-maxAge) } valset, err := LoadValidators(stateDB, evidence.Height()) if err != nil { // TODO: if err is just that we cant find it cuz we pruned, ignore. // TODO: if its actually bad evidence, punish peer return err } // The address must have been an active validator at the height. // NOTE: we will ignore evidence from H if the key was not a validator // at H, even if it is a validator at some nearby H' ev := evidence height, addr := ev.Height(), ev.Address() _, val := valset.GetByAddress(addr) if val == nil { return fmt.Errorf("Address %X was not a validator at height %d", addr, height) } if err := evidence.Verify(state.ChainID, val.PubKey); err != nil { return err } return nil }