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  }