l2_engine_api.go 22.6 KB
Newer Older
1
package engineapi
2 3 4

import (
	"context"
5 6
	"crypto/sha256"
	"encoding/binary"
7 8
	"errors"
	"fmt"
9
	"math/big"
10 11
	"time"

12
	"github.com/ethereum-optimism/optimism/op-service/eth"
13
	"github.com/ethereum/go-ethereum/beacon/engine"
14
	"github.com/ethereum/go-ethereum/common"
15
	"github.com/ethereum/go-ethereum/consensus"
16
	"github.com/ethereum/go-ethereum/consensus/misc/eip1559"
17
	"github.com/ethereum/go-ethereum/core/state"
18
	"github.com/ethereum/go-ethereum/core/stateless"
19
	"github.com/ethereum/go-ethereum/core/types"
20
	"github.com/ethereum/go-ethereum/core/vm"
21
	"github.com/ethereum/go-ethereum/eth/downloader"
22
	"github.com/ethereum/go-ethereum/log"
23
	"github.com/ethereum/go-ethereum/params"
24
	"github.com/holiman/uint256"
25 26
)

27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
type EngineBackend interface {
	CurrentSafeBlock() *types.Header
	CurrentFinalBlock() *types.Header
	GetBlockByHash(hash common.Hash) *types.Block
	GetBlock(hash common.Hash, number uint64) *types.Block
	HasBlockAndState(hash common.Hash, number uint64) bool
	GetCanonicalHash(n uint64) common.Hash

	GetVMConfig() *vm.Config
	Config() *params.ChainConfig
	// Engine retrieves the chain's consensus engine.
	Engine() consensus.Engine

	StateAt(root common.Hash) (*state.StateDB, error)

42
	InsertBlockWithoutSetHead(block *types.Block, makeWitness bool) (*stateless.Witness, error)
43 44 45
	SetCanonical(head *types.Block) (common.Hash, error)
	SetFinalized(header *types.Header)
	SetSafe(header *types.Header)
46 47

	consensus.ChainHeaderReader
48 49
}

50 51 52 53 54
type CachingEngineBackend interface {
	EngineBackend
	AssembleAndInsertBlockWithoutSetHead(processor *BlockProcessor) (*types.Block, error)
}

55 56 57
// L2EngineAPI wraps an engine actor, and implements the RPC backend required to serve the engine API.
// This re-implements some of the Geth API work, but changes the API backend so we can deterministically
// build and control the L2 block contents to reach very specific edge cases as desired for testing.
58 59 60 61
type L2EngineAPI struct {
	log     log.Logger
	backend EngineBackend

62 63 64 65
	// Functionality for snap sync
	remotes    map[common.Hash]*types.Block
	downloader *downloader.Downloader

66
	// L2 block building data
67 68 69 70
	blockProcessor *BlockProcessor
	pendingIndices map[common.Address]uint64 // per account, how many txs from the pool were already included in the block, since the pool is lagging behind block mining.
	l2ForceEmpty   bool                      // when no additional txs may be processed (i.e. when sequencer drift runs out)
	l2TxFailed     []*types.Transaction      // log of failed transactions which could not be included
71 72 73 74

	payloadID engine.PayloadID // ID of payload that is currently being built
}

75
func NewL2EngineAPI(log log.Logger, backend EngineBackend, downloader *downloader.Downloader) *L2EngineAPI {
76
	return &L2EngineAPI{
77 78 79 80
		log:        log,
		backend:    backend,
		remotes:    make(map[common.Hash]*types.Block),
		downloader: downloader,
81 82
	}
}
83 84

var (
85 86
	STATUS_INVALID = &eth.ForkchoiceUpdatedResult{PayloadStatus: eth.PayloadStatusV1{Status: eth.ExecutionInvalid}, PayloadID: nil}
	STATUS_SYNCING = &eth.ForkchoiceUpdatedResult{PayloadStatus: eth.PayloadStatusV1{Status: eth.ExecutionSyncing}, PayloadID: nil}
87 88
)

