-
Notifications
You must be signed in to change notification settings - Fork 2.2k
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): support delayed forced inclusion of txs #18826
Draft
dantaik
wants to merge
43
commits into
pacaya_fork
Choose a base branch
from
forced_tx_inclusion
base: pacaya_fork
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
43 commits
Select commit
Hold shift + click to select a range
e4a7d10
emit blob hashes in event
dantaik 56a6e93
Update TaikoInbox.sol
dantaik c38afbb
refactor
dantaik 4bde037
Update TaikoInbox.sol
dantaik bd2202f
Update Layer1Test.sol
dantaik e723138
BatchInfo
dantaik 5a5f2b1
more
dantaik 1266339
fix
dantaik 8519a57
fix
dantaik 24d0029
Update TaikoInbox.sol
dantaik 34ea458
forge fmt & update contract layout tables
dantaik 267719e
reorder
dantaik 08356e4
Update TaikoInbox.sol
dantaik 321002d
Update ITaikoInbox.sol
dantaik 19e00ab
more
dantaik 5a59c94
more
dantaik 5b39351
more
dantaik 95df593
Update ITaikoInbox.sol
dantaik b27e27d
Merge branch 'pacaya_fork' into emit_blob_hashes_in_event
dantaik 40dcfa8
show case an idea
dantaik 84d4493
Update TaikoInboxWithForcedTxInclusion.sol
dantaik 69848fe
split
dantaik e1290d4
Update TaikoInboxWithForcedTxInclusion.sol
dantaik 6eaa54b
Update TaikoInboxWithForcedTxInclusion.sol
dantaik f38b07e
Update TaikoInbox.sol
dantaik fec04d5
forge fmt & update contract layout tables
dantaik 3095e13
more
dantaik 48479b5
support blob hashes as parameters
dantaik cc6274b
Merge branch 'emit_blob_hashes_in_event' into forced_tx_inclusion
dantaik e532efe
removed 1 TODO
dantaik 022849d
Update TaikoInboxWithForcedTxInclusion.sol
dantaik 7a8e392
Merge branch 'pacaya_fork' into emit_blob_hashes_in_event
dantaik da354f5
Merge branch 'emit_blob_hashes_in_event' into forced_tx_inclusion
dantaik f9885e9
Update ITaikoInbox.sol
dantaik e5bf3f1
Merge branch 'pacaya_fork' into forced_tx_inclusion
dantaik 40b0db8
feat(protocol): forced inclusion store (#18829)
cyberhorsey 8fc98eb
merge pacaya_fork branch, update resolvers
cyberhorsey cf15174
add the forced inclusion inbox as a WL proposer
cyberhorsey b96c0ef
Merge branch 'pacaya_fork' into forced_tx_inclusion
dantaik c64a03b
fee => feeInGwei
dantaik f9fa4b4
move new files to a new dir
dantaik 06225d8
Update gen-layouts.sh
dantaik 13113d8
Merge branch 'pacaya_fork' into forced_tx_inclusion
dantaik File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
106 changes: 106 additions & 0 deletions
106
packages/protocol/contracts/layer1/forced-inclusion/ForcedInclusionInbox.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.24; | ||
|
||
import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; | ||
import "src/shared/common/EssentialContract.sol"; | ||
import "src/shared/based/ITaiko.sol"; | ||
import "src/shared/libs/LibMath.sol"; | ||
import "src/shared/libs/LibNetwork.sol"; | ||
import "src/shared/libs/LibStrings.sol"; | ||
import "src/shared/signal/ISignalService.sol"; | ||
import "src/layer1/verifiers/IVerifier.sol"; | ||
import "src/layer1/based/TaikoInbox.sol"; | ||
import "./ForcedInclusionStore.sol"; | ||
|
||
/// @title ForcedInclusionInbox | ||
/// @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: | ||
/// 1. **Fee-Based Request Prioritization**: | ||
/// - Proposers can selectively fulfill pending requests based on transaction fees. | ||
/// - Requests not yet due can be processed earlier if fees are attractive, incentivizing timely | ||
/// execution. | ||
/// | ||
/// 2. **Rate Limit Control**: | ||
/// - A rate-limiting mechanism ensures a minimum interval of 12*N seconds between request | ||
/// fulfillments. | ||
/// - Prevents proposers from being overwhelmed during high request volume, ensuring system | ||
/// stability. | ||
/// | ||
/// 3. **Calldata and Blob Support**: | ||
/// - Supports both calldata and blobs in the transaction list. | ||
/// | ||
/// 4. **Gas-Efficient Request Storage**: | ||
/// - Avoids storing full request data in contract storage. | ||
/// - Saves only the request hash and its timestamp. | ||
/// - Leverages Ethereum events to store request details off-chain. | ||
/// - Proposers can reconstruct requests as needed, minimizing on-chain storage and gas | ||
/// consumption. | ||
/// | ||
/// @custom:security-contact [email protected] | ||
contract ForcedInclusionInbox is EssentialContract { | ||
using LibMath for uint256; | ||
|
||
event ForcedInclusionProcessed(IForcedInclusionStore.ForcedInclusion); | ||
|
||
uint16 public constant MAX_FORCED_TXS_PER_FORCED_INCLUSION = 512; | ||
uint256[50] private __gap; | ||
|
||
constructor(address _resolver) EssentialContract(_resolver) { } | ||
|
||
function init(address _owner) external initializer { | ||
__Essential_init(_owner); | ||
} | ||
|
||
/// @notice Proposes a batch of blocks. | ||
/// @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. | ||
function proposeBatchWithForcedInclusion( | ||
bytes calldata _forcedInclusionParams, | ||
bytes calldata _params, | ||
bytes calldata _txList | ||
) | ||
external | ||
nonReentrant | ||
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 (inclusion.createdAt != 0) { | ||
ITaikoInbox.BatchParams memory params; | ||
|
||
if (_forcedInclusionParams.length != 0) { | ||
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 | ||
if (params.blocks.length == 0) { | ||
params.blocks = new ITaikoInbox.BlockParams[](1); | ||
} | ||
|
||
if (params.blocks[0].numTransactions < MAX_FORCED_TXS_PER_FORCED_INCLUSION) { | ||
params.blocks[0].numTransactions = MAX_FORCED_TXS_PER_FORCED_INCLUSION; | ||
} | ||
|
||
params.blobParams.blobHashes = new bytes32[](1); | ||
params.blobParams.blobHashes[0] = inclusion.blobHash; | ||
params.blobParams.byteOffset = inclusion.blobByteOffset; | ||
params.blobParams.byteSize = inclusion.blobByteSize; | ||
|
||
inbox.proposeBatch(abi.encode(params), ""); | ||
emit ForcedInclusionProcessed(inclusion); | ||
} | ||
} | ||
} |
99 changes: 99 additions & 0 deletions
99
packages/protocol/contracts/layer1/forced-inclusion/ForcedInclusionStore.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
// 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 "./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; | ||
|
||
uint256 private constant SECONDS_PER_BLOCK = 12; | ||
|
||
uint256 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; | ||
|
||
uint256[48] private __gap; | ||
|
||
constructor( | ||
address _resolver, | ||
uint256 _inclusionDelay, | ||
uint64 _feeInGwei | ||
) | ||
EssentialContract(_resolver) | ||
{ | ||
require(_inclusionDelay != 0 && _inclusionDelay % SECONDS_PER_BLOCK == 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), | ||
createdAt: uint64(block.timestamp), | ||
blobByteOffset: blobByteOffset, | ||
blobByteSize: blobByteSize | ||
}); | ||
|
||
queue[tail++] = inclusion; | ||
|
||
emit ForcedInclusionStored(inclusion); | ||
} | ||
|
||
function consumeForcedInclusion(address _feeRecipient) | ||
external | ||
nonReentrant | ||
onlyFromNamed(LibStrings.B_TAIKO_FORCED_INCLUSION_INBOX) | ||
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]; | ||
|
||
if (inclusion.createdAt != 0 && block.timestamp >= inclusionDelay + inclusion.createdAt) { | ||
inclusion_ = inclusion; | ||
_feeRecipient.sendEtherAndVerify(inclusion.feeInGwei * 1 gwei); | ||
delete queue[_head]; | ||
head = _head + 1; | ||
emit ForcedInclusionConsumed(inclusion); | ||
} | ||
} | ||
|
||
// @dev Override this function for easier testing blobs | ||
function _blobHash(uint8 blobIndex) internal view virtual returns (bytes32) { | ||
return blobhash(blobIndex); | ||
} | ||
} |
27 changes: 27 additions & 0 deletions
27
packages/protocol/contracts/layer1/forced-inclusion/IForcedInclusionInbox.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.24; | ||
|
||
import "src/layer1/based/TaikoInbox.sol"; | ||
import "./ForcedInclusionStore.sol"; | ||
|
||
/// @title IForcedInclusionInbox | ||
/// @custom:security-contact [email protected] | ||
interface IForcedInclusionInbox { | ||
event ForcedInclusionProcessed(IForcedInclusionStore.ForcedInclusion); | ||
|
||
/// @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, | ||
bytes calldata _txList | ||
) | ||
external | ||
returns (ITaikoInbox.BatchInfo memory info_, ITaikoInbox.BatchMetadata memory meta_); | ||
} |
47 changes: 47 additions & 0 deletions
47
packages/protocol/contracts/layer1/forced-inclusion/IForcedInclusionStore.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.24; | ||
|
||
/// @title IForcedInclusionStore | ||
/// @custom:security-contact [email protected] | ||
interface IForcedInclusionStore { | ||
/// @dev Error thrown when a blob is not found. | ||
error BlobNotFound(); | ||
/// @dev Error thrown when the parameters are invalid. | ||
error InvalidParams(); | ||
/// @dev Error thrown when the fee is incorrect. | ||
error IncorrectFee(); | ||
|
||
/// @dev Event emitted when a forced inclusion is stored. | ||
event ForcedInclusionStored(ForcedInclusion forcedInclusion); | ||
/// @dev Event emitted when a forced inclusion is consumed. | ||
event ForcedInclusionConsumed(ForcedInclusion forcedInclusion); | ||
|
||
struct ForcedInclusion { | ||
bytes32 blobHash; | ||
uint64 feeInGwei; | ||
uint64 createdAt; | ||
uint32 blobByteOffset; | ||
uint32 blobByteSize; | ||
} | ||
|
||
/// @dev Consume a forced inclusion request. | ||
/// The inclusion request must be marked as processed and the priority fee must be paid to the | ||
/// caller. | ||
/// @return inclusion_ The forced inclusion request. | ||
function consumeForcedInclusion(address _feeRecipient) | ||
external | ||
returns (ForcedInclusion memory); | ||
|
||
/// @dev Store a forced inclusion request. | ||
/// The priority fee must be paid to the contract. | ||
/// @param blobIndex The index of the blob that contains the transaction data. | ||
/// @param blobByteOffset The byte offset in the blob | ||
/// @param blobByteSize The size of the blob in bytes | ||
function storeForcedInclusion( | ||
uint8 blobIndex, | ||
uint32 blobByteOffset, | ||
uint32 blobByteSize | ||
) | ||
external | ||
payable; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,12 @@ | ||
// SPDX-License-Identifier: MIT | ||
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 "../iface/IPreconfRouter.sol"; | ||
import "../iface/IPreconfWhitelist.sol"; | ||
import "src/layer1/based/ITaikoInbox.sol"; | ||
import "src/shared/libs/LibStrings.sol"; | ||
import "src/shared/common/EssentialContract.sol"; | ||
|
||
/// @title PreconfRouter | ||
/// @custom:security-contact [email protected] | ||
|
@@ -20,7 +21,7 @@ contract PreconfRouter is EssentialContract, IPreconfRouter { | |
|
||
/// @inheritdoc IPreconfRouter | ||
function proposePreconfedBlocks( | ||
bytes calldata, | ||
bytes calldata _forcedInclusionParams, | ||
bytes calldata _batchParams, | ||
bytes calldata _batchTxList | ||
) | ||
|
@@ -32,9 +33,18 @@ contract PreconfRouter is EssentialContract, IPreconfRouter { | |
IPreconfWhitelist(resolve(LibStrings.B_PRECONF_WHITELIST, false)).getOperatorForEpoch(); | ||
require(msg.sender == selectedOperator, NotTheOperator()); | ||
|
||
// Call the proposeBatch function on the TaikoInbox | ||
address taikoInbox = resolve(LibStrings.B_TAIKO, false); | ||
(, meta_) = ITaikoInbox(taikoInbox).proposeBatch(_batchParams, _batchTxList); | ||
// check if we have a forced inclusion inbox | ||
address forcedInclusionInbox = resolve(LibStrings.B_TAIKO_FORCED_INCLUSION_INBOX, true); | ||
if (forcedInclusionInbox == 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( | ||
_forcedInclusionParams, _batchParams, _batchTxList | ||
); | ||
} | ||
|
||
// Verify that the sender had set itself as the proposer | ||
require(meta_.proposer == msg.sender, ProposerIsNotTheSender()); | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems that this is still only callable by the whitelisted preconfers right? If so, would say again that forced inclusion != inclusion. Wrote about that here before: #18815 (comment)
Inclusion can only be guaranteed when block proposing is made permissionless again (at least for the forced blocks, but may as well allow any block to be proposed when that happens because why not).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with Brecht's point (above) — in the current implementation, forced inclusion can only occur if there is at least one preconfer willing to propose blocks when it becomes the current preconfer.
To address this, one potential solution is to enable fully permissionless block proposing if no blocks have been proposed within the last N seconds (though some details still need to be ironed out). While this approach is related to forced inclusion, I believe it aligns more closely with the core protocol or preconfirmation design. Even without forced inclusion, this feature would be a valuable addition to the preconfirmation mechanism. Without it, if there are only a few preconfers and they all stop proposing blocks due to profitability issues, the chain could come to a halt.