github.com/ethereum-optimism/optimism@v1.7.2/op-node/rollup/derive/engine_update.go (about)

     1  package derive
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  
     8  	"github.com/ethereum/go-ethereum/core/types"
     9  	"github.com/ethereum/go-ethereum/log"
    10  
    11  	"github.com/ethereum-optimism/optimism/op-node/rollup/async"
    12  	"github.com/ethereum-optimism/optimism/op-node/rollup/conductor"
    13  	"github.com/ethereum-optimism/optimism/op-service/eth"
    14  )
    15  
    16  // isDepositTx checks an opaqueTx to determine if it is a Deposit Transaction
    17  // It has to return an error in the case the transaction is empty
    18  func isDepositTx(opaqueTx eth.Data) (bool, error) {
    19  	if len(opaqueTx) == 0 {
    20  		return false, errors.New("empty transaction")
    21  	}
    22  	return opaqueTx[0] == types.DepositTxType, nil
    23  }
    24  
    25  // lastDeposit finds the index of last deposit at the start of the transactions.
    26  // It walks the transactions from the start until it finds a non-deposit tx.
    27  // An error is returned if any looked at transaction cannot be decoded
    28  func lastDeposit(txns []eth.Data) (int, error) {
    29  	var lastDeposit int
    30  	for i, tx := range txns {
    31  		deposit, err := isDepositTx(tx)
    32  		if err != nil {
    33  			return 0, fmt.Errorf("invalid transaction at idx %d", i)
    34  		}
    35  		if deposit {
    36  			lastDeposit = i
    37  		} else {
    38  			break
    39  		}
    40  	}
    41  	return lastDeposit, nil
    42  }
    43  
    44  func sanityCheckPayload(payload *eth.ExecutionPayload) error {
    45  	// Sanity check payload before inserting it
    46  	if len(payload.Transactions) == 0 {
    47  		return errors.New("no transactions in returned payload")
    48  	}
    49  	if payload.Transactions[0][0] != types.DepositTxType {
    50  		return fmt.Errorf("first transaction was not deposit tx. Got %v", payload.Transactions[0][0])
    51  	}
    52  	// Ensure that the deposits are first
    53  	lastDeposit, err := lastDeposit(payload.Transactions)
    54  	if err != nil {
    55  		return fmt.Errorf("failed to find last deposit: %w", err)
    56  	}
    57  	// Ensure no deposits after last deposit
    58  	for i := lastDeposit + 1; i < len(payload.Transactions); i++ {
    59  		tx := payload.Transactions[i]
    60  		deposit, err := isDepositTx(tx)
    61  		if err != nil {
    62  			return fmt.Errorf("failed to decode transaction idx %d: %w", i, err)
    63  		}
    64  		if deposit {
    65  			return fmt.Errorf("deposit tx (%d) after other tx in l2 block with prev deposit at idx %d", i, lastDeposit)
    66  		}
    67  	}
    68  	return nil
    69  }
    70  
    71  type BlockInsertionErrType uint
    72  
    73  const (
    74  	// BlockInsertOK indicates that the payload was successfully executed and appended to the canonical chain.
    75  	BlockInsertOK BlockInsertionErrType = iota
    76  	// BlockInsertTemporaryErr indicates that the insertion failed but may succeed at a later time without changes to the payload.
    77  	BlockInsertTemporaryErr
    78  	// BlockInsertPrestateErr indicates that the pre-state to insert the payload could not be prepared, e.g. due to missing chain data.
    79  	BlockInsertPrestateErr
    80  	// BlockInsertPayloadErr indicates that the payload was invalid and cannot become canonical.
    81  	BlockInsertPayloadErr
    82  )
    83  
    84  // startPayload starts an execution payload building process in the provided Engine, with the given attributes.
    85  // The severity of the error is distinguished to determine whether the same payload attributes may be re-attempted later.
    86  func startPayload(ctx context.Context, eng ExecEngine, fc eth.ForkchoiceState, attrs *eth.PayloadAttributes) (id eth.PayloadID, errType BlockInsertionErrType, err error) {
    87  	fcRes, err := eng.ForkchoiceUpdate(ctx, &fc, attrs)
    88  	if err != nil {
    89  		var inputErr eth.InputError
    90  		if errors.As(err, &inputErr) {
    91  			switch inputErr.Code {
    92  			case eth.InvalidForkchoiceState:
    93  				return eth.PayloadID{}, BlockInsertPrestateErr, fmt.Errorf("pre-block-creation forkchoice update was inconsistent with engine, need reset to resolve: %w", inputErr.Unwrap())
    94  			case eth.InvalidPayloadAttributes:
    95  				return eth.PayloadID{}, BlockInsertPayloadErr, fmt.Errorf("payload attributes are not valid, cannot build block: %w", inputErr.Unwrap())
    96  			default:
    97  				return eth.PayloadID{}, BlockInsertPrestateErr, fmt.Errorf("unexpected error code in forkchoice-updated response: %w", err)
    98  			}
    99  		} else {
   100  			return eth.PayloadID{}, BlockInsertTemporaryErr, fmt.Errorf("failed to create new block via forkchoice: %w", err)
   101  		}
   102  	}
   103  
   104  	switch fcRes.PayloadStatus.Status {
   105  	// TODO(proto): snap sync - specify explicit different error type if node is syncing
   106  	case eth.ExecutionInvalid, eth.ExecutionInvalidBlockHash:
   107  		return eth.PayloadID{}, BlockInsertPayloadErr, eth.ForkchoiceUpdateErr(fcRes.PayloadStatus)
   108  	case eth.ExecutionValid:
   109  		id := fcRes.PayloadID
   110  		if id == nil {
   111  			return eth.PayloadID{}, BlockInsertTemporaryErr, errors.New("nil id in forkchoice result when expecting a valid ID")
   112  		}
   113  		return *id, BlockInsertOK, nil
   114  	default:
   115  		return eth.PayloadID{}, BlockInsertTemporaryErr, eth.ForkchoiceUpdateErr(fcRes.PayloadStatus)
   116  	}
   117  }
   118  
   119  // confirmPayload ends an execution payload building process in the provided Engine, and persists the payload as the canonical head.
   120  // If updateSafe is true, then the payload will also be recognized as safe-head at the same time.
   121  // The severity of the error is distinguished to determine whether the payload was valid and can become canonical.
   122  func confirmPayload(
   123  	ctx context.Context,
   124  	log log.Logger,
   125  	eng ExecEngine,
   126  	fc eth.ForkchoiceState,
   127  	payloadInfo eth.PayloadInfo,
   128  	updateSafe bool,
   129  	agossip async.AsyncGossiper,
   130  	sequencerConductor conductor.SequencerConductor,
   131  ) (out *eth.ExecutionPayloadEnvelope, errTyp BlockInsertionErrType, err error) {
   132  	var envelope *eth.ExecutionPayloadEnvelope
   133  	// if the payload is available from the async gossiper, it means it was not yet imported, so we reuse it
   134  	if cached := agossip.Get(); cached != nil {
   135  		envelope = cached
   136  		// log a limited amount of information about the reused payload, more detailed logging happens later down
   137  		log.Debug("found uninserted payload from async gossiper, reusing it and bypassing engine",
   138  			"hash", envelope.ExecutionPayload.BlockHash,
   139  			"number", uint64(envelope.ExecutionPayload.BlockNumber),
   140  			"parent", envelope.ExecutionPayload.ParentHash,
   141  			"txs", len(envelope.ExecutionPayload.Transactions))
   142  	} else {
   143  		envelope, err = eng.GetPayload(ctx, payloadInfo)
   144  	}
   145  	if err != nil {
   146  		// even if it is an input-error (unknown payload ID), it is temporary, since we will re-attempt the full payload building, not just the retrieval of the payload.
   147  		return nil, BlockInsertTemporaryErr, fmt.Errorf("failed to get execution payload: %w", err)
   148  	}
   149  	payload := envelope.ExecutionPayload
   150  	if err := sanityCheckPayload(payload); err != nil {
   151  		return nil, BlockInsertPayloadErr, err
   152  	}
   153  	if err := sequencerConductor.CommitUnsafePayload(ctx, envelope); err != nil {
   154  		return nil, BlockInsertTemporaryErr, fmt.Errorf("failed to commit unsafe payload to conductor: %w", err)
   155  	}
   156  	// begin gossiping as soon as possible
   157  	// agossip.Clear() will be called later if an non-temporary error is found, or if the payload is successfully inserted
   158  	agossip.Gossip(envelope)
   159  
   160  	status, err := eng.NewPayload(ctx, payload, envelope.ParentBeaconBlockRoot)
   161  	if err != nil {
   162  		return nil, BlockInsertTemporaryErr, fmt.Errorf("failed to insert execution payload: %w", err)
   163  	}
   164  	if status.Status == eth.ExecutionInvalid || status.Status == eth.ExecutionInvalidBlockHash {
   165  		agossip.Clear()
   166  		return nil, BlockInsertPayloadErr, eth.NewPayloadErr(payload, status)
   167  	}
   168  	if status.Status != eth.ExecutionValid {
   169  		return nil, BlockInsertTemporaryErr, eth.NewPayloadErr(payload, status)
   170  	}
   171  
   172  	fc.HeadBlockHash = payload.BlockHash
   173  	if updateSafe {
   174  		fc.SafeBlockHash = payload.BlockHash
   175  	}
   176  	fcRes, err := eng.ForkchoiceUpdate(ctx, &fc, nil)
   177  	if err != nil {
   178  		var inputErr eth.InputError
   179  		if errors.As(err, &inputErr) {
   180  			switch inputErr.Code {
   181  			case eth.InvalidForkchoiceState:
   182  				// if we succeed to update the forkchoice pre-payload, but fail post-payload, then it is a payload error
   183  				agossip.Clear()
   184  				return nil, BlockInsertPayloadErr, fmt.Errorf("post-block-creation forkchoice update was inconsistent with engine, need reset to resolve: %w", inputErr.Unwrap())
   185  			default:
   186  				agossip.Clear()
   187  				return nil, BlockInsertPrestateErr, fmt.Errorf("unexpected error code in forkchoice-updated response: %w", err)
   188  			}
   189  		} else {
   190  			return nil, BlockInsertTemporaryErr, fmt.Errorf("failed to make the new L2 block canonical via forkchoice: %w", err)
   191  		}
   192  	}
   193  	agossip.Clear()
   194  	if fcRes.PayloadStatus.Status != eth.ExecutionValid {
   195  		return nil, BlockInsertPayloadErr, eth.ForkchoiceUpdateErr(fcRes.PayloadStatus)
   196  	}
   197  	log.Info("inserted block", "hash", payload.BlockHash, "number", uint64(payload.BlockNumber),
   198  		"state_root", payload.StateRoot, "timestamp", uint64(payload.Timestamp), "parent", payload.ParentHash,
   199  		"prev_randao", payload.PrevRandao, "fee_recipient", payload.FeeRecipient,
   200  		"txs", len(payload.Transactions), "update_safe", updateSafe)
   201  	return envelope, BlockInsertOK, nil
   202  }