89
// computePayloadId computes a pseudo-random payloadid, based on the parameters.
90
func computePayloadId(headBlockHash common.Hash, attrs *eth.PayloadAttributes) engine.PayloadID {
91 92 93
	// Hash
	hasher := sha256.New()
	hasher.Write(headBlockHash[:])
94 95 96 97 98 99
	_ = binary.Write(hasher, binary.BigEndian, attrs.Timestamp)
	hasher.Write(attrs.PrevRandao[:])
	hasher.Write(attrs.SuggestedFeeRecipient[:])
	_ = binary.Write(hasher, binary.BigEndian, attrs.NoTxPool)
	_ = binary.Write(hasher, binary.BigEndian, uint64(len(attrs.Transactions)))
	for _, tx := range attrs.Transactions {
100 101 102
		_ = binary.Write(hasher, binary.BigEndian, uint64(len(tx))) // length-prefix to avoid collisions
		hasher.Write(tx)
	}
103
	_ = binary.Write(hasher, binary.BigEndian, *attrs.GasLimit)
104 105 106
	if attrs.EIP1559Params != nil {
		hasher.Write(attrs.EIP1559Params[:])
	}
107
	var out engine.PayloadID
108 109 110 111
	copy(out[:], hasher.Sum(nil)[:8])
	return out
}

112
func (ea *L2EngineAPI) RemainingBlockGas() uint64 {
113 114 115 116
	if ea.blockProcessor == nil {
		return 0
	}
	return ea.blockProcessor.gasPool.Gas()
117 118 119 120 121 122 123 124 125 126
}

func (ea *L2EngineAPI) ForcedEmpty() bool {
	return ea.l2ForceEmpty
}

func (ea *L2EngineAPI) PendingIndices(from common.Address) uint64 {
	return ea.pendingIndices[from]
}

127
var ErrNotBuildingBlock = errors.New("not currently building a block, cannot include tx from queue")
128 129

func (ea *L2EngineAPI) IncludeTx(tx *types.Transaction, from common.Address) error {
130
	if ea.blockProcessor == nil {
131 132
		return ErrNotBuildingBlock
	}
133

134 135 136 137 138
	if ea.l2ForceEmpty {
		ea.log.Info("Skipping including a transaction because e.L2ForceEmpty is true")
		return nil
	}

139 140 141
	err := ea.blockProcessor.CheckTxWithinGasLimit(tx)
	if err != nil {
		return err
142 143 144
	}

	ea.pendingIndices[from] = ea.pendingIndices[from] + 1 // won't retry the tx
145
	err = ea.blockProcessor.AddTx(tx)
146 147
	if err != nil {
		ea.l2TxFailed = append(ea.l2TxFailed, tx)
148
		return fmt.Errorf("invalid L2 block (tx %d): %w", len(ea.blockProcessor.transactions), err)
149 150 151 152
	}
	return nil
}

153
func (ea *L2EngineAPI) startBlock(parent common.Hash, attrs *eth.PayloadAttributes) error {
154 155
	if ea.blockProcessor != nil {
		ea.log.Warn("started building new block without ending previous block", "previous", ea.blockProcessor.header, "prev_payload_id", ea.payloadID)
156 157
	}

158
	processor, err := NewBlockProcessorFromPayloadAttributes(ea.backend, parent, attrs)
159
	if err != nil {
160
		return err
161
	}
162

163
	ea.blockProcessor = processor
164
	ea.pendingIndices = make(map[common.Address]uint64)
165 166
	ea.l2ForceEmpty = attrs.NoTxPool
	ea.payloadID = computePayloadId(parent, attrs)
167 168

	// pre-process the deposits
169
	for i, otx := range attrs.Transactions {
170 171
		var tx types.Transaction
		if err := tx.UnmarshalBinary(otx); err != nil {
Joshua Gutow's avatar
Joshua Gutow committed
172
			return fmt.Errorf("transaction %d is not valid: %w", i, err)
173
		}
174
		err := ea.blockProcessor.AddTx(&tx)
175 176 177 178 179 180
		if err != nil {
			ea.l2TxFailed = append(ea.l2TxFailed, &tx)
			return fmt.Errorf("failed to apply deposit transaction to L2 block (tx %d): %w", i, err)
		}
	}
	return nil
181 182 183
}

