Commit b0b8b4cf authored by protolambda's avatar protolambda Committed by GitHub

op-node: split start of payload building and confirmation of payload (#3641)

Co-authored-by: default avatarmergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
parent 0eaae3bf
...@@ -5,9 +5,10 @@ import ( ...@@ -5,9 +5,10 @@ import (
"errors" "errors"
"fmt" "fmt"
"github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/op-node/eth"
) )
// isDepositTx checks an opaqueTx to determine if it is a Deposit Transaction // isDepositTx checks an opaqueTx to determine if it is a Deposit Transaction
...@@ -68,9 +69,13 @@ func sanityCheckPayload(payload *eth.ExecutionPayload) error { ...@@ -68,9 +69,13 @@ func sanityCheckPayload(payload *eth.ExecutionPayload) error {
type BlockInsertionErrType uint type BlockInsertionErrType uint
const ( const (
// BlockInsertOK indicates that the payload was successfully executed and appended to the canonical chain.
BlockInsertOK BlockInsertionErrType = iota BlockInsertOK BlockInsertionErrType = iota
// BlockInsertTemporaryErr indicates that the insertion failed but may succeed at a later time without changes to the payload.
BlockInsertTemporaryErr BlockInsertTemporaryErr
// BlockInsertPrestateErr indicates that the pre-state to insert the payload could not be prepared, e.g. due to missing chain data.
BlockInsertPrestateErr BlockInsertPrestateErr
// BlockInsertPayloadErr indicates that the payload was invalid and cannot become canonical.
BlockInsertPayloadErr BlockInsertPayloadErr
) )
...@@ -80,37 +85,53 @@ const ( ...@@ -80,37 +85,53 @@ const (
// If updateSafe is true, the head block is considered to be the safe head as well as the head. // If updateSafe is true, the head block is considered to be the safe head as well as the head.
// It returns the payload, an RPC error (if the payload might still be valid), and a payload error (if the payload was not valid) // It returns the payload, an RPC error (if the payload might still be valid), and a payload error (if the payload was not valid)
func InsertHeadBlock(ctx context.Context, log log.Logger, eng Engine, fc eth.ForkchoiceState, attrs *eth.PayloadAttributes, updateSafe bool) (out *eth.ExecutionPayload, errTyp BlockInsertionErrType, err error) { func InsertHeadBlock(ctx context.Context, log log.Logger, eng Engine, fc eth.ForkchoiceState, attrs *eth.PayloadAttributes, updateSafe bool) (out *eth.ExecutionPayload, errTyp BlockInsertionErrType, err error) {
id, errTyp, err := StartPayload(ctx, eng, fc, attrs)
if err != nil {
return nil, errTyp, err
}
return ConfirmPayload(ctx, log, eng, fc, id, updateSafe)
}
// StartPayload starts an execution payload building process in the provided Engine, with the given attributes.
// The severity of the error is distinguished to determine whether the same payload attributes may be re-attempted later.
func StartPayload(ctx context.Context, eng Engine, fc eth.ForkchoiceState, attrs *eth.PayloadAttributes) (id eth.PayloadID, errType BlockInsertionErrType, err error) {
fcRes, err := eng.ForkchoiceUpdate(ctx, &fc, attrs) fcRes, err := eng.ForkchoiceUpdate(ctx, &fc, attrs)
if err != nil { if err != nil {
var inputErr eth.InputError var inputErr eth.InputError
if errors.As(err, &inputErr) { if errors.As(err, &inputErr) {
switch inputErr.Code { switch inputErr.Code {
case eth.InvalidForkchoiceState: case eth.InvalidForkchoiceState:
return nil, BlockInsertPrestateErr, fmt.Errorf("pre-block-creation forkchoice update was inconsistent with engine, need reset to resolve: %w", inputErr.Unwrap()) return eth.PayloadID{}, BlockInsertPrestateErr, fmt.Errorf("pre-block-creation forkchoice update was inconsistent with engine, need reset to resolve: %w", inputErr.Unwrap())
case eth.InvalidPayloadAttributes: case eth.InvalidPayloadAttributes:
return nil, BlockInsertPayloadErr, fmt.Errorf("payload attributes are not valid, cannot build block: %w", inputErr.Unwrap()) return eth.PayloadID{}, BlockInsertPayloadErr, fmt.Errorf("payload attributes are not valid, cannot build block: %w", inputErr.Unwrap())
default: default:
return nil, BlockInsertPrestateErr, fmt.Errorf("unexpected error code in forkchoice-updated response: %w", err) return eth.PayloadID{}, BlockInsertPrestateErr, fmt.Errorf("unexpected error code in forkchoice-updated response: %w", err)
} }
} else { } else {
return nil, BlockInsertTemporaryErr, fmt.Errorf("failed to create new block via forkchoice: %w", err) return eth.PayloadID{}, BlockInsertTemporaryErr, fmt.Errorf("failed to create new block via forkchoice: %w", err)
} }
} }
switch fcRes.PayloadStatus.Status { switch fcRes.PayloadStatus.Status {
// TODO(proto): snap sync - specify explicit different error type if node is syncing // TODO(proto): snap sync - specify explicit different error type if node is syncing
case eth.ExecutionInvalid, eth.ExecutionInvalidBlockHash: case eth.ExecutionInvalid, eth.ExecutionInvalidBlockHash:
return nil, BlockInsertPayloadErr, eth.ForkchoiceUpdateErr(fcRes.PayloadStatus) return eth.PayloadID{}, BlockInsertPayloadErr, eth.ForkchoiceUpdateErr(fcRes.PayloadStatus)
case eth.ExecutionValid: case eth.ExecutionValid:
break id := fcRes.PayloadID
if id == nil {
return eth.PayloadID{}, BlockInsertTemporaryErr, errors.New("nil id in forkchoice result when expecting a valid ID")
}
return *id, BlockInsertOK, nil
default: default:
return nil, BlockInsertTemporaryErr, eth.ForkchoiceUpdateErr(fcRes.PayloadStatus) return eth.PayloadID{}, BlockInsertTemporaryErr, eth.ForkchoiceUpdateErr(fcRes.PayloadStatus)
} }
id := fcRes.PayloadID }
if id == nil {
return nil, BlockInsertTemporaryErr, errors.New("nil id in forkchoice result when expecting a valid ID") // ConfirmPayload ends an execution payload building process in the provided Engine, and persists the payload as the canonical head.
} // If updateSafe is true, then the payload will also be recognized as safe-head at the same time.
payload, err := eng.GetPayload(ctx, *id) // The severity of the error is distinguished to determine whether the payload was valid and can become canonical.
func ConfirmPayload(ctx context.Context, log log.Logger, eng Engine, fc eth.ForkchoiceState, id eth.PayloadID, updateSafe bool) (out *eth.ExecutionPayload, errTyp BlockInsertionErrType, err error) {
payload, err := eng.GetPayload(ctx, id)
if err != nil { if err != nil {
// 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. // 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.
return nil, BlockInsertTemporaryErr, fmt.Errorf("failed to get execution payload: %w", err) return nil, BlockInsertTemporaryErr, fmt.Errorf("failed to get execution payload: %w", err)
...@@ -134,7 +155,7 @@ func InsertHeadBlock(ctx context.Context, log log.Logger, eng Engine, fc eth.For ...@@ -134,7 +155,7 @@ func InsertHeadBlock(ctx context.Context, log log.Logger, eng Engine, fc eth.For
if updateSafe { if updateSafe {
fc.SafeBlockHash = payload.BlockHash fc.SafeBlockHash = payload.BlockHash
} }
fcRes, err = eng.ForkchoiceUpdate(ctx, &fc, nil) fcRes, err := eng.ForkchoiceUpdate(ctx, &fc, nil)
if err != nil { if err != nil {
var inputErr eth.InputError var inputErr eth.InputError
if errors.As(err, &inputErr) { if errors.As(err, &inputErr) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment