Layer2扩容方案
章节导读
以太坊主网的性能瓶颈一直是区块链行业的关键问题。15 TPS的吞吐量和高昂的Gas费用严重限制了DApp的发展。Layer2扩容方案通过将计算转移到链下,同时继承主网的安全性,成为当前最主流的扩容路线。
本章将深入讲解Layer2的核心技术,包括Optimistic Rollup、ZK Rollup的原理和实现,以及Arbitrum、Optimism、Polygon等主流Layer2方案的技术细节。我们不仅学习理论,还会实现一个简化版的Rollup系统。
学习路线
Layer2基础
├── 扩容方案对比
├── Rollup原理
├── 状态通道
└── 侧链
Optimistic Rollup
├── 欺诈证明
├── 挑战期
├── 取款流程
└── Optimism架构
ZK Rollup
├── 零知识证明
├── zk-SNARK vs zk-STARK
├── 有效性证明
└── zkSync架构
主流方案
├── Arbitrum
├── Polygon
├── StarkNet
└── zkEVM
一、Layer2扩容方案概述
1.1 以太坊的扩容困境
区块链不可能三角
去中心化
/ \
/ \
/ \
/ \
安全性 ——————— 可扩展性
只能同时满足其中两个
以太坊主网现状
| 指标 | 数值 | 说明 |
|---|---|---|
| TPS | 15-30 | 每秒交易数 |
| 出块时间 | 12秒 | 平均出块时间 |
| Gas费用 | 10-100 Gwei | 拥堵时更高 |
| 确认时间 | 5-20分钟 | 等待足够确认 |
示例:一笔简单转账的成本
普通ETH转账: 21,000 Gas
Gas Price: 50 Gwei
成本: 21,000 × 50 = 1,050,000 Gwei = 0.00105 ETH
如果ETH价格 = $2,000
手续费 = $2.1
复杂的DeFi交易可能需要 200,000 - 500,000 Gas
成本可达 $20-$50
1.2 扩容方案分类
Layer1扩容 vs Layer2扩容
Layer1扩容(链上扩容)
├── 分片(Sharding) - ETH 2.0
├── 增大区块大小 - 牺牲去中心化
└── 减少出块时间 - 牺牲安全性
Layer2扩容(链下扩容)
├── State Channel(状态通道)
├── Plasma
├── Rollup
│ ├── Optimistic Rollup
│ └── ZK Rollup
└── Validium
Layer2方案对比
| 方案 | TPS | 安全性 | 取款时间 | 代表项目 |
|---|---|---|---|---|
| Optimistic Rollup | 200-2000 | 继承L1 | 7天 | Arbitrum, Optimism |
| ZK Rollup | 2000-4000 | 继承L1 | 数小时 | zkSync, StarkNet |
| Plasma | 1000+ | 较高 | 7-14天 | Polygon Plasma |
| State Channel | 无限 | 取决于参与者 | 即时 | Lightning Network |
| Validium | 9000+ | 数据可用性风险 | 数小时 | StarkEx |
1.3 Rollup核心思想
Rollup = 将计算搬到链下 + 将数据发布到链上
链上(Layer1)
├── 存储交易数据(压缩后)
├── 验证证明(欺诈证明或有效性证明)
└── 管理资产
链下(Layer2)
├── 执行交易
├── 生成证明
└── 压缩数据
为什么Rollup有效?
1. 计算成本高,数据成本低
- 在L1执行1个交易: 21,000 Gas
- 在L1存储1个交易数据: ~2,000 Gas
- 节省 90%+ 成本
2. 批量处理
- 将100个交易打包成1个Batch
- 分摊固定成本
3. 数据压缩
- 原始交易: 110字节
- 压缩后: 12字节
- 压缩率 89%
二、Optimistic Rollup
2.1 Optimistic Rollup原理
核心思想: 乐观假设 + 欺诈证明
乐观假设:
假设所有提交的交易都是有效的,除非有人证明它是欺诈的
欺诈证明(Fraud Proof):
如果有人提交了无效交易,任何人都可以提交欺诈证明来挑战
工作流程
1. Sequencer收集交易
├── 执行交易
├── 更新状态
└── 生成状态根
2. 提交到L1
├── 压缩交易数据
├── 提交状态根
└── 开启挑战期(7天)
3. 挑战期
├── 任何人可以验证
├── 发现欺诈 → 提交欺诈证明
└── 无人挑战 → 交易最终确认
4. 欺诈证明(如果有)
├── 在L1上重新执行交易
├── 验证状态根
├── 如果不匹配 → Sequencer被罚款
└── 如果匹配 → 挑战者被罚款
2.2 简化版Optimistic Rollup实现
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract SimpleOptimisticRollup {
// Sequencer地址
address public sequencer;
// 状态根
bytes32 public stateRoot;
// 批次信息
struct Batch {
bytes32 stateRoot;
bytes32 transactionsRoot; // 交易的Merkle根
uint256 timestamp;
bool finalized;
}
// 批次ID -> 批次信息
mapping(uint256 => Batch) public batches;
uint256 public batchCounter;
// 挑战期(简化为1小时)
uint256 public constant CHALLENGE_PERIOD = 1 hours;
// 质押金额
uint256 public constant STAKE_AMOUNT = 1 ether;
// 事件
event BatchSubmitted(uint256 indexed batchId, bytes32 stateRoot, bytes32 transactionsRoot);
event BatchFinalized(uint256 indexed batchId);
event FraudProven(uint256 indexed batchId, address indexed challenger);
constructor() {
sequencer = msg.sender;
}
// 提交批次
function submitBatch(
bytes32 newStateRoot,
bytes32 transactionsRoot
) external payable {
require(msg.sender == sequencer, "Not sequencer");
require(msg.value >= STAKE_AMOUNT, "Insufficient stake");
batchCounter++;
batches[batchCounter] = Batch({
stateRoot: newStateRoot,
transactionsRoot: transactionsRoot,
timestamp: block.timestamp,
finalized: false
});
emit BatchSubmitted(batchCounter, newStateRoot, transactionsRoot);
}
// 最终确认批次
function finalizeBatch(uint256 batchId) external {
Batch storage batch = batches[batchId];
require(!batch.finalized, "Already finalized");
require(
block.timestamp >= batch.timestamp + CHALLENGE_PERIOD,
"Challenge period not ended"
);
batch.finalized = true;
stateRoot = batch.stateRoot;
emit BatchFinalized(batchId);
}
// 提交欺诈证明(简化版)
function proveFraud(
uint256 batchId,
bytes calldata transactionData,
bytes32[] calldata merkleProof,
bytes32 preStateRoot,
bytes32 postStateRoot
) external {
Batch storage batch = batches[batchId];
require(!batch.finalized, "Already finalized");
// 1. 验证交易在批次中(Merkle Proof)
bytes32 txHash = keccak256(transactionData);
require(
verifyMerkleProof(merkleProof, batch.transactionsRoot, txHash),
"Invalid merkle proof"
);
// 2. 重新执行交易
bytes32 calculatedPostStateRoot = executeTransaction(preStateRoot, transactionData);
// 3. 验证状态根是否匹配
if (calculatedPostStateRoot != postStateRoot) {
// 证明欺诈成功
delete batches[batchId]; // 删除欺诈批次
// 奖励挑战者
payable(msg.sender).transfer(STAKE_AMOUNT);
emit FraudProven(batchId, msg.sender);
} else {
revert("Not fraud");
}
}
// 验证Merkle Proof
function verifyMerkleProof(
bytes32[] calldata proof,
bytes32 root,
bytes32 leaf
) internal pure returns (bool) {
bytes32 computedHash = leaf;
for (uint256 i = 0; i < proof.length; i++) {
computedHash = keccak256(
abi.encodePacked(
computedHash < proof[i] ? computedHash : proof[i],
computedHash < proof[i] ? proof[i] : computedHash
)
);
}
return computedHash == root;
}
// 执行交易(简化版)
function executeTransaction(
bytes32 preStateRoot,
bytes calldata transactionData
) internal pure returns (bytes32) {
// 实际实现中,这里会重新执行交易并计算新的状态根
// 这里简化为直接哈希
return keccak256(abi.encodePacked(preStateRoot, transactionData));
}
}
2.3 Optimism架构
核心组件
Optimism架构
├── Sequencer
│ ├── 收集交易
│ ├── 执行交易
│ ├── 生成区块
│ └── 提交到L1
│
├── Batch Submitter
│ ├── 压缩交易数据
│ ├── 提交到L1
│ └── 管理Gas成本
│
├── State Commitment Chain (SCC)
│ ├── 存储状态根
│ └── 管理挑战期
│
├── Canonical Transaction Chain (CTC)
│ ├── 存储交易数据
│ └── 保证数据可用性
│
└── Verifier
├── 监听L1
├── 验证批次
└── 提交欺诈证明
L1合约示例
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract CanonicalTransactionChain {
// 批次信息
struct Batch {
uint256 batchIndex;
bytes32 batchRoot;
uint256 batchSize;
uint256 prevTotalElements;
bytes extraData;
}
// 已提交的批次
Batch[] public batches;
// 排序器地址
address public sequencer;
event TransactionBatchAppended(
uint256 indexed batchIndex,
bytes32 batchRoot,
uint256 batchSize,
uint256 prevTotalElements,
bytes extraData
);
constructor(address _sequencer) {
sequencer = _sequencer;
}
// 添加交易批次
function appendSequencerBatch() external {
require(msg.sender == sequencer, "Not sequencer");
// 从calldata中解析批次数据
// 实际实现更复杂,这里简化
uint256 batchIndex = batches.length;
bytes32 batchRoot = keccak256(msg.data);
uint256 batchSize = msg.data.length;
uint256 prevTotalElements = getTotalElements();
batches.push(Batch({
batchIndex: batchIndex,
batchRoot: batchRoot,
batchSize: batchSize,
prevTotalElements: prevTotalElements,
extraData: ""
}));
emit TransactionBatchAppended(
batchIndex,
batchRoot,
batchSize,
prevTotalElements,
""
);
}
// 获取总元素数
function getTotalElements() public view returns (uint256) {
if (batches.length == 0) {
return 0;
}
Batch memory lastBatch = batches[batches.length - 1];
return lastBatch.prevTotalElements + lastBatch.batchSize;
}
// 获取批次数量
function getTotalBatches() public view returns (uint256) {
return batches.length;
}
}
2.4 跨链桥实现
存款(L1 → L2)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract L1Bridge {
// L2桥地址(在L1上记录)
address public l2Bridge;
// 已存款的总量
mapping(address => uint256) public deposits;
event DepositInitiated(
address indexed from,
address indexed to,
uint256 amount
);
constructor(address _l2Bridge) {
l2Bridge = _l2Bridge;
}
// 存款到L2
function depositETH(address to) external payable {
require(msg.value > 0, "Cannot deposit 0");
deposits[msg.sender] += msg.value;
// 发出事件,L2监听此事件并铸造相应的代币
emit DepositInitiated(msg.sender, to, msg.value);
}
// 完成取款(由L2发起)
function finalizeWithdrawal(
address to,
uint256 amount
) external {
// 实际应该验证消息来自L2
// 这里简化
require(address(this).balance >= amount, "Insufficient balance");
payable(to).transfer(amount);
}
}
取款(L2 → L1)
// L2合约(简化)
contract L2Bridge {
address public l1Bridge;
event WithdrawalInitiated(
address indexed from,
address indexed to,
uint256 amount
);
// 发起取款
function withdraw(address to, uint256 amount) external {
// 销毁L2上的ETH
// ...
// 发出事件
emit WithdrawalInitiated(msg.sender, to, amount);
// 实际实现中,需要将此消息提交到L1
// 并等待挑战期结束
}
}
2.5 取款流程详解
完整取款流程
Step 1: 在L2发起取款
├── 用户调用 L2Bridge.withdraw()
├── 销毁L2上的代币
└── 生成取款消息
Step 2: 等待消息被包含在批次中
├── Sequencer将取款消息打包
├── 提交批次到L1
└── 开始挑战期(7天)
Step 3: 挑战期等待
├── 任何人可以验证批次
├── 如果发现欺诈,提交欺诈证明
└── 7天后,批次最终确认
Step 4: 在L1完成取款
├── 用户提供Merkle Proof
├── 证明取款消息在已确认的批次中
└── L1Bridge释放资金
取款证明合约
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract WithdrawalProver {
// 已处理的取款
mapping(bytes32 => bool) public processedWithdrawals;
// 状态承诺链合约
address public scc;
struct WithdrawalMessage {
address from;
address to;
uint256 amount;
uint256 nonce;
}
event WithdrawalFinalized(
address indexed from,
address indexed to,
uint256 amount
);
// 完成取款
function finalizeWithdrawal(
WithdrawalMessage calldata message,
uint256 batchIndex,
bytes32[] calldata merkleProof
) external {
// 1. 计算消息哈希
bytes32 messageHash = keccak256(abi.encode(message));
// 2. 检查是否已处理
require(!processedWithdrawals[messageHash], "Already processed");
// 3. 验证批次已最终确认
require(isBatchFinalized(batchIndex), "Batch not finalized");
// 4. 验证Merkle Proof
bytes32 batchRoot = getBatchRoot(batchIndex);
require(
verifyMerkleProof(merkleProof, batchRoot, messageHash),
"Invalid proof"
);
// 5. 标记为已处理
processedWithdrawals[messageHash] = true;
// 6. 转账
payable(message.to).transfer(message.amount);
emit WithdrawalFinalized(message.from, message.to, message.amount);
}
function isBatchFinalized(uint256 batchIndex) internal view returns (bool) {
// 查询SCC合约
// 简化实现
return true;
}
function getBatchRoot(uint256 batchIndex) internal view returns (bytes32) {
// 查询SCC合约
// 简化实现
return bytes32(0);
}
function verifyMerkleProof(
bytes32[] calldata proof,
bytes32 root,
bytes32 leaf
) internal pure returns (bool) {
bytes32 computedHash = leaf;
for (uint256 i = 0; i < proof.length; i++) {
computedHash = keccak256(
abi.encodePacked(
computedHash < proof[i] ? computedHash : proof[i],
computedHash < proof[i] ? proof[i] : computedHash
)
);
}
return computedHash == root;
}
}
三、ZK Rollup
3.1 ZK Rollup原理
核心思想: 有效性证明(Validity Proof)
与Optimistic Rollup的区别:
Optimistic Rollup: 乐观假设 + 欺诈证明(如果有)
ZK Rollup: 每个批次都包含有效性证明
优势:
取款快(数小时 vs 7天)
更安全(主动验证 vs 被动挑战)
劣势:
生成证明成本高
通用性差(EVM兼容困难)
零知识证明(Zero-Knowledge Proof)
零知识证明允许证明者向验证者证明某个陈述是真的,
而不透露任何额外信息。
例子:
陈述: "我知道x,使得 hash(x) = y"
证明: 提供一个证明π
验证: 验证者可以验证π,确认陈述为真,但不知道x是什么
在Rollup中:
陈述: "这100个交易是有效的,新状态根是S"
证明: zk-SNARK证明
验证: L1合约验证证明(成本极低)
3.2 zk-SNARK vs zk-STARK
| 特性 | zk-SNARK | zk-STARK |
|---|---|---|
| 证明大小 | 小(~200字节) | 大(~100KB) |
| 验证成本 | 低 | 中等 |
| 生成时间 | 快 | 较慢 |
| 可信设置 | 需要 | 不需要 |
| 量子安全 | ||
| 透明性 | 较低 | 高 |
| 代表项目 | zkSync, Polygon zkEVM | StarkNet, StarkEx |
3.3 ZK Rollup架构
ZK Rollup架构
├── Sequencer
│ ├── 收集交易
│ ├── 执行交易
│ └── 生成批次
│
├── Prover
│ ├── 生成zk-SNARK证明
│ ├── 证明交易有效性
│ └── 提交到L1
│
├── L1 Verifier合约
│ ├── 验证zk-SNARK证明
│ ├── 更新状态根
│ └── 管理资产
│
└── L2 VM
├── zkEVM(兼容EVM)
└── Cairo VM(StarkNet)
3.4 简化版ZK Rollup实现
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
// 注意: 这是高度简化的示例,真实的ZK Rollup要复杂得多
contract SimpleZKRollup {
// 状态根
bytes32 public stateRoot;
// 验证者合约(用于验证zk-SNARK证明)
IVerifier public verifier;
// 批次计数器
uint256 public batchCounter;
// 批次信息
struct Batch {
bytes32 stateRoot;
uint256 timestamp;
}
mapping(uint256 => Batch) public batches;
event BatchCommitted(uint256 indexed batchId, bytes32 stateRoot);
constructor(address _verifier) {
verifier = IVerifier(_verifier);
stateRoot = bytes32(0);
}
// 提交批次(包含zk-SNARK证明)
function commitBatch(
bytes32 newStateRoot,
uint256[] calldata publicInputs,
bytes calldata proof
) external {
// 1. 验证zk-SNARK证明
require(
verifier.verify(publicInputs, proof),
"Invalid proof"
);
// 2. 验证公共输入
require(
publicInputs[0] == uint256(stateRoot), // 旧状态根
"Invalid old state root"
);
require(
publicInputs[1] == uint256(newStateRoot), // 新状态根
"Invalid new state root"
);
// 3. 更新状态根(立即生效,无需挑战期!)
stateRoot = newStateRoot;
// 4. 记录批次
batchCounter++;
batches[batchCounter] = Batch({
stateRoot: newStateRoot,
timestamp: block.timestamp
});
emit BatchCommitted(batchCounter, newStateRoot);
}
}
// zk-SNARK验证器接口
interface IVerifier {
function verify(
uint256[] calldata publicInputs,
bytes calldata proof
) external view returns (bool);
}
3.5 zkSync架构
zkSync Era (zkEVM)
zkSync Era特点
├── 完全兼容EVM
├── Solidity/Vyper支持
├── 账户抽象(Account Abstraction)
└── 原生支付Gas用任意代币
技术栈
├── zkEVM电路
├── Boojum证明系统
├── Redshift PLONK
└── FRI-based STARK
zkSync合约示例
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
// zkSync上的合约与以太坊几乎完全相同
contract zkSyncExample {
mapping(address => uint256) public balances;
function deposit() external payable {
balances[msg.sender] += msg.value;
}
function withdraw(uint256 amount) external {
require(balances[msg.sender] >= amount, "Insufficient balance");
balances[msg.sender] -= amount;
payable(msg.sender).transfer(amount);
}
}
// 特殊的zkSync功能
import "@matterlabs/zksync-contracts/l2/system-contracts/Constants.sol";
contract zkSyncSpecial {
// 使用任意代币支付Gas
function payWithToken() external {
// zkSync特有功能
}
// 账户抽象
// 用户可以使用智能合约钱包,支持多签、社交恢复等
}
3.6 StarkNet架构
Cairo语言
// StarkNet使用Cairo语言,而不是Solidity
%lang starknet
from starkware.cairo.common.cairo_builtins import HashBuiltin
// 存储
@storage_var
func balance(user: felt) -> (res: felt):
end
// 查询函数
@view
func get_balance{
syscall_ptr: felt*,
pedersen_ptr: HashBuiltin*,
range_check_ptr
}(user: felt) -> (balance: felt):
let (res) = balance.read(user)
return (res)
end
// 修改状态的函数
@external
func deposit{
syscall_ptr: felt*,
pedersen_ptr: HashBuiltin*,
range_check_ptr
}(amount: felt):
let (sender) = get_caller_address()
let (current_balance) = balance.read(sender)
balance.write(sender, current_balance + amount)
return ()
end
StarkNet的优势
1. 极低的Gas费用
- L2上的交易成本 < $0.01
2. 高吞吐量
- 理论TPS > 10,000
3. STARK证明
- 无需可信设置
- 量子安全
- 高度透明
4. 原生账户抽象
- 所有账户都是智能合约
四、Arbitrum
4.1 Arbitrum vs Optimism
| 特性 | Arbitrum | Optimism |
|---|---|---|
| 欺诈证明 | 交互式多轮 | 单轮 |
| 挑战成本 | 低 | 高 |
| EVM兼容 | 完全兼容 | 完全兼容 |
| 取款时间 | 7天 | 7天 |
| Sequencer | 去中心化计划 | 中心化 |
4.2 交互式欺诈证明
Optimism: 单轮欺诈证明
问题: 需要在L1上重新执行整个批次
成本高: 如果批次包含10,000个交易,需要重新执行全部
Arbitrum: 多轮交互式证明
Step 1: 二分查找争议点
├── Sequencer: "这10,000个交易都正确"
├── Challenger: "不对,有错误"
└── 合约: "那我们二分,前5,000个和后5,000个,哪个有错?"
Step 2: 继续二分
├── Challenger: "后5,000个有错"
├── 合约: "继续二分,第5,001-7,500和7,501-10,000,哪个有错?"
└── ...
Step N: 定位到单个交易
├── 最终定位到某一个交易
└── 在L1上只重新执行这一个交易
优势: 只需要在L1上执行1个交易,而不是10,000个
交互式证明合约示例
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract InteractiveFraudProof {
enum ChallengeState { None, Challenging, Resolved }
struct Challenge {
address challenger;
address sequencer;
uint256 batchId;
uint256 leftBound;
uint256 rightBound;
bytes32 claimedStateRoot;
ChallengeState state;
uint256 round;
}
mapping(uint256 => Challenge) public challenges;
uint256 public challengeCounter;
// 发起挑战
function initiateChallenge(
uint256 batchId,
uint256 numTransactions,
bytes32 claimedStateRoot
) external {
challengeCounter++;
challenges[challengeCounter] = Challenge({
challenger: msg.sender,
sequencer: address(0), // 从批次中获取
batchId: batchId,
leftBound: 0,
rightBound: numTransactions,
claimedStateRoot: claimedStateRoot,
state: ChallengeState.Challenging,
round: 0
});
}
// 二分查找
function bisect(
uint256 challengeId,
bytes32[] calldata intermediateStates
) external {
Challenge storage challenge = challenges[challengeId];
require(challenge.state == ChallengeState.Challenging, "Not challenging");
uint256 numSteps = challenge.rightBound - challenge.leftBound;
require(intermediateStates.length == numSteps + 1, "Invalid states");
// 找到第一个不匹配的状态
for (uint256 i = 0; i < intermediateStates.length - 1; i++) {
// 验证中间状态
// 如果发现不匹配,缩小范围
challenge.leftBound = challenge.leftBound + i;
challenge.rightBound = challenge.leftBound + 1;
challenge.round++;
break;
}
// 如果范围缩小到1个交易,进入最终验证
if (challenge.rightBound - challenge.leftBound == 1) {
finalizeChallenge(challengeId);
}
}
// 最终验证(在L1上执行单个交易)
function finalizeChallenge(uint256 challengeId) internal {
Challenge storage challenge = challenges[challengeId];
// 在L1上重新执行这个交易
// 比较结果
// 如果Sequencer错误,罚款并删除批次
// 如果Challenger错误,罚款Challenger
challenge.state = ChallengeState.Resolved;
}
}
4.3 Arbitrum Nitro
Nitro是Arbitrum的新版本,主要改进:
1. WASM支持
- 使用WebAssembly而不是自定义VM
- 更好的EVM兼容性
2. Geth核心
- 直接使用Geth的EVM实现
- 完全兼容以太坊
3. 更快的执行
- 10x-50x性能提升
4. 更低的费用
- Gas费用降低90%+
五、Polygon
5.1 Polygon生态
Polygon生态
├── Polygon PoS (侧链)
├── Polygon zkEVM (ZK Rollup)
├── Polygon Miden (ZK Rollup)
└── Polygon Avail (数据可用性层)
5.2 Polygon PoS
架构
Polygon PoS = Plasma + PoS
├── 独立的PoS链
├── 100+验证者
├── 检查点提交到以太坊
└── Plasma桥(安全性)
与L2的区别
| 特性 | Polygon PoS | Rollup |
|---|---|---|
| 类型 | 侧链 | Layer2 |
| 共识 | 独立PoS | 无(使用L1) |
| 安全性 | 依赖验证者 | 继承L1 |
| 性能 | 7000 TPS | 2000-4000 TPS |
| 取款 | 1-3小时 | 7天(OR) / 数小时(ZK) |
检查点合约
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract RootChainManager {
// 检查点信息
struct Checkpoint {
bytes32 root;
uint256 start;
uint256 end;
uint256 timestamp;
}
Checkpoint[] public checkpoints;
// 验证者集合
mapping(address => bool) public validators;
event NewCheckpoint(
uint256 indexed checkpointId,
bytes32 root,
uint256 start,
uint256 end
);
// 提交检查点(只有验证者可以)
function submitCheckpoint(
bytes32 root,
uint256 start,
uint256 end,
bytes[] calldata sigs
) external {
// 验证签名(需要2/3+1的验证者签名)
require(verifySignatures(root, start, end, sigs), "Invalid signatures");
checkpoints.push(Checkpoint({
root: root,
start: start,
end: end,
timestamp: block.timestamp
}));
emit NewCheckpoint(checkpoints.length - 1, root, start, end);
}
function verifySignatures(
bytes32 root,
uint256 start,
uint256 end,
bytes[] calldata sigs
) internal view returns (bool) {
// 验证签名逻辑
// 简化实现
return true;
}
}
5.3 Polygon zkEVM
完全兼容EVM的ZK Rollup
Polygon zkEVM特点
├── Type 2 zkEVM(几乎完全兼容)
├── 使用zk-SNARK证明
├── 可直接部署以太坊合约
└── 支持所有以太坊工具(Hardhat, Remix等)
六、跨Rollup通信
6.1 问题
问题: 如何在不同Rollup之间转移资产?
方案1: 通过L1中转
├── Arbitrum → 取款到L1(7天)
└── L1 → 存款到Optimism
方案2: 快速桥
├── 使用流动性池
└── 即时转账
方案3: 原生跨链
├── 共享桥合约
└── 统一消息传递
6.2 快速桥实现
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
// 部署在多个Rollup上
contract FastBridge {
// 流动性提供者
mapping(address => uint256) public liquidity;
// 待处理的转账
mapping(bytes32 => bool) public pendingTransfers;
event TransferInitiated(
address indexed from,
address indexed to,
uint256 amount,
uint256 destinationChainId,
bytes32 transferId
);
event TransferCompleted(
bytes32 indexed transferId,
address indexed to,
uint256 amount
);
// 发起跨链转账
function initiateTransfer(
address to,
uint256 destinationChainId
) external payable {
require(msg.value > 0, "Cannot transfer 0");
bytes32 transferId = keccak256(
abi.encodePacked(
msg.sender,
to,
msg.value,
block.chainid,
destinationChainId,
block.timestamp
)
);
pendingTransfers[transferId] = true;
emit TransferInitiated(
msg.sender,
to,
msg.value,
destinationChainId,
transferId
);
}
// 完成转账(在目标链上)
function completeTransfer(
bytes32 transferId,
address to,
uint256 amount,
bytes calldata proof
) external {
require(!pendingTransfers[transferId], "Already completed");
// 验证proof(来自源链的Merkle Proof)
// 实际实现需要验证L1上的数据
require(liquidity[msg.sender] >= amount, "Insufficient liquidity");
pendingTransfers[transferId] = true;
liquidity[msg.sender] -= amount;
payable(to).transfer(amount);
emit TransferCompleted(transferId, to, amount);
}
// 添加流动性
function addLiquidity() external payable {
liquidity[msg.sender] += msg.value;
}
// 移除流动性
function removeLiquidity(uint256 amount) external {
require(liquidity[msg.sender] >= amount, "Insufficient liquidity");
liquidity[msg.sender] -= amount;
payable(msg.sender).transfer(amount);
}
}
七、数据可用性(Data Availability)
7.1 数据可用性问题
问题: Rollup需要将交易数据发布到L1,以保证任何人都能重建状态
如果不发布数据:
├── Sequencer可以隐藏交易
├── 用户无法验证状态
└── 资金可能被盗
方案:
1. Rollup: 数据发布到L1(昂贵但安全)
2. Validium: 数据存储在链下(便宜但风险)
3. Volition: 用户选择(混合)
7.2 Validium vs Rollup
| 特性 | Rollup | Validium |
|---|---|---|
| 数据位置 | L1 | 链下 |
| 安全性 | 继承L1 | 依赖数据可用性委员会 |
| 成本 | 高 | 低 |
| 性能 | 2000-4000 TPS | 10,000+ TPS |
| 代表项目 | zkSync, Arbitrum | StarkEx, Immutable X |
7.3 数据可用性采样(DAS)
DAS = Data Availability Sampling
目标: 轻节点可以验证数据可用性,而无需下载全部数据
原理:
1. 将数据编码为Reed-Solomon编码
2. 轻节点随机采样少量数据块
3. 如果采样成功,高概率数据可用
八、实战项目:简化版Rollup系统
8.1 项目架构
简化版Rollup
├── L1合约
│ ├── RollupChain.sol (主合约)
│ ├── Bridge.sol (跨链桥)
│ └── Verifier.sol (验证器)
│
├── L2节点
│ ├── Sequencer (排序器)
│ ├── Executor (执行器)
│ └── Prover (证明生成器)
│
└── 前端
├── 钱包连接
├── 存款/取款
└── L2交易
8.2 主合约实现
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract MiniRollup {
// 账户余额(L2状态)
mapping(address => uint256) public balances;
// 状态根
bytes32 public stateRoot;
// 批次
struct Batch {
bytes32 stateRoot;
bytes32 transactionsHash;
uint256 timestamp;
bool finalized;
}
mapping(uint256 => Batch) public batches;
uint256 public batchCount;
// Sequencer
address public sequencer;
// 挑战期
uint256 public constant CHALLENGE_PERIOD = 1 hours; // 简化为1小时
event Deposited(address indexed user, uint256 amount);
event BatchSubmitted(uint256 indexed batchId, bytes32 stateRoot);
event WithdrawalInitiated(address indexed user, uint256 amount);
constructor() {
sequencer = msg.sender;
}
// 存款(L1 -> L2)
function deposit() external payable {
require(msg.value > 0, "Cannot deposit 0");
// 在L2状态中增加余额
balances[msg.sender] += msg.value;
emit Deposited(msg.sender, msg.value);
}
// 提交批次
function submitBatch(
bytes32 newStateRoot,
bytes calldata transactions
) external {
require(msg.sender == sequencer, "Not sequencer");
batchCount++;
bytes32 txHash = keccak256(transactions);
batches[batchCount] = Batch({
stateRoot: newStateRoot,
transactionsHash: txHash,
timestamp: block.timestamp,
finalized: false
});
emit BatchSubmitted(batchCount, newStateRoot);
}
// 最终确认批次
function finalizeBatch(uint256 batchId) external {
Batch storage batch = batches[batchId];
require(!batch.finalized, "Already finalized");
require(
block.timestamp >= batch.timestamp + CHALLENGE_PERIOD,
"Challenge period not ended"
);
batch.finalized = true;
stateRoot = batch.stateRoot;
}
// 发起取款(L2 -> L1)
function initiateWithdrawal(uint256 amount) external {
require(balances[msg.sender] >= amount, "Insufficient balance");
// 在L2状态中减少余额
balances[msg.sender] -= amount;
emit WithdrawalInitiated(msg.sender, amount);
// 实际实现中,这个事件会被包含在批次中
// 批次最终确认后,用户可以在L1上完成取款
}
// 完成取款
function finalizeWithdrawal(
uint256 amount,
uint256 batchId,
bytes32[] calldata proof
) external {
Batch storage batch = batches[batchId];
require(batch.finalized, "Batch not finalized");
// 验证取款消息在批次中(Merkle Proof)
// 简化实现,实际需要验证proof
// 转账
payable(msg.sender).transfer(amount);
}
}
8.3 Sequencer实现(Node.js)
const { ethers } = require('ethers');
class Sequencer {
constructor(provider, rollupAddress, privateKey) {
this.provider = provider;
this.wallet = new ethers.Wallet(privateKey, provider);
this.rollup = new ethers.Contract(rollupAddress, ROLLUP_ABI, this.wallet);
this.pendingTransactions = [];
this.stateTree = {}; // 简化的状态树
}
// 启动Sequencer
async start() {
console.log('Sequencer started');
// 定期提交批次
setInterval(() => {
this.submitBatch();
}, 60000); // 每分钟提交一次
// 监听L1存款事件
this.rollup.on('Deposited', (user, amount) => {
console.log(`Deposit: ${user} deposited ${amount}`);
this.handleDeposit(user, amount);
});
}
// 处理存款
handleDeposit(user, amount) {
// 更新L2状态
if (!this.stateTree[user]) {
this.stateTree[user] = ethers.BigNumber.from(0);
}
this.stateTree[user] = this.stateTree[user].add(amount);
}
// 接收L2交易
async receiveTransaction(tx) {
// 验证交易
if (!this.validateTransaction(tx)) {
throw new Error('Invalid transaction');
}
this.pendingTransactions.push(tx);
}
// 验证交易
validateTransaction(tx) {
const { from, to, amount, signature } = tx;
// 检查余额
if (!this.stateTree[from] || this.stateTree[from].lt(amount)) {
return false;
}
// 验证签名
const message = ethers.utils.solidityKeccak256(
['address', 'address', 'uint256'],
[from, to, amount]
);
const recoveredAddress = ethers.utils.verifyMessage(
ethers.utils.arrayify(message),
signature
);
return recoveredAddress === from;
}
// 执行交易
executeTransaction(tx) {
const { from, to, amount } = tx;
// 更新状态
this.stateTree[from] = this.stateTree[from].sub(amount);
if (!this.stateTree[to]) {
this.stateTree[to] = ethers.BigNumber.from(0);
}
this.stateTree[to] = this.stateTree[to].add(amount);
}
// 提交批次
async submitBatch() {
if (this.pendingTransactions.length === 0) {
console.log('No transactions to submit');
return;
}
console.log(`Submitting batch with ${this.pendingTransactions.length} transactions`);
// 执行所有交易
for (const tx of this.pendingTransactions) {
this.executeTransaction(tx);
}
// 计算新状态根
const newStateRoot = this.calculateStateRoot();
// 序列化交易
const transactionsData = this.serializeTransactions(this.pendingTransactions);
// 提交到L1
try {
const tx = await this.rollup.submitBatch(newStateRoot, transactionsData);
await tx.wait();
console.log(`Batch submitted: ${tx.hash}`);
// 清空待处理交易
this.pendingTransactions = [];
} catch (error) {
console.error('Failed to submit batch:', error);
}
}
// 计算状态根(简化版)
calculateStateRoot() {
const leaves = Object.entries(this.stateTree).map(([address, balance]) => {
return ethers.utils.solidityKeccak256(
['address', 'uint256'],
[address, balance]
);
});
// 构建Merkle树
return this.buildMerkleRoot(leaves);
}
// 构建Merkle根
buildMerkleRoot(leaves) {
if (leaves.length === 0) return ethers.constants.HashZero;
if (leaves.length === 1) return leaves[0];
const newLevel = [];
for (let i = 0; i < leaves.length; i += 2) {
if (i + 1 < leaves.length) {
const combined = ethers.utils.solidityKeccak256(
['bytes32', 'bytes32'],
[leaves[i], leaves[i + 1]]
);
newLevel.push(combined);
} else {
newLevel.push(leaves[i]);
}
}
return this.buildMerkleRoot(newLevel);
}
// 序列化交易
serializeTransactions(transactions) {
// 简化实现
return ethers.utils.hexlify(
ethers.utils.toUtf8Bytes(JSON.stringify(transactions))
);
}
}
// 使用示例
async function main() {
const provider = new ethers.providers.JsonRpcProvider('http://localhost:8545');
const sequencer = new Sequencer(
provider,
'0x...', // Rollup合约地址
'0x...' // Sequencer私钥
);
await sequencer.start();
}
main();
九、Layer2安全性分析
9.1 安全假设
Optimistic Rollup
安全假设:
至少有1个诚实的Verifier
L1正常运行
风险:
⚠️ 如果所有Verifier都不诚实/下线,欺诈交易可能通过
⚠️ Sequencer中心化风险
ZK Rollup
安全假设:
L1正常运行
密码学假设(zk-SNARK/STARK安全)
优势:
不依赖诚实的Verifier
数学保证正确性
风险:
⚠️ Prover中心化
⚠️ 密码学漏洞(理论上)
9.2 常见攻击向量
1. Sequencer审查
问题: Sequencer可能拒绝包含某些交易
缓解:
- 强制包含机制(Force Inclusion)
- 用户可以直接在L1上提交交易
- 去中心化Sequencer
2. MEV (Maximal Extractable Value)
问题: Sequencer可以重排交易以提取MEV
缓解:
- 公平排序(Fair Ordering)
- 加密mempool
- 去中心化Sequencer
3. 数据扣留(Data Withholding)
问题: Sequencer提交状态根但不发布交易数据
Rollup: 必须发布数据到L1,否则批次无效
Validium: 风险较高,依赖数据可用性委员会
十、总结
核心要点
Rollup是当前最主流的扩容方案
- 继承L1安全性
- 大幅降低成本(10-100x)
- 提升性能(10-100x)
Optimistic Rollup vs ZK Rollup
- OR: 取款慢(7天),EVM兼容好,成本低
- ZK: 取款快(数小时),安全性更高,成本高
主流方案
- Arbitrum: 交互式欺诈证明
- Optimism: 单轮欺诈证明
- zkSync: zkEVM
- StarkNet: Cairo VM
关键技术
- 欺诈证明/有效性证明
- 数据压缩
- 跨链桥
- 数据可用性
下一步学习
- 11-跨链技术.md - 学习不同区块链之间的互操作
- 13-链下数据与预言机.md - 了解如何将链下数据引入链上
实战建议
- 先在测试网部署和测试
- 了解不同Rollup的权衡
- 根据应用场景选择合适的方案
- 关注Sequencer去中心化进展
- 考虑跨Rollup互操作性