Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions op-node/rollup/derive/attributes.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,10 @@ func (ba *FetchingAttributesBuilder) PreparePayloadAttributes(ctx context.Contex
// deposits may never be ignored. Failing to process them is a critical error.
return nil, NewCriticalError(fmt.Errorf("failed to derive some deposits: %w", err))
}
// apply sysCfg changes
if err := UpdateSystemConfigWithL1Receipts(&sysConfig, receipts, ba.rollupCfg, info.Time()); err != nil {
return nil, NewCriticalError(fmt.Errorf("failed to apply derived L1 sysCfg updates: %w", err))
}

// errors from UpdateSystemConfigWithL1Receipts are ignored as they represent malformed or invalid updates
// and there is no recovery mechanism for malformed updates, we must process past them.
_ = UpdateSystemConfigWithL1Receipts(&sysConfig, receipts, ba.rollupCfg, info.Time())
Comment thread
ajsutton marked this conversation as resolved.

l1Info = info
depositTxs = deposits
Expand Down
5 changes: 3 additions & 2 deletions op-node/rollup/derive/l1_traversal.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,9 @@ func (l1t *L1Traversal) AdvanceL1Block(ctx context.Context) error {
return NewTemporaryError(fmt.Errorf("failed to fetch receipts of L1 block %s (parent: %s) for L1 sysCfg update: %w", nextL1Origin, origin, err))
}
if err := UpdateSystemConfigWithL1Receipts(&l1t.sysCfg, receipts, l1t.cfg, nextL1Origin.Time); err != nil {
// the sysCfg changes should always be formatted correctly.
return NewCriticalError(fmt.Errorf("failed to update L1 sysCfg with receipts from block %s: %w", nextL1Origin, err))
// if UpdateSystemConfigWithL1Receipts returns an error, it is because one or more of the receipts are malformed or invalid
// failure to apply is just informational, so we just log the error and continue
l1t.log.Warn("failed to fully update L1 sysCfg with receipts from block", "block", nextL1Origin, "error", err)
}

l1t.block = nextL1Origin
Expand Down
5 changes: 3 additions & 2 deletions op-node/rollup/derive/l1_traversal_managed.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,9 @@ func (l1t *L1TraversalManaged) ProvideNextL1(ctx context.Context, nextL1 eth.L1B
nextL1, nextL1.ParentID(), err))
}
if err := UpdateSystemConfigWithL1Receipts(&l1t.sysCfg, receipts, l1t.cfg, nextL1.Time); err != nil {
// the sysCfg changes should always be formatted correctly.
return NewCriticalError(fmt.Errorf("failed to update L1 sysCfg with receipts from block %s: %w", nextL1, err))
// if UpdateSystemConfigWithL1Receipts returns an error, it is because one or more of the receipts are malformed or invalid
// failure to apply is just informational, so we just log the error and continue
l1t.log.Warn("failed to fully update L1 sysCfg with receipts from block", "block", nextL1, "error", err)
}

