github.com/dim4egster/coreth@v0.10.2/plugin/evm/block_verification.go (about) 1 // (c) 2019-2020, Ava Labs, Inc. All rights reserved. 2 // See the file LICENSE for licensing terms. 3 4 package evm 5 6 import ( 7 "fmt" 8 "math/big" 9 10 "github.com/ethereum/go-ethereum/common" 11 12 safemath "github.com/dim4egster/qmallgo/utils/math" 13 14 "github.com/dim4egster/coreth/constants" 15 "github.com/dim4egster/coreth/core/types" 16 "github.com/dim4egster/coreth/params" 17 "github.com/dim4egster/coreth/trie" 18 ) 19 20 var ( 21 apricotPhase0MinGasPrice = big.NewInt(params.LaunchMinGasPrice) 22 apricotPhase1MinGasPrice = big.NewInt(params.ApricotPhase1MinGasPrice) 23 ) 24 25 type BlockValidator interface { 26 SyntacticVerify(b *Block, rules params.Rules) error 27 } 28 29 type blockValidator struct { 30 extDataHashes map[common.Hash]common.Hash 31 } 32 33 func NewBlockValidator(extDataHashes map[common.Hash]common.Hash) BlockValidator { 34 return &blockValidator{ 35 extDataHashes: extDataHashes, 36 } 37 } 38 39 func (v blockValidator) SyntacticVerify(b *Block, rules params.Rules) error { 40 if b == nil || b.ethBlock == nil { 41 return errInvalidBlock 42 } 43 44 ethHeader := b.ethBlock.Header() 45 blockHash := b.ethBlock.Hash() 46 47 if !rules.IsApricotPhase1 { 48 if v.extDataHashes != nil { 49 extData := b.ethBlock.ExtData() 50 extDataHash := types.CalcExtDataHash(extData) 51 // If there is no extra data, check that there is no extra data in the hash map either to ensure we do not 52 // have a block that is unexpectedly missing extra data. 53 expectedExtDataHash, ok := v.extDataHashes[blockHash] 54 if len(extData) == 0 { 55 if ok { 56 return fmt.Errorf("found block with unexpected missing extra data (%s, %d), expected extra data hash: %s", blockHash, b.Height(), expectedExtDataHash) 57 } 58 } else { 59 // If there is extra data, check to make sure that the extra data hash matches the expected extra data hash for this 60 // block 61 if extDataHash != expectedExtDataHash { 62 return fmt.Errorf("extra data hash in block (%s, %d): %s, did not match the expected extra data hash: %s", blockHash, b.Height(), extDataHash, expectedExtDataHash) 63 } 64 } 65 } 66 } 67 68 // Skip verification of the genesis block since it should already be marked as accepted. 69 if blockHash == b.vm.genesisHash { 70 return nil 71 } 72 73 // Verify the ExtDataHash field 74 if rules.IsApricotPhase1 { 75 if hash := types.CalcExtDataHash(b.ethBlock.ExtData()); ethHeader.ExtDataHash != hash { 76 return fmt.Errorf("extra data hash mismatch: have %x, want %x", ethHeader.ExtDataHash, hash) 77 } 78 } else { 79 if ethHeader.ExtDataHash != (common.Hash{}) { 80 return fmt.Errorf( 81 "expected ExtDataHash to be empty but got %x", 82 ethHeader.ExtDataHash, 83 ) 84 } 85 } 86 87 if !ethHeader.Number.IsUint64() { 88 return fmt.Errorf("invalid block number: %v", ethHeader.Number) 89 } 90 if !ethHeader.Difficulty.IsUint64() || ethHeader.Difficulty.Cmp(common.Big1) != 0 { 91 return fmt.Errorf("invalid difficulty: %d", ethHeader.Difficulty) 92 } 93 if ethHeader.Nonce.Uint64() != 0 { 94 return fmt.Errorf("invalid block nonce: %v", ethHeader.Nonce) 95 } 96 if ethHeader.MixDigest != (common.Hash{}) { 97 return fmt.Errorf("invalid mix digest: %v", ethHeader.MixDigest) 98 } 99 100 // Enforce static gas limit after ApricotPhase1 (prior to ApricotPhase1 it's handled in processing). 101 if rules.IsApricotPhase1 { 102 if ethHeader.GasLimit != params.ApricotPhase1GasLimit { 103 return fmt.Errorf( 104 "expected gas limit to be %d after apricot phase 1 but got %d", 105 params.ApricotPhase1GasLimit, ethHeader.GasLimit, 106 ) 107 } 108 } 109 110 // Check that the size of the header's Extra data field is correct for [rules]. 111 headerExtraDataSize := uint64(len(ethHeader.Extra)) 112 switch { 113 case rules.IsApricotPhase3: 114 if headerExtraDataSize != params.ApricotPhase3ExtraDataSize { 115 return fmt.Errorf( 116 "expected header ExtraData to be %d but got %d", 117 params.ApricotPhase3ExtraDataSize, headerExtraDataSize, 118 ) 119 } 120 case rules.IsApricotPhase1: 121 if headerExtraDataSize != 0 { 122 return fmt.Errorf( 123 "expected header ExtraData to be 0 but got %d", 124 headerExtraDataSize, 125 ) 126 } 127 default: 128 if headerExtraDataSize > params.MaximumExtraDataSize { 129 return fmt.Errorf( 130 "expected header ExtraData to be <= %d but got %d", 131 params.MaximumExtraDataSize, headerExtraDataSize, 132 ) 133 } 134 } 135 136 if b.ethBlock.Version() != 0 { 137 return fmt.Errorf("invalid version: %d", b.ethBlock.Version()) 138 } 139 140 // Check that the tx hash in the header matches the body 141 txsHash := types.DeriveSha(b.ethBlock.Transactions(), new(trie.Trie)) 142 if txsHash != ethHeader.TxHash { 143 return fmt.Errorf("invalid txs hash %v does not match calculated txs hash %v", ethHeader.TxHash, txsHash) 144 } 145 // Check that the uncle hash in the header matches the body 146 uncleHash := types.CalcUncleHash(b.ethBlock.Uncles()) 147 if uncleHash != ethHeader.UncleHash { 148 return fmt.Errorf("invalid uncle hash %v does not match calculated uncle hash %v", ethHeader.UncleHash, uncleHash) 149 } 150 // Coinbase must match the BlackholeAddr on C-Chain 151 if ethHeader.Coinbase != constants.BlackholeAddr { 152 return fmt.Errorf("invalid coinbase %v does not match required blackhole address %v", ethHeader.Coinbase, constants.BlackholeAddr) 153 } 154 // Block must not have any uncles 155 if len(b.ethBlock.Uncles()) > 0 { 156 return errUnclesUnsupported 157 } 158 159 // Block must not be empty 160 txs := b.ethBlock.Transactions() 161 if len(txs) == 0 && len(b.atomicTxs) == 0 { 162 return errEmptyBlock 163 } 164 165 // Enforce minimum gas prices here prior to dynamic fees going into effect. 166 switch { 167 case !rules.IsApricotPhase1: 168 // If we are in ApricotPhase0, enforce each transaction has a minimum gas price of at least the LaunchMinGasPrice 169 for _, tx := range b.ethBlock.Transactions() { 170 if tx.GasPrice().Cmp(apricotPhase0MinGasPrice) < 0 { 171 return fmt.Errorf("block contains tx %s with gas price too low (%d < %d)", tx.Hash(), tx.GasPrice(), params.LaunchMinGasPrice) 172 } 173 } 174 case !rules.IsApricotPhase3: 175 // If we are prior to ApricotPhase3, enforce each transaction has a minimum gas price of at least the ApricotPhase1MinGasPrice 176 for _, tx := range b.ethBlock.Transactions() { 177 if tx.GasPrice().Cmp(apricotPhase1MinGasPrice) < 0 { 178 return fmt.Errorf("block contains tx %s with gas price too low (%d < %d)", tx.Hash(), tx.GasPrice(), params.ApricotPhase1MinGasPrice) 179 } 180 } 181 } 182 183 // Make sure the block isn't too far in the future 184 // TODO: move this to only be part of semantic verification. 185 blockTimestamp := b.ethBlock.Time() 186 if maxBlockTime := uint64(b.vm.clock.Time().Add(maxFutureBlockTime).Unix()); blockTimestamp > maxBlockTime { 187 return fmt.Errorf("block timestamp is too far in the future: %d > allowed %d", blockTimestamp, maxBlockTime) 188 } 189 190 // Ensure BaseFee is non-nil as of ApricotPhase3. 191 if rules.IsApricotPhase3 { 192 if ethHeader.BaseFee == nil { 193 return errNilBaseFeeApricotPhase3 194 } 195 // TODO: this should be removed as 256 is the maximum possible bit length of a big int 196 if bfLen := ethHeader.BaseFee.BitLen(); bfLen > 256 { 197 return fmt.Errorf("too large base fee: bitlen %d", bfLen) 198 } 199 } 200 201 // If we are in ApricotPhase4, ensure that ExtDataGasUsed is populated correctly. 202 if rules.IsApricotPhase4 { 203 // Make sure ExtDataGasUsed is not nil and correct 204 if ethHeader.ExtDataGasUsed == nil { 205 return errNilExtDataGasUsedApricotPhase4 206 } 207 if rules.IsApricotPhase5 { 208 if ethHeader.ExtDataGasUsed.Cmp(params.AtomicGasLimit) == 1 { 209 return fmt.Errorf("too large extDataGasUsed: %d", ethHeader.ExtDataGasUsed) 210 } 211 } else { 212 if !ethHeader.ExtDataGasUsed.IsUint64() { 213 return fmt.Errorf("too large extDataGasUsed: %d", ethHeader.ExtDataGasUsed) 214 } 215 } 216 var totalGasUsed uint64 217 for _, atomicTx := range b.atomicTxs { 218 // We perform this check manually here to avoid the overhead of having to 219 // reparse the atomicTx in `CalcExtDataGasUsed`. 220 fixedFee := rules.IsApricotPhase5 // Charge the atomic tx fixed fee as of ApricotPhase5 221 gasUsed, err := atomicTx.GasUsed(fixedFee) 222 if err != nil { 223 return err 224 } 225 totalGasUsed, err = safemath.Add64(totalGasUsed, gasUsed) 226 if err != nil { 227 return err 228 } 229 } 230 231 switch { 232 case ethHeader.ExtDataGasUsed.Cmp(new(big.Int).SetUint64(totalGasUsed)) != 0: 233 return fmt.Errorf("invalid extDataGasUsed: have %d, want %d", ethHeader.ExtDataGasUsed, totalGasUsed) 234 235 // Make sure BlockGasCost is not nil 236 // NOTE: ethHeader.BlockGasCost correctness is checked in header verification 237 case ethHeader.BlockGasCost == nil: 238 return errNilBlockGasCostApricotPhase4 239 case !ethHeader.BlockGasCost.IsUint64(): 240 return fmt.Errorf("too large blockGasCost: %d", ethHeader.BlockGasCost) 241 } 242 } 243 244 if rules.IsClementine { 245 // In Clementine, ExtraStateRoot must not be empty (should contain the root of the atomic trie). 246 if ethHeader.ExtraStateRoot == (common.Hash{}) { 247 return fmt.Errorf("%w: ExtraStateRoot must not be empty", errInvalidExtraStateRoot) 248 } 249 } else { 250 // Before Clementine, ExtraStateRoot must be empty. 251 if ethHeader.ExtraStateRoot != (common.Hash{}) { 252 return fmt.Errorf("%w: ExtraStateRoot must be empty", errInvalidExtraStateRoot) 253 } 254 } 255 return nil 256 }