Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions contracts/contracts/l1/rollup/IRollup.sol
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,13 @@ interface IRollup {
BatchSignatureInput calldata batchSignatureInput
) external payable;

/// @notice Commit batch state when blob hash is already stored (recommit after revert without blob).
/// @dev Requires batchBlobVersionedHashes[nextBatchIndex] != 0.
function commitState(
BatchDataInput calldata batchDataInput,
BatchSignatureInput calldata batchSignatureInput
) external;

/// @notice Commit a batch with ZKP proof for permissionless submission.
/// @dev This function allows anyone to submit batches when the sequencer is offline or censoring.
///
Expand Down
42 changes: 37 additions & 5 deletions contracts/contracts/l1/rollup/Rollup.sol
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,10 @@ contract Rollup is IRollup, OwnableUpgradeable, PausableUpgradeable {
/// @dev After this period, anyone can submit batches if sequencers are offline or censoring.
uint256 public rollupDelayPeriod;

/// @notice Store blob versioned hash per batch index. Preserved across revertBatch so recommit can reuse.
/// @dev Placed after rollupDelayPeriod for upgrade-safe storage layout (forward compatibility).
mapping(uint256 batchIndex => bytes32 blobVersionedHash) public batchBlobVersionedHashes;
Comment thread
Kukoomomo marked this conversation as resolved.
Comment thread
Kukoomomo marked this conversation as resolved.

/**********************
* Function Modifiers *
**********************/
Expand Down Expand Up @@ -217,6 +221,7 @@ contract Rollup is IRollup, OwnableUpgradeable, PausableUpgradeable {

committedBatches[_batchIndex] = _batchHash;
batchDataStore[_batchIndex] = BatchData(block.timestamp, block.timestamp, 0, 0);
batchBlobVersionedHashes[_batchIndex] = BatchHeaderCodecV0.getBlobVersionedHash(memPtr);

committedStateRoots[_batchIndex] = _postStateRoot;
finalizedStateRoots[_batchIndex] = _postStateRoot;
Expand All @@ -232,20 +237,38 @@ contract Rollup is IRollup, OwnableUpgradeable, PausableUpgradeable {
BatchDataInput calldata batchDataInput,
BatchSignatureInput calldata batchSignatureInput
) external payable override onlyActiveStaker nonReqRevert whenNotPaused {
// check l1msg delay - sequencer must process L1 messages when delayed
if (
IL1MessageQueue(messageQueue).getFirstUnfinalizedMessageEnqueueTime() + rollupDelayPeriod < block.timestamp
) {
require(batchDataInput.numL1Messages > 0, "l1msg delay");
}
uint256 submitterBitmap = IL1Staking(l1StakingContract).getStakerBitmap(_msgSender());
_commitBatchWithBatchData(batchDataInput, batchSignatureInput, submitterBitmap);
_commitBatchWithBatchData(batchDataInput, batchSignatureInput, submitterBitmap, true);
}

/// @inheritdoc IRollup
/// @notice Commit batch state when blob hash is already stored (recommit after revert without blob).
function commitState(
BatchDataInput calldata batchDataInput,
BatchSignatureInput calldata batchSignatureInput
) external override onlyActiveStaker nonReqRevert whenNotPaused {
(uint256 _batchPtr,) = _loadBatchHeader(batchDataInput.parentBatchHeader);
uint256 _nextBatchIndex = BatchHeaderCodecV0.getBatchIndex(_batchPtr) + 1;
require(batchBlobVersionedHashes[_nextBatchIndex] != bytes32(0), "no stored blob hash for this batch");
if (
IL1MessageQueue(messageQueue).getFirstUnfinalizedMessageEnqueueTime() + rollupDelayPeriod < block.timestamp
) {
require(batchDataInput.numL1Messages > 0, "l1msg delay");
}
uint256 submitterBitmap = IL1Staking(l1StakingContract).getStakerBitmap(_msgSender());
_commitBatchWithBatchData(batchDataInput, batchSignatureInput, submitterBitmap, false);
}

function _commitBatchWithBatchData(
BatchDataInput calldata batchDataInput,
BatchSignatureInput calldata batchSignatureInput,
uint256 submitterBitmap
uint256 submitterBitmap,
bool requireBlobWhenNoStoredHash
) internal {
require(batchDataInput.version == 0 || batchDataInput.version == 1, "invalid version");
require(batchDataInput.prevStateRoot != bytes32(0), "previous state root is zero");
Expand Down Expand Up @@ -284,7 +307,14 @@ contract Rollup is IRollup, OwnableUpgradeable, PausableUpgradeable {
assembly {
_batchIndex := add(_batchIndex, 1) // increase batch index
}
bytes32 _blobVersionedHash = (blobhash(0) == bytes32(0)) ? ZERO_VERSIONED_HASH : blobhash(0);
// Prefer stored blob hash; when empty: commitBatch requires blob tx, commitBatchWithProof/commitState allow no blob
bytes32 _blobVersionedHash = batchBlobVersionedHashes[_batchIndex];
if (_blobVersionedHash == bytes32(0) ) {
if (requireBlobWhenNoStoredHash) {
require(blobhash(0) != bytes32(0), "blob required when no stored hash");
}
_blobVersionedHash = (blobhash(0) == bytes32(0)) ? ZERO_VERSIONED_HASH : blobhash(0);
}

{
uint256 _headerLength = BatchHeaderCodecV0.BATCH_HEADER_LENGTH;
Expand Down Expand Up @@ -314,6 +344,7 @@ contract Rollup is IRollup, OwnableUpgradeable, PausableUpgradeable {
}
committedBatches[_batchIndex] = BatchHeaderCodecV0.computeBatchHash(_batchPtr, _headerLength);
committedStateRoots[_batchIndex] = batchDataInput.postStateRoot;
batchBlobVersionedHashes[_batchIndex] = _blobVersionedHash;
uint256 proveRemainingTime = 0;
if (inChallenge) {
// Make the batch finalize time longer than the time required for the current challenge
Expand Down Expand Up @@ -369,7 +400,7 @@ contract Rollup is IRollup, OwnableUpgradeable, PausableUpgradeable {
}
require(rollupDelay || l1MsgQueueDelayed, "invalid timing");

_commitBatchWithBatchData(batchDataInput, batchSignatureInput,0);
_commitBatchWithBatchData(batchDataInput, batchSignatureInput, 0, false);

// get batch data from batch header
(uint256 memPtr, bytes32 _batchHash) = _loadBatchHeader(_batchHeader);
Expand Down Expand Up @@ -617,6 +648,7 @@ contract Rollup is IRollup, OwnableUpgradeable, PausableUpgradeable {

delete batchDataStore[_batchIndex - 1];
delete committedStateRoots[_batchIndex - 1];
delete batchBlobVersionedHashes[_batchIndex - 1];
delete challenges[_batchIndex - 1];

emit FinalizeBatch(
Expand Down
10 changes: 6 additions & 4 deletions contracts/contracts/test/Rollup.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,7 @@ contract RollupCommitBatchWithProofTest is L1MessageBaseTest {

// Slot constants for storage manipulation (from forge inspect Rollup storageLayout)
uint256 constant ROLLUP_DELAY_PERIOD_SLOT = 172; // slot for rollupDelayPeriod

// ZERO_VERSIONED_HASH constant from Rollup contract
bytes32 constant ZERO_VERSIONED_HASH = 0x010657f37554c781402a22917dee2f75def7ab966d7b770905398eba3c444014;


function setUp() public virtual override {
super.setUp();

Expand Down Expand Up @@ -763,6 +760,7 @@ contract RollupCommitBatchTest is L1MessageBaseTest {
abi.encodeCall(IL1Staking.getStakerBitmap, (address(0))),
abi.encode(2)
);
_setStoredBlobHash(1);
hevm.startPrank(address(0));
hevm.expectEmit(true, true, false, true);
emit IRollup.CommitBatch(1, bytes32(0xc1862b08d265f073817a8ce0d7cbb426c16d58a86b93464244ab1d027318642e));
Expand Down Expand Up @@ -834,6 +832,7 @@ contract RollupCommitBatchTest is L1MessageBaseTest {
abi.encodeCall(IL1Staking.getStakerBitmap, (address(0))),
abi.encode(2)
);
_setStoredBlobHash(2);
hevm.startPrank(address(0));
hevm.expectEmit(true, true, false, true);
emit IRollup.CommitBatch(2, bytes32(0x772132c2e12f21bfc5f2792838e480830f2c1dd2be0f3207b159905a9f321038));
Expand Down Expand Up @@ -992,6 +991,7 @@ contract RollupTest is L1MessageBaseTest {
hevm.stopPrank();

// commit batch with one chunk, no tx, correctly
_setStoredBlobHash(1);
hevm.startPrank(alice);
batchDataInput = IRollup.BatchDataInput(0, batchHeader0, 1, 0, stateRoot, stateRoot, getTreeRoot());
hevm.deal(address(0), 10 ether);
Expand Down Expand Up @@ -1029,6 +1029,7 @@ contract RollupTest is L1MessageBaseTest {
bytes32 batchHash0 = rollup.committedBatches(0);

// commit one batch
_setStoredBlobHash(1);
hevm.startPrank(alice);
batchDataInput = IRollup.BatchDataInput(0, batchHeader0, 1, 0, stateRoot, stateRoot, bytes32(uint256(4)));
rollup.commitBatch(batchDataInput, batchSignatureInput); // first chunk with too many txs
Expand All @@ -1054,6 +1055,7 @@ contract RollupTest is L1MessageBaseTest {
}

// commit another batch
_setStoredBlobHash(2);
hevm.startPrank(alice);
batchDataInput = IRollup.BatchDataInput(0, batchHeader1, 1, 0, stateRoot, stateRoot, bytes32(uint256(4)));

Expand Down
10 changes: 10 additions & 0 deletions contracts/contracts/test/base/L1MessageBase.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,16 @@ contract L1MessageBaseTest is CommonTest {

address public l1FeeVault = address(3033);

// Rollup storage slot for batchBlobVersionedHashes (forge inspect Rollup storage-layout)
uint256 internal constant BATCH_BLOB_VERSIONED_HASHES_SLOT = 173;
bytes32 internal constant ZERO_VERSIONED_HASH = 0x010657f37554c781402a22917dee2f75def7ab966d7b770905398eba3c444014;
Comment thread
Kukoomomo marked this conversation as resolved.

/// @dev Sets batchBlobVersionedHashes[batchIndex] so commitBatch can succeed without a blob tx in tests.
function _setStoredBlobHash(uint256 batchIndex) internal {
bytes32 slot = keccak256(abi.encode(batchIndex, BATCH_BLOB_VERSIONED_HASHES_SLOT));
hevm.store(address(rollup), slot, ZERO_VERSIONED_HASH);
}

function setUp() public virtual override {
super.setUp();
hevm.startPrank(multisig);
Expand Down
Loading