func (ea *L2EngineAPI) endBlock() (*types.Block, error) {
184
	if ea.blockProcessor == nil {
185 186
		return nil, fmt.Errorf("no block is being built currently (id %s)", ea.payloadID)
	}
187 188
	processor := ea.blockProcessor
	ea.blockProcessor = nil
189

190 191 192 193 194 195 196 197 198 199 200 201
	var block *types.Block
	var err error
	// If the backend supports it, write the newly created block to the database without making it canonical.
	// This avoids needing to reprocess the block if it is sent back via newPayload.
	// The block is not made canonical so if it is never sent back via newPayload worst case it just wastes some storage
	// In the context of the OP Stack derivation, the created block is always immediately imported so it makes sense to
	// optimise.
	if cachingBackend, ok := ea.backend.(CachingEngineBackend); ok {
		block, err = cachingBackend.AssembleAndInsertBlockWithoutSetHead(processor)
	} else {
		block, err = processor.Assemble()
	}
202 203 204
	if err != nil {
		return nil, fmt.Errorf("assemble block: %w", err)
	}
205
	return block, nil
206 207 208
}

func (ea *L2EngineAPI) GetPayloadV1(ctx context.Context, payloadId eth.PayloadID) (*eth.ExecutionPayload, error) {
209 210 211 212 213
	res, err := ea.getPayload(ctx, payloadId)
	if err != nil {
		return nil, err
	}
	return res.ExecutionPayload, nil
214 215 216
}

func (ea *L2EngineAPI) GetPayloadV2(ctx context.Context, payloadId eth.PayloadID) (*eth.ExecutionPayloadEnvelope, error) {
217 218 219 220 221
	return ea.getPayload(ctx, payloadId)
}

func (ea *L2EngineAPI) GetPayloadV3(ctx context.Context, payloadId eth.PayloadID) (*eth.ExecutionPayloadEnvelope, error) {
	return ea.getPayload(ctx, payloadId)
222 223
}

224 225 226 227
func (ea *L2EngineAPI) config() *params.ChainConfig {
	return ea.backend.Config()
}

228
func (ea *L2EngineAPI) ForkchoiceUpdatedV1(ctx context.Context, state *eth.ForkchoiceState, attr *eth.PayloadAttributes) (*eth.ForkchoiceUpdatedResult, error) {
229 230 231 232 233 234 235 236 237
	if attr != nil {
		if attr.Withdrawals != nil {
			return STATUS_INVALID, engine.InvalidParams.With(errors.New("withdrawals not supported in V1"))
		}
		if ea.config().IsShanghai(ea.config().LondonBlock, uint64(attr.Timestamp)) {
			return STATUS_INVALID, engine.InvalidParams.With(errors.New("forkChoiceUpdateV1 called post-shanghai"))
		}
	}

238 239 240 241
	return ea.forkchoiceUpdated(ctx, state, attr)
}

func (ea *L2EngineAPI) ForkchoiceUpdatedV2(ctx context.Context, state *eth.ForkchoiceState, attr *eth.PayloadAttributes) (*eth.ForkchoiceUpdatedResult, error) {
242 243 244 245 246 247
	if attr != nil {
		if err := ea.verifyPayloadAttributes(attr); err != nil {
			return STATUS_INVALID, engine.InvalidParams.With(err)
		}
	}

248 249 250
	return ea.forkchoiceUpdated(ctx, state, attr)
}

251 252 253 254 255 256 257 258 259 260 261
// Ported from: https://github.com/ethereum-optimism/op-geth/blob/c50337a60a1309a0f1dca3bf33ed1bb38c46cdd7/eth/catalyst/api.go#L197C1-L205C1
func (ea *L2EngineAPI) ForkchoiceUpdatedV3(ctx context.Context, state *eth.ForkchoiceState, attr *eth.PayloadAttributes) (*eth.ForkchoiceUpdatedResult, error) {
	if attr != nil {
		if err := ea.verifyPayloadAttributes(attr); err != nil {
			return STATUS_INVALID, engine.InvalidParams.With(err)
		}
	}
	return ea.forkchoiceUpdated(ctx, state, attr)
}

