mirror of
https://github.com/scroll-tech/scroll.git
synced 2026-01-10 22:48:14 -05:00
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:
@@ -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
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user