From 116598ab6bd6cd8deb08fa3a33fc62406e75963f Mon Sep 17 00:00:00 2001 From: Michael de Hoog Date: Fri, 27 Dec 2024 09:49:33 -1000 Subject: [PATCH] Deposit queue --- op-node/rollup/derive/deposit_log.go | 27 +++++++++++++--- op-node/rollup/derive/deposit_log_tob_test.go | 2 +- op-node/rollup/derive/system_config.go | 16 +++++++--- .../src/L1/OptimismPortal2.sol | 27 ++++++++++------ .../contracts-bedrock/src/L1/SystemConfig.sol | 32 ++++++++++++------- 5 files changed, 74 insertions(+), 30 deletions(-) diff --git a/op-node/rollup/derive/deposit_log.go b/op-node/rollup/derive/deposit_log.go index 74fde28c59882..283ca63e84ba0 100644 --- a/op-node/rollup/derive/deposit_log.go +++ b/op-node/rollup/derive/deposit_log.go @@ -17,7 +17,8 @@ import ( var ( DepositEventABI = "TransactionDeposited(address,address,uint256,bytes)" DepositEventABIHash = crypto.Keccak256Hash([]byte(DepositEventABI)) - DepositEventVersion0 = common.Hash{} + DepositEventVersion0 = uint64(0) + DepositEventVersion1 = uint64(1) ) // UnmarshalDepositLogEvent decodes an EVM log entry emitted by the deposit contract into typed deposit data. @@ -27,7 +28,7 @@ var ( // event TransactionDeposited( // address indexed from, // address indexed to, -// uint256 indexed version, +// uint256 indexed nonceAndVersion, // bytes opaqueData // ); // @@ -51,7 +52,7 @@ func UnmarshalDepositLogEvent(ev *types.Log) (*types.DepositTx, error) { // indexed 1 to := common.BytesToAddress(ev.Topics[2][12:]) // indexed 2 - version := ev.Topics[3] + nonceAndVersion := ev.Topics[3] // unindexed data // Solidity serializes the event's Data field as follows: // abi.encode(abi.encodPacked(uint256 mint, uint256 value, uint64 gasLimit, uint8 isCreation, bytes data)) @@ -83,10 +84,15 @@ func UnmarshalDepositLogEvent(ev *types.Log) (*types.DepositTx, error) { dep.From = from dep.IsSystemTransaction = false + _, versionBig := unpackNonceAndVersion(nonceAndVersion) + version := versionBig.Uint64() + var err error switch version { case DepositEventVersion0: err = unmarshalDepositVersion0(&dep, to, opaqueData) + case DepositEventVersion1: + err = unmarshalDepositVersion1(&dep, to, opaqueData) default: return nil, fmt.Errorf("invalid deposit version, got %s", version) } @@ -96,6 +102,14 @@ func UnmarshalDepositLogEvent(ev *types.Log) (*types.DepositTx, error) { return &dep, nil } +func unpackNonceAndVersion(nonceAndVersion common.Hash) (nonce *big.Int, version *big.Int) { + i := new(big.Int).SetBytes(nonceAndVersion[:]) + mask128 := new(big.Int).SetBytes(common.Hex2Bytes("ffffffffffffffffffffffffffffffff")) + version = mask128.And(mask128, i) + nonce = i.Lsh(i, 128) + return +} + func unmarshalDepositVersion0(dep *types.DepositTx, to common.Address, opaqueData []byte) error { if len(opaqueData) < 32+32+8+1 { return fmt.Errorf("unexpected opaqueData length: %d", len(opaqueData)) @@ -140,6 +154,11 @@ func unmarshalDepositVersion0(dep *types.DepositTx, to common.Address, opaqueDat return nil } +func unmarshalDepositVersion1(dep *types.DepositTx, to common.Address, opaqueData []byte) error { + // version 1 simply adds a nonce; the rest is the same + return unmarshalDepositVersion0(dep, to, opaqueData) +} + // MarshalDepositLogEvent returns an EVM log entry that encodes a TransactionDeposited event from the deposit contract. // This is the reverse of the deposit transaction derivation. func MarshalDepositLogEvent(depositContractAddr common.Address, deposit *types.DepositTx) (*types.Log, error) { @@ -151,7 +170,7 @@ func MarshalDepositLogEvent(depositContractAddr common.Address, deposit *types.D DepositEventABIHash, eth.AddressAsLeftPaddedHash(deposit.From), toBytes, - DepositEventVersion0, + common.BytesToHash(new(big.Int).SetUint64(DepositEventVersion0).Bytes()), } data := make([]byte, 64, 64+3*32) diff --git a/op-node/rollup/derive/deposit_log_tob_test.go b/op-node/rollup/derive/deposit_log_tob_test.go index 49721104b0867..42b92e96aa6f9 100644 --- a/op-node/rollup/derive/deposit_log_tob_test.go +++ b/op-node/rollup/derive/deposit_log_tob_test.go @@ -194,7 +194,7 @@ func FuzzDeriveDepositsBadVersion(f *testing.F) { } // Set our bad topic and update our state - log.Topics[3] = badTopic + log.Topics[3] = common.BytesToHash(new(big.Int).SetUint64(badTopic).Bytes()) hasBadDepositVersion = true } } diff --git a/op-node/rollup/derive/system_config.go b/op-node/rollup/derive/system_config.go index 72c4e713c30e9..fad968384dfa0 100644 --- a/op-node/rollup/derive/system_config.go +++ b/op-node/rollup/derive/system_config.go @@ -27,7 +27,8 @@ var ( var ( ConfigUpdateEventABI = "ConfigUpdate(uint256,uint8,bytes)" ConfigUpdateEventABIHash = crypto.Keccak256Hash([]byte(ConfigUpdateEventABI)) - ConfigUpdateEventVersion0 = common.Hash{} + ConfigUpdateEventVersion0 = uint64(0) + ConfigUpdateEventVersion1 = uint64(1) ) // UpdateSystemConfigWithL1Receipts filters all L1 receipts to find config updates and applies the config updates to the given sysCfg @@ -53,7 +54,7 @@ func UpdateSystemConfigWithL1Receipts(sysCfg *eth.SystemConfig, receipts []*type // parse log data for: // // event ConfigUpdate( -// uint256 indexed version, +// uint256 indexed nonceAndVersion, // UpdateType indexed updateType, // bytes data // ); @@ -66,10 +67,15 @@ func ProcessSystemConfigUpdateLogEvent(destSysCfg *eth.SystemConfig, ev *types.L } // indexed 0 - version := ev.Topics[1] - if version != ConfigUpdateEventVersion0 { - return fmt.Errorf("unrecognized SystemConfig update event version: %s", version) + _, versionBig := unpackNonceAndVersion(ev.Topics[1]) + version := versionBig.Uint64() + switch version { + case ConfigUpdateEventVersion0: + case ConfigUpdateEventVersion1: + default: + return fmt.Errorf("unrecognized SystemConfig update event version: %d", version) } + // indexed 1 updateType := ev.Topics[2] diff --git a/packages/contracts-bedrock/src/L1/OptimismPortal2.sol b/packages/contracts-bedrock/src/L1/OptimismPortal2.sol index 10448f0b7fb65..f089e4351c23f 100644 --- a/packages/contracts-bedrock/src/L1/OptimismPortal2.sol +++ b/packages/contracts-bedrock/src/L1/OptimismPortal2.sol @@ -71,7 +71,7 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver { uint256 internal immutable DISPUTE_GAME_FINALITY_DELAY_SECONDS; /// @notice Version of the deposit event. - uint256 internal constant DEPOSIT_VERSION = 0; + uint256 internal constant DEPOSIT_VERSION = 1; /// @notice The L2 gas limit set when eth is deposited using the receive() function. uint64 internal constant RECEIVE_DEFAULT_GAS_LIMIT = 100_000; @@ -141,14 +141,17 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver { /// It is not safe to trust `ERC20.balanceOf` as it may lie. uint256 internal _balance; + /// @notice Nonce incremented for each TransactionDeposited event + uint256 public transactionDepositedNonce; + /// @notice Emitted when a transaction is deposited from L1 to L2. /// The parameters of this event are read by the rollup node and used to derive deposit /// transactions on L2. - /// @param from Address that triggered the deposit transaction. - /// @param to Address that the deposit transaction is directed to. - /// @param version Version of this deposit transaction event. - /// @param opaqueData ABI encoded deposit data to be parsed off-chain. - event TransactionDeposited(address indexed from, address indexed to, uint256 indexed version, bytes opaqueData); + /// @param from Address that triggered the deposit transaction. + /// @param to Address that the deposit transaction is directed to. + /// @param nonceAndVersion Nonce (first 128-bits) and version (second 128-bits). + /// @param opaqueData ABI encoded deposit data to be parsed off-chain. + event TransactionDeposited(address indexed from, address indexed to, uint256 indexed nonceAndVersion, bytes opaqueData); /// @notice Emitted when a withdrawal transaction is proven. /// @param withdrawalHash Hash of the withdrawal transaction. @@ -597,7 +600,7 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver { // Emit a TransactionDeposited event so that the rollup node can derive a deposit // transaction for this deposit. - emit TransactionDeposited(from, _to, DEPOSIT_VERSION, opaqueData); + _emitTransactionDeposited(from, _to, opaqueData); } /// @notice Sets the gas paying token for the L2 system. This token is used as the @@ -611,10 +614,9 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver { // Emit the special deposit transaction directly that sets the gas paying // token in the L1Block predeploy contract. - emit TransactionDeposited( + _emitTransactionDeposited( Constants.DEPOSITOR_ACCOUNT, Predeploys.L1_BLOCK_ATTRIBUTES, - DEPOSIT_VERSION, abi.encodePacked( uint256(0), // mint uint256(0), // value @@ -625,6 +627,13 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver { ); } + /// @notice Emit a TransactionDeposited event and increment the nonce. + function _emitTransactionDeposited(address from, address to, bytes memory opaqueData) internal { + uint256 _version = transactionDepositedNonce << 128 | DEPOSIT_VERSION; + transactionDepositedNonce++; + emit TransactionDeposited(from, to, _version, opaqueData); + } + /// @notice Blacklists a dispute game. Should only be used in the event that a dispute game resolves incorrectly. /// @param _disputeGame Dispute game to blacklist. function blacklistDisputeGame(IDisputeGame _disputeGame) external { diff --git a/packages/contracts-bedrock/src/L1/SystemConfig.sol b/packages/contracts-bedrock/src/L1/SystemConfig.sol index 0b4562543d925..4b91c211232ba 100644 --- a/packages/contracts-bedrock/src/L1/SystemConfig.sol +++ b/packages/contracts-bedrock/src/L1/SystemConfig.sol @@ -49,7 +49,7 @@ contract SystemConfig is OwnableUpgradeable, ISemver, IGasToken { } /// @notice Version identifier, used for upgrades. - uint256 public constant VERSION = 0; + uint256 public constant VERSION = 1; /// @notice Storage slot that the unsafe block signer is stored at. /// Storing it at this deterministic storage slot allows for decoupling the storage @@ -130,11 +130,14 @@ contract SystemConfig is OwnableUpgradeable, ISemver, IGasToken { /// @notice The EIP-1559 elasticity multiplier. uint32 public eip1559Elasticity; + /// @notice Nonce incremented for each ConfigUpdate event + uint256 public configUpdateNonce; + /// @notice Emitted when configuration is updated. - /// @param version SystemConfig version. - /// @param updateType Type of update. - /// @param data Encoded update data. - event ConfigUpdate(uint256 indexed version, UpdateType indexed updateType, bytes data); + /// @param nonceAndVersion Nonce (first 128-bits) and version (second 128-bits). + /// @param updateType Type of update. + /// @param data Encoded update data. + event ConfigUpdate(uint256 indexed nonceAndVersion, UpdateType indexed updateType, bytes data); /// @notice Semantic version. /// @custom:semver 2.3.0-beta.9 @@ -323,7 +326,7 @@ contract SystemConfig is OwnableUpgradeable, ISemver, IGasToken { Storage.setAddress(UNSAFE_BLOCK_SIGNER_SLOT, _unsafeBlockSigner); bytes memory data = abi.encode(_unsafeBlockSigner); - emit ConfigUpdate(VERSION, UpdateType.UNSAFE_BLOCK_SIGNER, data); + _emitConfigUpdate(UpdateType.UNSAFE_BLOCK_SIGNER, data); } /// @notice Updates the batcher hash. Can only be called by the owner. @@ -338,7 +341,7 @@ contract SystemConfig is OwnableUpgradeable, ISemver, IGasToken { batcherHash = _batcherHash; bytes memory data = abi.encode(_batcherHash); - emit ConfigUpdate(VERSION, UpdateType.BATCHER, data); + _emitConfigUpdate(UpdateType.BATCHER, data); } /// @notice Updates gas config. Can only be called by the owner. @@ -359,7 +362,7 @@ contract SystemConfig is OwnableUpgradeable, ISemver, IGasToken { scalar = _scalar; bytes memory data = abi.encode(_overhead, _scalar); - emit ConfigUpdate(VERSION, UpdateType.FEE_SCALARS, data); + _emitConfigUpdate(UpdateType.FEE_SCALARS, data); } /// @notice Updates gas config as of the Ecotone upgrade. Can only be called by the owner. @@ -379,7 +382,7 @@ contract SystemConfig is OwnableUpgradeable, ISemver, IGasToken { scalar = (uint256(0x01) << 248) | (uint256(_blobbasefeeScalar) << 32) | _basefeeScalar; bytes memory data = abi.encode(overhead, scalar); - emit ConfigUpdate(VERSION, UpdateType.FEE_SCALARS, data); + _emitConfigUpdate(UpdateType.FEE_SCALARS, data); } /// @notice Updates the L2 gas limit. Can only be called by the owner. @@ -396,7 +399,7 @@ contract SystemConfig is OwnableUpgradeable, ISemver, IGasToken { gasLimit = _gasLimit; bytes memory data = abi.encode(_gasLimit); - emit ConfigUpdate(VERSION, UpdateType.GAS_LIMIT, data); + _emitConfigUpdate(UpdateType.GAS_LIMIT, data); } /// @notice Updates the EIP-1559 parameters of the chain. Can only be called by the owner. @@ -415,7 +418,14 @@ contract SystemConfig is OwnableUpgradeable, ISemver, IGasToken { eip1559Elasticity = _elasticity; bytes memory data = abi.encode(uint256(_denominator) << 32 | uint64(_elasticity)); - emit ConfigUpdate(VERSION, UpdateType.EIP_1559_PARAMS, data); + _emitConfigUpdate(UpdateType.EIP_1559_PARAMS, data); + } + + /// @notice Emit a ConfigUpdate event and increment the nonce. + function _emitConfigUpdate(UpdateType updateType, bytes memory data) internal { + uint256 _version = configUpdateNonce << 128 | VERSION; + configUpdateNonce++; + emit ConfigUpdate(_version, updateType, data); } /// @notice Sets the start block in a backwards compatible way. Proxies