block_processor.go 4.93 KB
Newer Older
1 2 3 4 5 6 7
package engineapi

import (
	"errors"
	"fmt"
	"math/big"

8
	"github.com/ethereum-optimism/optimism/op-service/eth"
9 10
	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/consensus"
11
	"github.com/ethereum/go-ethereum/consensus/misc/eip1559"
12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
	"github.com/ethereum/go-ethereum/core"
	"github.com/ethereum/go-ethereum/core/state"
	"github.com/ethereum/go-ethereum/core/types"
	"github.com/ethereum/go-ethereum/core/vm"
	"github.com/ethereum/go-ethereum/params"
)

var (
	ErrExceedsGasLimit = errors.New("tx gas exceeds block gas limit")
	ErrUsesTooMuchGas  = errors.New("action takes too much gas")
)

type BlockDataProvider interface {
	StateAt(root common.Hash) (*state.StateDB, error)
	GetHeader(common.Hash, uint64) *types.Header
	Engine() consensus.Engine
	GetVMConfig() *vm.Config
	Config() *params.ChainConfig
	consensus.ChainHeaderReader
}

type BlockProcessor struct {
	header       *types.Header
	state        *state.StateDB
	receipts     types.Receipts
	transactions types.Transactions
	gasPool      *core.GasPool
	dataProvider BlockDataProvider
}

42
func NewBlockProcessorFromPayloadAttributes(provider BlockDataProvider, parent common.Hash, attrs *eth.PayloadAttributes) (*BlockProcessor, error) {
43
	header := &types.Header{
44
		ParentHash:       parent,
45
		Coinbase:         attrs.SuggestedFeeRecipient,
46
		Difficulty:       common.Big0,
47 48
		GasLimit:         uint64(*attrs.GasLimit),
		Time:             uint64(attrs.Timestamp),
49
		Extra:            nil,
50
		MixDigest:        common.Hash(attrs.PrevRandao),
51
		Nonce:            types.EncodeNonce(0),
52
		ParentBeaconRoot: attrs.ParentBeaconBlockRoot,
53
	}
54 55

	// Ecotone
56
	if attrs.ParentBeaconBlockRoot != nil {
57 58 59 60 61
		zero := uint64(0)
		header.BlobGasUsed = &zero
		header.ExcessBlobGas = &zero
	}

62 63 64 65
	return NewBlockProcessorFromHeader(provider, header)
}

func NewBlockProcessorFromHeader(provider BlockDataProvider, h *types.Header) (*BlockProcessor, error) {
66
	header := types.CopyHeader(h) // Copy to avoid mutating the original header
67 68 69 70 71 72 73 74 75 76 77 78 79

	if header.GasLimit > params.MaxGasLimit {
		return nil, fmt.Errorf("invalid gasLimit: have %v, max %v", header.GasLimit, params.MaxGasLimit)
	}
	parentHeader := provider.GetHeaderByHash(header.ParentHash)
	if header.Time <= parentHeader.Time {
		return nil, errors.New("invalid timestamp")
	}
	statedb, err := provider.StateAt(parentHeader.Root)
	if err != nil {
		return nil, fmt.Errorf("get parent state: %w", err)
	}
	header.Number = new(big.Int).Add(parentHeader.Number, common.Big1)
80
	header.BaseFee = eip1559.CalcBaseFee(provider.Config(), parentHeader, header.Time)
81 82
	header.GasUsed = 0
	gasPool := new(core.GasPool).AddGas(header.GasLimit)
83 84 85 86
	if h.ParentBeaconRoot != nil {
		// Unfortunately this is not part of any Geth environment setup,
		// we just have to apply it, like how the Geth block-builder worker does.
		context := core.NewEVMBlockContext(header, provider, nil, provider.Config(), statedb)
87 88 89 90 91 92
		// NOTE: Unlikely to be needed for the beacon block root, but we setup any precompile overrides anyways for forwards-compatibility
		var precompileOverrides vm.PrecompileOverrides
		if vmConfig := provider.GetVMConfig(); vmConfig != nil && vmConfig.OptimismPrecompileOverrides != nil {
			precompileOverrides = vmConfig.OptimismPrecompileOverrides
		}
		vmenv := vm.NewEVM(context, vm.TxContext{}, statedb, provider.Config(), vm.Config{OptimismPrecompileOverrides: precompileOverrides})
93 94
		core.ProcessBeaconBlockRoot(*header.ParentBeaconRoot, vmenv, statedb)
	}
95
	return &BlockProcessor{
96
		header:       header,
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
		state:        statedb,
		gasPool:      gasPool,
		dataProvider: provider,
	}, nil
}

func (b *BlockProcessor) CheckTxWithinGasLimit(tx *types.Transaction) error {
	if tx.Gas() > b.header.GasLimit {
		return fmt.Errorf("%w tx gas: %d, block gas limit: %d", ErrExceedsGasLimit, tx.Gas(), b.header.GasLimit)
	}
	if tx.Gas() > b.gasPool.Gas() {
		return fmt.Errorf("%w: %d, only have %d", ErrUsesTooMuchGas, tx.Gas(), b.gasPool.Gas())
	}
	return nil
}

func (b *BlockProcessor) AddTx(tx *types.Transaction) error {
	txIndex := len(b.transactions)
	b.state.SetTxContext(tx.Hash(), txIndex)
	receipt, err := core.ApplyTransaction(b.dataProvider.Config(), b.dataProvider, &b.header.Coinbase,
		b.gasPool, b.state, b.header, tx, &b.header.GasUsed, *b.dataProvider.GetVMConfig())
	if err != nil {
119
		return fmt.Errorf("failed to apply transaction to L2 block (tx %d): %w", txIndex, err)
120 121 122 123 124 125 126 127 128 129 130
	}
	b.receipts = append(b.receipts, receipt)
	b.transactions = append(b.transactions, tx)
	return nil
}

func (b *BlockProcessor) Assemble() (*types.Block, error) {
	return b.dataProvider.Engine().FinalizeAndAssemble(b.dataProvider, b.header, b.state, b.transactions, nil, b.receipts, nil)
}

func (b *BlockProcessor) Commit() error {
131
	root, err := b.state.Commit(b.header.Number.Uint64(), b.dataProvider.Config().IsEIP158(b.header.Number))
132 133 134 135 136 137 138 139
	if err != nil {
		return fmt.Errorf("state write error: %w", err)
	}
	if err := b.state.Database().TrieDB().Commit(root, false); err != nil {
		return fmt.Errorf("trie write error: %w", err)
	}
	return nil
}