From 1166e5608dd5d1375b43ca6f9ef3faa634b7a992 Mon Sep 17 00:00:00 2001 From: Ratan Kaliani Date: Tue, 17 Dec 2024 11:15:58 -0800 Subject: [PATCH 1/6] feat: optimistic mode --- contracts/src/OPSuccinctL2OutputOracle.sol | 94 +++++++++++++++++++--- 1 file changed, 82 insertions(+), 12 deletions(-) diff --git a/contracts/src/OPSuccinctL2OutputOracle.sol b/contracts/src/OPSuccinctL2OutputOracle.sol index 22f3b244..690f3d0b 100644 --- a/contracts/src/OPSuccinctL2OutputOracle.sol +++ b/contracts/src/OPSuccinctL2OutputOracle.sol @@ -93,6 +93,9 @@ contract OPSuccinctL2OutputOracle is Initializable, ISemver { /// @notice A trusted mapping of block numbers to block hashes. mapping(uint256 => bytes32) public historicBlockHashes; + /// @notice Activate optimistic mode. When true, the contract will accept outputs without verification. + bool public optimisticMode; + //////////////////////////////////////////////////////////// // Events // //////////////////////////////////////////////////////////// @@ -146,6 +149,11 @@ contract OPSuccinctL2OutputOracle is Initializable, ISemver { /// @param newSubmissionInterval The new submission interval. event SubmissionIntervalUpdated(uint256 oldSubmissionInterval, uint256 newSubmissionInterval); + /// @notice Emitted when the optimistic mode is toggled. + /// @param enabled Indicates whether optimistic mode is enabled or disabled. + /// @param finalizationPeriodSeconds The new finalization period in seconds. + event OptimisticModeToggled(bool indexed enabled, uint256 finalizationPeriodSeconds); + //////////////////////////////////////////////////////////// // Errors // //////////////////////////////////////////////////////////// @@ -173,6 +181,16 @@ contract OPSuccinctL2OutputOracle is Initializable, ISemver { _; } + modifier whenOptimistic() { + require(optimisticMode, "L2OutputOracle: optimistic mode is not enabled"); + _; + } + + modifier whenNotOptimistic() { + require(!optimisticMode, "L2OutputOracle: optimistic mode is enabled"); + _; + } + //////////////////////////////////////////////////////////// // Functions // //////////////////////////////////////////////////////////// @@ -303,6 +321,7 @@ contract OPSuccinctL2OutputOracle is Initializable, ISemver { function proposeL2Output(bytes32 _outputRoot, uint256 _l2BlockNumber, uint256 _l1BlockNumber, bytes memory _proof) external payable + whenNotOptimistic { // The proposer must be explicitly approved, or the zero address must be approved (permissionless proposing). require( @@ -349,6 +368,53 @@ contract OPSuccinctL2OutputOracle is Initializable, ISemver { ); } + /// @notice Accepts an outputRoot and the timestamp of the corresponding L2 block. + /// The timestamp must be equal to the current value returned by `nextTimestamp()` in + /// order to be accepted. This function may only be called by the Proposer. + /// @param _outputRoot The L2 output of the checkpoint block. + /// @param _l2BlockNumber The L2 block number that resulted in _outputRoot. + /// @param _l1BlockHash A block hash which must be included in the current chain. + /// @param _l1BlockNumber The block number with the specified block hash. + /// @dev This function is sourced from the original L2OutputOracle contract. + function proposeL2Output(bytes32 _outputRoot, uint256 _l2BlockNumber, bytes32 _l1BlockHash, uint256 _l1BlockNumber) + external + payable + whenOptimistic + { + require(msg.sender == proposer, "L2OutputOracle: only the proposer address can propose new outputs"); + require( + _l2BlockNumber == nextBlockNumber(), + "L2OutputOracle: block number must be equal to next expected block number" + ); + require( + computeL2Timestamp(_l2BlockNumber) < block.timestamp, + "L2OutputOracle: cannot propose L2 output in the future" + ); + require(_outputRoot != bytes32(0), "L2OutputOracle: L2 output proposal cannot be the zero hash"); + if (_l1BlockHash != bytes32(0)) { + // This check allows the proposer to propose an output based on a given L1 block, + // without fear that it will be reorged out. + // It will also revert if the blockheight provided is more than 256 blocks behind the + // chain tip (as the hash will return as zero). This does open the door to a griefing + // attack in which the proposer's submission is censored until the block is no longer + // retrievable, if the proposer is experiencing this attack it can simply leave out the + // blockhash value, and delay submission until it is confident that the L1 block is + // finalized. + require( + blockhash(_l1BlockNumber) == _l1BlockHash, + "L2OutputOracle: block hash does not match the hash at the expected height" + ); + } + emit OutputProposed(_outputRoot, nextOutputIndex(), _l2BlockNumber, block.timestamp); + l2Outputs.push( + Types.OutputProposal({ + outputRoot: _outputRoot, + timestamp: uint128(block.timestamp), + l2BlockNumber: uint128(_l2BlockNumber) + }) + ); + } + /// @notice Checkpoints a block hash at a given block number. /// @param _blockNumber Block number to checkpoint the hash at. /// @dev If the block hash is not available, this will revert. @@ -360,18 +426,6 @@ contract OPSuccinctL2OutputOracle is Initializable, ISemver { historicBlockHashes[_blockNumber] = blockHash; } - /// @notice Forces an output root proposal. - /// @dev This function is only intended to be used when the L2OutputOracle is in a state where it's not possible to propose outputs. - function forceOutputRootProposal(bytes32 _outputRoot, uint256 _l2BlockNumber) external onlyOwner { - l2Outputs.push( - Types.OutputProposal({ - outputRoot: _outputRoot, - timestamp: uint128(block.timestamp), - l2BlockNumber: uint128(_l2BlockNumber) - }) - ); - } - /// @notice Returns an output by index. Needed to return a struct instead of a tuple. /// @param _l2OutputIndex Index of the output to return. /// @return The output at the given index. @@ -507,4 +561,20 @@ contract OPSuccinctL2OutputOracle is Initializable, ISemver { approvedProposers[_proposer] = false; emit ProposerUpdated(_proposer, false); } + + /// @notice Enables optimistic mode. + /// @param _finalizationPeriodSeconds The new finalization window. + function enableOptimisticMode(uint256 _finalizationPeriodSeconds) external onlyOwner whenNotOptimistic { + finalizationPeriodSeconds = _finalizationPeriodSeconds; + optimisticMode = true; + emit OptimisticModeToggled(true, _finalizationPeriodSeconds); + } + /// @notice Disables optimistic mode. + /// @param _finalizationPeriodSeconds The new finalization window. + + function disableOptimisticMode(uint256 _finalizationPeriodSeconds) external onlyOwner whenOptimistic { + finalizationPeriodSeconds = _finalizationPeriodSeconds; + optimisticMode = false; + emit OptimisticModeToggled(false, _finalizationPeriodSeconds); + } } From 08df1b191f1efede36075d8bce94d96a9d0bd659 Mon Sep 17 00:00:00 2001 From: Ratan Kaliani Date: Tue, 17 Dec 2024 11:17:23 -0800 Subject: [PATCH 2/6] add --- contracts/src/OPSuccinctL2OutputOracle.sol | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/contracts/src/OPSuccinctL2OutputOracle.sol b/contracts/src/OPSuccinctL2OutputOracle.sol index 690f3d0b..6d426c2b 100644 --- a/contracts/src/OPSuccinctL2OutputOracle.sol +++ b/contracts/src/OPSuccinctL2OutputOracle.sol @@ -382,15 +382,19 @@ contract OPSuccinctL2OutputOracle is Initializable, ISemver { whenOptimistic { require(msg.sender == proposer, "L2OutputOracle: only the proposer address can propose new outputs"); + require( _l2BlockNumber == nextBlockNumber(), "L2OutputOracle: block number must be equal to next expected block number" ); + require( computeL2Timestamp(_l2BlockNumber) < block.timestamp, "L2OutputOracle: cannot propose L2 output in the future" ); + require(_outputRoot != bytes32(0), "L2OutputOracle: L2 output proposal cannot be the zero hash"); + if (_l1BlockHash != bytes32(0)) { // This check allows the proposer to propose an output based on a given L1 block, // without fear that it will be reorged out. @@ -405,7 +409,9 @@ contract OPSuccinctL2OutputOracle is Initializable, ISemver { "L2OutputOracle: block hash does not match the hash at the expected height" ); } + emit OutputProposed(_outputRoot, nextOutputIndex(), _l2BlockNumber, block.timestamp); + l2Outputs.push( Types.OutputProposal({ outputRoot: _outputRoot, From 1bb93d01ecaada3c235dcf5e9f8c82559b60dfcb Mon Sep 17 00:00:00 2001 From: Ratan Kaliani Date: Tue, 17 Dec 2024 11:25:00 -0800 Subject: [PATCH 3/6] add --- contracts/src/OPSuccinctL2OutputOracle.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/src/OPSuccinctL2OutputOracle.sol b/contracts/src/OPSuccinctL2OutputOracle.sol index 6d426c2b..22e0a63c 100644 --- a/contracts/src/OPSuccinctL2OutputOracle.sol +++ b/contracts/src/OPSuccinctL2OutputOracle.sol @@ -575,9 +575,9 @@ contract OPSuccinctL2OutputOracle is Initializable, ISemver { optimisticMode = true; emit OptimisticModeToggled(true, _finalizationPeriodSeconds); } + /// @notice Disables optimistic mode. /// @param _finalizationPeriodSeconds The new finalization window. - function disableOptimisticMode(uint256 _finalizationPeriodSeconds) external onlyOwner whenOptimistic { finalizationPeriodSeconds = _finalizationPeriodSeconds; optimisticMode = false; From 60743c7bedd6485453b901c0525119141d5c7d02 Mon Sep 17 00:00:00 2001 From: Ratan Kaliani Date: Tue, 17 Dec 2024 13:13:51 -0800 Subject: [PATCH 4/6] feat: updated --- contracts/src/OPSuccinctL2OutputOracle.sol | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/contracts/src/OPSuccinctL2OutputOracle.sol b/contracts/src/OPSuccinctL2OutputOracle.sol index 22e0a63c..7777234d 100644 --- a/contracts/src/OPSuccinctL2OutputOracle.sol +++ b/contracts/src/OPSuccinctL2OutputOracle.sol @@ -375,13 +375,17 @@ contract OPSuccinctL2OutputOracle is Initializable, ISemver { /// @param _l2BlockNumber The L2 block number that resulted in _outputRoot. /// @param _l1BlockHash A block hash which must be included in the current chain. /// @param _l1BlockNumber The block number with the specified block hash. - /// @dev This function is sourced from the original L2OutputOracle contract. + /// @dev This function is sourced from the original L2OutputOracle contract. The only modification is that the proposer address must be in the approvedProposers mapping, or permissionless proposing is enabled. function proposeL2Output(bytes32 _outputRoot, uint256 _l2BlockNumber, bytes32 _l1BlockHash, uint256 _l1BlockNumber) external payable whenOptimistic { - require(msg.sender == proposer, "L2OutputOracle: only the proposer address can propose new outputs"); + // The proposer must be explicitly approved, or the zero address must be approved (permissionless proposing). + require( + approvedProposers[msg.sender] || approvedProposers[address(0)], + "L2OutputOracle: only approved proposers can propose new outputs" + ); require( _l2BlockNumber == nextBlockNumber(), From 8d9158fddd5b884257d8eb7f7804258a13616690 Mon Sep 17 00:00:00 2001 From: Ratan Kaliani Date: Tue, 17 Dec 2024 13:26:17 -0800 Subject: [PATCH 5/6] add --- book/SUMMARY.md | 1 + book/advanced/toggle-optimistic.md | 37 ++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 book/advanced/toggle-optimistic.md diff --git a/book/SUMMARY.md b/book/SUMMARY.md index 9a21f57d..d75c57a2 100644 --- a/book/SUMMARY.md +++ b/book/SUMMARY.md @@ -13,6 +13,7 @@ - [L2 Node Setup](./advanced/node-setup.md) - [Block Data CLI Tool](./advanced/block-data.md) - [Proposer](./advanced/proposer.md) + - [Toggle Optimistic Mode](./advanced/toggle-optimistic.md) - [Kurtosis](./advanced/kurtosis.md) - [Upgrade to a new `op-succinct` version](./advanced/upgrade.md) - [`OPSuccinctL2OutputOracle`](./contracts/intro.md) diff --git a/book/advanced/toggle-optimistic.md b/book/advanced/toggle-optimistic.md new file mode 100644 index 00000000..e549e8ef --- /dev/null +++ b/book/advanced/toggle-optimistic.md @@ -0,0 +1,37 @@ +# Toggle Optimistic Mode + +Optimistic mode is a feature that allows the L2OutputOracle to accept outputs without verification (mirroring the original `L2OutputOracle` contract). This is useful for testing and development purposes, and as a fallback for when `OPSuccinctL2OutputOracle` is unable to verify outputs. + +When optimistic mode is enabled, the `OPSuccinctL2OutputOracle`'s `proposeL2Output` function will match the interface of the original L2OutputOracle contract, with the modification that the proposer address must be in the `approvedProposers` mapping, or permissionless proposing must be enabled. + +## Enable Optimistic Mode + +To enable optimistic mode, call the `enableOptimisticMode` function on the `OPSuccinctL2OutputOracle` contract. + +```solidity +function enableOptimisticMode(uint256 _finalizationPeriodSeconds) external onlyOwner whenNotOptimistic { + finalizationPeriodSeconds = _finalizationPeriodSeconds; + optimisticMode = true; + emit OptimisticModeToggled(true, _finalizationPeriodSeconds); +} +``` + +Ensure that the `finalizationPeriodSeconds` is set to a value that is appropriate for your use case. The standard setting is 1 week (604800 seconds). + +The `finalizationPeriodSeconds` should never be 0. + +## Disable Optimistic Mode + +By default, optimistic mode is disabled. To switch back to validity mode, call the `disableOptimisticMode` function on the `OPSuccinctL2OutputOracle` contract. + +```solidity +function disableOptimisticMode(uint256 _finalizationPeriodSeconds) external onlyOwner whenOptimistic { + finalizationPeriodSeconds = _finalizationPeriodSeconds; + optimisticMode = false; + emit OptimisticModeToggled(false, _finalizationPeriodSeconds); +} +``` + +Set the `finalizationPeriodSeconds` to a value that is appropriate for your use case. An example configuration is 1 hour (3600 seconds). + +The `finalizationPeriodSeconds` should never be 0. From 6b3fc49f40f61a6e9f0dd382d9a99c7e86868095 Mon Sep 17 00:00:00 2001 From: Ratan Kaliani Date: Tue, 17 Dec 2024 13:27:57 -0800 Subject: [PATCH 6/6] add --- contracts/src/OPSuccinctL2OutputOracle.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/src/OPSuccinctL2OutputOracle.sol b/contracts/src/OPSuccinctL2OutputOracle.sol index 7777234d..6adcd576 100644 --- a/contracts/src/OPSuccinctL2OutputOracle.sol +++ b/contracts/src/OPSuccinctL2OutputOracle.sol @@ -166,8 +166,8 @@ contract OPSuccinctL2OutputOracle is Initializable, ISemver { error L1BlockHashNotCheckpointed(); /// @notice Semantic version. - /// @custom:semver v1.0.0-rc1 - string public constant version = "v1.0.0-rc1"; + /// @custom:semver v1.0.0-rc2 + string public constant version = "v1.0.0-rc2"; /// @notice The version of the initializer on the contract. Used for managing upgrades. uint8 public constant initializerVersion = 1;