gitlab.com/flarenetwork/coreth@v0.1.1/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  	"github.com/ethereum/go-ethereum/trie"
    12  	coreth "gitlab.com/flarenetwork/coreth/chain"
    13  	"gitlab.com/flarenetwork/coreth/core/types"
    14  	"gitlab.com/flarenetwork/coreth/params"
    15  )
    16  
    17  var (
    18  	phase0BlockValidator     = blockValidatorPhase0{}
    19  	apricotPhase0MinGasPrice = big.NewInt(params.LaunchMinGasPrice)
    20  	phase1BlockValidator     = blockValidatorPhase1{}
    21  	apricotPhase1MinGasPrice = big.NewInt(params.ApricotPhase1MinGasPrice)
    22  	phase3BlockValidator     = blockValidatorPhase3{}
    23  )
    24  
    25  type BlockValidator interface {
    26  	SyntacticVerify(b *Block) error
    27  }
    28  
    29  type blockValidatorPhase0 struct {
    30  	extDataHashes map[common.Hash]common.Hash
    31  }
    32  
    33  func (v blockValidatorPhase0) SyntacticVerify(b *Block) error {
    34  	if b == nil || b.ethBlock == nil {
    35  		return errInvalidBlock
    36  	}
    37  
    38  	blockHash := b.ethBlock.Hash()
    39  	if v.extDataHashes != nil {
    40  		extData := b.ethBlock.ExtData()
    41  		extDataHash := types.CalcExtDataHash(extData)
    42  		// If there is no extra data, check that there is no extra data in the hash map either to ensure we do not
    43  		// have a block that is unexpectedly missing extra data.
    44  		expectedExtDataHash, ok := v.extDataHashes[blockHash]
    45  		if len(extData) == 0 {
    46  			if ok {
    47  				return fmt.Errorf("found block with unexpected missing extra data (%s, %d), expected extra data hash: %s", blockHash, b.Height(), expectedExtDataHash)
    48  			}
    49  		} else {
    50  			// If there is extra data, check to make sure that the extra data hash matches the expected extra data hash for this
    51  			// block
    52  			if extDataHash != expectedExtDataHash {
    53  				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)
    54  			}
    55  		}
    56  	}
    57  
    58  	// Skip verification of the genesis block since it
    59  	// should already be marked as accepted
    60  	if blockHash == b.vm.genesisHash {
    61  		return nil
    62  	}
    63  
    64  	// Perform block and header sanity checks
    65  	ethHeader := b.ethBlock.Header()
    66  	if ethHeader.Number == nil || !ethHeader.Number.IsUint64() {
    67  		return errInvalidBlock
    68  	}
    69  	if ethHeader.Difficulty == nil || !ethHeader.Difficulty.IsUint64() ||
    70  		ethHeader.Difficulty.Uint64() != 1 {
    71  		return fmt.Errorf(
    72  			"expected difficulty to be 1 but got %v: %w",
    73  			ethHeader.Difficulty, errInvalidDifficulty,
    74  		)
    75  	}
    76  	if ethHeader.Nonce.Uint64() != 0 {
    77  		return fmt.Errorf(
    78  			"expected nonce to be 0 but got %d: %w",
    79  			ethHeader.Nonce.Uint64(), errInvalidNonce,
    80  		)
    81  	}
    82  	if ethHeader.MixDigest != (common.Hash{}) {
    83  		return fmt.Errorf(
    84  			"expected MixDigest to be empty but got %x: %w",
    85  			ethHeader.MixDigest, errInvalidMixDigest,
    86  		)
    87  	}
    88  	if ethHeader.ExtDataHash != (common.Hash{}) {
    89  		return fmt.Errorf(
    90  			"expected ExtDataHash to be empty but got %x: %w",
    91  			ethHeader.ExtDataHash, errInvalidExtDataHash,
    92  		)
    93  	}
    94  	headerExtraDataSize := uint64(len(ethHeader.Extra))
    95  	if headerExtraDataSize > params.MaximumExtraDataSize {
    96  		return fmt.Errorf(
    97  			"expected header ExtraData to be <= %d but got %d: %w",
    98  			params.MaximumExtraDataSize, headerExtraDataSize, errHeaderExtraDataTooBig,
    99  		)
   100  	}
   101  	if b.ethBlock.Version() != 0 {
   102  		return fmt.Errorf(
   103  			"expected block version to be 0 but got %d: %w",
   104  			b.ethBlock.Version(), errInvalidBlockVersion,
   105  		)
   106  	}
   107  
   108  	// Check that the tx hash in the header matches the body
   109  	txsHash := types.DeriveSha(b.ethBlock.Transactions(), new(trie.Trie))
   110  	if txsHash != ethHeader.TxHash {
   111  		return errTxHashMismatch
   112  	}
   113  	// Check that the uncle hash in the header matches the body
   114  	uncleHash := types.CalcUncleHash(b.ethBlock.Uncles())
   115  	if uncleHash != ethHeader.UncleHash {
   116  		return errUncleHashMismatch
   117  	}
   118  	// Coinbase must be zero on C-Chain
   119  	if b.ethBlock.Coinbase() != coreth.BlackholeAddr {
   120  		return errInvalidBlock
   121  	}
   122  	// Block must not have any uncles
   123  	if len(b.ethBlock.Uncles()) > 0 {
   124  		return errUnclesUnsupported
   125  	}
   126  	// Block must not be empty
   127  	//
   128  	// Note: extractAtomicTx also asserts a maximum size
   129  	atomicTx, err := b.vm.extractAtomicTx(b.ethBlock)
   130  	if err != nil {
   131  		return err
   132  	}
   133  	txs := b.ethBlock.Transactions()
   134  	if len(txs) == 0 && atomicTx == nil {
   135  		return errEmptyBlock
   136  	}
   137  
   138  	// Make sure that all the txs have the correct fee set.
   139  	for _, tx := range txs {
   140  		if tx.GasPrice().Cmp(apricotPhase0MinGasPrice) < 0 {
   141  			return fmt.Errorf("block contains tx %s with gas price too low (%d < %d)", tx.Hash(), tx.GasPrice(), params.LaunchMinGasPrice)
   142  		}
   143  	}
   144  
   145  	// Make sure the block isn't too far in the future
   146  	blockTimestamp := b.ethBlock.Time()
   147  	if maxBlockTime := uint64(b.vm.clock.Time().Add(maxFutureBlockTime).Unix()); blockTimestamp > maxBlockTime {
   148  		return fmt.Errorf("block timestamp is too far in the future: %d > allowed %d", blockTimestamp, maxBlockTime)
   149  	}
   150  	return nil
   151  }
   152  
   153  type blockValidatorPhase1 struct{}
   154  
   155  func (blockValidatorPhase1) SyntacticVerify(b *Block) error {
   156  	if b == nil || b.ethBlock == nil {
   157  		return errInvalidBlock
   158  	}
   159  
   160  	// Skip verification of the genesis block since it
   161  	// should already be marked as accepted
   162  	if b.ethBlock.Hash() == b.vm.genesisHash {
   163  		return nil
   164  	}
   165  
   166  	// Perform block and header sanity checks
   167  	ethHeader := b.ethBlock.Header()
   168  	if ethHeader.Number == nil || !ethHeader.Number.IsUint64() {
   169  		return errInvalidBlock
   170  	}
   171  	if ethHeader.Difficulty == nil || !ethHeader.Difficulty.IsUint64() ||
   172  		ethHeader.Difficulty.Uint64() != 1 {
   173  		return fmt.Errorf(
   174  			"expected difficulty to be 1 but got %v: %w",
   175  			ethHeader.Difficulty, errInvalidDifficulty,
   176  		)
   177  	}
   178  	if ethHeader.Nonce.Uint64() != 0 {
   179  		return fmt.Errorf(
   180  			"expected nonce to be 0 but got %d: %w",
   181  			ethHeader.Nonce.Uint64(), errInvalidNonce,
   182  		)
   183  	}
   184  	if ethHeader.GasLimit != params.ApricotPhase1GasLimit {
   185  		return fmt.Errorf(
   186  			"expected gas limit to be %d in apricot phase 1 but got %d",
   187  			params.ApricotPhase1GasLimit, ethHeader.GasLimit,
   188  		)
   189  	}
   190  	if ethHeader.MixDigest != (common.Hash{}) {
   191  		return fmt.Errorf(
   192  			"expected MixDigest to be empty but got %x: %w",
   193  			ethHeader.MixDigest, errInvalidMixDigest,
   194  		)
   195  	}
   196  	if hash := types.CalcExtDataHash(b.ethBlock.ExtData()); ethHeader.ExtDataHash != hash {
   197  		return fmt.Errorf("extra data hash mismatch: have %x, want %x", ethHeader.ExtDataHash, hash)
   198  	}
   199  	headerExtraDataSize := uint64(len(ethHeader.Extra))
   200  	if headerExtraDataSize > 0 {
   201  		return fmt.Errorf(
   202  			"expected header ExtraData to be <= 0 but got %d: %w",
   203  			headerExtraDataSize, errHeaderExtraDataTooBig,
   204  		)
   205  	}
   206  	if b.ethBlock.Version() != 0 {
   207  		return fmt.Errorf(
   208  			"expected block version to be 0 but got %d: %w",
   209  			b.ethBlock.Version(), errInvalidBlockVersion,
   210  		)
   211  	}
   212  
   213  	// Check that the tx hash in the header matches the body
   214  	txsHash := types.DeriveSha(b.ethBlock.Transactions(), new(trie.Trie))
   215  	if txsHash != ethHeader.TxHash {
   216  		return errTxHashMismatch
   217  	}
   218  	// Check that the uncle hash in the header matches the body
   219  	uncleHash := types.CalcUncleHash(b.ethBlock.Uncles())
   220  	if uncleHash != ethHeader.UncleHash {
   221  		return errUncleHashMismatch
   222  	}
   223  	// Coinbase must be zero on C-Chain
   224  	if b.ethBlock.Coinbase() != coreth.BlackholeAddr {
   225  		return errInvalidBlock
   226  	}
   227  	// Block must not have any uncles
   228  	if len(b.ethBlock.Uncles()) > 0 {
   229  		return errUnclesUnsupported
   230  	}
   231  	// Block must not be empty
   232  	//
   233  	// Note: extractAtomicTx also asserts a maximum size
   234  	atomicTx, err := b.vm.extractAtomicTx(b.ethBlock)
   235  	if err != nil {
   236  		return err
   237  	}
   238  	txs := b.ethBlock.Transactions()
   239  	if len(txs) == 0 && atomicTx == nil {
   240  		return errEmptyBlock
   241  	}
   242  
   243  	// Make sure that all the txs have the correct fee set.
   244  	for _, tx := range txs {
   245  		if tx.GasPrice().Cmp(apricotPhase1MinGasPrice) < 0 {
   246  			return fmt.Errorf("block contains tx %s with gas price too low (%d < %d)", tx.Hash(), tx.GasPrice(), params.ApricotPhase1MinGasPrice)
   247  		}
   248  	}
   249  
   250  	// Make sure the block isn't too far in the future
   251  	blockTimestamp := b.ethBlock.Time()
   252  	if maxBlockTime := uint64(b.vm.clock.Time().Add(maxFutureBlockTime).Unix()); blockTimestamp > maxBlockTime {
   253  		return fmt.Errorf("block timestamp is too far in the future: %d > allowed %d", blockTimestamp, maxBlockTime)
   254  	}
   255  	return nil
   256  }
   257  
   258  type blockValidatorPhase3 struct{}
   259  
   260  func (blockValidatorPhase3) SyntacticVerify(b *Block) error {
   261  	if b == nil || b.ethBlock == nil {
   262  		return errInvalidBlock
   263  	}
   264  
   265  	// Skip verification of the genesis block since it
   266  	// should already be marked as accepted
   267  	if b.ethBlock.Hash() == b.vm.genesisHash {
   268  		return nil
   269  	}
   270  
   271  	// Perform block and header sanity checks
   272  	ethHeader := b.ethBlock.Header()
   273  	if ethHeader.Number == nil || !ethHeader.Number.IsUint64() {
   274  		return errInvalidBlock
   275  	}
   276  	if ethHeader.Difficulty == nil || !ethHeader.Difficulty.IsUint64() ||
   277  		ethHeader.Difficulty.Uint64() != 1 {
   278  		return fmt.Errorf(
   279  			"expected difficulty to be 1 but got %v: %w",
   280  			ethHeader.Difficulty, errInvalidDifficulty,
   281  		)
   282  	}
   283  	if ethHeader.Nonce.Uint64() != 0 {
   284  		return fmt.Errorf(
   285  			"expected nonce to be 0 but got %d: %w",
   286  			ethHeader.Nonce.Uint64(), errInvalidNonce,
   287  		)
   288  	}
   289  	if ethHeader.GasLimit != params.ApricotPhase1GasLimit {
   290  		return fmt.Errorf(
   291  			"expected gas limit to be %d in apricot phase 1 but got %d",
   292  			params.ApricotPhase1GasLimit, ethHeader.GasLimit,
   293  		)
   294  	}
   295  	if ethHeader.MixDigest != (common.Hash{}) {
   296  		return fmt.Errorf(
   297  			"expected MixDigest to be empty but got %x: %w",
   298  			ethHeader.MixDigest, errInvalidMixDigest,
   299  		)
   300  	}
   301  	if hash := types.CalcExtDataHash(b.ethBlock.ExtData()); ethHeader.ExtDataHash != hash {
   302  		return fmt.Errorf("extra data hash mismatch: have %x, want %x", ethHeader.ExtDataHash, hash)
   303  	}
   304  	if headerExtraDataSize := len(ethHeader.Extra); headerExtraDataSize != params.ApricotPhase3ExtraDataSize {
   305  		return fmt.Errorf(
   306  			"expected header ExtraData to be %d but got %d: %w",
   307  			params.ApricotPhase3ExtraDataSize, headerExtraDataSize, errHeaderExtraDataTooBig,
   308  		)
   309  	}
   310  	if ethHeader.BaseFee == nil {
   311  		return fmt.Errorf("base fee is not set with apricot phase 3 enabled for block: %s", ethHeader.Hash())
   312  	}
   313  	if bfLen := ethHeader.BaseFee.BitLen(); bfLen > 256 {
   314  		return fmt.Errorf("too large base fee: bitlen %d", bfLen)
   315  	}
   316  	if b.ethBlock.Version() != 0 {
   317  		return fmt.Errorf(
   318  			"expected block version to be 0 but got %d: %w",
   319  			b.ethBlock.Version(), errInvalidBlockVersion,
   320  		)
   321  	}
   322  
   323  	// Check that the tx hash in the header matches the body
   324  	txsHash := types.DeriveSha(b.ethBlock.Transactions(), new(trie.Trie))
   325  	if txsHash != ethHeader.TxHash {
   326  		return errTxHashMismatch
   327  	}
   328  	// Check that the uncle hash in the header matches the body
   329  	uncleHash := types.CalcUncleHash(b.ethBlock.Uncles())
   330  	if uncleHash != ethHeader.UncleHash {
   331  		return errUncleHashMismatch
   332  	}
   333  	// Coinbase must be zero on C-Chain
   334  	if b.ethBlock.Coinbase() != coreth.BlackholeAddr {
   335  		return errInvalidBlock
   336  	}
   337  	// Block must not have any uncles
   338  	if len(b.ethBlock.Uncles()) > 0 {
   339  		return errUnclesUnsupported
   340  	}
   341  	// Block must not be empty
   342  	//
   343  	// Note: extractAtomicTx also asserts a maximum size
   344  	atomicTx, err := b.vm.extractAtomicTx(b.ethBlock)
   345  	if err != nil {
   346  		return err
   347  	}
   348  	txs := b.ethBlock.Transactions()
   349  	if len(txs) == 0 && atomicTx == nil {
   350  		return errEmptyBlock
   351  	}
   352  
   353  	// Make sure the block isn't too far in the future
   354  	blockTimestamp := b.ethBlock.Time()
   355  	if maxBlockTime := uint64(b.vm.clock.Time().Add(maxFutureBlockTime).Unix()); blockTimestamp > maxBlockTime {
   356  		return fmt.Errorf("block timestamp is too far in the future: %d > allowed %d", blockTimestamp, maxBlockTime)
   357  	}
   358  	return nil
   359  }