diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 68ca8f229..81d0d7843 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,8 +23,8 @@ jobs: uses: golangci/golangci-lint-action@v3 with: working-directory: op-node - version: latest - args: -E goimports,sqlclosecheck,bodyclose,asciicheck,misspell,errorlint --timeout 5m -e "errors.As" -e "errors.Is" + version: v1.63.4 + args: -E goimports,sqlclosecheck,bodyclose,asciicheck,misspell,errorlint --timeout 15m -e "errors.As" -e "errors.Is" op-batcher-lint: runs-on: ubuntu-latest @@ -42,8 +42,8 @@ jobs: uses: golangci/golangci-lint-action@v3 with: working-directory: op-batcher - version: latest - args: -E goimports,sqlclosecheck,bodyclose,asciicheck,misspell,errorlint --timeout 5m -e "errors.As" -e "errors.Is" + version: v1.63.4 + args: -E goimports,sqlclosecheck,bodyclose,asciicheck,misspell,errorlint --timeout 15m -e "errors.As" -e "errors.Is" op-proposer-lint: runs-on: ubuntu-latest @@ -61,8 +61,8 @@ jobs: uses: golangci/golangci-lint-action@v3 with: working-directory: op-proposer - version: latest - args: -E goimports,sqlclosecheck,bodyclose,asciicheck,misspell,errorlint --timeout 5m -e "errors.As" -e "errors.Is" + version: v1.63.4 + args: -E goimports,sqlclosecheck,bodyclose,asciicheck,misspell,errorlint --timeout 15m -e "errors.As" -e "errors.Is" op-service-lint: runs-on: ubuntu-latest @@ -80,8 +80,8 @@ jobs: uses: golangci/golangci-lint-action@v3 with: working-directory: op-service - version: latest - args: -E goimports,sqlclosecheck,bodyclose,asciicheck,misspell,errorlint --timeout 5m -e "errors.As" -e "errors.Is" + version: v1.63.4 + args: -E goimports,sqlclosecheck,bodyclose,asciicheck,misspell,errorlint --timeout 15m -e "errors.As" -e "errors.Is" op-node-test: runs-on: ubuntu-latest diff --git a/CHANGELOG.md b/CHANGELOG.md index abd1cd666..81cef167a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,32 @@ # Changelog +## v0.5.2 + +This is a minor release and must upgrade to this release before the Pascal & Prague hardforks time of BSC, supports EIP-7702. + +- Testnet: Feb-25-2025 06:00 AM +UTC +- Mainnet: Late March. + +This release adds a new bootnode in the US region for testnet, improves sequencer recovery functionality, introduces a new engine_opSealPayload API, and updates documentation links. + +This release supports EIP-7702, op-node is compatible with type 7702 transactions from L1. + +### What's Changed + +* chore: add bootnode in us region(testnet) by @krish-nr in https://github.com/bnb-chain/opbnb/pull/249 +* feat: handle sequencer recover related logic by @krish-nr in https://github.com/bnb-chain/opbnb/pull/250 +* feat: add new engine_opSealPayload API by @bnoieh in https://github.com/bnb-chain/opbnb/pull/248 +* doc: update outdated links in readme by @owen-reorg in https://github.com/bnb-chain/opbnb/pull/253 +* feat: EIP-7702 adaption by @krish-nr in https://github.com/bnb-chain/opbnb/pull/261 + +### Docker Images + +- ghcr.io/bnb-chain/op-node:v0.5.2 +- ghcr.io/bnb-chain/op-batcher:v0.5.2 +- ghcr.io/bnb-chain/op-proposer:v0.5.2 + +**Full Changelog**: https://github.com/bnb-chain/opbnb/compare/v0.5.1...v0.5.2 + ## v0.5.1 This is a minor release and upgrading is optional. @@ -262,7 +289,7 @@ https://github.com/bnb-chain/opbnb/compare/v0.3.0...v0.3.1 ## v0.3.0 -This is a recommended release for op-node. This release brings in upstream updates, see https://github.com/bnb-chain/opbnb/pull/121 for the contents. This is also a ready release for the next planed fork, which will bring in canyon fork from upstream as well. +This is a recommended release for op-node. This release brings in upstream updates, see https://github.com/bnb-chain/opbnb/pull/121 for the contents. This is also a ready release for the next planned fork, which will bring in canyon fork from upstream as well. ### User Facing Changes diff --git a/README.md b/README.md index a23ce04a2..84010aaea 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ # opBNB - High-performance layer 2 solution -The opBNB network is the Layer 2 scaling solution for the BNB Smart Chain(BSC) powered by [bedrock version](https://community.optimism.io/docs/developers/bedrock/) of Optimism OP Stack. +The opBNB network is the Layer 2 scaling solution for the BNB Smart Chain(BSC) powered by bedrock version of [Optimism OP Stack](https://docs.optimism.io/stack/getting-started). It works by offloading transaction processing and resource usage from the BNB Smart Chain, while still posting data to the underlying network. Users interact with the opBNB network by depositing funds from BSC and using applications and contracts on opBNB. At its core, opBNB allows users to deposit and withdraw funds, use smart contracts, and view network data with high throughput and low fees. By leveraging Layer 2, opBNB is able to scale beyond the constraints of the BNB Smart Chain and provide an improved experience for users. ## Comparison -Besides the [differentiators of bedrock](https://community.optimism.io/docs/developers/bedrock/differences/), opBNB is the solution that we aim to provide the best optimistic rollup solution on the BSC. +Besides the [differentiators of OP Stack](https://docs.optimism.io/stack/differences), opBNB is the solution that we aim to provide the best optimistic rollup solution on the BSC. - Capacity can reach to 100m gas per second, which is much higher than other layer 2 solutions on the Ethereum. - Gas fee of transfer can reach as low as $0.005 on average. diff --git a/go.mod b/go.mod index 3e5d4a828..77bcf120c 100644 --- a/go.mod +++ b/go.mod @@ -277,7 +277,7 @@ require ( rsc.io/tmplfunc v0.0.3 // indirect ) -replace github.com/ethereum/go-ethereum v1.13.15 => github.com/bnb-chain/op-geth v1.101315.2-0.0.20240823030609-c0f12a4e1b05 +replace github.com/ethereum/go-ethereum v1.13.15 => github.com/bnb-chain/op-geth v1.101315.2-0.0.20250213114253-fbecb252643d replace github.com/cometbft/cometbft => github.com/bnb-chain/greenfield-cometbft v1.0.0 diff --git a/go.sum b/go.sum index bf3c1d73c..dda996ef9 100644 --- a/go.sum +++ b/go.sum @@ -184,8 +184,8 @@ github.com/bnb-chain/fastssz v0.1.2 h1:vTcXw5SwCtRYnl/BEclujiml7GXiVOZ74tub4GHpv github.com/bnb-chain/fastssz v0.1.2/go.mod h1:KcabV+OEw2QwgyY8Fc88ZG79CKYkFdu0kKWyfA3dI6o= github.com/bnb-chain/greenfield-cometbft v1.0.0 h1:0r6hOJWD/+es0gxP/exKuN/krgXAr3LCn5/XlcgDWr8= github.com/bnb-chain/greenfield-cometbft v1.0.0/go.mod h1:f35mk/r5ab6yvzlqEWZt68LfUje68sYgMpVlt2CUYMk= -github.com/bnb-chain/op-geth v1.101315.2-0.0.20240823030609-c0f12a4e1b05 h1:Tyov2UCjIxxHt6ZX46T09CiKpKDY72AtjHzUQFK3SBA= -github.com/bnb-chain/op-geth v1.101315.2-0.0.20240823030609-c0f12a4e1b05/go.mod h1:hyHrrcHkUe3lRwfJs+JGrbOHp+pRdheRk+ren4TPhF8= +github.com/bnb-chain/op-geth v1.101315.2-0.0.20250213114253-fbecb252643d h1:zYSisO6VUWaQzieOnP7NcEXfwbcYa86CipNS+sXHb7Q= +github.com/bnb-chain/op-geth v1.101315.2-0.0.20250213114253-fbecb252643d/go.mod h1:hyHrrcHkUe3lRwfJs+JGrbOHp+pRdheRk+ren4TPhF8= github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4= github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= diff --git a/op-batcher/batcher/driver.go b/op-batcher/batcher/driver.go index b70ffe239..0df382ac1 100644 --- a/op-batcher/batcher/driver.go +++ b/op-batcher/batcher/driver.go @@ -1,6 +1,7 @@ package batcher import ( + "bytes" "context" "errors" "fmt" @@ -12,6 +13,14 @@ import ( "sync/atomic" "time" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/consensus/misc/eip4844" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum-optimism/optimism/op-batcher/flags" "github.com/ethereum-optimism/optimism/op-batcher/metrics" "github.com/ethereum-optimism/optimism/op-node/rollup" @@ -20,12 +29,6 @@ import ( "github.com/ethereum-optimism/optimism/op-service/dial" "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/txmgr" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/consensus/misc/eip4844" - "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/params" ) const LimitLoadBlocksOneTime uint64 = 30 @@ -678,10 +681,31 @@ func (l *BatchSubmitter) sendTransaction(ctx context.Context, txdata txData, que candidate = l.calldataTxCandidate(data) } - intrinsicGas, err := core.IntrinsicGas(candidate.TxData, nil, false, true, true, false) + intrinsicGas, err := core.IntrinsicGas(candidate.TxData, nil, nil, true, true, false, false) if err != nil { // we log instead of return an error here because txmgr can do its own gas estimation l.Log.Error("Failed to calculate intrinsic gas", "err", err) + } else if candidate.Blobs == nil { + minimumGasRequired, err := func(data []byte) (uint64, error) { + var ( + z = uint64(bytes.Count(data, []byte{0})) + nz = uint64(len(data)) - z + tokens = nz*params.TxTokenPerNonZeroByte + z + ) + if (math.MaxUint64-params.TxGas)/params.TxCostFloorPerToken < tokens { + return 0, errors.New("intrinsic gas too low") + } + return params.TxGas + tokens*params.TxCostFloorPerToken, nil + }(candidate.TxData) + + if err != nil { + return err + } + // + baseGas := intrinsicGas - params.TxGas + finalGasLimit := max(baseGas, minimumGasRequired) + params.TxGas + + candidate.GasLimit = finalGasLimit } else { candidate.GasLimit = intrinsicGas } diff --git a/op-chain-ops/cmd/check-derivation/main.go b/op-chain-ops/cmd/check-derivation/main.go index dd5d63194..b867ea23a 100644 --- a/op-chain-ops/cmd/check-derivation/main.go +++ b/op-chain-ops/cmd/check-derivation/main.go @@ -225,7 +225,7 @@ func getRandomSignedTransaction(ctx context.Context, ethClient *ethclient.Client var txData types.TxData switch txType { case types.LegacyTxType: - gasLimit, err := core.IntrinsicGas(data, nil, false, true, true, false) + gasLimit, err := core.IntrinsicGas(data, nil, nil, false, true, false, false) if err != nil { return nil, fmt.Errorf("failed to get intrinsicGas: %w", err) } @@ -242,7 +242,7 @@ func getRandomSignedTransaction(ctx context.Context, ethClient *ethclient.Client Address: randomAddress, StorageKeys: []common.Hash{common.HexToHash("0x1234")}, }} - gasLimit, err := core.IntrinsicGas(data, accessList, false, true, true, false) + gasLimit, err := core.IntrinsicGas(data, accessList, nil, false, true, false, false) if err != nil { return nil, fmt.Errorf("failed to get intrinsicGas: %w", err) } @@ -257,7 +257,7 @@ func getRandomSignedTransaction(ctx context.Context, ethClient *ethclient.Client Data: data, } case types.DynamicFeeTxType: - gasLimit, err := core.IntrinsicGas(data, nil, false, true, true, false) + gasLimit, err := core.IntrinsicGas(data, nil, nil, false, true, false, false) if err != nil { return nil, fmt.Errorf("failed to get intrinsicGas: %w", err) } diff --git a/op-e2e/actions/l2_batcher.go b/op-e2e/actions/l2_batcher.go index 310a6cade..1a955f851 100644 --- a/op-e2e/actions/l2_batcher.go +++ b/op-e2e/actions/l2_batcher.go @@ -277,7 +277,7 @@ func (s *L2Batcher) ActL2BatchSubmit(t Testing, txOpts ...func(tx *types.Dynamic opt(rawTx) } - gas, err := core.IntrinsicGas(rawTx.Data, nil, false, true, true, false) + gas, err := core.IntrinsicGas(rawTx.Data, nil, nil, false, true, false, false) require.NoError(t, err, "need to compute intrinsic gas") rawTx.Gas = gas txData = rawTx @@ -468,7 +468,7 @@ func (s *L2Batcher) ActL2BatchSubmitGarbage(t Testing, kind GarbageKind) { GasFeeCap: gasFeeCap, Data: outputFrame, } - gas, err := core.IntrinsicGas(rawTx.Data, nil, false, true, true, false) + gas, err := core.IntrinsicGas(rawTx.Data, nil, nil, false, true, false, false) require.NoError(t, err, "need to compute intrinsic gas") rawTx.Gas = gas diff --git a/op-e2e/actions/l2_batcher_test.go b/op-e2e/actions/l2_batcher_test.go index 3a137ce99..27f567b32 100644 --- a/op-e2e/actions/l2_batcher_test.go +++ b/op-e2e/actions/l2_batcher_test.go @@ -496,7 +496,7 @@ func BigL2Txs(gt *testing.T, deltaTimeOffset *hexutil.Uint64) { data := make([]byte, 120_000) // very large L2 txs, as large as the tx-pool will accept _, err := rng.Read(data[:]) // fill with random bytes, to make compression ineffective require.NoError(t, err) - gas, err := core.IntrinsicGas(data, nil, false, true, true, false) + gas, err := core.IntrinsicGas(data, nil, nil, false, true, true, false) require.NoError(t, err) if gas > engine.engineApi.RemainingBlockGas() { break diff --git a/op-e2e/actions/l2_verifier.go b/op-e2e/actions/l2_verifier.go index 4fd260779..7130f943b 100644 --- a/op-e2e/actions/l2_verifier.go +++ b/op-e2e/actions/l2_verifier.go @@ -71,7 +71,7 @@ type safeDB interface { func NewL2Verifier(t Testing, log log.Logger, l1 derive.L1Fetcher, blobsSrc derive.L1BlobsFetcher, plasmaSrc driver.PlasmaIface, eng L2API, cfg *rollup.Config, syncCfg *sync.Config, safeHeadListener safeDB) *L2Verifier { metrics := &testutils.TestDerivationMetrics{} - engine := derive.NewEngineController(eng, log, metrics, cfg, syncCfg) + engine := derive.NewEngineController(eng, log, metrics, cfg, syncCfg, false) clSync := clsync.NewCLSync(log, cfg, metrics, engine) diff --git a/op-e2e/actions/span_batch_test.go b/op-e2e/actions/span_batch_test.go index 6ccb76a46..b3bc2d0d1 100644 --- a/op-e2e/actions/span_batch_test.go +++ b/op-e2e/actions/span_batch_test.go @@ -524,7 +524,7 @@ func TestSpanBatchLowThroughputChain(gt *testing.T) { data := make([]byte, rand.Intn(100)) _, err := crand.Read(data[:]) // fill with random bytes require.NoError(t, err) - gas, err := core.IntrinsicGas(data, nil, false, true, true, false) + gas, err := core.IntrinsicGas(data, nil, nil, false, true, false, false) require.NoError(t, err) baseFee := seqEngine.l2Chain.CurrentBlock().BaseFee nonce, err := cl.PendingNonceAt(t.Ctx(), addrs[userIdx]) @@ -663,7 +663,7 @@ func TestBatchEquivalence(gt *testing.T) { data := make([]byte, rand.Intn(100)) _, err := crand.Read(data[:]) // fill with random bytes require.NoError(t, err) - gas, err := core.IntrinsicGas(data, nil, false, true, true, false) + gas, err := core.IntrinsicGas(data, nil, nil, false, true, false, false) require.NoError(t, err) baseFee := seqEngine.l2Chain.CurrentBlock().BaseFee nonce, err := seqEngCl.PendingNonceAt(t.Ctx(), addrs[userIdx]) diff --git a/op-e2e/actions/sync_test.go b/op-e2e/actions/sync_test.go index e7521bdd8..b856413f3 100644 --- a/op-e2e/actions/sync_test.go +++ b/op-e2e/actions/sync_test.go @@ -896,7 +896,7 @@ func TestInvalidPayloadInSpanBatch(gt *testing.T) { aliceNonce, err := seqEng.EthClient().PendingNonceAt(t.Ctx(), dp.Addresses.Alice) require.NoError(t, err) data := make([]byte, rand.Intn(100)) - gas, err := core.IntrinsicGas(data, nil, false, true, true, false) + gas, err := core.IntrinsicGas(data, nil, nil, false, true, false, false) require.NoError(t, err) baseFee := seqEng.l2Chain.CurrentBlock().BaseFee tx := types.MustSignNewTx(dp.Secrets.Alice, signer, &types.DynamicFeeTx{ diff --git a/op-e2e/brotli_batcher_test.go b/op-e2e/brotli_batcher_test.go index 97211c471..c000a6f42 100644 --- a/op-e2e/brotli_batcher_test.go +++ b/op-e2e/brotli_batcher_test.go @@ -85,7 +85,7 @@ func TestBrotliBatcherFjord(t *testing.T) { opts.Value = big.NewInt(1_000_000_000) opts.Nonce = 1 // Already have deposit opts.ToAddr = &common.Address{0xff, 0xff} - opts.Gas, err = core.IntrinsicGas(opts.Data, nil, false, true, true, false) + opts.Gas, err = core.IntrinsicGas(opts.Data, nil, nil, false, true, false, false) require.NoError(t, err) opts.VerifyOnClients(l2Verif) }) diff --git a/op-e2e/eip4844_test.go b/op-e2e/eip4844_test.go index 5b5cc1d53..37b243ffb 100644 --- a/op-e2e/eip4844_test.go +++ b/op-e2e/eip4844_test.go @@ -102,7 +102,7 @@ func testSystem4844E2E(t *testing.T, multiBlob bool) { opts.ToAddr = &common.Address{0xff, 0xff} // put some random data in the tx to make it fill up 6 blobs (multi-blob case) opts.Data = testutils.RandomData(rand.New(rand.NewSource(420)), 400) - opts.Gas, err = core.IntrinsicGas(opts.Data, nil, false, true, true, false) + opts.Gas, err = core.IntrinsicGas(opts.Data, nil, nil, false, true, false, false) require.NoError(t, err) opts.VerifyOnClients(l2Verif) }) diff --git a/op-node/flags/flags.go b/op-node/flags/flags.go index 03c48f0f3..811e0fba2 100644 --- a/op-node/flags/flags.go +++ b/op-node/flags/flags.go @@ -251,6 +251,12 @@ var ( EnvVars: prefixEnvVars("SEQUENCER_PRIORITY"), Category: SequencerCategory, } + SequencerCombinedEngineFlag = &cli.BoolFlag{ + Name: "sequencer.combined-engine", + Usage: "Enable sequencer select combined engine api when sealing payload.", + EnvVars: prefixEnvVars("SEQUENCER_COMBINED_ENGINE"), + Category: SequencerCategory, + } SequencerL1Confs = &cli.Uint64Flag{ Name: "sequencer.l1-confs", Usage: "Number of L1 blocks to keep distance from the L1 head as a sequencer for picking an L1 origin.", @@ -437,6 +443,7 @@ var optionalFlags = []cli.Flag{ SequencerStoppedFlag, SequencerMaxSafeLagFlag, SequencerPriorityFlag, + SequencerCombinedEngineFlag, SequencerL1Confs, L1EpochPollIntervalFlag, RuntimeConfigReloadIntervalFlag, diff --git a/op-node/p2p/config.go b/op-node/p2p/config.go index 34a4acf97..65cd55a4f 100644 --- a/op-node/p2p/config.go +++ b/op-node/p2p/config.go @@ -57,6 +57,7 @@ var OpBNBTestnetBootnodes = []string{ // op-node "enr:-J24QGQBeMsXOaCCaLWtNFSfb2Gv50DjGOKToH2HUTAIn9yXImowlRoMDNuPNhSBZNQGCCE8eAl5O3dsONuuQp5Qix2GAYjB7KHSgmlkgnY0gmlwhDREiqaHb3BzdGFja4PrKwCJc2VjcDI1NmsxoQL4I9wpEVDcUb8bLWu6V8iPoN5w8E8q-GrS5WUCygYUQ4N0Y3CCIyuDdWRwgiMr", "enr:-J24QJKXHEkIhy0tmIk2EscMZ2aRrivNsZf_YhgIU51g4ZKHWY0BxW6VedRJ1jxmneW9v7JjldPOPpLkaNSo6cXGFxqGAYpK96oCgmlkgnY0gmlwhANzx96Hb3BzdGFja4PrKwCJc2VjcDI1NmsxoQMOCzUFffz04eyDrmkbaSCrMEvLvn5O4RZaZ5k1GV4wa4N0Y3CCIyuDdWRwgiMr", + "enr:-J24QJefH6RDbHDnNbL9ZNXS-eTEwX_OnpuQOjIpqKFGU6YLGpj5MAAPZ9ChvOhMfqAMaBhTkpMCFK-r6LZJ0WZqUBeGAZIOsduvgmlkgnY0gmlwhAPlGvCHb3BzdGFja4PrKwCJc2VjcDI1NmsxoQLCihReuE6ZRbGMotobGXEiNQ-GWFZ0R7VZWHitpi03sYN0Y3CCIyuDdWRwgiMs", } type HostMetrics interface { diff --git a/op-node/rollup/attributes/attributes.go b/op-node/rollup/attributes/attributes.go index 52f04170c..a2ca56ff1 100644 --- a/op-node/rollup/attributes/attributes.go +++ b/op-node/rollup/attributes/attributes.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "io" + "strings" "time" "github.com/ethereum/go-ethereum" @@ -153,6 +154,9 @@ func (eq *AttributesHandler) forceNextSafeAttributes(ctx context.Context, attrib _ = eq.ec.CancelPayload(ctx, true) return derive.NewResetError(fmt.Errorf("need reset to resolve pre-state problem: %w", err)) case derive.BlockInsertPayloadErr: + if strings.Contains(err.Error(), "INCONSISTENT") { + return derive.NewTemporaryError(fmt.Errorf("temporarily cannot insert new safe block: %w", err)) + } _ = eq.ec.CancelPayload(ctx, true) eq.log.Warn("could not process payload derived from L1 data, dropping batch", "err", err) // Count the number of deposits to see if the tx list is deposit only. diff --git a/op-node/rollup/attributes/attributes_test.go b/op-node/rollup/attributes/attributes_test.go index 4f66b93fd..99510c485 100644 --- a/op-node/rollup/attributes/attributes_test.go +++ b/op-node/rollup/attributes/attributes_test.go @@ -181,7 +181,7 @@ func TestAttributesHandler(t *testing.T) { t.Run("drop stale attributes", func(t *testing.T) { logger := testlog.Logger(t, log.LevelInfo) eng := &testutils.MockEngine{} - ec := derive.NewEngineController(eng, logger, metrics.NoopMetrics, cfg, &sync.Config{SyncMode: sync.CLSync}) + ec := derive.NewEngineController(eng, logger, metrics.NoopMetrics, cfg, &sync.Config{SyncMode: sync.CLSync}, false) ah := NewAttributesHandler(logger, cfg, ec, eng) defer eng.AssertExpectations(t) @@ -195,7 +195,7 @@ func TestAttributesHandler(t *testing.T) { t.Run("pending gets reorged", func(t *testing.T) { logger := testlog.Logger(t, log.LevelInfo) eng := &testutils.MockEngine{} - ec := derive.NewEngineController(eng, logger, metrics.NoopMetrics, cfg, &sync.Config{SyncMode: sync.CLSync}) + ec := derive.NewEngineController(eng, logger, metrics.NoopMetrics, cfg, &sync.Config{SyncMode: sync.CLSync}, false) ah := NewAttributesHandler(logger, cfg, ec, eng) defer eng.AssertExpectations(t) @@ -210,7 +210,7 @@ func TestAttributesHandler(t *testing.T) { t.Run("consolidation fails", func(t *testing.T) { logger := testlog.Logger(t, log.LevelInfo) eng := &testutils.MockEngine{} - ec := derive.NewEngineController(eng, logger, metrics.NoopMetrics, cfg, &sync.Config{SyncMode: sync.CLSync}) + ec := derive.NewEngineController(eng, logger, metrics.NoopMetrics, cfg, &sync.Config{SyncMode: sync.CLSync}, false) ah := NewAttributesHandler(logger, cfg, ec, eng) ec.SetUnsafeHead(refA1) @@ -264,7 +264,7 @@ func TestAttributesHandler(t *testing.T) { fn := func(t *testing.T, lastInSpan bool) { logger := testlog.Logger(t, log.LevelInfo) eng := &testutils.MockEngine{} - ec := derive.NewEngineController(eng, logger, metrics.NoopMetrics, cfg, &sync.Config{SyncMode: sync.CLSync}) + ec := derive.NewEngineController(eng, logger, metrics.NoopMetrics, cfg, &sync.Config{SyncMode: sync.CLSync}, false) ah := NewAttributesHandler(logger, cfg, ec, eng) ec.SetUnsafeHead(refA1) @@ -323,7 +323,7 @@ func TestAttributesHandler(t *testing.T) { logger := testlog.Logger(t, log.LevelInfo) eng := &testutils.MockEngine{} - ec := derive.NewEngineController(eng, logger, metrics.NoopMetrics, cfg, &sync.Config{SyncMode: sync.CLSync}) + ec := derive.NewEngineController(eng, logger, metrics.NoopMetrics, cfg, &sync.Config{SyncMode: sync.CLSync}, false) ah := NewAttributesHandler(logger, cfg, ec, eng) ec.SetUnsafeHead(refA0) @@ -374,7 +374,7 @@ func TestAttributesHandler(t *testing.T) { logger := testlog.Logger(t, log.LevelInfo) eng := &testutils.MockEngine{} - ec := derive.NewEngineController(eng, logger, metrics.NoopMetrics, cfg, &sync.Config{SyncMode: sync.CLSync}) + ec := derive.NewEngineController(eng, logger, metrics.NoopMetrics, cfg, &sync.Config{SyncMode: sync.CLSync}, false) ah := NewAttributesHandler(logger, cfg, ec, eng) ec.SetUnsafeHead(refA0) @@ -398,7 +398,7 @@ func TestAttributesHandler(t *testing.T) { t.Run("no attributes", func(t *testing.T) { logger := testlog.Logger(t, log.LevelInfo) eng := &testutils.MockEngine{} - ec := derive.NewEngineController(eng, logger, metrics.NoopMetrics, cfg, &sync.Config{SyncMode: sync.CLSync}) + ec := derive.NewEngineController(eng, logger, metrics.NoopMetrics, cfg, &sync.Config{SyncMode: sync.CLSync}, false) ah := NewAttributesHandler(logger, cfg, ec, eng) defer eng.AssertExpectations(t) diff --git a/op-node/rollup/derive/engine_controller.go b/op-node/rollup/derive/engine_controller.go index f53ad7065..e0e8d2548 100644 --- a/op-node/rollup/derive/engine_controller.go +++ b/op-node/rollup/derive/engine_controller.go @@ -50,6 +50,7 @@ type ExecEngine interface { GetPayload(ctx context.Context, payloadInfo eth.PayloadInfo) (*eth.ExecutionPayloadEnvelope, error) ForkchoiceUpdate(ctx context.Context, state *eth.ForkchoiceState, attr *eth.PayloadAttributes) (*eth.ForkchoiceUpdatedResult, error) NewPayload(ctx context.Context, payload *eth.ExecutionPayload, parentBeaconBlockRoot *common.Hash) (*eth.PayloadStatusV1, error) + SealPayload(ctx context.Context, payloadInfo eth.PayloadInfo, fc *eth.ForkchoiceState, needPayload bool) (*eth.SealPayloadResponse, string, error) L2BlockRefByLabel(ctx context.Context, label eth.BlockLabel) (eth.L2BlockRef, error) } @@ -84,9 +85,11 @@ type EngineController struct { buildingInfo eth.PayloadInfo buildingSafe bool safeAttrs *AttributesWithParent + + combinedAPI bool } -func NewEngineController(engine ExecEngine, log log.Logger, metrics Metrics, rollupCfg *rollup.Config, syncConfig *sync.Config) *EngineController { +func NewEngineController(engine ExecEngine, log log.Logger, metrics Metrics, rollupCfg *rollup.Config, syncConfig *sync.Config, combinedAPI bool) *EngineController { syncStatus := syncStatusCL if syncConfig.SyncMode == sync.ELSync { syncStatus = syncStatusWillStartEL @@ -102,6 +105,7 @@ func NewEngineController(engine ExecEngine, log log.Logger, metrics Metrics, rol elTriggerGap: syncConfig.ELTriggerGap, syncStatus: syncStatus, clock: clock.SystemClock, + combinedAPI: combinedAPI, } } @@ -267,7 +271,13 @@ func (e *EngineController) ConfirmPayload(ctx context.Context, agossip async.Asy } // Update the safe head if the payload is built with the last attributes in the batch. updateSafe := e.buildingSafe && e.safeAttrs != nil && e.safeAttrs.IsLastInSpan - envelope, errTyp, err := confirmPayload(ctx, e.log, e.engine, fc, e.buildingInfo, updateSafe, agossip, sequencerConductor, e.metrics) + + var envelope *eth.ExecutionPayloadEnvelope + if e.combinedAPI && !e.buildingSafe { + envelope, errTyp, err = confirmPayloadCombined(ctx, e.log, e.engine, fc, e.buildingInfo, updateSafe, agossip, sequencerConductor, e.metrics) + } else { + envelope, errTyp, err = confirmPayload(ctx, e.log, e.engine, fc, e.buildingInfo, updateSafe, agossip, sequencerConductor, e.metrics) + } if err != nil { return nil, errTyp, fmt.Errorf("failed to complete building on top of L2 chain %s, id: %s, error (%d): %w", e.buildingOnto, e.buildingInfo.ID, errTyp, err) } diff --git a/op-node/rollup/derive/engine_queue_test.go b/op-node/rollup/derive/engine_queue_test.go index 80e755aac..9b92e795e 100644 --- a/op-node/rollup/derive/engine_queue_test.go +++ b/op-node/rollup/derive/engine_queue_test.go @@ -291,7 +291,7 @@ func TestEngineQueue_ResetWhenUnsafeOriginNotCanonical(t *testing.T) { SyncMode: sync.CLSync, SkipSyncStartCheck: false, ELTriggerGap: 0, - }) + }, false) eq := NewEngineQueue(logger, cfg, eng, ec, metrics, prev, l1F, &sync.Config{}, safedb.Disabled, noopFinality{}, &fakeAttributesHandler{}) require.ErrorIs(t, eq.Reset(context.Background(), eth.L1BlockRef{}, eth.SystemConfig{}), io.EOF) @@ -634,7 +634,7 @@ func TestVerifyNewL1Origin(t *testing.T) { SyncMode: sync.CLSync, SkipSyncStartCheck: false, ELTriggerGap: 0, - }) + }, false) eq := NewEngineQueue(logger, cfg, eng, ec, metrics, prev, l1F, &sync.Config{}, safedb.Disabled, noopFinality{}, &fakeAttributesHandler{}) require.ErrorIs(t, eq.Reset(context.Background(), eth.L1BlockRef{}, eth.SystemConfig{}), io.EOF) @@ -738,7 +738,7 @@ func TestBlockBuildingRace(t *testing.T) { SyncMode: sync.CLSync, SkipSyncStartCheck: false, ELTriggerGap: 0, - }) + }, false) attribHandler := &fakeAttributesHandler{} eq := NewEngineQueue(logger, cfg, eng, ec, metrics, prev, l1F, &sync.Config{}, safedb.Disabled, noopFinality{}, attribHandler) require.ErrorIs(t, eq.Reset(context.Background(), eth.L1BlockRef{}, eth.SystemConfig{}), io.EOF) @@ -858,7 +858,7 @@ func TestResetLoop(t *testing.T) { SyncMode: sync.CLSync, SkipSyncStartCheck: false, ELTriggerGap: 0, - }) + }, false) eq := NewEngineQueue(logger, cfg, eng, ec, metrics.NoopMetrics, prev, l1F, &sync.Config{}, safedb.Disabled, noopFinality{}, &fakeAttributesHandler{}) eq.ec.SetUnsafeHead(refA2) eq.ec.SetSafeHead(refA1) diff --git a/op-node/rollup/derive/engine_update.go b/op-node/rollup/derive/engine_update.go index d3e3c8bf1..656e52474 100644 --- a/op-node/rollup/derive/engine_update.go +++ b/op-node/rollup/derive/engine_update.go @@ -166,7 +166,7 @@ func confirmPayload( if err != nil { return nil, BlockInsertTemporaryErr, fmt.Errorf("failed to insert execution payload: %w", err) } - if status.Status == eth.ExecutionInvalid || status.Status == eth.ExecutionInvalidBlockHash { + if status.Status == eth.ExecutionInvalid || status.Status == eth.ExecutionInvalidBlockHash || status.Status == eth.ExecutionInconsistent { agossip.Clear() return nil, BlockInsertPayloadErr, eth.NewPayloadErr(payload, status) } @@ -208,3 +208,131 @@ func confirmPayload( "txs", len(payload.Transactions), "update_safe", updateSafe) return envelope, BlockInsertOK, nil } + +// confirmPayloadCombined is equal to confirmPayload but using engine_opSealPayload API to combine GetPayload, NewPayload, ForckchoiceUpdated calls +func confirmPayloadCombined( + ctx context.Context, + log log.Logger, + eng ExecEngine, + fc eth.ForkchoiceState, + payloadInfo eth.PayloadInfo, + updateSafe bool, + agossip async.AsyncGossiper, + sequencerConductor conductor.SequencerConductor, + metrics Metrics, +) (out *eth.ExecutionPayloadEnvelope, errTyp BlockInsertionErrType, err error) { + start := time.Now() + type SealPayloadRet struct { + res *eth.SealPayloadResponse + errStage string + err error + } + sealPayloadRetCh := make(chan SealPayloadRet, 1) + go func() { + res, errStage, err := eng.SealPayload(ctx, payloadInfo, &fc, false) + sealPayloadRetCh <- SealPayloadRet{res, errStage, err} + }() + + type GetPayloadRet struct { + res *eth.ExecutionPayloadEnvelope + err error + } + getPayloadRetCh := make(chan GetPayloadRet, 1) + go func() { + res, err := eng.GetPayload(ctx, payloadInfo) + getPayloadRetCh <- GetPayloadRet{res, err} + }() + + getPayloadRet := <-getPayloadRetCh + envelope := getPayloadRet.res + getPayloadErr := getPayloadRet.err + validatePayloadErr := error(nil) + if getPayloadErr == nil { + payload := envelope.ExecutionPayload + validatePayloadErr = sanityCheckPayload(payload) + if validatePayloadErr == nil { + // TODO handle sequencerConductor component + if err := sequencerConductor.CommitUnsafePayload(ctx, envelope); err != nil { + log.Error("failed to commit unsafe payload to conductor", "payloadID", payloadInfo.ID, "err", err) + } + agossip.Gossip(envelope) + } + } + + sealPayloadRet := <-sealPayloadRetCh + sealRes := sealPayloadRet.res + errStage := sealPayloadRet.errStage + sealPayloadErr := sealPayloadRet.err + switch errStage { + case eth.GetPayloadStage: + return nil, BlockInsertTemporaryErr, fmt.Errorf("failed to get execution payload: %w", sealPayloadErr) + case eth.NewPayloadStage: + if sealPayloadErr != nil { + return nil, BlockInsertTemporaryErr, fmt.Errorf("failed to insert execution payload: %w", sealPayloadErr) + } + validationError := "validation error is nil" + if sealRes.PayloadStatus.ValidationError != nil { + validationError = *sealRes.PayloadStatus.ValidationError + } + if sealRes.PayloadStatus.Status == eth.ExecutionInvalid || sealRes.PayloadStatus.Status == eth.ExecutionInvalidBlockHash { + agossip.Clear() + log.Error("Seal payload failed to new payload", "payloadID", payloadInfo.ID, "status", sealRes.PayloadStatus) + return nil, BlockInsertPayloadErr, fmt.Errorf("failed to new payload, status: %s, validationError: %v", sealRes.PayloadStatus.Status, validationError) + } + if sealRes.PayloadStatus.Status != eth.ExecutionValid { + return nil, BlockInsertTemporaryErr, fmt.Errorf("failed to new payload, status: %s, validationError: %v", sealRes.PayloadStatus.Status, validationError) + } + case eth.ForkchoiceUpdatedStage: + if sealPayloadErr != nil { + var inputErr eth.InputError + if errors.As(sealPayloadErr, &inputErr) { + switch inputErr.Code { + case eth.InvalidForkchoiceState: + // if we succeed to update the forkchoice pre-payload, but fail post-payload, then it is a payload error + agossip.Clear() + return nil, BlockInsertPayloadErr, fmt.Errorf("post-block-creation forkchoice update was inconsistent with engine, need reset to resolve: %w", inputErr.Unwrap()) + default: + agossip.Clear() + return nil, BlockInsertPrestateErr, fmt.Errorf("unexpected error code in forkchoice-updated response: %w", sealPayloadErr) + } + } else { + agossip.Clear() + return nil, BlockInsertTemporaryErr, NewTemporaryError(fmt.Errorf("failed to make the new L2 block canonical via forkchoice: %w", sealPayloadErr)) + } + } + validationError := "validation error is nil" + if sealRes.PayloadStatus.ValidationError != nil { + validationError = *sealRes.PayloadStatus.ValidationError + } + if sealRes.PayloadStatus.Status != eth.ExecutionValid { + agossip.Clear() + return nil, BlockInsertPayloadErr, fmt.Errorf("failed to forkchoice update, status: %s, validationError: %v", sealRes.PayloadStatus.Status, validationError) + } + default: + if sealPayloadErr != nil { + return nil, BlockInsertTemporaryErr, NewTemporaryError(fmt.Errorf("failed to seal payload, err: %w", sealPayloadErr)) + } + if sealRes == nil { + return nil, BlockInsertTemporaryErr, NewTemporaryError(fmt.Errorf("failed to seal payload, got empty response")) + } + if sealRes.PayloadStatus.Status != eth.ExecutionValid { + return nil, BlockInsertTemporaryErr, NewTemporaryError(fmt.Errorf("failed to seal payload, status: %s", sealRes.PayloadStatus.Status)) + } + } + + if getPayloadErr != nil { + return nil, BlockInsertTemporaryErr, NewTemporaryError(fmt.Errorf("failed to get payload: %w", getPayloadErr)) + } + if validatePayloadErr != nil { + return nil, BlockInsertPayloadErr, NewCriticalError(fmt.Errorf("failed to validate payload but seal succeed: %w", validatePayloadErr)) + } + + agossip.Clear() + payload := envelope.ExecutionPayload + metrics.RecordSequencerStepTime("sealPayload", time.Since(start)) + log.Info("Sealed block succeed", "hash", payload.BlockHash, "number", uint64(payload.BlockNumber), + "state_root", payload.StateRoot, "timestamp", uint64(payload.Timestamp), "parent", payload.ParentHash, + "prev_randao", payload.PrevRandao, "fee_recipient", payload.FeeRecipient, + "txs", len(payload.Transactions), "update_safe", updateSafe) + return envelope, BlockInsertOK, nil +} diff --git a/op-node/rollup/derive/span_batch_tx.go b/op-node/rollup/derive/span_batch_tx.go index cd0a471ab..f489dafed 100644 --- a/op-node/rollup/derive/span_batch_tx.go +++ b/op-node/rollup/derive/span_batch_tx.go @@ -8,6 +8,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/rlp" + "github.com/holiman/uint256" ) type spanBatchTxData interface { @@ -45,6 +46,17 @@ type spanBatchDynamicFeeTxData struct { func (txData *spanBatchDynamicFeeTxData) txType() byte { return types.DynamicFeeTxType } +type spanBatchSetCodeTxData struct { + Value *big.Int // wei amount + GasTipCap *big.Int // maxPriorityFeePerGas (EIP-1559) + GasFeeCap *big.Int // maxFeePerGas (EIP-1559) + Data []byte // contract invocation input data + AccessList types.AccessList // EIP-2930 access list + AuthList []types.SetCodeAuthorization // EIP-7702 authorization list +} + +func (txData *spanBatchSetCodeTxData) txType() byte { return types.SetCodeTxType } + // Type returns the transaction type. func (tx *spanBatchTx) Type() uint8 { return tx.inner.txType() @@ -93,6 +105,13 @@ func (tx *spanBatchTx) decodeTyped(b []byte) (spanBatchTxData, error) { return nil, fmt.Errorf("failed to decode spanBatchDynamicFeeTxData: %w", err) } return &inner, nil + case types.SetCodeTxType: + var inner spanBatchSetCodeTxData + err := rlp.DecodeBytes(b[1:], &inner) + if err != nil { + return nil, fmt.Errorf("failed to decode spanBatchSetCodeTxData: %w", err) + } + return &inner, nil default: return nil, types.ErrTxTypeNotSupported } @@ -168,6 +187,23 @@ func (tx *spanBatchTx) convertToFullTx(nonce, gas uint64, to *common.Address, ch R: R, S: S, } + case types.SetCodeTxType: + batchTxInner := tx.inner.(*spanBatchSetCodeTxData) + inner = &types.SetCodeTx{ + ChainID: uint256.MustFromBig(chainID), + Nonce: nonce, + GasTipCap: uint256.MustFromBig(batchTxInner.GasTipCap), + GasFeeCap: uint256.MustFromBig(batchTxInner.GasFeeCap), + Gas: gas, + To: *to, + Value: uint256.MustFromBig(batchTxInner.Value), + Data: batchTxInner.Data, + AccessList: batchTxInner.AccessList, + AuthList: batchTxInner.AuthList, + V: uint256.MustFromBig(V), + R: uint256.MustFromBig(R), + S: uint256.MustFromBig(S), + } default: return nil, fmt.Errorf("invalid tx type: %d", tx.Type()) } @@ -199,6 +235,16 @@ func newSpanBatchTx(tx types.Transaction) (*spanBatchTx, error) { Data: tx.Data(), AccessList: tx.AccessList(), } + case types.SetCodeTxType: + inner = &spanBatchSetCodeTxData{ + GasTipCap: tx.GasTipCap(), + GasFeeCap: tx.GasFeeCap(), + Value: tx.Value(), + Data: tx.Data(), + AccessList: tx.AccessList(), + AuthList: tx.SetCodeAuthorizations(), + } + default: return nil, fmt.Errorf("invalid tx type: %d", tx.Type()) } diff --git a/op-node/rollup/derive/span_batch_txs.go b/op-node/rollup/derive/span_batch_txs.go index 305aafefe..35d2a2f55 100644 --- a/op-node/rollup/derive/span_batch_txs.go +++ b/op-node/rollup/derive/span_batch_txs.go @@ -271,6 +271,8 @@ func (btx *spanBatchTxs) recoverV(chainID *big.Int) error { v = bit case types.DynamicFeeTxType: v = bit + case types.SetCodeTxType: + v = bit default: return fmt.Errorf("invalid tx type: %d", txType) } @@ -386,6 +388,8 @@ func convertVToYParity(v uint64, txType int) (uint, error) { yParityBit = uint(v) case types.DynamicFeeTxType: yParityBit = uint(v) + case types.SetCodeTxType: + yParityBit = uint(v) default: return 0, fmt.Errorf("invalid tx type: %d", txType) } diff --git a/op-node/rollup/driver/config.go b/op-node/rollup/driver/config.go index fa0d6932c..42ebe6f63 100644 --- a/op-node/rollup/driver/config.go +++ b/op-node/rollup/driver/config.go @@ -23,4 +23,6 @@ type Config struct { // SequencerPriority is true when sequencer step takes precedence over other steps. SequencerPriority bool `json:"sequencer_priority"` + + SequencerCombinedEngine bool `json:"sequencer_combined_engine"` } diff --git a/op-node/rollup/driver/driver.go b/op-node/rollup/driver/driver.go index f7f610f4f..7b127eccb 100644 --- a/op-node/rollup/driver/driver.go +++ b/op-node/rollup/driver/driver.go @@ -157,7 +157,7 @@ func NewDriver( sequencerConfDepth := NewConfDepth(driverCfg.SequencerConfDepth, l1State.L1Head, l1) findL1Origin := NewL1OriginSelector(log, cfg, sequencerConfDepth) verifConfDepth := NewConfDepth(driverCfg.VerifierConfDepth, l1State.L1Head, l1) - engine := derive.NewEngineController(l2, log, metrics, cfg, syncCfg) + engine := derive.NewEngineController(l2, log, metrics, cfg, syncCfg, driverCfg.SequencerCombinedEngine) clSync := clsync.NewCLSync(log, cfg, metrics, engine) var finalizer Finalizer diff --git a/op-node/rollup/types.go b/op-node/rollup/types.go index 6b1449949..3d3aa2075 100644 --- a/op-node/rollup/types.go +++ b/op-node/rollup/types.go @@ -508,6 +508,16 @@ func (c *Config) GetPayloadVersion(timestamp uint64) eth.EngineAPIMethod { } } +// SealPayloadVersion returns the EngineAPIMethod suitable for the chain hard fork version. +func (c *Config) SealPayloadVersion(timestamp uint64) eth.EngineAPIMethod { + if c.IsEcotone(timestamp) { + // Cancun + return eth.SealPayloadV3 + } else { + return eth.SealPayloadV2 + } +} + // GetOPPlasmaConfig validates and returns the plasma config from the rollup config. func (c *Config) GetOPPlasmaConfig() (plasma.Config, error) { if c.PlasmaConfig == nil { diff --git a/op-node/service.go b/op-node/service.go index c1fd6f8ab..134a916ee 100644 --- a/op-node/service.go +++ b/op-node/service.go @@ -204,12 +204,13 @@ func NewConfigPersistence(ctx *cli.Context) node.ConfigPersistence { func NewDriverConfig(ctx *cli.Context) *driver.Config { return &driver.Config{ - VerifierConfDepth: ctx.Uint64(flags.VerifierL1Confs.Name), - SequencerConfDepth: ctx.Uint64(flags.SequencerL1Confs.Name), - SequencerEnabled: ctx.Bool(flags.SequencerEnabledFlag.Name), - SequencerStopped: ctx.Bool(flags.SequencerStoppedFlag.Name), - SequencerMaxSafeLag: ctx.Uint64(flags.SequencerMaxSafeLagFlag.Name), - SequencerPriority: ctx.Bool(flags.SequencerPriorityFlag.Name), + VerifierConfDepth: ctx.Uint64(flags.VerifierL1Confs.Name), + SequencerConfDepth: ctx.Uint64(flags.SequencerL1Confs.Name), + SequencerEnabled: ctx.Bool(flags.SequencerEnabledFlag.Name), + SequencerStopped: ctx.Bool(flags.SequencerStoppedFlag.Name), + SequencerMaxSafeLag: ctx.Uint64(flags.SequencerMaxSafeLagFlag.Name), + SequencerPriority: ctx.Bool(flags.SequencerPriorityFlag.Name), + SequencerCombinedEngine: ctx.Bool(flags.SequencerCombinedEngineFlag.Name), } } diff --git a/op-program/client/driver/driver.go b/op-program/client/driver/driver.go index 46fcade40..eab8a41de 100644 --- a/op-program/client/driver/driver.go +++ b/op-program/client/driver/driver.go @@ -58,7 +58,7 @@ func NewDriver(logger log.Logger, cfg *rollup.Config, l1Source derive.L1Fetcher, SyncMode: sync.CLSync, SkipSyncStartCheck: false, ELTriggerGap: 0, - }) + }, false) attributesHandler := attributes.NewAttributesHandler(logger, cfg, engine, l2Source) pipeline := derive.NewDerivationPipeline(logger, cfg, l1Source, l1BlobsSource, plasma.Disabled, l2Source, engine, metrics.NoopMetrics, &sync.Config{}, safedb.Disabled, NoopFinalizer{}, attributesHandler) pipeline.Reset() diff --git a/op-program/client/l2/engine.go b/op-program/client/l2/engine.go index 34b8a5115..9dac9a782 100644 --- a/op-program/client/l2/engine.go +++ b/op-program/client/l2/engine.go @@ -89,6 +89,10 @@ func (o *OracleEngine) NewPayload(ctx context.Context, payload *eth.ExecutionPay } } +func (o *OracleEngine) SealPayload(ctx context.Context, payloadInfo eth.PayloadInfo, fc *eth.ForkchoiceState, needPayload bool) (*eth.SealPayloadResponse, string, error) { + return nil, "", nil +} + func (o *OracleEngine) PayloadByHash(ctx context.Context, hash common.Hash) (*eth.ExecutionPayloadEnvelope, error) { block := o.backend.GetBlockByHash(hash) if block == nil { diff --git a/op-program/client/mpt/db.go b/op-program/client/mpt/db.go index 6728d4101..460d571c2 100644 --- a/op-program/client/mpt/db.go +++ b/op-program/client/mpt/db.go @@ -1,6 +1,8 @@ package mpt -import "github.com/ethereum/go-ethereum/ethdb" +import ( + "github.com/ethereum/go-ethereum/ethdb" +) type Hooks struct { Get func(key []byte) []byte @@ -13,6 +15,42 @@ type DB struct { db Hooks } +func (p *DB) StateStoreReader() ethdb.Reader { + return p +} + +func (p *DB) BlockStoreReader() ethdb.Reader { + return p +} + +func (p *DB) BlockStoreWriter() ethdb.Writer { + return p +} + +func (p *DB) StateStore() ethdb.Database { + return p +} + +func (p *DB) SetStateStore(state ethdb.Database) { + panic("not supported") +} + +func (p *DB) GetStateStore() ethdb.Database { + return p +} + +func (p *DB) BlockStore() ethdb.Database { + return p +} + +func (p *DB) SetBlockStore(block ethdb.Database) { + panic("not supported") +} + +func (p *DB) HasSeparateBlockStore() bool { + return false +} + func (p *DB) Has(key []byte) (bool, error) { panic("not supported") } diff --git a/op-service/eth/status.go b/op-service/eth/status.go index 3baab5725..def3faef7 100644 --- a/op-service/eth/status.go +++ b/op-service/eth/status.go @@ -34,6 +34,8 @@ func NewPayloadErr(payload *ExecutionPayload, payloadStatus *PayloadStatusV1) er return fmt.Errorf("engine is misconfigured. Received invalid-terminal-block error while engine API should be active at genesis. err: %v", payloadStatus.ValidationError) case ExecutionAccepted: return fmt.Errorf("execution payload cannot be validated yet, latest valid hash is %s", payloadStatus.LatestValidHash) + case ExecutionInconsistent: + return fmt.Errorf("execution payload meets an INCONSISTENT status, latest valid hash is %s", payloadStatus.LatestValidHash) default: return fmt.Errorf("unknown execution status on %s: %q, ", payload.ID(), string(payloadStatus.Status)) } diff --git a/op-service/eth/types.go b/op-service/eth/types.go index 605c10ffc..290cffd3e 100644 --- a/op-service/eth/types.go +++ b/op-service/eth/types.go @@ -21,11 +21,17 @@ import ( type ErrorCode int const ( - UnknownPayload ErrorCode = -32001 // Payload does not exist / is not available. + UnknownPayload ErrorCode = -38001 // Payload does not exist / is not available. InvalidForkchoiceState ErrorCode = -38002 // Forkchoice state is invalid / inconsistent. InvalidPayloadAttributes ErrorCode = -38003 // Payload attributes are invalid / inconsistent. ) +const ( + GetPayloadStage = "sealApiGetPayloadErrStage" + NewPayloadStage = "sealApiNewPayloadErrStage" + ForkchoiceUpdatedStage = "sealApiForkchoiceUpdatedErrStage" +) + var ErrBedrockScalarPaddingNotEmpty = errors.New("version 0 scalar value has non-empty padding") // InputError distinguishes an user-input error from regular rpc errors, @@ -367,6 +373,12 @@ type ForkchoiceUpdatedResult struct { PayloadID *PayloadID `json:"payloadId"` } +type SealPayloadResponse struct { + ErrStage string `json:"errStage"` + PayloadStatus PayloadStatusV1 `json:"payloadStatus"` + Payload *ExecutionPayloadEnvelope `json:"payload"` +} + // SystemConfig represents the rollup system configuration that carries over in every L2 block, // and may be changed through L1 system config events. // The initial SystemConfig at rollup genesis is embedded in the rollup configuration. @@ -512,4 +524,7 @@ const ( GetPayloadV2 EngineAPIMethod = "engine_getPayloadV2" GetPayloadV3 EngineAPIMethod = "engine_getPayloadV3" + + SealPayloadV2 EngineAPIMethod = "engine_opSealPayloadV2" + SealPayloadV3 EngineAPIMethod = "engine_opSealPayloadV3" ) diff --git a/op-service/sources/engine_client.go b/op-service/sources/engine_client.go index 9490df78f..bb844a297 100644 --- a/op-service/sources/engine_client.go +++ b/op-service/sources/engine_client.go @@ -61,6 +61,7 @@ type EngineVersionProvider interface { ForkchoiceUpdatedVersion(attr *eth.PayloadAttributes) eth.EngineAPIMethod NewPayloadVersion(timestamp uint64) eth.EngineAPIMethod GetPayloadVersion(timestamp uint64) eth.EngineAPIMethod + SealPayloadVersion(timestamp uint64) eth.EngineAPIMethod } func NewEngineAPIClient(rpc client.RPC, l log.Logger, evp EngineVersionProvider) *EngineAPIClient { @@ -178,6 +179,64 @@ func (s *EngineAPIClient) GetPayload(ctx context.Context, payloadInfo eth.Payloa return &result, nil } +// SealPayload is a combined call of GetPayload, NewPayload, ForkchoiceUpdated via engine_opSealPayload API +func (s *EngineAPIClient) SealPayload(ctx context.Context, payloadInfo eth.PayloadInfo, fc *eth.ForkchoiceState, needPayload bool) (*eth.SealPayloadResponse, string, error) { + e := s.log.New("payload_id", payloadInfo.ID) + e.Trace("sealing payload") + sCtx, sCancel := context.WithTimeout(ctx, time.Second*10) + defer sCancel() + var result eth.SealPayloadResponse + method := s.evp.SealPayloadVersion(payloadInfo.Timestamp) + err := s.RPC.CallContext(sCtx, &result, string(method), payloadInfo.ID, fc, needPayload) + if err != nil { + switch { + case strings.Contains(err.Error(), eth.GetPayloadStage): + e.Error("Seal payload get payload stage failed", "payload_id", payloadInfo.ID, "err", err) + if rpcErr, ok := err.(rpc.Error); ok { + code := eth.ErrorCode(rpcErr.ErrorCode()) + e.Error("Seal payload get payload stage failed", "err code", code, "payload_id", payloadInfo.ID, "err", err) + switch code { + case eth.UnknownPayload: + return nil, eth.GetPayloadStage, eth.InputError{ + Inner: err, + Code: code, + } + default: + return nil, eth.GetPayloadStage, fmt.Errorf("seal payload get payload stage unrecognized rpc error: %w", err) + } + } + return nil, eth.GetPayloadStage, err + case strings.Contains(err.Error(), eth.NewPayloadStage): + e.Error("Seal payload new payload stage execution failed", "payload_id", payloadInfo.ID, "err", err) + if strings.Contains(err.Error(), derive.ErrELSyncTriggerUnexpected.Error()) { + result.PayloadStatus.Status = eth.ExecutionSyncing + return &result, eth.NewPayloadStage, err + } + return nil, eth.NewPayloadStage, fmt.Errorf("seal payload failed to execute payload: %w", err) + case strings.Contains(err.Error(), eth.ForkchoiceUpdatedStage): + e.Error("Seal payload forkchoice updated stage failed to share forkchoice-updated signal", "payload_id", payloadInfo.ID, "err", err) + if rpcErr, ok := err.(rpc.Error); ok { + code := eth.ErrorCode(rpcErr.ErrorCode()) + switch code { + case eth.InvalidForkchoiceState, eth.InvalidPayloadAttributes: + return nil, eth.ForkchoiceUpdatedStage, eth.InputError{ + Inner: err, + Code: code, + } + default: + return nil, eth.ForkchoiceUpdatedStage, fmt.Errorf("seal payload forkchoice updated stage unrecognized rpc error: %w", err) + } + } + return nil, eth.ForkchoiceUpdatedStage, err + default: + e.Error("Seal payload network stage failed", "payload_id", payloadInfo.ID, "err", err) + return nil, result.ErrStage, err + } + } + e.Trace("Sealed payload") + return &result, result.ErrStage, nil +} + func (s *EngineAPIClient) SignalSuperchainV1(ctx context.Context, recommended, required params.ProtocolVersion) (params.ProtocolVersion, error) { var result params.ProtocolVersion err := s.RPC.CallContext(ctx, &result, "engine_signalSuperchainV1", &catalyst.SuperchainSignal{ diff --git a/op-service/sources/eth_client.go b/op-service/sources/eth_client.go index 5ffa3f07e..e6879047f 100644 --- a/op-service/sources/eth_client.go +++ b/op-service/sources/eth_client.go @@ -285,7 +285,9 @@ func (s *EthClient) InfoByLabel(ctx context.Context, label eth.BlockLabel) (eth. func (s *EthClient) BSCInfoByLabel(ctx context.Context, label eth.BlockLabel) (eth.BlockInfo, error) { // can't hit the cache when querying the head due to reorgs / changes. if label == eth.Finalized { - return s.bscFinalizedHeader(ctx, 21) + // -3 means automatically use the len(validators) of BSC network + // refs: https://github.com/bnb-chain/bsc/pull/2844 + return s.bscFinalizedHeader(ctx, -3) } return s.headerCall(ctx, "eth_getBlockByNumber", label) } diff --git a/op-service/testutils/mock_engine.go b/op-service/testutils/mock_engine.go index fa1f716d1..7d3351896 100644 --- a/op-service/testutils/mock_engine.go +++ b/op-service/testutils/mock_engine.go @@ -40,6 +40,15 @@ func (m *MockEngine) ExpectNewPayload(payload *eth.ExecutionPayload, parentBeaco m.Mock.On("NewPayload", mustJson(payload), mustJson(parentBeaconBlockRoot)).Once().Return(result, err) } +func (m *MockEngine) SealPayload(ctx context.Context, payloadInfo eth.PayloadInfo, fc *eth.ForkchoiceState, needPayload bool) (*eth.SealPayloadResponse, string, error) { + out := m.Mock.Called(payloadInfo.ID, fc, needPayload) + return out.Get(0).(*eth.SealPayloadResponse), out.Get(1).(string), out.Error(1) +} + +func (m *MockEngine) ExpectSealPayload(payloadInfo eth.PayloadInfo, fc *eth.ForkchoiceState, needPayload bool, result *eth.SealPayloadResponse, errStage string, err error) { + m.Mock.On("SealPayload", payloadInfo, fc, needPayload).Once().Return(result, errStage, err) +} + func (m *MockEngine) CachePayloadByHash(payload *eth.ExecutionPayloadEnvelope) bool { return true } diff --git a/op-wheel/engine/version_provider.go b/op-wheel/engine/version_provider.go index d3aaa377e..ae39d949c 100644 --- a/op-wheel/engine/version_provider.go +++ b/op-wheel/engine/version_provider.go @@ -42,3 +42,14 @@ func (v StaticVersionProvider) GetPayloadVersion(uint64) eth.EngineAPIMethod { panic("invalid Engine API version: " + strconv.Itoa(int(v))) } } + +func (v StaticVersionProvider) SealPayloadVersion(uint64) eth.EngineAPIMethod { + switch int(v) { + case 1, 2: + return eth.SealPayloadV2 + case 3: + return eth.SealPayloadV3 + default: + panic("invalid Engine API version: " + strconv.Itoa(int(v))) + } +}