logger.Info("Derivation continued with next L1 block")
Expand Down
246 changes: 158 additions & 88 deletions op-node/rollup/derive/system_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,10 @@ import (
"errors"
"fmt"

"github.com/hashicorp/go-multierror"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/hashicorp/go-multierror"

"github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-service/eth"
Expand All @@ -34,15 +33,24 @@ var (
)

// UpdateSystemConfigWithL1Receipts filters all L1 receipts to find config updates and applies the config updates to the given sysCfg
// Updates are applied individually, and any malformed or invalid updates are ignored.
// Any errors encountered during the update process are returned as a multierror.
func UpdateSystemConfigWithL1Receipts(sysCfg *eth.SystemConfig, receipts []*types.Receipt, cfg *rollup.Config, l1Time uint64) error {
var result error
for i, rec := range receipts {
if rec.Status != types.ReceiptStatusSuccessful {
continue
}
for j, log := range rec.Logs {
// copy sysConfig to an update structure to preserve the original in case of error
updated := *sysCfg
if log.Address == cfg.L1SystemConfigAddress && len(log.Topics) > 0 && log.Topics[0] == ConfigUpdateEventABIHash {
if err := ProcessSystemConfigUpdateLogEvent(sysCfg, log, cfg, l1Time); err != nil {
err := ProcessSystemConfigUpdateLogEvent(&updated, log, cfg, l1Time)
if err == nil {
// apply the updated structure
*sysCfg = updated
} else {
// or append the error to the result
Comment thread
axelKingsley marked this conversation as resolved.
result = multierror.Append(result, fmt.Errorf("malformatted L1 system sysCfg log in receipt %d, log %d: %w", i, j, err))
}
}
Expand Down Expand Up @@ -76,44 +84,19 @@ func ProcessSystemConfigUpdateLogEvent(destSysCfg *eth.SystemConfig, ev *types.L
// indexed 1
updateType := ev.Topics[2]

// Create a reader of the unindexed data
reader := bytes.NewReader(ev.Data)
Comment thread
axelKingsley marked this conversation as resolved.

// Attempt to read unindexed data
switch updateType {
case SystemConfigUpdateBatcher:
if pointer, err := solabi.ReadUint64(reader); err != nil || pointer != 32 {
return NewCriticalError(errors.New("invalid pointer field"))
}
if length, err := solabi.ReadUint64(reader); err != nil || length != 32 {
return NewCriticalError(errors.New("invalid length field"))
}
address, err := solabi.ReadAddress(reader)
addr, err := parseSystemConfigUpdateBatcher(ev.Data)
if err != nil {
return NewCriticalError(errors.New("could not read address"))
}
if !solabi.EmptyReader(reader) {
return NewCriticalError(errors.New("too many bytes"))
return err
}
destSysCfg.BatcherAddr = address
destSysCfg.BatcherAddr = addr
return nil
case SystemConfigUpdateFeeScalars:
if pointer, err := solabi.ReadUint64(reader); err != nil || pointer != 32 {
return NewCriticalError(errors.New("invalid pointer field"))
}
if length, err := solabi.ReadUint64(reader); err != nil || length != 64 {
return NewCriticalError(errors.New("invalid length field"))
}
overhead, err := solabi.ReadEthBytes32(reader)
overhead, scalar, err := parseSystemConfigUpdateFeeScalars(ev.Data)
if err != nil {
return NewCriticalError(errors.New("could not read overhead"))
}
scalar, err := solabi.ReadEthBytes32(reader)
if err != nil {
return NewCriticalError(errors.New("could not read scalar"))
}
if !solabi.EmptyReader(reader) {
return NewCriticalError(errors.New("too many bytes"))
return err
}
if rollupCfg.IsEcotone(l1Time) {
if err := eth.CheckEcotoneL1SystemConfigScalar(scalar); err != nil {
Expand All @@ -129,89 +112,176 @@ func ProcessSystemConfigUpdateLogEvent(destSysCfg *eth.SystemConfig, ev *types.L
}
return nil
case SystemConfigUpdateGasLimit:
if pointer, err := solabi.ReadUint64(reader); err != nil || pointer != 32 {
return NewCriticalError(errors.New("invalid pointer field"))
}
if length, err := solabi.ReadUint64(reader); err != nil || length != 32 {
return NewCriticalError(errors.New("invalid length field"))
}
gasLimit, err := solabi.ReadUint64(reader)
gasLimit, err := parseSystemConfigUpdateGasLimit(ev.Data)
if err != nil {
return NewCriticalError(errors.New("could not read gas limit"))
}
if !solabi.EmptyReader(reader) {
return NewCriticalError(errors.New("too many bytes"))
return err
}
destSysCfg.GasLimit = gasLimit
return nil
case SystemConfigUpdateEIP1559Params:
if pointer, err := solabi.ReadUint64(reader); err != nil || pointer != 32 {
return NewCriticalError(errors.New("invalid pointer field"))
}
if length, err := solabi.ReadUint64(reader); err != nil || length != 32 {
return NewCriticalError(errors.New("invalid length field"))
}
params, err := solabi.ReadEthBytes32(reader)
params, err := parseSystemConfigUpdateEIP1559Params(ev.Data)
if err != nil {
return NewCriticalError(errors.New("could not read eip-1559 params"))
}
if !solabi.EmptyReader(reader) {
return NewCriticalError(errors.New("too many bytes"))
return err
}
copy(destSysCfg.EIP1559Params[:], params[24:32])
return nil
case SystemConfigUpdateOperatorFeeParams:
if pointer, err := solabi.ReadUint64(reader); err != nil || pointer != 32 {
return NewCriticalError(errors.New("invalid pointer field"))
}
if length, err := solabi.ReadUint64(reader); err != nil || length != 32 {
return NewCriticalError(errors.New("invalid length field"))
}
params, err := solabi.ReadEthBytes32(reader)
params, err := parseSystemConfigUpdateOperatorFeeParams(ev.Data)
if err != nil {
return NewCriticalError(errors.New("could not read operator fee params"))
}
if !solabi.EmptyReader(reader) {
return NewCriticalError(errors.New("too many bytes"))
return err
}
destSysCfg.OperatorFeeParams = params
return nil
case SystemConfigUpdateUnsafeBlockSigner:
// Ignored in derivation. This configurable applies to runtime configuration outside of the derivation.
return nil
case SystemConfigUpdateMinBaseFee:
if pointer, err := solabi.ReadUint64(reader); err != nil || pointer != 32 {
return NewCriticalError(errors.New("invalid pointer field"))
}
if length, err := solabi.ReadUint64(reader); err != nil || length != 32 {
return NewCriticalError(errors.New("invalid length field"))
}
minBaseFee, err := solabi.ReadUint64(reader)
minBaseFee, err := parseSystemConfigUpdateMinBaseFee(ev.Data)
if err != nil {
return NewCriticalError(errors.New("could not read minBaseFee"))
}
if !solabi.EmptyReader(reader) {
return NewCriticalError(errors.New("too many bytes"))
return err
}
destSysCfg.MinBaseFee = minBaseFee
return nil
case SystemConfigUpdateDAFootprintGasScalar:
if pointer, err := solabi.ReadUint64(reader); err != nil || pointer != 32 {
return NewCriticalError(errors.New("invalid pointer field"))
}
if length, err := solabi.ReadUint64(reader); err != nil || length != 32 {
return NewCriticalError(errors.New("invalid length field"))
}
daFootprintGasScalar, err := solabi.ReadUint16(reader)
daFootprintGasScalar, err := parseSystemConfigUpdateDAFootprintGasScalar(ev.Data)
if err != nil {
return NewCriticalError(errors.New("could not read DA footprint gas scalar"))
}
if !solabi.EmptyReader(reader) {
return NewCriticalError(errors.New("too many bytes"))
return err
}
destSysCfg.DAFootprintGasScalar = daFootprintGasScalar
return nil
default:
return fmt.Errorf("unrecognized L1 sysCfg update type: %s", updateType)
}
}

var ErrParsingSystemConfig = NewCriticalError(errors.New("error parsing system config"))

func parseSystemConfigUpdateBatcher(data []byte) (common.Address, error) {
reader := bytes.NewReader(data)
if pointer, err := solabi.ReadUint64(reader); err != nil || pointer != 32 {
return common.Address{}, fmt.Errorf("%w: invalid pointer field", ErrParsingSystemConfig)
}
if length, err := solabi.ReadUint64(reader); err != nil || length != 32 {
return common.Address{}, fmt.Errorf("%w: invalid length field", ErrParsingSystemConfig)
}
address, err := solabi.ReadAddress(reader)
if err != nil {
return common.Address{}, fmt.Errorf("%w: could not read address", ErrParsingSystemConfig)
}
if !solabi.EmptyReader(reader) {
return common.Address{}, fmt.Errorf("%w: too many bytes", ErrParsingSystemConfig)
}
return address, nil
}

func parseSystemConfigUpdateFeeScalars(data []byte) (eth.Bytes32, eth.Bytes32, error) {
reader := bytes.NewReader(data)
if pointer, err := solabi.ReadUint64(reader); err != nil || pointer != 32 {
return eth.Bytes32{}, eth.Bytes32{}, fmt.Errorf("%w: invalid pointer field", ErrParsingSystemConfig)
}
if length, err := solabi.ReadUint64(reader); err != nil || length != 64 {
return eth.Bytes32{}, eth.Bytes32{}, fmt.Errorf("%w: invalid length field", ErrParsingSystemConfig)
}
overhead, err := solabi.ReadEthBytes32(reader)
if err != nil {
return eth.Bytes32{}, eth.Bytes32{}, fmt.Errorf("%w: could not read overhead", ErrParsingSystemConfig)
}
scalar, err := solabi.ReadEthBytes32(reader)
if err != nil {
return eth.Bytes32{}, eth.Bytes32{}, fmt.Errorf("%w: could not read scalar", ErrParsingSystemConfig)
}
if !solabi.EmptyReader(reader) {
return eth.Bytes32{}, eth.Bytes32{}, fmt.Errorf("%w: too many bytes", ErrParsingSystemConfig)
}
return overhead, scalar, nil
}

func parseSystemConfigUpdateGasLimit(data []byte) (uint64, error) {
reader := bytes.NewReader(data)
if pointer, err := solabi.ReadUint64(reader); err != nil || pointer != 32 {
return 0, fmt.Errorf("%w: invalid pointer field", ErrParsingSystemConfig)
}
if length, err := solabi.ReadUint64(reader); err != nil || length != 32 {
return 0, fmt.Errorf("%w: invalid length field", ErrParsingSystemConfig)
}
gasLimit, err := solabi.ReadUint64(reader)
if err != nil {
return 0, fmt.Errorf("%w: could not read gas limit", ErrParsingSystemConfig)
}
if !solabi.EmptyReader(reader) {
return 0, fmt.Errorf("%w: too many bytes", ErrParsingSystemConfig)
}
return gasLimit, nil
}

func parseSystemConfigUpdateEIP1559Params(data []byte) (eth.Bytes32, error) {
reader := bytes.NewReader(data)
if pointer, err := solabi.ReadUint64(reader); err != nil || pointer != 32 {
return eth.Bytes32{}, fmt.Errorf("%w: invalid pointer field", ErrParsingSystemConfig)
}
if length, err := solabi.ReadUint64(reader); err != nil || length != 32 {
return eth.Bytes32{}, fmt.Errorf("%w: invalid length field", ErrParsingSystemConfig)
}
params, err := solabi.ReadEthBytes32(reader)
if err != nil {
return eth.Bytes32{}, fmt.Errorf("%w: could not read eip-1559 params", ErrParsingSystemConfig)
}
if !solabi.EmptyReader(reader) {
return eth.Bytes32{}, fmt.Errorf("%w: too many bytes", ErrParsingSystemConfig)
}
return params, nil
}

func parseSystemConfigUpdateOperatorFeeParams(data []byte) (eth.Bytes32, error) {
reader := bytes.NewReader(data)
if pointer, err := solabi.ReadUint64(reader); err != nil || pointer != 32 {
return eth.Bytes32{}, fmt.Errorf("%w: invalid pointer field", ErrParsingSystemConfig)
}
if length, err := solabi.ReadUint64(reader); err != nil || length != 32 {
return eth.Bytes32{}, fmt.Errorf("%w: invalid length field", ErrParsingSystemConfig)
}
params, err := solabi.ReadEthBytes32(reader)
if err != nil {
return eth.Bytes32{}, fmt.Errorf("%w: could not read operator fee params", ErrParsingSystemConfig)
}
if !solabi.EmptyReader(reader) {
return eth.Bytes32{}, fmt.Errorf("%w: too many bytes", ErrParsingSystemConfig)
}
return params, nil
}

func parseSystemConfigUpdateMinBaseFee(data []byte) (uint64, error) {
reader := bytes.NewReader(data)
if pointer, err := solabi.ReadUint64(reader); err != nil || pointer != 32 {
return 0, fmt.Errorf("%w: invalid pointer field", ErrParsingSystemConfig)
}
if length, err := solabi.ReadUint64(reader); err != nil || length != 32 {
return 0, fmt.Errorf("%w: invalid length field", ErrParsingSystemConfig)
}
minBaseFee, err := solabi.ReadUint64(reader)
if err != nil {
return 0, fmt.Errorf("%w: could not read minBaseFee", ErrParsingSystemConfig)
}
if !solabi.EmptyReader(reader) {
return 0, fmt.Errorf("%w: too many bytes", ErrParsingSystemConfig)
}
return minBaseFee, nil
}

func parseSystemConfigUpdateDAFootprintGasScalar(data []byte) (uint16, error) {
reader := bytes.NewReader(data)
if pointer, err := solabi.ReadUint64(reader); err != nil || pointer != 32 {
return 0, fmt.Errorf("%w: invalid pointer field", ErrParsingSystemConfig)
}
if length, err := solabi.ReadUint64(reader); err != nil || length != 32 {
return 0, fmt.Errorf("%w: invalid length field", ErrParsingSystemConfig)
}
daFootprintGasScalar, err := solabi.ReadUint16(reader)
if err != nil {
return 0, fmt.Errorf("%w: could not read DA footprint gas scalar", ErrParsingSystemConfig)
}
if !solabi.EmptyReader(reader) {
return 0, fmt.Errorf("%w: too many bytes", ErrParsingSystemConfig)
}
return daFootprintGasScalar, nil
}
Loading