diff --git a/packages/protocol/contracts/layer1/based/ITaikoL1.sol b/packages/protocol/contracts/layer1/based/ITaikoL1.sol index 9ea3977ecdd..fbd2eca8185 100644 --- a/packages/protocol/contracts/layer1/based/ITaikoL1.sol +++ b/packages/protocol/contracts/layer1/based/ITaikoL1.sol @@ -87,6 +87,10 @@ interface ITaikoL1 { view returns (TaikoData.TransitionState memory); + /// @notice Retrieves the ID of the L1 block where the most recent L2 block was proposed. + /// @return The ID of the Li block where the most recent block was proposed. + function lastProposedIn() external view returns (uint56); + /// @notice Gets the configuration of the TaikoL1 contract. /// @return Config struct containing configuration parameters. function getConfig() external pure returns (TaikoData.Config memory); diff --git a/packages/protocol/contracts/layer1/based/LibProposing.sol b/packages/protocol/contracts/layer1/based/LibProposing.sol index 27a2424fe91..1e220e1b8e1 100644 --- a/packages/protocol/contracts/layer1/based/LibProposing.sol +++ b/packages/protocol/contracts/layer1/based/LibProposing.sol @@ -278,6 +278,7 @@ library LibProposing { unchecked { ++_state.slotB.numBlocks; } + _state.slotB.lastProposedIn = uint56(block.number); LibBonds.debitBond(_state, _resolver, local.params.proposer, meta_.id, _config.livenessBond); diff --git a/packages/protocol/contracts/layer1/based/TaikoData.sol b/packages/protocol/contracts/layer1/based/TaikoData.sol index f9a2a59cb7c..893e6c7cb3c 100644 --- a/packages/protocol/contracts/layer1/based/TaikoData.sol +++ b/packages/protocol/contracts/layer1/based/TaikoData.sol @@ -191,9 +191,7 @@ library TaikoData { uint64 numBlocks; uint64 lastVerifiedBlockId; bool provingPaused; - uint8 __reservedB1; - uint16 __reservedB2; - uint32 __reservedB3; + uint56 lastProposedIn; uint64 lastUnpausedAt; } diff --git a/packages/protocol/contracts/layer1/based/TaikoL1.sol b/packages/protocol/contracts/layer1/based/TaikoL1.sol index a9c1e5054bb..f96916cbd09 100644 --- a/packages/protocol/contracts/layer1/based/TaikoL1.sol +++ b/packages/protocol/contracts/layer1/based/TaikoL1.sol @@ -58,10 +58,6 @@ contract TaikoL1 is EssentialContract, ITaikoL1, TaikoEvents { /// @notice This function shall be called by previously deployed contracts. function init2() external onlyOwner reinitializer(2) { - // reset some previously used slots for future reuse - state.slotB.__reservedB1 = 0; - state.slotB.__reservedB2 = 0; - state.slotB.__reservedB3 = 0; state.__reserve1 = 0; } @@ -287,6 +283,12 @@ contract TaikoL1 is EssentialContract, ITaikoL1, TaikoEvents { return state.slotB.lastUnpausedAt; } + /// @notice Retrieves the ID of the L1 block where the most recent L2 block was proposed. + /// @return The ID of the Li block where the most recent block was proposed. + function lastProposedIn() external view returns (uint56) { + return state.slotB.lastProposedIn; + } + /// @inheritdoc ITaikoL1 function getConfig() public pure virtual returns (TaikoData.Config memory) { return TaikoData.Config({ diff --git a/packages/protocol/contracts/layer1/provers/ProverSet.sol b/packages/protocol/contracts/layer1/provers/ProverSet.sol index d78df93f2ff..ca7098d6a98 100644 --- a/packages/protocol/contracts/layer1/provers/ProverSet.sol +++ b/packages/protocol/contracts/layer1/provers/ProverSet.sol @@ -30,6 +30,7 @@ contract ProverSet is EssentialContract, IERC1271 { error INVALID_STATUS(); error PERMISSION_DENIED(); + error NOT_FIRST_PROPOSAL(); modifier onlyAuthorized() { require( @@ -81,6 +82,20 @@ contract ProverSet is EssentialContract, IERC1271 { LibAddress.sendEtherAndVerify(admin, _amount); } + /// @notice Proposes a block only when it is the first block proposal in the current L1 block. + function proposeBlockV2Conditionally( + bytes calldata _params, + bytes calldata _txList + ) + external + onlyProver + { + ITaikoL1 taiko = ITaikoL1(taikoL1()); + // Ensure this block is the first block proposed in the current L1 block. + require(taiko.lastProposedIn() != block.number, NOT_FIRST_PROPOSAL()); + taiko.proposeBlockV2(_params, _txList); + } + /// @notice Propose a Taiko block. function proposeBlockV2(bytes calldata _params, bytes calldata _txList) external onlyProver { ITaikoL1(taikoL1()).proposeBlockV2(_params, _txList); diff --git a/packages/protocol/test/layer1/based/MockTaikoL1.sol b/packages/protocol/test/layer1/based/MockTaikoL1.sol index 344a51ffc7f..2deb5cef503 100644 --- a/packages/protocol/test/layer1/based/MockTaikoL1.sol +++ b/packages/protocol/test/layer1/based/MockTaikoL1.sol @@ -62,5 +62,7 @@ contract MockTaikoL1 is ITaikoL1 { returns (TaikoData.TransitionState memory) { } + function lastProposedIn() external view returns (uint56) { } + function getConfig() external pure virtual returns (TaikoData.Config memory) { } }