// Ported from: https://github.com/ethereum-optimism/op-geth/blob/c50337a60a1309a0f1dca3bf33ed1bb38c46cdd7/eth/catalyst/api.go#L206-L218
262 263
func (ea *L2EngineAPI) verifyPayloadAttributes(attr *eth.PayloadAttributes) error {
	c := ea.config()
264
	t := uint64(attr.Timestamp)
265 266

	// Verify withdrawals attribute for Shanghai.
267
	if err := checkAttribute(c.IsShanghai, attr.Withdrawals != nil, c.LondonBlock, t); err != nil {
268 269
		return fmt.Errorf("invalid withdrawals: %w", err)
	}
270
	// Verify beacon root attribute for Cancun.
271
	if err := checkAttribute(c.IsCancun, attr.ParentBeaconBlockRoot != nil, c.LondonBlock, t); err != nil {
272 273
		return fmt.Errorf("invalid parent beacon block root: %w", err)
	}
274 275 276 277 278 279 280 281 282 283 284
	// Verify EIP-1559 params for Holocene.
	if c.IsHolocene(t) {
		if attr.EIP1559Params == nil {
			return errors.New("got nil eip-1559 params while Holocene is active")
		}
		if err := eip1559.ValidateHolocene1559Params(attr.EIP1559Params[:]); err != nil {
			return fmt.Errorf("invalid Holocene params: %w", err)
		}
	} else if attr.EIP1559Params != nil {
		return errors.New("got Holocene params though fork not active")
	}
285

286 287 288 289 290 291 292 293 294 295 296 297 298
	return nil
}

func checkAttribute(active func(*big.Int, uint64) bool, exists bool, block *big.Int, time uint64) error {
	if active(block, time) && !exists {
		return errors.New("fork active, missing expected attribute")
	}
	if !active(block, time) && exists {
		return errors.New("fork inactive, unexpected attribute set")
	}
	return nil
}

299
func (ea *L2EngineAPI) NewPayloadV1(ctx context.Context, payload *eth.ExecutionPayload) (*eth.PayloadStatusV1, error) {
300 301 302 303
	if payload.Withdrawals != nil {
		return &eth.PayloadStatusV1{Status: eth.ExecutionInvalid}, engine.InvalidParams.With(errors.New("withdrawals not supported in V1"))
	}

304
	return ea.newPayload(ctx, payload, nil, nil)
305 306
}

Danyal Prout's avatar
Danyal Prout committed
307
func (ea *L2EngineAPI) NewPayloadV2(ctx context.Context, payload *eth.ExecutionPayload) (*eth.PayloadStatusV1, error) {
308 309 310 311 312 313 314 315
	if ea.config().IsShanghai(new(big.Int).SetUint64(uint64(payload.BlockNumber)), uint64(payload.Timestamp)) {
		if payload.Withdrawals == nil {
			return &eth.PayloadStatusV1{Status: eth.ExecutionInvalid}, engine.InvalidParams.With(errors.New("nil withdrawals post-shanghai"))
		}
	} else if payload.Withdrawals != nil {
		return &eth.PayloadStatusV1{Status: eth.ExecutionInvalid}, engine.InvalidParams.With(errors.New("non-nil withdrawals pre-shanghai"))
	}

316
	return ea.newPayload(ctx, payload, nil, nil)
Danyal Prout's avatar
Danyal Prout committed
317 318
}

319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337
// Ported from: https://github.com/ethereum-optimism/op-geth/blob/c50337a60a1309a0f1dca3bf33ed1bb38c46cdd7/eth/catalyst/api.go#L486C1-L507
func (ea *L2EngineAPI) NewPayloadV3(ctx context.Context, params *eth.ExecutionPayload, versionedHashes []common.Hash, beaconRoot *common.Hash) (*eth.PayloadStatusV1, error) {
	if params.ExcessBlobGas == nil {
		return &eth.PayloadStatusV1{Status: eth.ExecutionInvalid}, engine.InvalidParams.With(errors.New("nil excessBlobGas post-cancun"))
	}
	if params.BlobGasUsed == nil {
		return &eth.PayloadStatusV1{Status: eth.ExecutionInvalid}, engine.InvalidParams.With(errors.New("nil params.BlobGasUsed post-cancun"))
	}
	if versionedHashes == nil {
		return &eth.PayloadStatusV1{Status: eth.ExecutionInvalid}, engine.InvalidParams.With(errors.New("nil versionedHashes post-cancun"))
	}
	if beaconRoot == nil {
		return &eth.PayloadStatusV1{Status: eth.ExecutionInvalid}, engine.InvalidParams.With(errors.New("nil parentBeaconBlockRoot post-cancun"))
	}

	if !ea.config().IsCancun(new(big.Int).SetUint64(uint64(params.BlockNumber)), uint64(params.Timestamp)) {
		return &eth.PayloadStatusV1{Status: eth.ExecutionInvalid}, engine.UnsupportedFork.With(errors.New("newPayloadV3 called pre-cancun"))
	}

338 339 340 341 342 343 344
	// Payload must have eip-1559 params in ExtraData after Holocene
	if ea.config().IsHolocene(uint64(params.Timestamp)) {
		if err := eip1559.ValidateHoloceneExtraData(params.ExtraData); err != nil {
			return &eth.PayloadStatusV1{Status: eth.ExecutionInvalid}, engine.UnsupportedFork.With(errors.New("invalid holocene extraData post-holoocene"))
		}
	}

345 346 347
	return ea.newPayload(ctx, params, versionedHashes, beaconRoot)
}

