Skip to content

Commit

Permalink
feat(protocol): support delayed forced inclusion of txs (#18883)
Browse files Browse the repository at this point in the history
Co-authored-by: dantaik <dantaik@users.noreply.github.com>
Co-authored-by: xiaodino <ruby@taiko.xyz>
Co-authored-by: xiaodino <xiaodino@users.noreply.github.com>
Co-authored-by: David <david@taiko.xyz>
Co-authored-by: davidtaikocha <davidtaikocha@users.noreply.github.com>
Co-authored-by: Anshu Jalan <anshujalan206@gmail.com>
Co-authored-by: AnshuJalan <AnshuJalan@users.noreply.github.com>
Co-authored-by: davidtaikocha <104078303+davidtaikocha@users.noreply.github.com>
Co-authored-by: smtmfft <99081233+smtmfft@users.noreply.github.com>
Co-authored-by: smtmfft <smtmfft@users.noreply.github.com>
Co-authored-by: maskpp <maskpp266@gmail.com>
Co-authored-by: jeff <113397187+cyberhorsey@users.noreply.github.com>
Co-authored-by: Gavin Yu <gavin@taiko.xyz>
Co-authored-by: YoGhurt111 <23009382+YoGhurt111@users.noreply.github.com>
Co-authored-by: Jeffery Walsh <cyberhorsey@gmail.com>
  • Loading branch information
16 people authored Feb 11, 2025
1 parent cdeadc0 commit a244be2
Show file tree
Hide file tree
Showing 21 changed files with 820 additions and 69 deletions.
140 changes: 140 additions & 0 deletions packages/protocol/contract_layout_layer1.md
Original file line number Diff line number Diff line change
Expand Up @@ -1171,3 +1171,143 @@
╰---------------+-------------+------+--------+-------+--------------------------------------------------------╯


## TaikoWrapper

╭-----------------------------+-------------+------+--------+-------+-----------------------------------------------------------------╮
| Name | Type | Slot | Offset | Bytes |
+=====================================================================================================================================+
| _initialized | uint8 | 0 | 0 | 1 |
|
| _initializing | bool | 0 | 1 | 1 |
|
| __gap | uint256[50] | 1 | 0 | 1600 |
|
| _owner | address | 51 | 0 | 20 |
|
| __gap | uint256[49] | 52 | 0 | 1568 |
|
| _pendingOwner | address | 101 | 0 | 20 |
|
| __gap | uint256[49] | 102 | 0 | 1568 |
|
| __gapFromOldAddressResolver | uint256[50] | 151 | 0 | 1600 |
|
| __reentry | uint8 | 201 | 0 | 1 |
|
| __paused | uint8 | 201 | 1 | 1 |
|
| __gap | uint256[49] | 202 | 0 | 1568 |
|
| __gap | uint256[50] | 251 | 0 | 1600 |
╰-----------------------------+-------------+------+--------+-------+-----------------------------------------------------------------╯


## ForcedInclusionStore

╭-----------------------------+------------------------------------------------------------------+------+--------+-------+---------------------------------------------------------------------------------╮
| Name | Type | Slot | Offset | Bytes |
+==========================================================================================================================================================================================================+
| _initialized | uint8 | 0 | 0 | 1 |
|
| _initializing | bool | 0 | 1 | 1 |
|
| __gap | uint256[50] | 1 | 0 | 1600 |
|
| _owner | address | 51 | 0 | 20 |
|
| __gap | uint256[49] | 52 | 0 | 1568 |
|
| _pendingOwner | address | 101 | 0 | 20 |
|
| __gap | uint256[49] | 102 | 0 | 1568 |
|
| __gapFromOldAddressResolver | uint256[50] | 151 | 0 | 1600 |
|
| __reentry | uint8 | 201 | 0 | 1 |
|
| __paused | uint8 | 201 | 1 | 1 |
|
| __gap | uint256[49] | 202 | 0 | 1568 |
|
| queue | mapping(uint256 => struct IForcedInclusionStore.ForcedInclusion) | 251 | 0 | 32 |
|
| head | uint64 | 252 | 0 | 8 |
|
| tail | uint64 | 252 | 8 | 8 |
|
| lastProcessedAtBatchId | uint64 | 252 | 16 | 8 |
|
| __reserved1 | uint64 | 252 | 24 | 8 |
|
| __gap | uint256[48] | 253 | 0 | 1536 |
╰-----------------------------+------------------------------------------------------------------+------+--------+-------+---------------------------------------------------------------------------------╯


## PreconfRouter

╭-----------------------------+-------------+------+--------+-------+---------------------------------------------------------------╮
| Name | Type | Slot | Offset | Bytes |
+===================================================================================================================================+
| _initialized | uint8 | 0 | 0 | 1 |
|
| _initializing | bool | 0 | 1 | 1 |
|
| __gap | uint256[50] | 1 | 0 | 1600 |
|
| _owner | address | 51 | 0 | 20 |
|
| __gap | uint256[49] | 52 | 0 | 1568 |
|
| _pendingOwner | address | 101 | 0 | 20 |
|
| __gap | uint256[49] | 102 | 0 | 1568 |
|
| __gapFromOldAddressResolver | uint256[50] | 151 | 0 | 1600 |
|
| __reentry | uint8 | 201 | 0 | 1 |
|
| __paused | uint8 | 201 | 1 | 1 |
|
| __gap | uint256[49] | 202 | 0 | 1568 |
|
| __gap | uint256[50] | 251 | 0 | 1600 |
╰-----------------------------+-------------+------+--------+-------+---------------------------------------------------------------╯


## PreconfWhitelist

╭-----------------------------+-----------------------------+------+--------+-------+---------------------------------------------------------------------╮
| Name | Type | Slot | Offset | Bytes |
+=========================================================================================================================================================+
| _initialized | uint8 | 0 | 0 | 1 |
|
| _initializing | bool | 0 | 1 | 1 |
|
| __gap | uint256[50] | 1 | 0 | 1600 |
|
| _owner | address | 51 | 0 | 20 |
|
| __gap | uint256[49] | 52 | 0 | 1568 |
|
| _pendingOwner | address | 101 | 0 | 20 |
|
| __gap | uint256[49] | 102 | 0 | 1568 |
|
| __gapFromOldAddressResolver | uint256[50] | 151 | 0 | 1600 |
|
| __reentry | uint8 | 201 | 0 | 1 |
|
| __paused | uint8 | 201 | 1 | 1 |
|
| __gap | uint256[49] | 202 | 0 | 1568 |
|
| operatorCount | uint256 | 251 | 0 | 32 |
|
| isOperator | mapping(address => bool) | 252 | 0 | 32 |
|
| operatorIndexToOperator | mapping(uint256 => address) | 253 | 0 | 32 |
|
| __gap | uint256[47] | 254 | 0 | 1504 |
╰-----------------------------+-----------------------------+------+--------+-------+---------------------------------------------------------------------╯


23 changes: 23 additions & 0 deletions packages/protocol/contracts/layer1/based/IProposeBatch.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import "./ITaikoInbox.sol";

/// @title IProposeBatch
/// @notice This interface defines the proposeBatch function that is also part of the ITaikoInbox
/// interface.
/// @custom:security-contact security@taiko.xyz
interface IProposeBatch {
/// @notice Proposes a batch of blocks.
/// @param _params ABI-encoded parameters.
/// @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 mmetadata of the proposed batch.
function proposeBatch(
bytes calldata _params,
bytes calldata _txList
)
external
returns (ITaikoInbox.BatchInfo memory info_, ITaikoInbox.BatchMetadata memory meta_);
}
6 changes: 3 additions & 3 deletions packages/protocol/contracts/layer1/based/ITaikoInbox.sol
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ interface ITaikoInbox {
error MsgValueNotZero();
error NoBlocksToProve();
error NotFirstProposal();
error NotInboxOperator();
error NotInboxWrapper();
error ParentMetaHashMismatch();
error SameTransition();
error SignalNotSent();
Expand All @@ -293,7 +293,7 @@ interface ITaikoInbox {
error ZeroAnchorBlockHash();

/// @notice Proposes a batch of blocks.
/// @param _params ABI-encoded BlockParams.
/// @param _params ABI-encoded parameters.
/// @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.
Expand All @@ -303,7 +303,7 @@ interface ITaikoInbox {
bytes calldata _txList
)
external
returns (BatchInfo memory info_, BatchMetadata memory meta_);
returns (ITaikoInbox.BatchInfo memory info_, ITaikoInbox.BatchMetadata memory meta_);

/// @notice Proves state transitions for multiple batches with a single aggregated proof.
/// @param _params ABI-encoded parameter containing:
Expand Down
23 changes: 13 additions & 10 deletions packages/protocol/contracts/layer1/based/TaikoInbox.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import "src/shared/libs/LibStrings.sol";
import "src/shared/signal/ISignalService.sol";
import "src/layer1/verifiers/IVerifier.sol";
import "./ITaikoInbox.sol";
import "./IProposeBatch.sol";

/// @title TaikoInbox
/// @notice Acts as the inbox for the Taiko Alethia protocol, a simplified version of the
Expand All @@ -25,7 +26,7 @@ import "./ITaikoInbox.sol";
///
/// @dev Registered in the address resolver as "taiko".
/// @custom:security-contact security@taiko.xyz
abstract contract TaikoInbox is EssentialContract, ITaikoInbox, ITaiko {
abstract contract TaikoInbox is EssentialContract, ITaikoInbox, IProposeBatch, ITaiko {
using LibMath for uint256;
using SafeERC20 for IERC20;

Expand All @@ -51,6 +52,7 @@ abstract contract TaikoInbox is EssentialContract, ITaikoInbox, ITaiko {
bytes calldata _txList
)
public
override(ITaikoInbox, IProposeBatch)
nonReentrant
returns (BatchInfo memory info_, BatchMetadata memory meta_)
{
Expand All @@ -68,15 +70,15 @@ abstract contract TaikoInbox is EssentialContract, ITaikoInbox, ITaiko {
BatchParams memory params = abi.decode(_params, (BatchParams));

{
address operator = resolve(LibStrings.B_INBOX_OPERATOR, true);
if (operator == address(0)) {
address wrapper = resolve(LibStrings.B_INBOX_WRAPPER, true);
if (wrapper == address(0)) {
require(params.proposer == address(0), CustomProposerNotAllowed());
params.proposer = msg.sender;

// blob hashes are only accepted if the caller is trusted.
require(params.blobParams.blobHashes.length == 0, InvalidBlobParams());
} else {
require(msg.sender == operator, NotInboxOperator());
require(msg.sender == wrapper, NotInboxWrapper());
require(params.proposer != address(0), CustomProposerMissing());
}

Expand All @@ -95,10 +97,8 @@ abstract contract TaikoInbox is EssentialContract, ITaikoInbox, ITaiko {
if (params.blobParams.blobHashes.length == 0) {
require(params.blobParams.numBlobs != 0, BlobNotSpecified());
} else {
require(
params.blobParams.numBlobs == 0 && params.blobParams.firstBlobIndex == 0,
InvalidBlobParams()
);
require(params.blobParams.numBlobs == 0, InvalidBlobParams());
require(params.blobParams.firstBlobIndex == 0, InvalidBlobParams());
}
}

Expand Down Expand Up @@ -340,7 +340,9 @@ abstract contract TaikoInbox is EssentialContract, ITaikoInbox, ITaiko {
external
onlyOwner
{
require(_blockHash != 0 && _parentHash != 0 && _stateRoot != 0, InvalidParams());
require(_blockHash != 0, InvalidParams());
require(_parentHash != 0, InvalidParams());
require(_stateRoot != 0, InvalidParams());
require(_batchId > state.stats2.lastVerifiedBatchId, BatchVerified());

Config memory config = pacayaConfig();
Expand Down Expand Up @@ -425,7 +427,8 @@ abstract contract TaikoInbox is EssentialContract, ITaikoInbox, ITaiko {
uint256 slot = _batchId % config.batchRingBufferSize;
Batch storage batch = state.batches[slot];
require(batch.batchId == _batchId, BatchNotFound());
require(_tid != 0 && _tid < batch.nextTransitionId, TransitionNotFound());
require(_tid != 0, TransitionNotFound());
require(_tid < batch.nextTransitionId, TransitionNotFound());
return state.transitions[slot][_tid];
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

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
/// @dev A contract for storing and managing forced inclusion requests. Forced inclusions allow
/// users to pay a fee to ensure their transactions are included in a block. The contract maintains
/// a FIFO queue of inclusion requests.
/// @custom:security-contact
contract ForcedInclusionStore is EssentialContract, IForcedInclusionStore {
using LibAddress for address;
using LibMath for uint256;

uint8 public immutable inclusionDelay; // measured in the number of batches
uint64 public immutable feeInGwei;

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

uint256[48] private __gap;

constructor(
address _resolver,
uint8 _inclusionDelay,
uint64 _feeInGwei
)
EssentialContract(_resolver)
{
require(_inclusionDelay != 0, InvalidParams());
require(_feeInGwei != 0, InvalidParams());

inclusionDelay = _inclusionDelay;
feeInGwei = _feeInGwei;
}

function init(address _owner) external initializer {
__Essential_init(_owner);
}

function storeForcedInclusion(
uint8 blobIndex,
uint32 blobByteOffset,
uint32 blobByteSize
)
external
payable
nonReentrant
{
bytes32 blobHash = _blobHash(blobIndex);
require(blobHash != bytes32(0), BlobNotFound());
require(msg.value == feeInGwei * 1 gwei, IncorrectFee());

ForcedInclusion memory inclusion = ForcedInclusion({
blobHash: blobHash,
feeInGwei: uint64(msg.value / 1 gwei),
createdAtBatchId: _nextBatchId(),
blobByteOffset: blobByteOffset,
blobByteSize: blobByteSize
});

queue[tail++] = inclusion;

emit ForcedInclusionStored(inclusion);
}

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

inclusion_ = inclusion;

lastProcessedAtBatchId = _nextBatchId();

unchecked {
delete queue[head++];
_feeRecipient.sendEtherAndVerify(inclusion_.feeInGwei * 1 gwei);
}
emit ForcedInclusionConsumed(inclusion_);
}

function getForcedInclusion(uint256 index) external view returns (ForcedInclusion memory) {
require(index >= head, InvalidIndex());
require(index < tail, InvalidIndex());
return queue[index];
}

function getOldestForcedInclusionDeadline() public view returns (uint256) {
if (head == tail) return type(uint256).max;

ForcedInclusion storage inclusion = queue[head];
if (inclusion.createdAtBatchId == 0) return type(uint256).max;

unchecked {
return uint256(lastProcessedAtBatchId).max(inclusion.createdAtBatchId) + inclusionDelay;
}
}

function isOldestForcedInclusionDue() external view returns (bool) {
uint256 deadline = getOldestForcedInclusionDeadline();
return deadline != type(uint256).max && _nextBatchId() >= deadline;
}

// @dev Override this function for easier testing blobs
function _blobHash(uint8 blobIndex) internal view virtual returns (bytes32) {
return blobhash(blobIndex);
}

function _nextBatchId() private view returns (uint64) {
return ITaikoInbox(resolve(LibStrings.B_TAIKO, false)).getStats2().numBatches;
}
}
Loading

0 comments on commit a244be2

Please sign in to comment.