Skip to content

feat: replace sequence and confirmBy with hostBlockNumber #48

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

Merged
merged 5 commits into from
Jul 1, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
11 changes: 5 additions & 6 deletions .gas-snapshot
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
ZenithTest:test_badSequence() (gas: 60268)
ZenithTest:test_badSignature() (gas: 66936)
ZenithTest:test_blockExpired() (gas: 55499)
ZenithTest:test_notSequencer() (gas: 58618)
ZenithTest:test_onePerBlock() (gas: 104426)
ZenithTest:test_submitBlock() (gas: 88526)
ZenithTest:test_badSignature() (gas: 37377)
ZenithTest:test_incorrectHostBlock() (gas: 35200)
ZenithTest:test_notSequencer() (gas: 34145)
ZenithTest:test_onePerBlock() (gas: 68365)
ZenithTest:test_submitBlock() (gas: 61143)
66 changes: 13 additions & 53 deletions src/Zenith.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,25 @@ contract Zenith is Passage {
/// @notice The address that is allowed to set/remove sequencers.
address public immutable sequencerAdmin;

/// @notice The block number at which the Zenith contract was deployed.
uint256 public deployBlockNumber;

/// @notice Block header information for the rollup block, signed by the sequencer.
/// @param rollupChainId - the chainId of the rollup chain. Any chainId is accepted by the contract.
/// @param sequence - the sequence number of the rollup block. Must be monotonically increasing. Enforced by the contract.
/// @param confirmBy - the timestamp by which the block must be submitted. Enforced by the contract.
/// @param hostBlockNumber - the host block number in which the rollup block must be submitted. Enforced by the contract.
/// @param gasLimit - the gas limit for the rollup block. Ignored by the contract; enforced by the Node.
/// @param rewardAddress - the address to receive the rollup block reward. Ignored by the contract; enforced by the Node.
/// @param blockDataHash - keccak256(rlp-encoded transactions). the Node will discard the block if the hash doens't match.
/// this allows the sequencer to sign over finalized set of transactions,
/// without the Zenith contract needing to interact with raw transaction data (which may be provided via blobs or calldata).
struct BlockHeader {
uint256 rollupChainId;
uint256 sequence;
uint256 confirmBy;
uint256 hostBlockNumber;
uint256 gasLimit;
address rewardAddress;
bytes32 blockDataHash;
}

/// @notice The sequence number of the next block that can be submitted for a given rollup chainId.
/// @dev Because sequences must start at 1, accessors must add 1 to this number.
/// rollupChainId => (nextSequence - 1)
mapping(uint256 => uint256) sequences;

/// @notice The host block number that a block was last submitted at for a given rollup chainId.
/// rollupChainId => host blockNumber that block was last submitted at
mapping(uint256 => uint256) public lastSubmittedAtBlock;
Expand All @@ -38,13 +34,8 @@ contract Zenith is Passage {
/// address => TRUE if it's a permissioned sequencer
mapping(address => bool) public isSequencer;

/// @notice Thrown when a block submission is attempted with a sequence number that is not the next block for the rollup chainId.
/// @dev Blocks must be submitted in strict monotonic increasing order.
/// @param expected - the correct next sequence number for the given rollup chainId.
error BadSequence(uint256 expected);

/// @notice Thrown when a block submission is attempted when the confirmBy time has passed.
error BlockExpired();
/// @notice Thrown when a block submission is attempted in the incorrect host block.
error IncorrectHostBlock();

/// @notice Thrown when a block submission is attempted with a signature by a non-permissioned sequencer,
/// OR when signature is produced over different block header than is provided.
Expand All @@ -60,17 +51,13 @@ contract Zenith is Passage {
/// @notice Emitted when a new rollup block is successfully submitted.
/// @param sequencer - the address of the sequencer that signed the block.
/// @param rollupChainId - the chainId of the rollup chain.
/// @param sequence - the sequence number of the rollup block.
/// @param confirmBy - the timestamp by which the block must be submitted.
/// @param gasLimit - the gas limit for the rollup block.
/// @param rewardAddress - the address to receive the rollup block reward.
/// @param blockDataHash - keccak256(rlp-encoded transactions). the Node will discard the block if the hash doens't match transactions provided.
/// @dev including blockDataHash allows the sequencer to sign over finalized block data, without needing to calldatacopy the `blockData` param.
event BlockSubmitted(
address indexed sequencer,
uint256 indexed rollupChainId,
uint256 indexed sequence,
uint256 confirmBy,
uint256 gasLimit,
address rewardAddress,
bytes32 blockDataHash
Expand All @@ -83,22 +70,7 @@ contract Zenith is Passage {
Passage(_defaultRollupChainId, _withdrawalAdmin)
{
sequencerAdmin = _sequencerAdmin;
}

/// @notice Returns the next sequence number.
/// @dev Because sequences must start at 1, we add 1 to the mapping value.
/// @param _rollupChainId - the chainId of the rollup chain. Any chainId is accepted by the contract.
/// @return The next sequence number.
function nextSequence(uint256 _rollupChainId) public view returns (uint256) {
return sequences[_rollupChainId] + 1;
}

/// @notice Increments the sequence number and returns it.
/// @dev Because sequences must start at 1, we add 1 to the mapping value.
/// @param _rollupChainId - the chainId of the rollup chain. Any chainId is accepted by the contract.
/// @return The next sequence number.
function incrementSequence(uint256 _rollupChainId) internal returns (uint256) {
return ++sequences[_rollupChainId];
deployBlockNumber = block.number;
}

/// @notice Add a sequencer to the permissioned sequencer list.
Expand Down Expand Up @@ -129,19 +101,14 @@ contract Zenith is Passage {
/// @param v - the v component of the Sequencer's ECSDA signature over the block header.
/// @param r - the r component of the Sequencer's ECSDA signature over the block header.
/// @param s - the s component of the Sequencer's ECSDA signature over the block header.
/// @custom:reverts BadSequence if the sequence number is not the next block for the given rollup chainId.
/// @custom:reverts BlockExpired if the confirmBy time has passed.
/// @custom:reverts IncorrectHostBlock if the hostBlockNumber does not match the current block.
/// @custom:reverts BadSignature if the signer is not a permissioned sequencer,
/// OR if the signature provided commits to a different header.
/// @custom:reverts OneRollupBlockPerHostBlock if attempting to submit a second rollup block within one host block.
/// @custom:emits BlockSubmitted if the block is successfully submitted.
function submitBlock(BlockHeader memory header, uint8 v, bytes32 r, bytes32 s, bytes calldata) external {
// assert that the sequence number is valid and increment it
uint256 _nextSequence = incrementSequence(header.rollupChainId);
if (_nextSequence != header.sequence) revert BadSequence(_nextSequence);

// assert that confirmBy time has not passed
if (block.timestamp > header.confirmBy) revert BlockExpired();
// assert that the host block number matches the current block
if (block.number != header.hostBlockNumber) revert IncorrectHostBlock();

// derive sequencer from signature over block header
bytes32 blockCommit = blockCommitment(header);
Expand All @@ -156,13 +123,7 @@ contract Zenith is Passage {

// emit event
emit BlockSubmitted(
sequencer,
header.rollupChainId,
header.sequence,
header.confirmBy,
header.gasLimit,
header.rewardAddress,
header.blockDataHash
sequencer, header.rollupChainId, header.gasLimit, header.rewardAddress, header.blockDataHash
);
}

Expand All @@ -174,9 +135,8 @@ contract Zenith is Passage {
"init4.sequencer.v0",
block.chainid,
header.rollupChainId,
header.sequence,
header.hostBlockNumber,
header.gasLimit,
header.confirmBy,
header.rewardAddress,
header.blockDataHash
);
Expand Down
58 changes: 11 additions & 47 deletions test/Zenith.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ contract ZenithTest is Test {
event BlockSubmitted(
address indexed sequencer,
uint256 indexed rollupChainId,
uint256 indexed sequence,
uint256 confirmBy,
uint256 gasLimit,
address rewardAddress,
bytes32 blockDataHash
Expand All @@ -31,8 +29,7 @@ contract ZenithTest is Test {

// set default block values
header.rollupChainId = block.chainid + 1;
header.sequence = 1; // first block has index 1
header.confirmBy = block.timestamp + 10 minutes;
header.hostBlockNumber = block.number;
header.gasLimit = 30_000_000;
header.rewardAddress = address(this);
header.blockDataHash = keccak256(blockData);
Expand All @@ -41,29 +38,16 @@ contract ZenithTest is Test {
commit = target.blockCommitment(header);
}

// cannot submit block with incorrect sequence number
function test_badSequence() public {
// change to incorrect sequence number
header.sequence = 100;
// cannot submit block with incorrect host block number
function test_incorrectHostBlock() public {
// change to incorrect host block number
header.hostBlockNumber = 100;
commit = target.blockCommitment(header);

// sign block commitmenet with sequencer key
(uint8 v, bytes32 r, bytes32 s) = vm.sign(sequencerKey, commit);

vm.expectRevert(abi.encodeWithSelector(Zenith.BadSequence.selector, 1));
target.submitBlock(header, v, r, s, blockData);
}

// cannot submit block with expired confirmBy time
function test_blockExpired() public {
// change to expired confirmBy time
header.confirmBy = block.timestamp - 1;
commit = target.blockCommitment(header);

// sign block commitmenet with sequencer key
(uint8 v, bytes32 r, bytes32 s) = vm.sign(sequencerKey, commit);

vm.expectRevert(abi.encodeWithSelector(Zenith.BlockExpired.selector));
vm.expectRevert(Zenith.IncorrectHostBlock.selector);
target.submitBlock(header, v, r, s, blockData);
}

Expand All @@ -75,18 +59,9 @@ contract ZenithTest is Test {
// should emit BlockSubmitted event
vm.expectEmit();
emit BlockSubmitted(
vm.addr(sequencerKey),
header.rollupChainId,
header.sequence,
header.confirmBy,
header.gasLimit,
header.rewardAddress,
header.blockDataHash
vm.addr(sequencerKey), header.rollupChainId, header.gasLimit, header.rewardAddress, header.blockDataHash
);
target.submitBlock(header, v, r, s, blockData);

// should increment sequence number
assertEq(target.nextSequence(header.rollupChainId), header.sequence + 1);
}

// cannot submit block with invalid sequencer signer from non-permissioned key
Expand All @@ -104,7 +79,7 @@ contract ZenithTest is Test {
(uint8 v, bytes32 r, bytes32 s) = vm.sign(sequencerKey, commit);

// change header data from what was signed by sequencer
header.confirmBy = block.timestamp + 15 minutes;
header.rewardAddress = address(0);
bytes32 newCommit = target.blockCommitment(header);
address derivedSigner = ecrecover(newCommit, v, r, s);

Expand All @@ -120,24 +95,13 @@ contract ZenithTest is Test {
// should emit BlockSubmitted event
vm.expectEmit();
emit BlockSubmitted(
vm.addr(sequencerKey),
header.rollupChainId,
header.sequence,
header.confirmBy,
header.gasLimit,
header.rewardAddress,
header.blockDataHash
vm.addr(sequencerKey), header.rollupChainId, header.gasLimit, header.rewardAddress, header.blockDataHash
);
target.submitBlock(header, v, r, s, blockData);

// incerement the header sequence
header.sequence += 1;
commit = target.blockCommitment(header);
(v, r, s) = vm.sign(sequencerKey, commit);

// should revert with OneRollupBlockPerHostBlock
// (NOTE: this test works because forge does not increment block.number when it mines a transaction)
vm.expectRevert(abi.encodeWithSelector(Zenith.OneRollupBlockPerHostBlock.selector));
// (NOTE: this test works without changing the Header because forge does not increment block.number when it mines a transaction)
vm.expectRevert(Zenith.OneRollupBlockPerHostBlock.selector);
target.submitBlock(header, v, r, s, blockData);
}
}