Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(protocol): use num proposals to measure forced inclusion deadline #18842

Merged
merged 11 commits into from
Feb 1, 2025
4 changes: 4 additions & 0 deletions packages/protocol/contract_layout_layer1.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,7 @@

## ForkRouter

## contracts/layer1/forced-inclusion/TaikoWrapper

## contracts/layer1/forced-inclusion/ForcedInclusionStore

Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import "src/shared/common/EssentialContract.sol";
import "src/shared/libs/LibMath.sol";
import "src/shared/libs/LibAddress.sol";
import "src/shared/libs/LibStrings.sol";
import "src/layer1/based/ITaikoInbox.sol";
import "./IForcedInclusionStore.sol";

/// @title ForcedInclusionStore
Expand All @@ -19,19 +20,20 @@ contract ForcedInclusionStore is EssentialContract, IForcedInclusionStore {

uint256 private constant SECONDS_PER_BLOCK = 12;

uint256 public immutable inclusionDelay;
uint8 public immutable inclusionDelay;
uint64 public immutable feeInGwei;

mapping(uint256 id => ForcedInclusion inclusion) public queue; // slot 1
uint64 public head; // slot 2
uint64 public tail;
uint128 private __reserved1;
uint64 public lastProcessedAtBatchId;
uint64 private __reserved1;

uint256[48] private __gap;

constructor(
address _resolver,
uint256 _inclusionDelay,
uint8 _inclusionDelay,
uint64 _feeInGwei
)
EssentialContract(_resolver)
Expand Down Expand Up @@ -60,10 +62,12 @@ contract ForcedInclusionStore is EssentialContract, IForcedInclusionStore {
require(blobHash != bytes32(0), BlobNotFound());
require(msg.value == feeInGwei * 1 gwei, IncorrectFee());

ITaikoInbox inbox = ITaikoInbox(resolve(LibStrings.B_TAIKO, false));

ForcedInclusion memory inclusion = ForcedInclusion({
blobHash: blobHash,
feeInGwei: uint64(msg.value / 1 gwei),
createdAt: uint64(block.timestamp),
createdAtBatchId: inbox.getStats2().numBatches,
blobByteOffset: blobByteOffset,
blobByteSize: blobByteSize
});
Expand All @@ -73,23 +77,47 @@ contract ForcedInclusionStore is EssentialContract, IForcedInclusionStore {
emit ForcedInclusionStored(inclusion);
}

function consumeForcedInclusion(address _feeRecipient)
function consumeOldestForcedInclusion(address _feeRecipient)
external
nonReentrant
onlyFromNamed(LibStrings.B_TAIKO_FORCED_INCLUSION_INBOX)
onlyFromNamed(LibStrings.B_TAIKO_WRAPPER)
returns (ForcedInclusion memory inclusion_)
{
// we only need to check the first one, since it will be the oldest.
uint64 _head = head;
ForcedInclusion storage inclusion = queue[_head];
require(inclusion.createdAtBatchId != 0, NoForcedInclusionFound());

ITaikoInbox inbox = ITaikoInbox(resolve(LibStrings.B_TAIKO, false));

inclusion_ = inclusion;
delete queue[_head];

if (inclusion.createdAt != 0 && block.timestamp >= inclusionDelay + inclusion.createdAt) {
inclusion_ = inclusion;
_feeRecipient.sendEtherAndVerify(inclusion.feeInGwei * 1 gwei);
delete queue[_head];
unchecked {
lastProcessedAtBatchId = inbox.getStats2().numBatches;
head = _head + 1;
emit ForcedInclusionConsumed(inclusion);
}

emit ForcedInclusionConsumed(inclusion_);
_feeRecipient.sendEtherAndVerify(inclusion_.feeInGwei * 1 gwei);
}

function getForcedInclusion(uint256 index) external view returns (ForcedInclusion memory) {
return queue[index];
}

function getOldestForcedInclusionDeadline() public view returns (uint256) {
unchecked {
ForcedInclusion storage inclusion = queue[head];
return inclusion.createdAtBatchId == 0
? type(uint64).max
: uint256(lastProcessedAtBatchId).max(inclusion.createdAtBatchId) + inclusionDelay;
}
}

function isOldestForcedInclusionDue() external view returns (bool) {
ITaikoInbox inbox = ITaikoInbox(resolve(LibStrings.B_TAIKO, false));
return inbox.getStats2().numBatches >= getOldestForcedInclusionDeadline();
}

// @dev Override this function for easier testing blobs
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ interface IForcedInclusionStore {
/// @dev Error thrown when the fee is incorrect.
error IncorrectFee();

error NoForcedInclusionFound();

/// @dev Event emitted when a forced inclusion is stored.
event ForcedInclusionStored(ForcedInclusion forcedInclusion);
/// @dev Event emitted when a forced inclusion is consumed.
Expand All @@ -19,16 +21,30 @@ interface IForcedInclusionStore {
struct ForcedInclusion {
bytes32 blobHash;
uint64 feeInGwei;
uint64 createdAt;
uint64 createdAtBatchId;
uint32 blobByteOffset;
uint32 blobByteSize;
}

/// @dev Retrieve a forced inclusion request by its index.
/// @param index The index of the forced inclusion request in the queue.
/// @return The forced inclusion request at the specified index.
function getForcedInclusion(uint256 index) external view returns (ForcedInclusion memory);

/// @dev Get the deadline for the oldest forced inclusion.
/// @return The deadline for the oldest forced inclusion.
function getOldestForcedInclusionDeadline() external view returns (uint256);

/// @dev Check if the oldest forced inclusion is due.
/// @return True if the oldest forced inclusion is due, false otherwise.
function isOldestForcedInclusionDue() external view returns (bool);

/// @dev Consume a forced inclusion request.
/// The inclusion request must be marked as processed and the priority fee must be paid to the
/// caller.
/// @param _feeRecipient The address to receive the priority fee.
/// @return inclusion_ The forced inclusion request.
function consumeForcedInclusion(address _feeRecipient)
function consumeOldestForcedInclusion(address _feeRecipient)
external
returns (ForcedInclusion memory);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import "src/layer1/verifiers/IVerifier.sol";
import "src/layer1/based/TaikoInbox.sol";
import "./ForcedInclusionStore.sol";

/// @title ForcedInclusionInbox
/// @title TaikoWrapper
/// @dev This contract is part of a delayed inbox implementation to enforce the inclusion of
/// transactions.
/// The current design is a simplified and can be improved with the following ideas:
Expand All @@ -38,12 +38,18 @@ import "./ForcedInclusionStore.sol";
/// consumption.
///
/// @custom:security-contact [email protected]
contract ForcedInclusionInbox is EssentialContract {

contract TaikoWrapper is EssentialContract {
using LibMath for uint256;

/// @dev Event emitted when a forced inclusion is processed.
event ForcedInclusionProcessed(IForcedInclusionStore.ForcedInclusion);
/// @dev Error thrown when the oldest forced inclusion is due.

error OldestForcedInclusionDue();

uint16 public constant MAX_FORCED_TXS_PER_FORCED_INCLUSION = 512;

uint256[50] private __gap;

constructor(address _resolver) EssentialContract(_resolver) { }
Expand All @@ -52,12 +58,14 @@ contract ForcedInclusionInbox is EssentialContract {
__Essential_init(_owner);
}

/// @notice Proposes a batch of blocks.
/// @notice Proposes a batch of blocks with forced inclusion.
/// @param _forcedInclusionParams An optional ABI-encoded BlockParams for the forced inclusion
/// batch.
/// @param _params ABI-encoded BlockParams.
/// @param _txList The transaction list in calldata. If the txList is empty, blob will be used
/// for data availability.
/// @return info_ The info of the proposed batch.
/// @return meta_ The metadata of the proposed batch.
function proposeBatchWithForcedInclusion(
bytes calldata _forcedInclusionParams,
bytes calldata _params,
Expand All @@ -68,21 +76,18 @@ contract ForcedInclusionInbox is EssentialContract {
returns (ITaikoInbox.BatchInfo memory info_, ITaikoInbox.BatchMetadata memory meta_)
{
ITaikoInbox inbox = ITaikoInbox(resolve(LibStrings.B_TAIKO, false));
(info_, meta_) = inbox.proposeBatch(_params, _txList);

// Process the next forced inclusion.
IForcedInclusionStore store =
IForcedInclusionStore(resolve(LibStrings.B_FORCED_INCLUSION_STORE, false));

IForcedInclusionStore.ForcedInclusion memory inclusion =
store.consumeForcedInclusion(msg.sender);
if (_forcedInclusionParams.length == 0) {
require(!store.isOldestForcedInclusionDue(), OldestForcedInclusionDue());
} else {
IForcedInclusionStore.ForcedInclusion memory inclusion =
store.consumeOldestForcedInclusion(msg.sender);

if (inclusion.createdAt != 0) {
ITaikoInbox.BatchParams memory params;

if (_forcedInclusionParams.length != 0) {
params = abi.decode(_forcedInclusionParams, (ITaikoInbox.BatchParams));
}
ITaikoInbox.BatchParams memory params =
abi.decode(_forcedInclusionParams, (ITaikoInbox.BatchParams));

// Overwrite the batch params to have only 1 block and up to
// MAX_FORCED_TXS_PER_FORCED_INCLUSION transactions
Expand All @@ -102,5 +107,7 @@ contract ForcedInclusionInbox is EssentialContract {
inbox.proposeBatch(abi.encode(params), "");
emit ForcedInclusionProcessed(inclusion);
}

(info_, meta_) = inbox.proposeBatch(_params, _txList);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pragma solidity ^0.8.24;
import "src/shared/common/EssentialContract.sol";
import "src/shared/libs/LibStrings.sol";
import "src/layer1/based/TaikoInbox.sol";
import "src/layer1/forced-inclusion/IForcedInclusionInbox.sol";
import "src/layer1/forced-inclusion/TaikoWrapper.sol";
import "../iface/IPreconfRouter.sol";
import "../iface/IPreconfWhitelist.sol";

Expand Down Expand Up @@ -34,14 +34,14 @@ contract PreconfRouter is EssentialContract, IPreconfRouter {
require(msg.sender == selectedOperator, NotTheOperator());

// check if we have a forced inclusion inbox
address forcedInclusionInbox = resolve(LibStrings.B_TAIKO_FORCED_INCLUSION_INBOX, true);
if (forcedInclusionInbox == address(0)) {
address wrapper = resolve(LibStrings.B_TAIKO_WRAPPER, true);
if (wrapper == address(0)) {
// Call the proposeBatch function on the TaikoInbox
address taikoInbox = resolve(LibStrings.B_TAIKO, false);
(, meta_) = ITaikoInbox(taikoInbox).proposeBatch(_batchParams, _batchTxList);
} else {
// Call the proposeBatchWithForcedInclusion function on the ForcedInclusionInbox
(, meta_) = IForcedInclusionInbox(forcedInclusionInbox).proposeBatchWithForcedInclusion(
(, meta_) = TaikoWrapper(wrapper).proposeBatchWithForcedInclusion(
_forcedInclusionParams, _batchParams, _batchTxList
);
}
Expand Down
3 changes: 1 addition & 2 deletions packages/protocol/contracts/shared/libs/LibStrings.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,8 @@ library LibStrings {
bytes32 internal constant B_SGX_WATCHDOG = bytes32("sgx_watchdog");
bytes32 internal constant B_SIGNAL_SERVICE = bytes32("signal_service");
bytes32 internal constant B_TAIKO = bytes32("taiko");
bytes32 internal constant B_TAIKO_FORCED_INCLUSION_INBOX =
bytes32("taiko_forced_inclusion_inbox");
bytes32 internal constant B_TAIKO_TOKEN = bytes32("taiko_token");
bytes32 internal constant B_TAIKO_WRAPPER = bytes32("taiko_wrapper");
bytes32 internal constant B_WITHDRAWER = bytes32("withdrawer");
bytes32 internal constant H_SIGNAL_ROOT = keccak256("SIGNAL_ROOT");
bytes32 internal constant H_STATE_ROOT = keccak256("STATE_ROOT");
Expand Down
2 changes: 1 addition & 1 deletion packages/protocol/script/gen-layouts.sh
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ contracts_layer1=(
"contracts/layer1/team/TokenUnlock.sol:TokenUnlock"
"contracts/layer1/provers/ProverSet.sol:ProverSet"
"contracts/layer1/based/ForkRouter.sol:ForkRouter"
"contracts/layer1/forced-inclusion/ForcedInclusionInbox"
"contracts/layer1/forced-inclusion/TaikoWrapper"
"contracts/layer1/forced-inclusion/ForcedInclusionStore"
)

Expand Down
10 changes: 5 additions & 5 deletions packages/protocol/script/layer1/based/DeployProtocolOnL1.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import "src/layer1/devnet/verifiers/DevnetVerifier.sol";
import "src/layer1/mainnet/MainnetInbox.sol";
import "src/layer1/based/TaikoInbox.sol";
import "src/layer1/fork-router/ForkRouter.sol";
import "src/layer1/forced-inclusion/ForcedInclusionInbox.sol";
import "src/layer1/forced-inclusion/TaikoWrapper.sol";
import "src/layer1/forced-inclusion/ForcedInclusionStore.sol";
import "src/layer1/mainnet/multirollup/MainnetBridge.sol";
import "src/layer1/mainnet/multirollup/MainnetERC1155Vault.sol";
Expand Down Expand Up @@ -413,7 +413,7 @@ contract DeployProtocolOnL1 is DeployCapability {
impl: address(
new ForcedInclusionStore(
resolver,
vm.envUint("INCLUSION_WINDOW"),
uint8(vm.envUint("INCLUSION_WINDOW")),
uint64(vm.envUint("INCLUSION_FEE_IN_GWEI"))
)
),
Expand All @@ -422,9 +422,9 @@ contract DeployProtocolOnL1 is DeployCapability {
});

forcedInclusionInbox = deployProxy({
name: "taiko_forced_inclusion_inbox",
impl: address(new ForcedInclusionInbox(resolver)),
data: abi.encodeCall(ForcedInclusionInbox.init, (owner)),
name: "taiko_wrapper",
impl: address(new TaikoWrapper(resolver)),
data: abi.encodeCall(TaikoWrapper.init, (owner)),
registerTo: resolver
});

Expand Down
14 changes: 7 additions & 7 deletions packages/protocol/test/layer1/Layer1Test.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
pragma solidity ^0.8.24;

import "src/layer1/based/TaikoInbox.sol";
import "src/layer1/forced-inclusion/ForcedInclusionInbox.sol";
import "src/layer1/forced-inclusion/TaikoWrapper.sol";
import "src/layer1/forced-inclusion/ForcedInclusionStore.sol";
import "src/layer1/token/TaikoToken.sol";
import "src/layer1/verifiers/SgxVerifier.sol";
Expand Down Expand Up @@ -66,18 +66,18 @@ abstract contract Layer1Test is CommonTest {
);
}

function deployForcedInclusionInbox() internal returns (ForcedInclusionInbox) {
return ForcedInclusionInbox(
function deployForcedInclusionInbox() internal returns (TaikoWrapper) {
return TaikoWrapper(
deploy({
name: "taiko_forced_inclusion_inbox",
impl: address(new ForcedInclusionInbox(address(resolver))),
data: abi.encodeCall(ForcedInclusionInbox.init, (address(0)))
name: "taiko_wrapper",
impl: address(new TaikoWrapper(address(resolver))),
data: abi.encodeCall(TaikoWrapper.init, (address(0)))
})
);
}

function deployForcedInclusionStore(
uint256 inclusionDelay,
uint8 inclusionDelay,
uint64 feeInGwei,
address owner
)
Expand Down
Loading