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

Open
wants to merge 11 commits into
base: forced_tx_inclusion
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import "src/shared/signal/ISignalService.sol";
import "src/layer1/verifiers/IVerifier.sol";
import "src/layer1/based/TaikoInbox.sol";
import "./ForcedInclusionStore.sol";

import "./IForcedInclusionInbox.sol";
/// @title ForcedInclusionInbox
/// @dev This contract is part of a delayed inbox implementation to enforce the inclusion of
/// transactions.
Expand All @@ -38,12 +38,12 @@ import "./ForcedInclusionStore.sol";
/// consumption.
///
/// @custom:security-contact [email protected]
contract ForcedInclusionInbox is EssentialContract {
using LibMath for uint256;

event ForcedInclusionProcessed(IForcedInclusionStore.ForcedInclusion);
contract ForcedInclusionInbox is EssentialContract, IForcedInclusionInbox {
using LibMath for uint256;

uint16 public constant MAX_FORCED_TXS_PER_FORCED_INCLUSION = 512;

uint256[50] private __gap;

constructor(address _resolver) EssentialContract(_resolver) { }
Expand All @@ -68,21 +68,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 +99,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 @@ -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,19 @@ 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;

uint256[48] private __gap;

constructor(
address _resolver,
uint256 _inclusionDelay,
uint8 _inclusionDelay,
uint64 _feeInGwei
)
EssentialContract(_resolver)
Expand Down Expand Up @@ -60,10 +61,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,7 +76,7 @@ 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)
Expand All @@ -82,14 +85,34 @@ contract ForcedInclusionStore is EssentialContract, IForcedInclusionStore {
// 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 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
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ import "./ForcedInclusionStore.sol";
/// @title IForcedInclusionInbox
/// @custom:security-contact [email protected]
interface IForcedInclusionInbox {
/// @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();

/// @notice Proposes a batch of blocks with forced inclusion.
/// @param _forcedInclusionParams An optional ABI-encoded BlockParams for the forced inclusion
Expand Down
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,25 @@ interface IForcedInclusionStore {
struct ForcedInclusion {
bytes32 blobHash;
uint64 feeInGwei;
uint64 createdAt;
uint64 createdAtBatchId;
uint32 blobByteOffset;
uint32 blobByteSize;
}

/// @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 @@ -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 Down
2 changes: 1 addition & 1 deletion packages/protocol/test/layer1/Layer1Test.sol
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ abstract contract Layer1Test is CommonTest {
}

function deployForcedInclusionStore(
uint256 inclusionDelay,
uint8 inclusionDelay,
uint64 feeInGwei,
address owner
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import "src/layer1/forced-inclusion/ForcedInclusionStore.sol";
contract ForcedInclusionStoreForTest is ForcedInclusionStore {
constructor(
address _resolver,
uint256 _inclusionDelay,
uint8 _inclusionDelay,
uint64 _feeInGwei
)
ForcedInclusionStore(_resolver, _inclusionDelay, _feeInGwei)
Expand All @@ -21,7 +21,7 @@ contract ForcedInclusionStoreForTest is ForcedInclusionStore {
abstract contract ForcedInclusionStoreTestBase is CommonTest {
address internal storeOwner = Alice;
address internal whitelistedProposer = Alice;
uint64 internal constant inclusionDelay = 24 seconds;
uint8 internal constant inclusionDelay = 12;
uint64 internal constant feeInGwei = 0.001 ether / 1 gwei;

ForcedInclusionStore internal store;
Expand Down Expand Up @@ -106,13 +106,14 @@ contract ForcedInclusionStoreTest is ForcedInclusionStoreTestBase {
vm.warp(createdAt + inclusionDelay);

vm.prank(whitelistedProposer);
IForcedInclusionStore.ForcedInclusion memory consumed = store.consumeForcedInclusion(Bob);
IForcedInclusionStore.ForcedInclusion memory consumed =
store.consumeOldestForcedInclusion(Bob);

assertEq(consumed.blobHash, bytes32(uint256(1)));
assertEq(consumed.blobByteOffset, 0);
assertEq(consumed.blobByteSize, 1024);
assertEq(consumed.feeInGwei, _feeInGwei);
assertEq(consumed.createdAt, createdAt);
assertEq(consumed.createdAtBatchId, 0);
assertEq(Bob.balance, _feeInGwei * 1 gwei);
}

Expand All @@ -134,12 +135,13 @@ contract ForcedInclusionStoreTest is ForcedInclusionStoreTestBase {

vm.prank(Carol);
vm.expectRevert(EssentialContract.ACCESS_DENIED.selector);
store.consumeForcedInclusion(Bob);
store.consumeOldestForcedInclusion(Bob);
}

function test_storeConsumeForcedInclusion_noEligibleInclusion() public {
vm.prank(whitelistedProposer);
IForcedInclusionStore.ForcedInclusion memory inclusion = store.consumeForcedInclusion(Bob);
IForcedInclusionStore.ForcedInclusion memory inclusion =
store.consumeOldestForcedInclusion(Bob);
assertEq(inclusion.blobHash, bytes32(0));
assertEq(inclusion.blobByteOffset, 0);
assertEq(inclusion.blobByteSize, 0);
Expand All @@ -158,7 +160,8 @@ contract ForcedInclusionStoreTest is ForcedInclusionStoreTestBase {

vm.warp(block.timestamp + inclusionDelay - 1);
vm.prank(whitelistedProposer);
IForcedInclusionStore.ForcedInclusion memory inclusion = store.consumeForcedInclusion(Bob);
IForcedInclusionStore.ForcedInclusion memory inclusion =
store.consumeOldestForcedInclusion(Bob);
assertEq(inclusion.blobHash, bytes32(0));
assertEq(inclusion.blobByteOffset, 0);
assertEq(inclusion.blobByteSize, 0);
Expand Down