348
func (ea *L2EngineAPI) getPayload(_ context.Context, payloadId eth.PayloadID) (*eth.ExecutionPayloadEnvelope, error) {
349 350 351
	ea.log.Trace("L2Engine API request received", "method", "GetPayload", "id", payloadId)
	if ea.payloadID != payloadId {
		ea.log.Warn("unexpected payload ID requested for block building", "expected", ea.payloadID, "got", payloadId)
352
		return nil, engine.UnknownPayload
353 354 355 356
	}
	bl, err := ea.endBlock()
	if err != nil {
		ea.log.Error("failed to finish block building", "err", err)
357
		return nil, engine.UnknownPayload
358
	}
359

360
	return eth.BlockAsPayloadEnv(bl, ea.config().ShanghaiTime)
361 362
}

363
func (ea *L2EngineAPI) forkchoiceUpdated(_ context.Context, state *eth.ForkchoiceState, attr *eth.PayloadAttributes) (*eth.ForkchoiceUpdatedResult, error) {
364 365 366 367 368 369 370 371
	ea.log.Trace("L2Engine API request received", "method", "ForkchoiceUpdated", "head", state.HeadBlockHash, "finalized", state.FinalizedBlockHash, "safe", state.SafeBlockHash)
	if state.HeadBlockHash == (common.Hash{}) {
		ea.log.Warn("Forkchoice requested update to zero hash")
		return STATUS_INVALID, nil
	}
	// Check whether we have the block yet in our database or not. If not, we'll
	// need to either trigger a sync, or to reject this forkchoice update for a
	// reason.
372
	block := ea.backend.GetBlockByHash(state.HeadBlockHash)
373
	if block == nil {
374 375 376 377 378 379 380 381 382 383 384 385 386 387
		if ea.downloader == nil {
			ea.log.Warn("Must register downloader to be able to snap sync")
			return STATUS_SYNCING, nil
		}
		// If the head hash is unknown (was not given to us in a newPayload request),
		// we cannot resolve the header, so not much to do. This could be extended in
		// the future to resolve from the `eth` network, but it's an unexpected case
		// that should be fixed, not papered over.
		header := ea.remotes[state.HeadBlockHash]
		if header == nil {
			ea.log.Warn("Forkchoice requested unknown head", "hash", state.HeadBlockHash)
			return STATUS_SYNCING, nil
		}

388
		ea.log.Info("Forkchoice requested sync to new head", "number", header.Number(), "hash", header.Hash())
389 390 391
		if err := ea.downloader.BeaconSync(downloader.SnapSync, header.Header(), nil); err != nil {
			return STATUS_SYNCING, err
		}
392 393 394 395
		return STATUS_SYNCING, nil
	}
	// Block is known locally, just sanity check that the beacon client does not
	// attempt to push us back to before the merge.
396
	// Note: Differs from op-geth implementation as pre-merge blocks are never supported here
397
	if block.Difficulty().BitLen() > 0 && block.NumberU64() > 0 {
398
		return STATUS_INVALID, errors.New("pre-merge blocks not supported")
399
	}
400
	valid := func(id *engine.PayloadID) *eth.ForkchoiceUpdatedResult {
401 402 403 404 405
		return &eth.ForkchoiceUpdatedResult{
			PayloadStatus: eth.PayloadStatusV1{Status: eth.ExecutionValid, LatestValidHash: &state.HeadBlockHash},
			PayloadID:     id,
		}
	}
406
	if ea.backend.GetCanonicalHash(block.NumberU64()) != state.HeadBlockHash {
407
		// Block is not canonical, set head.
408
		if latestValid, err := ea.backend.SetCanonical(block); err != nil {
409 410
			return &eth.ForkchoiceUpdatedResult{PayloadStatus: eth.PayloadStatusV1{Status: eth.ExecutionInvalid, LatestValidHash: &latestValid}}, err
		}
411
	} else if ea.backend.CurrentHeader().Hash() == state.HeadBlockHash {
412 413 414
		// If the specified head matches with our local head, do nothing and keep
		// generating the payload. It's a special corner case that a few slots are
		// missing and we are requested to generate the payload in slot.
415
	} else if ea.backend.Config().Optimism == nil { // minor L2Engine API divergence: allow proposers to reorg their own chain
416 417 418 419 420 421 422
		panic("engine not configured as optimism engine")
	}

	// If the beacon client also advertised a finalized block, mark the local
	// chain final and completely in PoS mode.
	if state.FinalizedBlockHash != (common.Hash{}) {
		// If the finalized block is not in our canonical tree, somethings wrong
423
		finalHeader := ea.backend.GetHeaderByHash(state.FinalizedBlockHash)
424
		if finalHeader == nil {
425
			ea.log.Warn("Final block not available in database", "hash", state.FinalizedBlockHash)
426
			return STATUS_INVALID, engine.InvalidForkChoiceState.With(errors.New("final block not available in database"))
427
		} else if ea.backend.GetCanonicalHash(finalHeader.Number.Uint64()) != state.FinalizedBlockHash {
428
			ea.log.Warn("Final block not in canonical chain", "number", block.NumberU64(), "hash", state.HeadBlockHash)
429
			return STATUS_INVALID, engine.InvalidForkChoiceState.With(errors.New("final block not in canonical chain"))
430 431
		}
		// Set the finalized block
432
		ea.backend.SetFinalized(finalHeader)
433 434 435
	}
	// Check if the safe block hash is in our canonical tree, if not somethings wrong
	if state.SafeBlockHash != (common.Hash{}) {
436
		safeHeader := ea.backend.GetHeaderByHash(state.SafeBlockHash)
437
		if safeHeader == nil {
438
			ea.log.Warn("Safe block not available in database")
439
			return STATUS_INVALID, engine.InvalidForkChoiceState.With(errors.New("safe block not available in database"))
440
		}
441
		if ea.backend.GetCanonicalHash(safeHeader.Number.Uint64()) != state.SafeBlockHash {
442
			ea.log.Warn("Safe block not in canonical chain")
443
			return STATUS_INVALID, engine.InvalidForkChoiceState.With(errors.New("safe block not in canonical chain"))
444 445
		}
		// Set the safe block
446
		ea.backend.SetSafe(safeHeader)
447 448 449 450 451 452 453 454
	}
	// If payload generation was requested, create a new block to be potentially
	// sealed by the beacon client. The payload will be requested later, and we
	// might replace it arbitrarily many times in between.
	if attr != nil {
		err := ea.startBlock(state.HeadBlockHash, attr)
		if err != nil {
			ea.log.Error("Failed to start block building", "err", err, "noTxPool", attr.NoTxPool, "txs", len(attr.Transactions), "timestamp", attr.Timestamp)
455
			return STATUS_INVALID, engine.InvalidPayloadAttributes.With(err)
456 457 458 459 460 461 462
		}

		return valid(&ea.payloadID), nil
	}
	return valid(nil), nil
}

