block_processor.go 5.54 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 56 57 58 59 60 61
	if attrs.EIP1559Params != nil {
		d, e := eip1559.DecodeHolocene1559Params(attrs.EIP1559Params[:])
		if d == 0 {
			d = provider.Config().BaseFeeChangeDenominator(header.Time)
			e = provider.Config().ElasticityMultiplier()
		}
		header.Extra = eip1559.EncodeHoloceneExtraData(d, e)
	}
62

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

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

	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)
81
	header.BaseFee = eip1559.CalcBaseFee(provider.Config(), parentHeader, header.Time)
82 83
	header.GasUsed = 0
	gasPool := new(core.GasPool).AddGas(header.GasLimit)
84
	mkEVM := func() *vm.EVM {
85 86 87
		// 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)
88 89
		// NOTE: Unlikely to be needed for the beacon block root, but we setup any precompile overrides anyways for forwards-compatibility
		var precompileOverrides vm.PrecompileOverrides
90 91
		if vmConfig := provider.GetVMConfig(); vmConfig != nil && vmConfig.PrecompileOverrides != nil {
			precompileOverrides = vmConfig.PrecompileOverrides
92
		}
93
		vmenv := vm.NewEVM(context, vm.TxContext{}, statedb, provider.Config(), vm.Config{PrecompileOverrides: precompileOverrides})
94 95 96 97 98 99 100 101 102 103
		return vmenv
	}
	if h.ParentBeaconRoot != nil {
		if provider.Config().IsCancun(header.Number, header.Time) {
			// Blob tx not supported on optimism chains but fields must be set when Cancun is active.
			zero := uint64(0)
			header.BlobGasUsed = &zero
			header.ExcessBlobGas = &zero
		}
		vmenv := mkEVM()
104 105
		core.ProcessBeaconBlockRoot(*header.ParentBeaconRoot, vmenv, statedb)
	}
106 107 108 109
	if provider.Config().IsPrague(header.Number, header.Time) {
		vmenv := mkEVM()
		core.ProcessParentBlockHash(header.ParentHash, vmenv, statedb)
	}
110
	return &BlockProcessor{
111
		header:       header,
112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133
		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 {
134
		return fmt.Errorf("failed to apply transaction to L2 block (tx %d): %w", txIndex, err)
135 136 137 138 139 140 141
	}
	b.receipts = append(b.receipts, receipt)
	b.transactions = append(b.transactions, tx)
	return nil
}

func (b *BlockProcessor) Assemble() (*types.Block, error) {
142 143 144 145 146
	body := types.Body{
		Transactions: b.transactions,
	}

	return b.dataProvider.Engine().FinalizeAndAssemble(b.dataProvider, b.header, b.state, &body, b.receipts)
147 148 149
}

func (b *BlockProcessor) Commit() error {
150
	root, err := b.state.Commit(b.header.Number.Uint64(), b.dataProvider.Config().IsEIP158(b.header.Number))
151 152 153 154 155 156 157 158
	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
}