feat(contracts): commit DA for multiple blocks (#40)

Co-authored-by: Scroll Dev <dev@scroll.io>
Co-authored-by: HAOYUatHZ <37070449+HAOYUatHZ@users.noreply.github.com>
This commit is contained in:
Xi Lin
2022-11-02 18:46:11 +08:00
committed by GitHub
parent 0892813867
commit a648073962
8 changed files with 417 additions and 188 deletions

View File

@@ -38,13 +38,13 @@ Append a cross chain message to message queue.
|---|---|---|
| _0 | uint256 | undefined |
### blocks
### batches
```solidity
function blocks(bytes32) external view returns (struct IZKRollup.BlockHeader header, bytes32 transactionRoot, bool verified)
function batches(bytes32) external view returns (bytes32 batchHash, bytes32 parentHash, uint64 batchIndex, bool verified)
```
Mapping from block hash to block index.
Mapping from batch id to batch struct.
@@ -58,14 +58,40 @@ Mapping from block hash to block index.
| Name | Type | Description |
|---|---|---|
| header | IZKRollup.BlockHeader | undefined |
| transactionRoot | bytes32 | undefined |
| batchHash | bytes32 | undefined |
| parentHash | bytes32 | undefined |
| batchIndex | uint64 | undefined |
| verified | bool | undefined |
### commitBlock
### blocks
```solidity
function commitBlock(IZKRollup.BlockHeader _header, IZKRollup.Layer2Transaction[] _txn) external nonpayable
function blocks(bytes32) external view returns (bytes32 parentHash, bytes32 transactionRoot, uint64 blockHeight, uint64 batchIndex)
```
Mapping from block hash to block struct.
#### Parameters
| Name | Type | Description |
|---|---|---|
| _0 | bytes32 | undefined |
#### Returns
| Name | Type | Description |
|---|---|---|
| parentHash | bytes32 | undefined |
| transactionRoot | bytes32 | undefined |
| blockHeight | uint64 | undefined |
| batchIndex | uint64 | undefined |
### commitBatch
```solidity
function commitBatch(IZKRollup.Layer2Batch _batch) external nonpayable
```
@@ -76,16 +102,15 @@ function commitBlock(IZKRollup.BlockHeader _header, IZKRollup.Layer2Transaction[
| Name | Type | Description |
|---|---|---|
| _header | IZKRollup.BlockHeader | undefined |
| _txn | IZKRollup.Layer2Transaction[] | undefined |
| _batch | IZKRollup.Layer2Batch | undefined |
### finalizeBlockWithProof
### finalizeBatchWithProof
```solidity
function finalizeBlockWithProof(bytes32 _blockHash, uint256[] _proof, uint256[] _instances) external nonpayable
function finalizeBatchWithProof(bytes32 _batchId, uint256[] _proof, uint256[] _instances) external nonpayable
```
finalize commited block in layer 1
finalize commited batch in layer 1
*will add more parameters if needed.*
@@ -93,17 +118,17 @@ finalize commited block in layer 1
| Name | Type | Description |
|---|---|---|
| _blockHash | bytes32 | The block hash of the commited block. |
| _proof | uint256[] | The corresponding proof of the commited block. |
| _instances | uint256[] | Instance used to verify, generated from block. |
| _batchId | bytes32 | The identification of the commited batch. |
| _proof | uint256[] | The corresponding proof of the commited batch. |
| _instances | uint256[] | Instance used to verify, generated from batch. |
### finalizedBlocks
### finalizedBatches
```solidity
function finalizedBlocks(uint256) external view returns (bytes32)
function finalizedBatches(uint256) external view returns (bytes32)
```
Mapping from block height to finalized block hash.
Mapping from batch index to finalized batch id.
@@ -178,7 +203,7 @@ Return the total number of appended message.
### importGenesisBlock
```solidity
function importGenesisBlock(IZKRollup.BlockHeader _genesis) external nonpayable
function importGenesisBlock(IZKRollup.Layer2BlockHeader _genesis) external nonpayable
```
@@ -189,7 +214,7 @@ function importGenesisBlock(IZKRollup.BlockHeader _genesis) external nonpayable
| Name | Type | Description |
|---|---|---|
| _genesis | IZKRollup.BlockHeader | undefined |
| _genesis | IZKRollup.Layer2BlockHeader | undefined |
### initialize
@@ -207,13 +232,13 @@ function initialize(uint256 _chainId) external nonpayable
|---|---|---|
| _chainId | uint256 | undefined |
### lastFinalizedBlockHash
### lastFinalizedBatchID
```solidity
function lastFinalizedBlockHash() external view returns (bytes32)
function lastFinalizedBatchID() external view returns (bytes32)
```
The hash of the latest finalized block.
The latest finalized batch id.
@@ -325,21 +350,21 @@ function renounceOwnership() external nonpayable
*Leaves the contract without owner. It will not be possible to call `onlyOwner` functions anymore. Can only be called by the current owner. NOTE: Renouncing ownership will leave the contract without an owner, thereby removing any functionality that is only available to the owner.*
### revertBlock
### revertBatch
```solidity
function revertBlock(bytes32 _blockHash) external nonpayable
function revertBatch(bytes32 _batchId) external nonpayable
```
revert a pending block.
revert a pending batch.
*one can only revert unfinalized blocks.*
*one can only revert unfinalized batches.*
#### Parameters
| Name | Type | Description |
|---|---|---|
| _blockHash | bytes32 | The block hash of the block. |
| _batchId | bytes32 | The identification of the batch. |
### transferOwnership
@@ -392,7 +417,7 @@ Update the address of operator.
### verifyMessageStateProof
```solidity
function verifyMessageStateProof(uint256 _blockNumber) external view returns (bool)
function verifyMessageStateProof(uint256 _batchIndex, uint256 _blockHeight) external view returns (bool)
```
Verify a state proof for message relay.
@@ -403,7 +428,8 @@ Verify a state proof for message relay.
| Name | Type | Description |
|---|---|---|
| _blockNumber | uint256 | undefined |
| _batchIndex | uint256 | undefined |
| _blockHeight | uint256 | undefined |
#### Returns
@@ -415,13 +441,13 @@ Verify a state proof for message relay.
## Events
### CommitBlock
### CommitBatch
```solidity
event CommitBlock(bytes32 indexed _blockHash, uint64 indexed _blockHeight, bytes32 _parentHash)
event CommitBatch(bytes32 indexed _batchId, bytes32 _batchHash, uint256 _batchIndex, bytes32 _parentHash)
```
Emitted when a new batch is commited.
@@ -429,17 +455,18 @@ event CommitBlock(bytes32 indexed _blockHash, uint64 indexed _blockHeight, bytes
| Name | Type | Description |
|---|---|---|
| _blockHash `indexed` | bytes32 | undefined |
| _blockHeight `indexed` | uint64 | undefined |
| _batchId `indexed` | bytes32 | undefined |
| _batchHash | bytes32 | undefined |
| _batchIndex | uint256 | undefined |
| _parentHash | bytes32 | undefined |
### FinalizeBlock
### FinalizeBatch
```solidity
event FinalizeBlock(bytes32 indexed _blockHash, uint64 indexed _blockHeight)
event FinalizeBatch(bytes32 indexed _batchId, bytes32 _batchHash, uint256 _batchIndex, bytes32 _parentHash)
```
Emitted when a batch is finalized.
@@ -447,8 +474,10 @@ event FinalizeBlock(bytes32 indexed _blockHash, uint64 indexed _blockHeight)
| Name | Type | Description |
|---|---|---|
| _blockHash `indexed` | bytes32 | undefined |
| _blockHeight `indexed` | uint64 | undefined |
| _batchId `indexed` | bytes32 | undefined |
| _batchHash | bytes32 | undefined |
| _batchIndex | uint256 | undefined |
| _parentHash | bytes32 | undefined |
### OwnershipTransferred
@@ -467,13 +496,13 @@ event OwnershipTransferred(address indexed previousOwner, address indexed newOwn
| previousOwner `indexed` | address | undefined |
| newOwner `indexed` | address | undefined |
### RevertBlock
### RevertBatch
```solidity
event RevertBlock(bytes32 indexed _blockHash)
event RevertBatch(bytes32 indexed _batchId)
```
Emitted when a batch is reverted.
@@ -481,7 +510,7 @@ event RevertBlock(bytes32 indexed _blockHash)
| Name | Type | Description |
|---|---|---|
| _blockHash `indexed` | bytes32 | undefined |
| _batchId `indexed` | bytes32 | undefined |
### UpdateMesssenger

View File

@@ -56,6 +56,7 @@ describe("ERC20Gateway", async () => {
gasUsed: 0,
timestamp: 0,
extraData: "0x",
txs: [],
});
// deploy L1ScrollMessenger in layer 1
@@ -425,7 +426,7 @@ describe("ERC20Gateway", async () => {
deadline,
nonce,
messageData,
{ blockNumber: 0, merkleProof: "0x" }
{ batchIndex: 0, blockHeight: 0, merkleProof: "0x" }
);
await relayTx.wait();
// should emit RelayedMessage
@@ -477,7 +478,7 @@ describe("ERC20Gateway", async () => {
deadline,
nonce,
messageData,
{ blockNumber: 0, merkleProof: "0x" }
{ batchIndex: 0, blockHeight: 0, merkleProof: "0x" }
);
await relayTx.wait();
// should emit RelayedMessage
@@ -744,7 +745,7 @@ describe("ERC20Gateway", async () => {
deadline,
nonce,
messageData,
{ blockNumber: 0, merkleProof: "0x" }
{ batchIndex: 0, blockHeight: 0, merkleProof: "0x" }
);
await relayTx.wait();
const afterBalanceLayer1 = await l1WETH.balanceOf(recipient.address);
@@ -801,7 +802,7 @@ describe("ERC20Gateway", async () => {
deadline,
nonce,
messageData,
{ blockNumber: 0, merkleProof: "0x" }
{ batchIndex: 0, blockHeight: 0, merkleProof: "0x" }
);
await relayTx.wait();
const afterBalanceLayer1 = await l1WETH.balanceOf(recipient.address);

View File

@@ -43,6 +43,7 @@ describe("GatewayRouter", async () => {
gasUsed: 0,
timestamp: 0,
extraData: "0x",
txs: []
});
// deploy L1ScrollMessenger in layer 1
@@ -195,7 +196,7 @@ describe("GatewayRouter", async () => {
deadline,
nonce,
messageData,
{ blockNumber: 0, merkleProof: "0x" }
{ batchIndex: 0, blockHeight: 0, merkleProof: "0x" }
);
await relayTx.wait();
const afterBalanceLayer1 = await ethers.provider.getBalance(recipient.address);

View File

@@ -7,7 +7,8 @@ import { IScrollMessenger } from "../libraries/IScrollMessenger.sol";
interface IL1ScrollMessenger is IScrollMessenger {
struct L2MessageProof {
// @todo add more fields
uint256 blockNumber;
uint256 batchIndex;
uint256 blockHeight;
bytes merkleProof;
}

View File

@@ -97,7 +97,7 @@ contract L1ScrollMessenger is OwnableUpgradeable, PausableUpgradeable, ScrollMes
require(!isMessageExecuted[_msghash], "Message successfully executed");
// @todo check proof
require(IZKRollup(rollup).verifyMessageStateProof(_proof.blockNumber), "invalid state proof");
require(IZKRollup(rollup).verifyMessageStateProof(_proof.batchIndex, _proof.blockHeight), "invalid state proof");
require(ZkTrieVerifier.verifyMerkleProof(_proof.merkleProof), "invalid proof");
// @todo check `_to` address to avoid attack.

View File

@@ -5,11 +5,21 @@ pragma solidity ^0.8.0;
interface IZKRollup {
/**************************************** Events ****************************************/
event CommitBlock(bytes32 indexed _blockHash, uint64 indexed _blockHeight, bytes32 _parentHash);
/// @notice Emitted when a new batch is commited.
/// @param _batchHash The hash of the batch
/// @param _batchIndex The index of the batch
/// @param _parentHash The hash of parent batch
event CommitBatch(bytes32 indexed _batchId, bytes32 _batchHash, uint256 _batchIndex, bytes32 _parentHash);
event RevertBlock(bytes32 indexed _blockHash);
/// @notice Emitted when a batch is reverted.
/// @param _batchId The identification of the batch.
event RevertBatch(bytes32 indexed _batchId);
event FinalizeBlock(bytes32 indexed _blockHash, uint64 indexed _blockHeight);
/// @notice Emitted when a batch is finalized.
/// @param _batchHash The hash of the batch
/// @param _batchIndex The index of the batch
/// @param _parentHash The hash of parent batch
event FinalizeBatch(bytes32 indexed _batchId, bytes32 _batchHash, uint256 _batchIndex, bytes32 _parentHash);
/// @dev The transanction struct
struct Layer2Transaction {
@@ -20,10 +30,14 @@ interface IZKRollup {
uint256 gasPrice;
uint256 value;
bytes data;
// signature
uint256 r;
uint256 s;
uint64 v;
}
/// @dev The block header struct
struct BlockHeader {
struct Layer2BlockHeader {
bytes32 blockHash;
bytes32 parentHash;
uint256 baseFee;
@@ -32,6 +46,15 @@ interface IZKRollup {
uint64 gasUsed;
uint64 timestamp;
bytes extraData;
Layer2Transaction[] txs;
}
/// @dev The batch struct, the batch hash is always the last block hash of `blocks`.
struct Layer2Batch {
uint64 batchIndex;
// The hash of the last block in the parent batch
bytes32 parentHash;
Layer2BlockHeader[] blocks;
}
/**************************************** View Functions ****************************************/
@@ -49,7 +72,7 @@ interface IZKRollup {
/// @notice Verify a state proof for message relay.
/// @dev add more fields.
function verifyMessageStateProof(uint256 _blockNumber) external view returns (bool);
function verifyMessageStateProof(uint256 _batchIndex, uint256 _blockHeight) external view returns (bool);
/**************************************** Mutated Functions ****************************************/
@@ -72,24 +95,23 @@ interface IZKRollup {
uint256 _gasLimit
) external returns (uint256);
/// @notice commit block in layer 1
/// @dev will add more parameters if needed.
/// @param _header The block header.
/// @param _txns The transactions included in the block.
function commitBlock(BlockHeader memory _header, Layer2Transaction[] memory _txns) external;
/// @notice commit a batch in layer 1
/// @dev store in a more compacted form later.
/// @param _batch The layer2 batch to commit.
function commitBatch(Layer2Batch memory _batch) external;
/// @notice revert a pending block.
/// @dev one can only revert unfinalized blocks.
/// @param _blockHash The block hash of the block.
function revertBlock(bytes32 _blockHash) external;
/// @notice revert a pending batch.
/// @dev one can only revert unfinalized batches.
/// @param _batchId The identification of the batch.
function revertBatch(bytes32 _batchId) external;
/// @notice finalize commited block in layer 1
/// @notice finalize commited batch in layer 1
/// @dev will add more parameters if needed.
/// @param _blockHash The block hash of the commited block.
/// @param _proof The corresponding proof of the commited block.
/// @param _instances Instance used to verify, generated from block.
function finalizeBlockWithProof(
bytes32 _blockHash,
/// @param _batchId The identification of the commited batch.
/// @param _proof The corresponding proof of the commited batch.
/// @param _instances Instance used to verify, generated from batch.
function finalizeBatchWithProof(
bytes32 _batchId,
uint256[] memory _proof,
uint256[] memory _instances
) external;

View File

@@ -7,6 +7,8 @@ import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/O
import { IZKRollup } from "./IZKRollup.sol";
import { RollupVerifier } from "../../libraries/verifier/RollupVerifier.sol";
// solhint-disable reason-string
/// @title ZKRollup
/// @notice This contract maintains essential data for zk rollup, including:
///
@@ -29,10 +31,17 @@ contract ZKRollup is OwnableUpgradeable, IZKRollup {
/**************************************** Variables ****************************************/
struct Block {
// @todo simplify fields later
BlockHeader header;
struct Layer2BlockStored {
bytes32 parentHash;
bytes32 transactionRoot;
uint64 blockHeight;
uint64 batchIndex;
}
struct Layer2BatchStored {
bytes32 batchHash;
bytes32 parentHash;
uint64 batchIndex;
bool verified;
}
@@ -52,14 +61,17 @@ contract ZKRollup is OwnableUpgradeable, IZKRollup {
/// @dev The list of appended message hash.
bytes32[] private messageQueue;
/// @notice The hash of the latest finalized block.
bytes32 public lastFinalizedBlockHash;
/// @notice The latest finalized batch id.
bytes32 public lastFinalizedBatchID;
/// @notice Mapping from block hash to block index.
mapping(bytes32 => Block) public blocks;
/// @notice Mapping from block hash to block struct.
mapping(bytes32 => Layer2BlockStored) public blocks;
/// @notice Mapping from block height to finalized block hash.
mapping(uint256 => bytes32) public finalizedBlocks;
/// @notice Mapping from batch id to batch struct.
mapping(bytes32 => Layer2BatchStored) public batches;
/// @notice Mapping from batch index to finalized batch id.
mapping(uint256 => bytes32) public finalizedBatches;
modifier OnlyOperator() {
// @todo In the decentralize mode, it should be only called by a list of validator.
@@ -99,12 +111,18 @@ contract ZKRollup is OwnableUpgradeable, IZKRollup {
}
/// @inheritdoc IZKRollup
function verifyMessageStateProof(uint256 _blockNumber) external view override returns (bool) {
if (finalizedBlocks[_blockNumber] != bytes32(0)) {
return true;
function verifyMessageStateProof(uint256 _batchIndex, uint256 _blockHeight) external view returns (bool) {
bytes32 _batchId = finalizedBatches[_batchIndex];
// check if batch is verified
if (_batchId == bytes32(0)) return false;
uint256 _maxBlockHeightInBatch = blocks[batches[_batchId].batchHash].blockHeight;
// check block height is in batch range.
if (_maxBlockHeightInBatch == 0) return _blockHeight == 0;
else {
uint256 _minBlockHeightInBatch = blocks[batches[_batchId].parentHash].blockHeight + 1;
return _minBlockHeightInBatch <= _blockHeight && _blockHeight <= _maxBlockHeightInBatch;
}
Block storage _block = blocks[lastFinalizedBlockHash];
return uint256(_block.header.blockHeight) >= _blockNumber;
}
/**************************************** Mutated Functions ****************************************/
@@ -133,73 +151,113 @@ contract ZKRollup is OwnableUpgradeable, IZKRollup {
}
/// @notice Import layer 2 genesis block
function importGenesisBlock(BlockHeader memory _genesis) external onlyOwner {
require(lastFinalizedBlockHash == bytes32(0), "genesis block imported");
require(_genesis.blockHash != bytes32(0), "invalid block hash");
require(_genesis.blockHeight == 0, "not genesis block");
require(_genesis.parentHash == bytes32(0), "parent hash not empty");
function importGenesisBlock(Layer2BlockHeader memory _genesis) external onlyOwner {
require(lastFinalizedBatchID == bytes32(0), "Genesis block imported");
require(_genesis.blockHash != bytes32(0), "Block hash is zero");
require(_genesis.blockHeight == 0, "Block is not genesis");
require(_genesis.parentHash == bytes32(0), "Parent hash not empty");
Block storage _block = blocks[_genesis.blockHash];
_block.header = _genesis;
_block.verified = true; // force commited
require(_verifyBlockHash(_genesis), "Block hash verification failed");
lastFinalizedBlockHash = _genesis.blockHash;
finalizedBlocks[0] = _genesis.blockHash;
Layer2BlockStored storage _block = blocks[_genesis.blockHash];
_block.transactionRoot = _computeTransactionRoot(_genesis.txs);
emit CommitBlock(_genesis.blockHash, 0, bytes32(0));
bytes32 _batchId = _computeBatchId(_genesis.blockHash, bytes32(0), 0);
Layer2BatchStored storage _batch = batches[_batchId];
_batch.batchHash = _genesis.blockHash;
_batch.verified = true;
lastFinalizedBatchID = _batchId;
finalizedBatches[0] = _batchId;
emit CommitBatch(_batchId, _genesis.blockHash, 0, bytes32(0));
emit FinalizeBatch(_batchId, _genesis.blockHash, 0, bytes32(0));
}
/// @inheritdoc IZKRollup
function commitBlock(BlockHeader memory _header, Layer2Transaction[] memory _txn) external override OnlyOperator {
Block storage _block = blocks[_header.blockHash];
require(_block.header.blockHash == bytes32(0), "Block has been committed before");
require(blocks[_header.parentHash].header.blockHash != bytes32(0), "Parent hasn't been committed");
function commitBatch(Layer2Batch memory _batch) external override OnlyOperator {
// check whether the batch is empty
require(_batch.blocks.length > 0, "Batch is empty");
uint256 _parentHeight = blocks[_header.parentHash].header.blockHeight;
// solhint-disable-next-line reason-string
require(_parentHeight + 1 == _header.blockHeight, "Block height and parent block height mismatch");
bytes32 _batchHash = _batch.blocks[_batch.blocks.length - 1].blockHash;
bytes32 _batchId = _computeBatchId(_batchHash, _batch.parentHash, _batch.batchIndex);
Layer2BatchStored storage _batchStored = batches[_batchId];
bytes32[] memory _hashes = new bytes32[](_txn.length);
for (uint256 i = 0; i < _txn.length; i++) {
// @todo use rlp
_hashes[i] = keccak256(
abi.encode(
_txn[i].caller,
_txn[i].nonce,
_txn[i].target,
_txn[i].gas,
_txn[i].gasPrice,
_txn[i].value,
_txn[i].data
)
);
// check whether the batch is commited before
require(_batchStored.batchHash == bytes32(0), "Batch has been committed before");
// make sure the parent batch is commited before
Layer2BlockStored storage _parentBlock = blocks[_batch.parentHash];
require(_parentBlock.transactionRoot != bytes32(0), "Parent batch hasn't been committed");
require(_parentBlock.batchIndex + 1 == _batch.batchIndex, "Batch index and parent batch index mismatch");
// check whether the blocks are correct.
unchecked {
uint256 _expectedBlockHeight = _parentBlock.blockHeight + 1;
bytes32 _expectedParentHash = _batch.parentHash;
for (uint256 i = 0; i < _batch.blocks.length; i++) {
Layer2BlockHeader memory _block = _batch.blocks[i];
require(_verifyBlockHash(_block), "Block hash verification failed");
require(_block.parentHash == _expectedParentHash, "Block parent hash mismatch");
require(_block.blockHeight == _expectedBlockHeight, "Block height mismatch");
require(blocks[_block.blockHash].transactionRoot == bytes32(0), "Block has been commited before");
_expectedBlockHeight += 1;
_expectedParentHash = _block.blockHash;
}
}
_block.header = _header;
_block.transactionRoot = keccak256(abi.encode(_hashes));
emit CommitBlock(_header.blockHash, _header.blockHeight, _header.parentHash);
// do block commit
for (uint256 i = 0; i < _batch.blocks.length; i++) {
Layer2BlockHeader memory _block = _batch.blocks[i];
Layer2BlockStored storage _blockStored = blocks[_block.blockHash];
_blockStored.parentHash = _block.parentHash;
_blockStored.transactionRoot = _computeTransactionRoot(_block.txs);
_blockStored.blockHeight = _block.blockHeight;
_blockStored.batchIndex = _batch.batchIndex;
}
_batchStored.batchHash = _batchHash;
_batchStored.parentHash = _batch.parentHash;
_batchStored.batchIndex = _batch.batchIndex;
emit CommitBatch(_batchId, _batchHash, _batch.batchIndex, _batch.parentHash);
}
/// @inheritdoc IZKRollup
function revertBlock(bytes32 _blockHash) external override OnlyOperator {
Block storage _block = blocks[_blockHash];
require(_block.header.blockHash != bytes32(0), "No such block");
require(!_block.verified, "Unable to revert verified block");
function revertBatch(bytes32 _batchId) external override OnlyOperator {
Layer2BatchStored storage _batch = batches[_batchId];
delete blocks[_blockHash];
require(_batch.batchHash != bytes32(0), "No such batch");
require(!_batch.verified, "Unable to revert verified batch");
emit RevertBlock(_blockHash);
bytes32 _blockHash = _batch.batchHash;
bytes32 _parentHash = _batch.parentHash;
// delete commited blocks
while (_blockHash != _parentHash) {
bytes32 _nextBlockHash = blocks[_blockHash].parentHash;
delete blocks[_blockHash];
_blockHash = _nextBlockHash;
}
// delete commited batch
delete batches[_batchId];
emit RevertBatch(_batchId);
}
/// @inheritdoc IZKRollup
function finalizeBlockWithProof(
bytes32 _blockHash,
function finalizeBatchWithProof(
bytes32 _batchId,
uint256[] memory _proof,
uint256[] memory _instances
) external override OnlyOperator {
Block storage _block = blocks[_blockHash];
require(_block.header.blockHash != bytes32(0), "No such block");
require(!_block.verified, "Block already verified");
Layer2BatchStored storage _batch = batches[_batchId];
require(_batch.batchHash != bytes32(0), "No such batch");
require(!_batch.verified, "Batch already verified");
// @note skip parent check for now, since we may not prove blocks in order.
// bytes32 _parentHash = _block.header.parentHash;
@@ -210,14 +268,16 @@ contract ZKRollup is OwnableUpgradeable, IZKRollup {
// @todo add verification logic
RollupVerifier.verify(_proof, _instances);
uint256 _height = _block.header.blockHeight; // gas saving
_block.verified = true;
finalizedBlocks[_height] = _blockHash;
Block storage _finalizedBlock = blocks[lastFinalizedBlockHash];
if (_height > _finalizedBlock.header.blockHeight) {
lastFinalizedBlockHash = _blockHash;
uint256 _batchIndex = _batch.batchIndex;
finalizedBatches[_batchIndex] = _batchId;
_batch.verified = true;
Layer2BatchStored storage _finalizedBatch = batches[lastFinalizedBatchID];
if (_batchIndex > _finalizedBatch.batchIndex) {
lastFinalizedBatchID = _batchId;
}
emit FinalizeBlock(_blockHash, uint64(_height));
emit FinalizeBatch(_batchId, _batch.batchHash, _batchIndex, _batch.parentHash);
}
/**************************************** Restricted Functions ****************************************/
@@ -245,4 +305,49 @@ contract ZKRollup is OwnableUpgradeable, IZKRollup {
emit UpdateMesssenger(_oldMessenger, _newMessenger);
}
/**************************************** Internal Functions ****************************************/
function _verifyBlockHash(Layer2BlockHeader memory) internal pure returns (bool) {
// @todo finish logic after more discussions
return true;
}
/// @dev Internal function to compute a unique batch id for mapping.
/// @param _batchHash The hash of the batch.
/// @param _parentHash The hash of the batch.
/// @param _batchIndex The index of the batch.
/// @return Return the computed batch id.
function _computeBatchId(
bytes32 _batchHash,
bytes32 _parentHash,
uint256 _batchIndex
) internal pure returns (bytes32) {
return keccak256(abi.encode(_batchHash, _parentHash, _batchIndex));
}
/// @dev Internal function to compute transaction root.
/// @param _txn The list of transactions in the block.
/// @return Return the hash of transaction root.
function _computeTransactionRoot(Layer2Transaction[] memory _txn) internal pure returns (bytes32) {
bytes32[] memory _hashes = new bytes32[](_txn.length);
for (uint256 i = 0; i < _txn.length; i++) {
// @todo use rlp
_hashes[i] = keccak256(
abi.encode(
_txn[i].caller,
_txn[i].nonce,
_txn[i].target,
_txn[i].gas,
_txn[i].gasPrice,
_txn[i].value,
_txn[i].data,
_txn[i].r,
_txn[i].s,
_txn[i].v
)
);
}
return keccak256(abi.encode(_hashes));
}
}

View File

@@ -60,7 +60,7 @@ contract ZKRollupTest is DSTestPlus {
rollup.updateMessenger(_messenger);
}
function testImportGenesisBlock(IZKRollup.BlockHeader memory _genesis) public {
function testImportGenesisBlock(IZKRollup.Layer2BlockHeader memory _genesis) public {
if (_genesis.blockHash == bytes32(0)) {
_genesis.blockHash = bytes32(uint256(1));
}
@@ -75,82 +75,142 @@ contract ZKRollupTest is DSTestPlus {
// not genesis block, should revert
_genesis.blockHeight = 1;
hevm.expectRevert("not genesis block");
hevm.expectRevert("Block is not genesis");
rollup.importGenesisBlock(_genesis);
_genesis.blockHeight = 0;
// parent hash not empty, should revert
_genesis.parentHash = bytes32(uint256(2));
hevm.expectRevert("parent hash not empty");
hevm.expectRevert("Parent hash not empty");
rollup.importGenesisBlock(_genesis);
_genesis.parentHash = bytes32(0);
// invalid block hash, should revert
bytes32 _originalHash = _genesis.blockHash;
_genesis.blockHash = bytes32(0);
hevm.expectRevert("invalid block hash");
hevm.expectRevert("Block hash is zero");
rollup.importGenesisBlock(_genesis);
_genesis.blockHash = _originalHash;
// TODO: add Block hash verification failed
// import correctly
assertEq(rollup.finalizedBlocks(0), bytes32(0));
assertEq(rollup.finalizedBatches(0), bytes32(0));
rollup.importGenesisBlock(_genesis);
(IZKRollup.BlockHeader memory _header, , bool _verified) = rollup.blocks(_genesis.blockHash);
assertEq(_genesis.blockHash, rollup.lastFinalizedBlockHash());
assertEq(_genesis.blockHash, _header.blockHash);
assertEq(_genesis.parentHash, _header.parentHash);
assertEq(_genesis.baseFee, _header.baseFee);
assertEq(_genesis.stateRoot, _header.stateRoot);
assertEq(_genesis.blockHeight, _header.blockHeight);
assertEq(_genesis.gasUsed, _header.gasUsed);
assertEq(_genesis.timestamp, _header.timestamp);
assertBytesEq(_genesis.extraData, _header.extraData);
assertBoolEq(_verified, true);
assertEq(rollup.finalizedBlocks(0), _genesis.blockHash);
{
(bytes32 parentHash, , uint64 blockHeight, uint64 batchIndex) = rollup.blocks(_genesis.blockHash);
assertEq(_genesis.parentHash, parentHash);
assertEq(_genesis.blockHeight, blockHeight);
assertEq(batchIndex, 0);
}
{
bytes32 _batchId = keccak256(abi.encode(_genesis.blockHash, bytes32(0), 0));
assertEq(rollup.finalizedBatches(0), _batchId);
assertEq(rollup.lastFinalizedBatchID(), _batchId);
(bytes32 batchHash, bytes32 parentHash, uint64 batchIndex, bool verified) = rollup.batches(_batchId);
assertEq(batchHash, _genesis.blockHash);
assertEq(parentHash, bytes32(0));
assertEq(batchIndex, 0);
assertBoolEq(verified, true);
}
// genesis block imported
hevm.expectRevert("genesis block imported");
hevm.expectRevert("Genesis block imported");
rollup.importGenesisBlock(_genesis);
}
function testCommitBlockFailed() public {
function testCommitBatchFailed() public {
rollup.updateOperator(address(1));
IZKRollup.BlockHeader memory _header;
IZKRollup.Layer2Transaction[] memory _txns = new IZKRollup.Layer2Transaction[](0);
IZKRollup.Layer2BlockHeader memory _header;
IZKRollup.Layer2Batch memory _batch;
// not operator call, should revert
hevm.expectRevert("caller not operator");
rollup.commitBlock(_header, _txns);
rollup.commitBatch(_batch);
// import fake genesis
_header.blockHash = bytes32(uint256(1));
rollup.importGenesisBlock(_header);
hevm.startPrank(address(1));
// batch is empty
hevm.expectRevert("Batch is empty");
rollup.commitBatch(_batch);
// block submitted, should revert
_header.blockHash = bytes32(uint256(1));
hevm.expectRevert("Block has been committed before");
rollup.commitBlock(_header, _txns);
_batch.blocks = new IZKRollup.Layer2BlockHeader[](1);
_batch.blocks[0] = _header;
_batch.batchIndex = 0;
_batch.parentHash = bytes32(0);
hevm.expectRevert("Batch has been committed before");
rollup.commitBatch(_batch);
// no parent block, should revert
// no parent batch, should revert
_header.blockHash = bytes32(uint256(2));
hevm.expectRevert("Parent hasn't been committed");
rollup.commitBlock(_header, _txns);
_batch.blocks = new IZKRollup.Layer2BlockHeader[](1);
_batch.blocks[0] = _header;
_batch.batchIndex = 0;
_batch.parentHash = bytes32(0);
hevm.expectRevert("Parent batch hasn't been committed");
rollup.commitBatch(_batch);
// block height mismatch, should revert
// Batch index and parent batch index mismatch
_header.blockHash = bytes32(uint256(2));
_batch.blocks = new IZKRollup.Layer2BlockHeader[](1);
_batch.blocks[0] = _header;
_batch.batchIndex = 2;
_batch.parentHash = bytes32(uint256(1));
hevm.expectRevert("Batch index and parent batch index mismatch");
rollup.commitBatch(_batch);
// BLock parent hash mismatch
_header.blockHash = bytes32(uint256(2));
_header.parentHash = bytes32(0);
_batch.blocks = new IZKRollup.Layer2BlockHeader[](1);
_batch.blocks[0] = _header;
_batch.batchIndex = 1;
_batch.parentHash = bytes32(uint256(1));
hevm.expectRevert("Block parent hash mismatch");
rollup.commitBatch(_batch);
// Block height mismatch
_header.blockHash = bytes32(uint256(2));
_header.parentHash = bytes32(uint256(1));
hevm.expectRevert("Block height and parent block height mismatch");
rollup.commitBlock(_header, _txns);
_header.blockHeight = 2;
hevm.expectRevert("Block height and parent block height mismatch");
rollup.commitBlock(_header, _txns);
_batch.blocks = new IZKRollup.Layer2BlockHeader[](1);
_batch.blocks[0] = _header;
_batch.batchIndex = 1;
_batch.parentHash = bytes32(uint256(1));
hevm.expectRevert("Block height mismatch");
rollup.commitBatch(_batch);
_header.blockHash = bytes32(uint256(2));
_header.parentHash = bytes32(uint256(1));
_header.blockHeight = 0;
_batch.blocks = new IZKRollup.Layer2BlockHeader[](1);
_batch.blocks[0] = _header;
_batch.batchIndex = 1;
_batch.parentHash = bytes32(uint256(1));
hevm.expectRevert("Block height mismatch");
rollup.commitBatch(_batch);
// Block has been commited before
_header.blockHash = bytes32(uint256(1));
_header.parentHash = bytes32(uint256(1));
_header.blockHeight = 1;
_batch.blocks = new IZKRollup.Layer2BlockHeader[](1);
_batch.blocks[0] = _header;
_batch.batchIndex = 1;
_batch.parentHash = bytes32(uint256(1));
hevm.expectRevert("Block has been commited before");
rollup.commitBatch(_batch);
hevm.stopPrank();
}
function testCommitBlock(IZKRollup.BlockHeader memory _header) public {
function testCommitBatch(IZKRollup.Layer2BlockHeader memory _header) public {
if (_header.parentHash == bytes32(0)) {
_header.parentHash = bytes32(uint256(1));
}
@@ -159,30 +219,40 @@ contract ZKRollupTest is DSTestPlus {
}
rollup.updateOperator(address(1));
IZKRollup.Layer2Transaction[] memory _txns = new IZKRollup.Layer2Transaction[](0);
// import fake genesis
IZKRollup.BlockHeader memory _genesis;
IZKRollup.Layer2BlockHeader memory _genesis;
_genesis.blockHash = _header.parentHash;
rollup.importGenesisBlock(_genesis);
_header.blockHeight = 1;
IZKRollup.Layer2Batch memory _batch;
_batch.blocks = new IZKRollup.Layer2BlockHeader[](1);
_batch.blocks[0] = _header;
_batch.batchIndex = 1;
_batch.parentHash = _header.parentHash;
// mock caller as operator
assertEq(rollup.finalizedBlocks(1), bytes32(0));
assertEq(rollup.finalizedBatches(1), bytes32(0));
hevm.startPrank(address(1));
rollup.commitBlock(_header, _txns);
rollup.commitBatch(_batch);
hevm.stopPrank();
(IZKRollup.BlockHeader memory _storedHeader, , bool _verified) = rollup.blocks(_header.blockHash);
assertEq(_header.blockHash, _storedHeader.blockHash);
assertEq(_header.parentHash, _storedHeader.parentHash);
assertEq(_header.baseFee, _storedHeader.baseFee);
assertEq(_header.stateRoot, _storedHeader.stateRoot);
assertEq(_header.blockHeight, _storedHeader.blockHeight);
assertEq(_header.gasUsed, _storedHeader.gasUsed);
assertEq(_header.timestamp, _storedHeader.timestamp);
assertBytesEq(_header.extraData, _storedHeader.extraData);
assertBoolEq(_verified, false);
assertEq(rollup.finalizedBlocks(1), bytes32(0));
// verify block
{
(bytes32 parentHash, , uint64 blockHeight, uint64 batchIndex) = rollup.blocks(_header.blockHash);
assertEq(parentHash, _header.parentHash);
assertEq(blockHeight, _header.blockHeight);
assertEq(batchIndex, _batch.batchIndex);
}
// verify batch
{
bytes32 _batchId = keccak256(abi.encode(_header.blockHash, _header.parentHash, 1));
(bytes32 batchHash, bytes32 parentHash, uint64 batchIndex, bool verified) = rollup.batches(_batchId);
assertEq(batchHash, _header.blockHash);
assertEq(parentHash, _batch.parentHash);
assertEq(batchIndex, _batch.batchIndex);
assertBoolEq(verified, false);
assertEq(rollup.finalizedBatches(1), bytes32(0));
}
}
}