# Zksync合约解析
用户可以layer2或者layer1(priority operation)上创建交易.
处理完用户交易请求后,operator会打包区块,并创建一个rollup操作。operator会提交block commitment到合约。zksync合约验证proof是否为true,为true则可以确定新状态。

## 1 发起交易
以depositETH为例子,
会将用户请求encode成pubdata,
```
abi.encodePacked(
uint8(OpType.Deposit),
bytes4(0), // accountId (ignored) (update when ACCOUNT_ID_BYTES is changed)
op.tokenId, // tokenId
op.amount, // amount
op.owner // owner
```
然后放入priorityRequests队列中(用来存储链上交易的队列),可以看一下 PriorityOperation 的数据结构(表示layer1链上的交易,优先级较高)。
```
/// @notice Priority Operation container
/// @member hashedPubData Hashed priority operation public data
/// @member expirationBlock Expiration block number (ETH block) for this request (must be satisfied before)
/// @member opType Priority operation type
struct PriorityOperation {
bytes20 hashedPubData; //交易数据的hash
uint64 expirationBlock; //超时时间
Operations.OpType opType; //操作类型
}
```
这个队列存储用户这笔交易pubdata的hash,超时时间,和操作类型;
```
priorityRequests[nextPriorityRequestId] = PriorityOperation({
hashedPubData: hashedPubData,
expirationBlock: expirationBlock,
opType: _opType
});
```
并发出事件,后续Server服务监听处理。
```
emit NewPriorityRequest(msg.sender, nextPriorityRequestId, _opType, _pubData, uint256(expirationBlock));
```
## 2 提交区块 commitblock
operator提交区块函数如下:
```
function commitBlocks(StoredBlockInfo memory _lastCommittedBlockData, CommitBlockInfo[] memory _newBlocksData)
external
nonReentrant
```
- _lastCommittedBlockData: 上一次已经committed的block,committed的区块在链上为StoredBlockInfo形式存储.
- _newBlocksData: 新commit的block.
先看数据结构
```
/// @notice Data needed to process onchain operation from block public data.
struct OnchainOperationData {
bytes ethWitness; // 只在ChangePubKey类型交易的时候用到
uint32 publicDataOffset; // 每笔交易在总publicdata里的偏移
}
```
以及提交区块的数据结构
```
/// @notice Data needed to commit new block
struct CommitBlockInfo {
bytes32 newStateHash; //新的状态hash,只在createBlockCommitment时候用一下,后端传入。
bytes publicData; //打包的交易数据
uint256 timestamp;
OnchainOperationData[] onchainOperations; //待处理交易,里面包含每笔交易的偏移,以及ethwitness
uint32 blockNumber; //区块高度
uint32 feeAccount;
}
```
commitBlock函数最终返回StoredBlockInfo, 保存在链上。它的作用主要是对交易数据生成hash。
```
struct StoredBlockInfo {
uint32 blockNumber; // rollup block number
uint64 priorityOperations; // 本区块priority operations 的数量
bytes32 pendingOnchainOperationsHash; // 所有交易的hash
uint256 timestamp;
bytes32 stateHash; //root hash of the rollup account tree state
bytes32 commitment; //rollup 区块承诺
}
```
然后依次执行每个commitBlockInfo,主要调用commitOneBlock函数
- 1 检查时间戳
- 2 执行collectOnchainOps(_newBlock)函数。
计算出 所有操作数据pubdata的hash, 优先交易的数量,以及onchainOperation的偏移。
```
/// @dev processableOperationsHash - hash of the all operations that needs to be executed (Deposit, Exits, ChangPubKey)
/// @dev priorityOperationsProcessed - number of priority operations processed in this block (Deposits, FullExits)
/// @dev offsetsbment - array where 1 is stored in chunk where onchainOperation begins and other are 0 (used in commitments)
(
bytes32 pendingOnchainOpsHash,
uint64 priorityReqCommitted,
bytes memory onchainOpsOffsetCommitment
) = collectOnchainOps(_newBlock);
```
- 3 对区块创建区块的commitment。
区块的commit:包含块高,feeAccount, 上一个块stateHash,当前块状态hash,时间戳,以及当前块的数据交易数据的整体hash.
```
function createBlockCommitment(
StoredBlockInfo memory _previousBlock,CommitBlockInfo memory _newBlockData, bytes memory_offsetCommitment
) internal view returns (bytes32 commitment) {
bytes32 hash = sha256(abi.encodePacked(uint256(_newBlockData.blockNumber), uint256(_newBlockData.feeAccount)));
hash = sha256(abi.encodePacked(hash, _previousBlock.stateHash));
hash = sha256(abi.encodePacked(hash, _newBlockData.newStateHash));
hash = sha256(abi.encodePacked(hash, uint256(_newBlockData.timestamp)));
bytes memory pubdata = abi.encodePacked(_newBlockData.publicData, _offsetCommitment);
commitment = sha256(abi.encodePacked(hash, publicData)) // blockcommit
```
最后返回
```
StoredBlockInfo(
_newBlock.blockNumber,
priorityReqCommitted,
pendingOnchainOpsHash,
_newBlock.timestamp,
_newBlock.newStateHash,
commitment
);
```
可见stateHash是由传入的newBlock里面的newStateHash直接赋值的。 (需要进一步看后端的newStateHash的生成逻辑)
## 3 proveBlock 验证零知识证明
```
function proveBlocks(StoredBlockInfo[] memory _committedBlocks, ProofInput memory _proof) external nonReentrant
```
主要逻辑是验证proof,并更改totalBlocksProven(当前已经证明的block)状态,传入已经commitBlock数组。
```
bool success = verifier.verifyAggregatedBlockProof(
_proof.recursiveInput,
_proof.proof,
_proof.vkIndexes, //此参数用于获取对应VK
_proof.commitments,
_proof.subproofsLimbs
);
```
## 4 executeOneBlock, 执行block交易,处理资金充提;
执行区块,完成高优先级交易,以及处理资金提取
函数如下:
```
executeBlocks(ExecuteBlockInfo[] memory _blocksData, bool _completeWithdrawals)
```
可以查看
```
struct ExecuteBlockInfo {
StoredBlockInfo storedBlock;
bytes[] pendingOnchainOpsPubdata;
}
```
根据pendingOnchainOpsPubdata执行交易。
然后更改各存储状态,主要是更改最新请求的ID和已执行区块数量。
```
firstPriorityRequestId += priorityRequestsExecuted;
totalCommittedPriorityRequests -= priorityRequestsExecuted;
totalOpenPriorityRequests -= priorityRequestsExecuted;
totalBlocksExecuted += nBlocks;
```
## 证明部分
### verify合约
```
function verifyAggregatedBlockProof(
uint256[] memory _recursiveInput,
uint256[] calldata _proof,
uint8[] memory _vkIndexes,
uint256[] memory _individualVksInputs,
uint256[16] memory _subproofsLimbs
) external view returns (bool)
```
- _recursiveInput - input of the recursive aggregated circuit
- _vkIndexes - index of the verification keys in the aggregated verification keys tree
- _individualVksInputs - block commitments to be verified
- _subproofsLimbs - data needed for recursive proof verification
### 获取VK
#### 四种VK获取
1 KeysWithPlonkVerifier合约, 获取VK有
1 function getVkAggregated(uint32 _proofs) internal pure returns (VerificationKey memory vk) {
if (_proofs == uint32(1)) { return getVkAggregated1(); }
else if (_proofs == uint32(4)) { return getVkAggregated4(); }
else if (_proofs == uint32(8)) { return getVkAggregated8(); }
}
2 getVkExit()
### 区块,账户电路
#### 1 block commitment
区块电路:一批交易作用后,从上一个区块状态到新的区块状态的状态转移函数.
Public inputs(电路的公开输入):
- pub_data_commitment:
区块的新状态的承诺,是一个hash,包含以下内容:
old_root, new_root, block_number, validator_address, pub_data_rolling_hash , timestamp, onhain_ops_offset_commitment
Witness(电路的见证):
1. old_root,
2. new_root,
3. block_number,
4. validator_address,
5. pub_data,
6. pub_data_rolling_hash,
7. list of transactions,
8. state Merkle trees.
- oncahin_ops_offset_commitment
是字节数组,其中一个字节对应于每个公共数据块(chunk),如果数据块是onchain操作的第一个数据块,则被设置为1,否则为0。
#### 2 账户树
主要有accout tree 和 balance tree;
account tree height(24)
- nonce
- pubkey_hash //RollupPubkeyHash
- address //EthAddress
- state_tree_root
balance tree height(8)
即各种token的余额

整个状态是由账户和Token信息组成的两层Merkle树。账户是以追加方式一个个添加。账户信息比较少的情况下,存在大量的空节点。
有一个主要的account tree,并且叶子账户都有一个余额树。
整个只维护状态树的root即可。
在执行完当前Operation后,当前的branch的相关信息(nonce,balance等等)都会更新。需要更新一下对应的状态根,因为下一个Operation需要在新的状态根上更新。注意,下一个Operation提供的branch witness信息是在上一个Operation更新的基础上产生的。电路只需要证明新的Root正确即可。这个也是原因,整个证明电路并没有维护整个状态树的原因。
## 疑问

order没有tree。 更改状态都是在account tree上
## 参考链接:
- https://github.com/matter-labs/zksync/blob/master/docs/protocol.md#rollup-operation-8
- https://docs.zksync.io/dev/swaps/#atomic-swaps
- https://mp.weixin.qq.com/s/lTuKbI2S1tOHeYYNm9A2dQ
- https://docs.starkware.co/starkex/oracle-price-tick.html