Danyal Prout's avatar
Danyal Prout committed
463 464 465 466 467
func toGethWithdrawals(payload *eth.ExecutionPayload) []*types.Withdrawal {
	if payload.Withdrawals == nil {
		return nil
	}

Danyal Prout's avatar
Danyal Prout committed
468
	result := make([]*types.Withdrawal, 0, len(*payload.Withdrawals))
Danyal Prout's avatar
Danyal Prout committed
469 470 471 472 473 474 475 476 477 478 479 480 481

	for _, w := range *payload.Withdrawals {
		result = append(result, &types.Withdrawal{
			Index:     w.Index,
			Validator: w.Validator,
			Address:   w.Address,
			Amount:    w.Amount,
		})
	}

	return result
}

482
func (ea *L2EngineAPI) newPayload(_ context.Context, payload *eth.ExecutionPayload, hashes []common.Hash, root *common.Hash) (*eth.PayloadStatusV1, error) {
483 484 485 486 487
	ea.log.Trace("L2Engine API request received", "method", "ExecutePayload", "number", payload.BlockNumber, "hash", payload.BlockHash)
	txs := make([][]byte, len(payload.Transactions))
	for i, tx := range payload.Transactions {
		txs[i] = tx
	}
488
	block, err := engine.ExecutableDataToBlock(engine.ExecutableData{
489 490 491 492 493 494 495 496 497 498 499
		ParentHash:    payload.ParentHash,
		FeeRecipient:  payload.FeeRecipient,
		StateRoot:     common.Hash(payload.StateRoot),
		ReceiptsRoot:  common.Hash(payload.ReceiptsRoot),
		LogsBloom:     payload.LogsBloom[:],
		Random:        common.Hash(payload.PrevRandao),
		Number:        uint64(payload.BlockNumber),
		GasLimit:      uint64(payload.GasLimit),
		GasUsed:       uint64(payload.GasUsed),
		Timestamp:     uint64(payload.Timestamp),
		ExtraData:     payload.ExtraData,
500
		BaseFeePerGas: (*uint256.Int)(&payload.BaseFeePerGas).ToBig(),
501 502
		BlockHash:     payload.BlockHash,
		Transactions:  txs,
Danyal Prout's avatar
Danyal Prout committed
503
		Withdrawals:   toGethWithdrawals(payload),
504 505
		ExcessBlobGas: (*uint64)(payload.ExcessBlobGas),
		BlobGasUsed:   (*uint64)(payload.BlobGasUsed),
506
	}, hashes, root, ea.backend.Config())
507 508 509 510 511 512
	if err != nil {
		log.Debug("Invalid NewPayload params", "params", payload, "error", err)
		return &eth.PayloadStatusV1{Status: eth.ExecutionInvalidBlockHash}, nil
	}
	// If we already have the block locally, ignore the entire execution and just
	// return a fake success.
513
	if block := ea.backend.GetBlock(payload.BlockHash, uint64(payload.BlockNumber)); block != nil {
514
		ea.log.Info("Using existing beacon payload", "number", payload.BlockNumber, "hash", payload.BlockHash, "age", common.PrettyAge(time.Unix(int64(block.Time()), 0)))
515 516 517 518
		hash := block.Hash()
		return &eth.PayloadStatusV1{Status: eth.ExecutionValid, LatestValidHash: &hash}, nil
	}

519
	// Skip invalid ancestor check (i.e. not remembering previously failed blocks)
520

521
	parent := ea.backend.GetBlock(block.ParentHash(), block.NumberU64()-1)
522
	if parent == nil {
523
		ea.remotes[block.Hash()] = block
524
		// Return accepted if we don't know the parent block. Note that there's no actual sync to activate.
525 526
		return &eth.PayloadStatusV1{Status: eth.ExecutionAccepted, LatestValidHash: nil}, nil
	}
527 528 529 530 531 532 533

	if block.Time() <= parent.Time() {
		log.Warn("Invalid timestamp", "parent", block.Time(), "block", block.Time())
		return ea.invalid(errors.New("invalid timestamp"), parent.Header()), nil
	}

	if !ea.backend.HasBlockAndState(block.ParentHash(), block.NumberU64()-1) {
534 535 536
		ea.log.Warn("State not available, ignoring new payload")
		return &eth.PayloadStatusV1{Status: eth.ExecutionAccepted}, nil
	}
537
	log.Trace("Inserting block without sethead", "hash", block.Hash(), "number", block.Number)
538
	if _, err := ea.backend.InsertBlockWithoutSetHead(block, false); err != nil {
539
		ea.log.Warn("NewPayloadV1: inserting block failed", "error", err)
540
		// Skip remembering the block was invalid, but do return the invalid response.
541 542 543 544 545 546 547
		return ea.invalid(err, parent.Header()), nil
	}
	hash := block.Hash()
	return &eth.PayloadStatusV1{Status: eth.ExecutionValid, LatestValidHash: &hash}, nil
}

func (ea *L2EngineAPI) invalid(err error, latestValid *types.Header) *eth.PayloadStatusV1 {
548
	currentHash := ea.backend.CurrentHeader().Hash()
549 550 551 552 553 554 555 556 557 558 559
	if latestValid != nil {
		// Set latest valid hash to 0x0 if parent is PoW block
		currentHash = common.Hash{}
		if latestValid.Difficulty.BitLen() == 0 {
			// Otherwise set latest valid hash to parent hash
			currentHash = latestValid.Hash()
		}
	}
	errorMsg := err.Error()
	return &eth.PayloadStatusV1{Status: eth.ExecutionInvalid, LatestValidHash: &currentHash, ValidationError: &errorMsg}
}