HiHuo
首页
博客
手册
工具
关于
首页
博客
手册
工具
关于
  • Web3 完整技术体系

    • Web3 完整技术体系
    • 区块链基础与密码学
    • 第一章:比特币原理与实现
    • 第二章:以太坊架构与核心概念
    • Solidity智能合约开发基础
    • 04-Solidity进阶与安全
    • 05-ERC标准详解
    • 06-DeFi核心协议-去中心化交易所
    • 07-DeFi核心协议-借贷协议
    • 08-DeFi核心协议-稳定币
    • NFT与元宇宙
    • Layer2扩容方案
    • 跨链技术
    • Web3前端开发
    • 链下数据与预言机
    • 智能合约测试与部署
    • MEV与交易优化
    • DAO治理
    • 项目实战:完整DeFi借贷协议

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 以太坊的扩容困境

区块链不可能三角

       去中心化
         /    \
        /      \
       /        \
      /          \
   安全性 ——————— 可扩展性

只能同时满足其中两个

以太坊主网现状

指标数值说明
TPS15-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 Rollup200-2000继承L17天Arbitrum, Optimism
ZK Rollup2000-4000继承L1数小时zkSync, StarkNet
Plasma1000+较高7-14天Polygon Plasma
State Channel无限取决于参与者即时Lightning Network
Validium9000+数据可用性风险数小时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-SNARKzk-STARK
证明大小小(~200字节)大(~100KB)
验证成本低中等
生成时间快较慢
可信设置需要不需要
量子安全
透明性较低高
代表项目zkSync, Polygon zkEVMStarkNet, 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

特性ArbitrumOptimism
欺诈证明交互式多轮单轮
挑战成本低高
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 PoSRollup
类型侧链Layer2
共识独立PoS无(使用L1)
安全性依赖验证者继承L1
性能7000 TPS2000-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

特性RollupValidium
数据位置L1链下
安全性继承L1依赖数据可用性委员会
成本高低
性能2000-4000 TPS10,000+ TPS
代表项目zkSync, ArbitrumStarkEx, 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: 风险较高,依赖数据可用性委员会

十、总结

核心要点

  1. Rollup是当前最主流的扩容方案

    • 继承L1安全性
    • 大幅降低成本(10-100x)
    • 提升性能(10-100x)
  2. Optimistic Rollup vs ZK Rollup

    • OR: 取款慢(7天),EVM兼容好,成本低
    • ZK: 取款快(数小时),安全性更高,成本高
  3. 主流方案

    • Arbitrum: 交互式欺诈证明
    • Optimism: 单轮欺诈证明
    • zkSync: zkEVM
    • StarkNet: Cairo VM
  4. 关键技术

    • 欺诈证明/有效性证明
    • 数据压缩
    • 跨链桥
    • 数据可用性

下一步学习

  • 11-跨链技术.md - 学习不同区块链之间的互操作
  • 13-链下数据与预言机.md - 了解如何将链下数据引入链上

实战建议

  1. 先在测试网部署和测试
  2. 了解不同Rollup的权衡
  3. 根据应用场景选择合适的方案
  4. 关注Sequencer去中心化进展
  5. 考虑跨Rollup互操作性
Prev
NFT与元宇宙
Next
跨链技术