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

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

第二章:以太坊架构与核心概念

2.1 以太坊概述

以太坊(Ethereum)是由Vitalik Buterin在2013年提出的区块链平台,于2015年正式启动。与比特币专注于数字货币不同,以太坊的目标是成为"世界计算机",提供一个图灵完备的去中心化计算平台。

2.1.1 以太坊的设计理念

以太坊的核心设计理念包括:

  1. 通用性:不仅是货币,而是可编程的区块链平台
  2. 图灵完备:支持任意复杂的计算逻辑
  3. 智能合约:自动执行的程序,无需中介
  4. 去中心化应用:DApp可在以太坊上运行
  5. 开发者友好:提供完善的开发工具和生态

2.1.2 以太坊与比特币的核心区别

特性比特币以太坊
定位数字货币智能合约平台
账本模型UTXO模型账户模型
脚本语言非图灵完备图灵完备(Solidity等)
出块时间约10分钟约12秒
共识机制PoW(SHA-256)PoW → PoS(2022年合并)
状态存储简单(UTXO集)复杂(世界状态树)
Gas机制无有(防止无限循环)
供应上限2100万枚无硬性上限

2.1.3 以太坊的发展历程

以太坊经历了多个重要阶段:

Frontier(前沿,2015年7月)

  • 初始版本,仅供开发者测试
  • PoW共识,Ethash算法
  • 基础功能上线

Homestead(家园,2016年3月)

  • 首个稳定版本
  • 移除"Canary"合约
  • 改进Gas机制

Metropolis(大都会)

  • Byzantium(拜占庭,2017年10月):引入zk-SNARKs,难度炸弹延迟
  • Constantinople(君士坦丁堡,2019年2月):优化Gas成本,区块奖励降至2 ETH

The Merge(合并,2022年9月)

  • 从PoW切换到PoS
  • 信标链与主网合并
  • 能耗降低99.95%

Shanghai/Capella(上海升级,2023年4月)

  • 启用质押提款功能
  • 优化EVM性能

未来计划:

  • The Surge:通过分片提升扩展性
  • The Scourge:去中心化排序,抗MEV
  • The Verge:无状态客户端,Verkle树
  • The Purge:简化协议,清理历史数据
  • The Splurge:其他重要升级

2.2 账户模型详解

以太坊采用账户模型(Account Model),这是与比特币UTXO模型的最大区别之一。

2.2.1 账户类型

以太坊有两种账户类型:

外部账户(EOA - Externally Owned Account)

结构:
{
    Nonce: 交易计数器
    Balance: 以太币余额(单位:Wei)
    StorageRoot: 空(0x)
    CodeHash: 空代码哈希
}

特点:
- 由私钥控制
- 可以发起交易
- 无关联代码
- 创建账户无需Gas

合约账户(Contract Account)

结构:
{
    Nonce: 创建的合约数量
    Balance: 以太币余额
    StorageRoot: 存储树根哈希(状态变量)
    CodeHash: 合约代码哈希
}

特点:
- 由代码控制
- 不能主动发起交易
- 包含智能合约代码
- 创建需要Gas费用

2.2.2 账户地址生成

EOA地址生成:

1. 生成私钥(256位随机数)
   PrivateKey = random(256 bits)

2. 计算公钥(使用secp256k1)
   PublicKey = PrivateKey × G

3. 计算公钥哈希
   Hash = Keccak256(PublicKey)

4. 取后20字节作为地址
   Address = Hash[12:32]

示例:
私钥: 0x1234567890abcdef...
公钥: 0x04xyz...(64字节)
哈希: 0xabc...def(32字节)
地址: 0x...(20字节,40个十六进制字符)

合约地址生成:

方式1:CREATE操作码
Address = Keccak256(RLP(sender, nonce))[12:32]

方式2:CREATE2操作码(确定性部署)
Address = Keccak256(0xff, sender, salt, Keccak256(bytecode))[12:32]

优势:
- CREATE2允许预先计算地址
- 适用于工厂模式和跨链部署

2.2.3 账户模型的优势与劣势

优势:

  1. 简洁直观:余额直接记录在账户中,易于理解
  2. 状态管理:天然支持复杂状态存储(智能合约)
  3. Gas优化:不需要查找和验证多个UTXO
  4. 原子性:交易要么全部成功,要么全部失败
  5. 可编程性:合约可以维护复杂的内部状态

劣势:

  1. 隐私性较弱:所有余额变化都关联到同一地址
  2. 并发性受限:同一账户的交易必须按Nonce顺序执行
  3. 状态膨胀:全局状态不断增长
  4. 重放攻击:需要Nonce和ChainID防护

2.3 世界状态与状态树

2.3.1 世界状态(World State)

以太坊的世界状态是所有账户状态的集合,可以理解为一个巨大的键值对映射:

WorldState = {
    Address1 → Account1,
    Address2 → Account2,
    ...
    AddressN → AccountN
}

每个Account包含:
{
    nonce: uint64
    balance: *big.Int
    storageRoot: Hash (指向存储树)
    codeHash: Hash (合约代码哈希)
}

世界状态在每个区块执行后都会更新,形成新的状态根(State Root)。

2.3.2 Merkle Patricia Trie(MPT)

以太坊使用改进的Merkle树结构:Merkle Patricia Trie(MPT),结合了Merkle树和Patricia树的优点。

MPT特点:

  1. 确定性:相同数据总是产生相同的树结构
  2. 高效验证:通过Merkle证明验证数据存在性
  3. 压缩路径:Patricia树压缩相同前缀
  4. 动态更新:支持高效的增删改查

MPT节点类型:

// 扩展节点(Extension Node)
type ExtensionNode struct {
    SharedNibbles []byte  // 共享的路径片段
    Next          Hash    // 下一个节点的哈希
}

// 分支节点(Branch Node)
type BranchNode struct {
    Branches [16]Hash  // 16个分支(0-F)
    Value    []byte    // 可选的值(如果路径在此结束)
}

// 叶子节点(Leaf Node)
type LeafNode struct {
    Path  []byte  // 剩余路径
    Value []byte  // 实际数据
}

MPT示例:

存储以下键值对:

"dog" → "puppy"
"dodge" → "coin"
"horse" → "stallion"

转换为十六进制路径:

"dog"   → 0x646f67
"dodge" → 0x646f646765
"horse" → 0x686f727365

构建的MPT结构:

                  Root
                   |
              Extension(0x)
                   |
              Branch Node
              /          \
         (0x64)          (0x68)
           |               |
    Extension(0x6f)    Extension(0x6f727365)
           |               |
      Branch Node       Leaf("" → "stallion")
       /       \
   (0x67)   (0x646765)
     |           |
  Leaf       Leaf
("" →      ("" → "coin")
"puppy")

2.3.3 三种状态树

以太坊实际维护三种MPT:

1. 状态树(State Trie)

Key: Keccak256(Address)
Value: RLP([nonce, balance, storageRoot, codeHash])

作用:存储所有账户的基本信息
根哈希:包含在区块头的stateRoot字段

2. 存储树(Storage Trie)

Key: Keccak256(StorageKey)
Value: StorageValue

作用:每个合约账户都有自己的存储树
根哈希:包含在账户的storageRoot字段

示例:
合约状态变量:
mapping(address => uint256) balances;

存储位置:
key = Keccak256(address, slot)
value = balance

3. 交易树(Transaction Trie)

Key: RLP(transactionIndex)
Value: Transaction Data

作用:存储区块内的所有交易
根哈希:包含在区块头的transactionRoot字段
特点:每个区块一个独立的交易树

4. 收据树(Receipt Trie)

Key: RLP(transactionIndex)
Value: Transaction Receipt

Receipt包含:
- Status(成功/失败)
- CumulativeGas(累计Gas)
- Logs(事件日志)
- Bloom Filter(日志布隆过滤器)

根哈希:包含在区块头的receiptRoot字段

2.3.4 状态根的作用

Block Header {
    ...
    StateRoot: 世界状态树根哈希
    TransactionRoot: 交易树根哈希
    ReceiptRoot: 收据树根哈希
    ...
}

状态根的重要性:

  1. 完整性验证:一个哈希代表整个世界状态
  2. 轻节点验证:只需区块头即可验证状态
  3. 历史回溯:可以查询任意历史区块的状态
  4. 共识保证:所有节点必须达成相同的状态根

状态转换:

StateRoot_N = ApplyTransactions(StateRoot_N-1, Transactions_N)

执行流程:
1. 从StateRoot_N-1加载世界状态
2. 按顺序执行Transactions_N中的每笔交易
3. 更新受影响账户的状态
4. 计算新的StateRoot_N
5. 所有节点必须得到相同的StateRoot_N

2.4 交易结构与生命周期

2.4.1 交易结构

以太坊交易包含以下字段:

type Transaction struct {
    Nonce    uint64      // 发送者交易计数
    GasPrice *big.Int    // Gas价格(Wei/Gas)
    GasLimit uint64      // Gas上限
    To       *Address    // 接收地址(nil表示合约创建)
    Value    *big.Int    // 转账金额(Wei)
    Data     []byte      // 交易数据/合约调用
    V, R, S  *big.Int    // ECDSA签名
}

// EIP-1559后的新交易类型
type DynamicFeeTx struct {
    ChainID    *big.Int
    Nonce      uint64
    GasTipCap  *big.Int    // 小费上限(优先费)
    GasFeeCap  *big.Int    // Gas费用上限
    GasLimit   uint64
    To         *Address
    Value      *big.Int
    Data       []byte
    AccessList []AccessTuple  // EIP-2930访问列表
    V, R, S    *big.Int
}

交易类型:

  1. 转账交易:To指向EOA,Data为空
  2. 合约调用:To指向合约地址,Data包含函数调用
  3. 合约创建:To为nil,Data包含合约字节码

2.4.2 交易签名

以太坊使用ECDSA签名,并包含ChainID防止重放攻击:

签名过程:
1. 构造交易数据
   txData = RLP([nonce, gasPrice, gasLimit, to, value, data, chainID, 0, 0])

2. 计算交易哈希
   txHash = Keccak256(txData)

3. 使用私钥签名
   (r, s, v) = ECDSA_Sign(privateKey, txHash)

4. 计算恢复标识
   v = recoveryID + 35 + 2 × chainID  (EIP-155)

验证过程:
1. 从签名恢复公钥
   publicKey = ECDSA_Recover(txHash, v, r, s)

2. 从公钥计算地址
   sender = Keccak256(publicKey)[12:32]

3. 验证发送者
   确保sender有足够余额和正确的nonce

EIP-155(防重放攻击):

主网:chainID = 1
Ropsten测试网:chainID = 3
Rinkeby测试网:chainID = 4
Goerli测试网:chainID = 5
Sepolia测试网:chainID = 11155111

v值计算:
v = {27, 28}  (原始ECDSA)
v = {35 + 2×chainID, 36 + 2×chainID}  (EIP-155)

主网示例:
v ∈ {37, 38}  (35 + 2×1, 36 + 2×1)

2.4.3 交易生命周期

完整的交易生命周期包括以下阶段:

1. 创建与签名

// Web3.js示例
const tx = {
    from: '0xSenderAddress',
    to: '0xReceiverAddress',
    value: web3.utils.toWei('1', 'ether'),
    gas: 21000,
    gasPrice: web3.utils.toWei('50', 'gwei'),
    nonce: await web3.eth.getTransactionCount(senderAddress)
};

const signedTx = await web3.eth.accounts.signTransaction(tx, privateKey);

2. 广播到网络

用户 → 连接节点 → 内存池(Mempool) → 全网传播

广播协议:
1. 使用RLPx协议发送交易
2. 节点验证交易有效性
3. 加入本地待处理交易池
4. 通过gossip协议传播

3. 进入交易池(Mempool)

交易池管理:
1. 待处理队列(Pending):可执行的交易
2. 队列(Queued):Nonce不连续的交易
3. 优先级排序:按Gas价格排序
4. 容量限制:防止内存溢出

验证规则:
- 签名有效性
- Nonce正确性(≥ 账户当前nonce)
- 余额充足(value + gasLimit × gasPrice)
- Gas价格满足最低要求
- 交易格式正确

4. 矿工/验证者选择

交易选择策略:
1. 按Gas价格降序排序
2. 优先选择高Gas价格交易
3. 考虑Nonce顺序(同一账户)
4. 限制区块Gas总量(Gas Limit)

EIP-1559后:
1. 基础费用(Base Fee)自动调整
2. 优先费(Priority Fee)给矿工
3. 用户设置最大费用(Max Fee)
4. 超出基础费用部分被销毁

5. 执行与打包

执行流程:
1. 检查发送者余额和nonce
2. 扣除初始Gas费用(gasLimit × gasPrice)
3. 执行交易(转账或合约调用)
4. 计算实际Gas消耗
5. 退还未使用的Gas
6. 更新账户状态(余额、nonce、存储)
7. 生成收据和日志
8. 更新状态根

伪代码:
if sender.balance < value + gasLimit × gasPrice {
    return "Insufficient funds"
}
if sender.nonce != tx.nonce {
    return "Invalid nonce"
}

sender.balance -= gasLimit × gasPrice
gasUsed = ExecuteTransaction(tx)
sender.balance += (gasLimit - gasUsed) × gasPrice
sender.nonce += 1

6. 确认与最终性

确认阶段:
- 0确认:交易在内存池中
- 1确认:交易被打包进区块
- 6确认:相对安全(约72秒)
- 32确认:高安全性(约6.4分钟)

PoS后的最终性:
- Justified:2/3验证者投票确认
- Finalized:连续两个Epoch被确认(约12.8分钟)
- 最终性后交易不可逆

2.4.4 Nonce机制

Nonce是防止重放攻击和保证交易顺序的关键:

Nonce特性:
1. 从0开始,每笔交易递增1
2. 必须连续,不能跳过
3. 同一账户的交易按Nonce顺序执行

示例:
当前账户nonce = 5

情况1:发送nonce=5的交易 → 立即执行
情况2:发送nonce=6的交易 → 等待nonce=5执行
情况3:发送nonce=4的交易 → 拒绝(已使用)

问题场景:
用户发送:
Tx1 (nonce=10, gasPrice=20 Gwei)
Tx2 (nonce=11, gasPrice=50 Gwei)

即使Tx2 Gas更高,也必须等Tx1执行

加速/取消交易:

方法1:提高Gas价格(Replace-by-Fee)
- 发送相同nonce的交易
- 提高Gas价格(至少提高10%)
- 新交易会替换旧交易

示例:
原交易:nonce=10, gasPrice=20 Gwei
新交易:nonce=10, gasPrice=22 Gwei (提高10%)

方法2:取消交易
- 发送nonce相同、to为自己、value=0的交易
- 使用更高的Gas价格
- 占用该nonce位置,原交易失效

2.5 Gas机制深度解析

2.5.1 为什么需要Gas

Gas机制是以太坊的核心创新,解决以下问题:

  1. 防止无限循环:图灵完备导致的停机问题
  2. 资源计量:量化计算、存储、带宽消耗
  3. 经济激励:补偿矿工/验证者的资源消耗
  4. 防止垃圾交易:提高攻击成本
  5. 优先级排序:市场化的交易排序机制

图灵完备的代价:

// 危险的无限循环(无Gas会导致网络瘫痪)
contract Dangerous {
    function infiniteLoop() public {
        while(true) {
            // 永远不会停止
        }
    }
}

// Gas机制解决方案
// 当Gas耗尽,交易回滚,状态不变

2.5.2 Gas的基本概念

核心概念:
1. Gas:计量单位,衡量计算复杂度
2. Gas Price:用户愿意为每单位Gas支付的价格(Gwei)
3. Gas Limit:用户愿意为交易支付的最大Gas数量
4. Gas Used:实际消耗的Gas数量

计算公式:
交易费用 = Gas Used × Gas Price

单位换算:
1 Ether = 10^9 Gwei = 10^18 Wei
1 Gwei = 10^9 Wei

示例计算:

简单转账:
Gas Used: 21,000
Gas Price: 50 Gwei
Total Fee = 21,000 × 50 = 1,050,000 Gwei
         = 0.00105 ETH
         ≈ $2 (假设ETH价格=$2000)

复杂合约调用:
Gas Used: 150,000
Gas Price: 100 Gwei
Total Fee = 150,000 × 100 = 15,000,000 Gwei
         = 0.015 ETH
         ≈ $30

2.5.3 Gas消耗明细

不同操作消耗的Gas量:

基础操作:
ADD/SUB/MUL/DIV       : 3-5 Gas
KECCAK256 (哈希)      : 30 Gas + 6 Gas/word
BALANCE (查询余额)     : 2600 Gas (冷访问)
SLOAD (读取存储)       : 2100 Gas (冷访问), 100 Gas (热访问)
SSTORE (写入存储)      : 20,000 Gas (从零到非零)
                      : 5,000 Gas (修改非零值)
                      : 2,900 Gas (删除,退还15,000)
CALL (外部调用)        : 2600 Gas + 传输成本
CREATE (创建合约)      : 32,000 Gas + 部署代码成本
SELFDESTRUCT          : 5,000 Gas (有退款)

交易基础成本:
Base Fee              : 21,000 Gas (所有交易)
Calldata (零字节)      : 4 Gas/byte
Calldata (非零字节)    : 16 Gas/byte
合约创建额外成本       : 32,000 Gas

示例:计算合约调用Gas

contract Example {
    uint256 public value;  // 存储插槽0

    function setValue(uint256 _value) public {
        value = _value;
    }
}

调用setValue(123)的Gas消耗:
1. 基础交易成本:21,000 Gas
2. Calldata:
   - 函数选择器:4字节 × 16 = 64 Gas
   - 参数(123):32字节 × 16 = 512 Gas
3. SSTORE操作:
   - 从0改为123:20,000 Gas (假设初始为0)
4. 执行开销:约2,000 Gas

总计:≈ 43,576 Gas

2.5.4 EIP-1559:新Gas模型

2021年8月的伦敦升级引入EIP-1559,彻底改变了Gas机制:

旧模型(拍卖模式):

用户设置Gas Price(出价)
矿工选择高价交易
费用全部给矿工
价格波动大,难以预测

新模型(EIP-1559):

组成部分:
1. Base Fee(基础费用):
   - 协议自动调整
   - 随区块拥堵度动态变化
   - 被销毁(Burn),不给验证者

2. Priority Fee(优先费/小费):
   - 用户自定义
   - 给验证者的激励
   - 加速交易打包

3. Max Fee(最大费用):
   - 用户愿意支付的上限
   - 实际费用 = min(Base Fee + Priority Fee, Max Fee)

计算公式:
实际Gas Price = Base Fee + min(Priority Fee, Max Fee - Base Fee)
未使用部分退还用户

Base Fee调整算法:

def calculate_base_fee(parent_base_fee, parent_gas_used, parent_gas_limit):
    """
    目标:保持区块50%满(15M Gas)
    最大区块容量:30M Gas(2倍目标)
    """
    target_gas = parent_gas_limit // 2

    if parent_gas_used == target_gas:
        return parent_base_fee

    elif parent_gas_used > target_gas:
        # 区块超过目标,增加Base Fee
        gas_delta = parent_gas_used - target_gas
        fee_delta = parent_base_fee * gas_delta // target_gas // 8
        return parent_base_fee + max(fee_delta, 1)

    else:
        # 区块低于目标,降低Base Fee
        gas_delta = target_gas - parent_gas_used
        fee_delta = parent_base_fee * gas_delta // target_gas // 8
        return parent_base_fee - fee_delta

# 每个区块最多变化12.5%(1/8)
# 平滑调整,避免剧烈波动

EIP-1559优势:

  1. 费用预测:Base Fee可预测,用户体验更好
  2. 通缩机制:销毁Base Fee,减少ETH供应
  3. 防止操纵:矿工无法通过打包自己的交易来哄抬价格
  4. 弹性区块:短期可容纳2倍Gas,处理峰值需求

示例:

当前Base Fee: 50 Gwei
用户设置:
Max Fee: 100 Gwei
Priority Fee: 2 Gwei

实际费用计算:
Gas Price = 50 (Base Fee) + 2 (Priority Fee) = 52 Gwei
退还金额 = (100 - 52) × Gas Used

如果下个区块Base Fee涨到70 Gwei:
Gas Price = 70 + 2 = 72 Gwei (仍低于100 Max Fee)

如果Base Fee涨到99 Gwei:
Gas Price = 99 + 1 = 100 Gwei (触达Max Fee上限)

2.5.5 Gas优化技巧

智能合约开发中的Gas优化策略:

1. 存储优化

// 不推荐:每次写入消耗大量Gas
contract Inefficient {
    uint256[] public data;

    function addMultiple(uint256[] memory values) public {
        for(uint i = 0; i < values.length; i++) {
            data.push(values[i]);  // 每次SSTORE
        }
    }
}

// 推荐:批量操作
contract Efficient {
    uint256[] public data;

    function addMultiple(uint256[] memory values) public {
        uint256 len = data.length;
        for(uint i = 0; i < values.length; i++) {
            data.push();  // 扩展数组
        }
        for(uint i = 0; i < values.length; i++) {
            data[len + i] = values[i];  // 批量写入
        }
    }
}

// 最优:使用内存临时存储
contract Optimal {
    function processData(uint256[] memory values) public pure returns(uint256) {
        uint256 sum = 0;
        for(uint i = 0; i < values.length; i++) {
            sum += values[i];  // 内存操作,Gas低
        }
        return sum;
    }
}

2. 变量打包

// 不推荐:3个存储槽(96 Gas读取)
contract Unoptimized {
    uint256 a;  // 槽0
    uint256 b;  // 槽1
    uint256 c;  // 槽2
}

// 推荐:1个存储槽(32 Gas读取)
contract Optimized {
    uint128 a;  // 槽0 (前16字节)
    uint64 b;   // 槽0 (中8字节)
    uint64 c;   // 槽0 (后8字节)
}

// 类型对齐
contract BetterPacked {
    uint128 a;
    uint128 b;  // 与a共用槽0
    address c;  // 槽1(20字节)
    uint96 d;   // 槽1(12字节,填满)
}

3. 短路运算

// 推荐:便宜的条件在前
function check(uint256 value) public view returns(bool) {
    return value > 0 && expensiveCheck(value);
    // 如果value <= 0,不会调用expensiveCheck
}

// 不推荐:昂贵的条件在前
function checkBad(uint256 value) public view returns(bool) {
    return expensiveCheck(value) && value > 0;
    // 总是先执行expensiveCheck
}

4. 循环优化

// 不推荐
function sumArray(uint256[] memory arr) public pure returns(uint256) {
    uint256 sum = 0;
    for(uint256 i = 0; i < arr.length; i++) {  // 每次读取arr.length
        sum += arr[i];
    }
    return sum;
}

// 推荐
function sumArrayOptimized(uint256[] memory arr) public pure returns(uint256) {
    uint256 sum = 0;
    uint256 len = arr.length;  // 缓存长度
    for(uint256 i = 0; i < len; ++i) {  // 使用++i而非i++
        sum += arr[i];
    }
    return sum;
}

5. 事件代替存储

// 不推荐:存储历史记录(Gas高)
contract Expensive {
    struct Record {
        uint256 value;
        uint256 timestamp;
    }
    Record[] public history;

    function addRecord(uint256 value) public {
        history.push(Record(value, block.timestamp));
    }
}

// 推荐:使用事件(Gas低)
contract Cheap {
    event RecordAdded(uint256 value, uint256 timestamp);

    function addRecord(uint256 value) public {
        emit RecordAdded(value, block.timestamp);
        // 事件存储在日志中,Gas消耗远低于存储
    }
}

2.6 以太坊虚拟机(EVM)

2.6.1 EVM架构

EVM是以太坊的核心,一个基于栈的虚拟机,负责执行智能合约代码。

EVM组件:

1. 程序计数器(PC):指向当前执行的指令
2. 栈(Stack):256位元素,最大深度1024
3. 内存(Memory):临时存储,按字节寻址,扩展式增长
4. 存储(Storage):持久化键值存储,每个合约独立
5. 代码(Code):不可变的合约字节码
6. Gas:剩余可用Gas
7. 调用栈深度:最大1024层

架构图:
┌─────────────────────────────────┐
│         EVM Instance            │
├─────────────────────────────────┤
│  PC: 0x0000                     │
│  Gas: 100000                    │
│  Stack: [item1, item2, ...]     │
│  Memory: [0x00, 0x01, ...]      │
│  Storage: {key→value}           │
│  Code: [0x60, 0x80, ...]        │
└─────────────────────────────────┘

2.6.2 EVM指令集

EVM有140多个操作码(Opcode),分为以下类别:

1. 算术运算

ADD     (0x01): a + b
MUL     (0x02): a × b
SUB     (0x03): a - b
DIV     (0x04): a ÷ b (整数除法)
MOD     (0x06): a % b
ADDMOD  (0x08): (a + b) % N
MULMOD  (0x09): (a × b) % N
EXP     (0x0a): a^b
SIGNEXTEND (0x0b): 符号扩展

2. 比较与位运算

LT      (0x10): a < b
GT      (0x11): a > b
EQ      (0x14): a == b
ISZERO  (0x15): a == 0
AND     (0x16): a & b
OR      (0x17): a | b
XOR     (0x18): a ^ b
NOT     (0x19): ~a
BYTE    (0x1a): 获取字节
SHL     (0x1b): 左移
SHR     (0x1c): 右移
SAR     (0x1d): 算术右移

3. 栈操作

POP     (0x50): 弹出栈顶
PUSH1-32(0x60-0x7f): 压入1-32字节常量
DUP1-16 (0x80-0x8f): 复制栈中第N个元素
SWAP1-16(0x90-0x9f): 交换栈顶与第N个元素

4. 内存操作

MLOAD   (0x51): 从内存加载32字节
MSTORE  (0x52): 存储32字节到内存
MSTORE8 (0x53): 存储1字节到内存
MSIZE   (0x59): 获取内存大小

5. 存储操作

SLOAD   (0x54): 从存储读取
SSTORE  (0x55): 写入存储

6. 控制流

JUMP    (0x56): 无条件跳转
JUMPI   (0x57): 条件跳转
PC      (0x58): 获取程序计数器
JUMPDEST(0x5b): 跳转目标标记

7. 区块信息

BLOCKHASH  (0x40): 获取区块哈希
COINBASE   (0x41): 矿工地址
TIMESTAMP  (0x42): 区块时间戳
NUMBER     (0x43): 区块号
DIFFICULTY (0x44): 难度(PoS后为PREVRANDAO)
GASLIMIT   (0x45): 区块Gas限制
CHAINID    (0x46): 链ID
BASEFEE    (0x48): EIP-1559基础费用

8. 交易信息

ORIGIN     (0x32): 原始发送者(EOA)
CALLER     (0x33): 直接调用者
CALLVALUE  (0x34): 调用附带的ETH
CALLDATALOAD(0x35): 加载调用数据
CALLDATASIZE(0x36): 调用数据大小
CALLDATACOPY(0x37): 复制调用数据
GASPRICE   (0x3a): 交易Gas价格

9. 账户操作

ADDRESS    (0x30): 当前合约地址
BALANCE    (0x31): 查询余额
SELFBALANCE(0x47): 当前合约余额

10. 合约调用

CALL       (0xf1): 普通调用
CALLCODE   (0xf2): 已弃用
DELEGATECALL(0xf4): 委托调用(保持msg.sender)
STATICCALL (0xfa): 静态调用(只读)
CREATE     (0xf0): 创建合约
CREATE2    (0xf5): 确定性创建合约
SELFDESTRUCT(0xff): 销毁合约

2.6.3 字节码示例

简单Solidity合约及其字节码:

// Solidity源代码
pragma solidity ^0.8.0;

contract SimpleStorage {
    uint256 value;

    function set(uint256 _value) public {
        value = _value;
    }

    function get() public view returns (uint256) {
        return value;
    }
}

编译后的字节码(简化版):

部署字节码(Constructor):
608060405234801561001057600080fd5b50610150806100206000396000f3fe

运行时字节码:
6080604052348015600f57600080fd5b506004361060325760003560e01c806360fe47b11460375780636d4ce63c146062575b600080fd5b606060048036036020811015604b57600080fd5b8101908080359060200190929190505050607e565b005b60686088565b6040518082815260200191505060405180910390f35b8060008190555050565b6000805490509056fea265627a7a72315820...

指令分解(set函数):
PUSH1 0x80        // 60 80 - 压入0x80
PUSH1 0x40        // 60 40 - 压入0x40
MSTORE            // 52    - 存储到内存0x40
...
PUSH1 0x00        // 60 00 - 压入存储槽0
SSTORE            // 55    - 存储value
STOP              // 00    - 停止执行

函数选择器(Function Selector):

计算方式:
selector = Keccak256("set(uint256)")[0:4]
         = 0x60fe47b1

selector = Keccak256("get()")[0:4]
         = 0x6d4ce63c

调用数据格式:
set(123)的calldata:
0x60fe47b1                              // 函数选择器
000000000000000000000000000000000000000000000000000000000000007b  // 参数123

2.6.4 EVM执行模型

执行流程:

1. 初始化EVM实例
   - 设置Gas限制
   - 加载合约代码
   - 准备调用数据

2. 执行字节码
   while gas > 0 and not stopped:
       opcode = code[PC]
       gasRequired = getGasCost(opcode)

       if gas < gasRequired:
           raise OutOfGas

       gas -= gasRequired
       execute(opcode)
       PC += 1

3. 处理结果
   - 成功:返回数据,更新状态
   - 失败:回滚状态,消耗所有Gas(旧版)或部分退款(新版)

4. 生成收据
   - Status (成功/失败)
   - Gas使用量
   - 日志和事件

栈操作示例:

代码:ADD
执行前:
Stack: [5, 3, ...]

执行后:
Stack: [8, ...]  // 5 + 3 = 8

代码:PUSH1 0x10, PUSH1 0x20, ADD
执行过程:
1. PUSH1 0x10 → Stack: [0x10]
2. PUSH1 0x20 → Stack: [0x20, 0x10]
3. ADD        → Stack: [0x30]  // 0x10 + 0x20

2.6.5 预编译合约

以太坊在特定地址内置了一些预编译合约,用于高效执行特定操作:

地址              | 功能
-----------------|------------------
0x01             | ecRecover (ECDSA恢复公钥)
0x02             | SHA256哈希
0x03             | RIPEMD160哈希
0x04             | Identity (数据复制)
0x05             | ModExp (模幂运算)
0x06             | ecAdd (椭圆曲线加法)
0x07             | ecMul (椭圆曲线乘法)
0x08             | ecPairing (配对检查,用于zk-SNARKs)
0x09             | Blake2压缩函数

优势:
- 原生实现,比EVM字节码快数百倍
- Gas消耗低
- 用于密码学操作

使用示例:

// 恢复签名者地址
function recoverSigner(bytes32 hash, bytes memory signature)
    public pure returns (address)
{
    bytes32 r;
    bytes32 s;
    uint8 v;

    assembly {
        r := mload(add(signature, 32))
        s := mload(add(signature, 64))
        v := byte(0, mload(add(signature, 96)))
    }

    // 调用预编译合约0x01
    return ecrecover(hash, v, r, s);
}

2.7 智能合约生命周期

2.7.1 合约创建

创建方式1:交易创建

用户发送交易:
{
    from: 用户地址
    to: null (表示合约创建)
    data: 合约字节码
    value: 初始ETH (可选)
    gas: Gas限制
}

执行流程:
1. 执行构造函数(Constructor)
2. 返回运行时字节码
3. 将字节码存储到新地址
4. 计算合约地址:
   address = Keccak256(RLP(sender, nonce))[12:32]

创建方式2:合约创建合约

// 使用new关键字
contract Factory {
    function createChild() public returns (address) {
        Child child = new Child();
        return address(child);
    }
}

// 使用CREATE2(确定性地址)
contract FactoryV2 {
    function createChild(bytes32 salt) public returns (address) {
        Child child = new Child{salt: salt}();
        return address(child);
    }

    // 预计算地址
    function computeAddress(bytes32 salt) public view returns (address) {
        bytes32 hash = keccak256(
            abi.encodePacked(
                bytes1(0xff),
                address(this),
                salt,
                keccak256(type(Child).creationCode)
            )
        );
        return address(uint160(uint256(hash)));
    }
}

2.7.2 合约调用

外部调用方式:

contract Caller {
    // 1. 直接调用
    function callDirect(address target) public {
        ITarget(target).someFunction();
    }

    // 2. call (低级调用)
    function callLowLevel(address target, bytes memory data)
        public returns (bool, bytes memory)
    {
        return target.call(data);
    }

    // 3. delegatecall (委托调用,使用调用者存储)
    function delegateCallExample(address target, bytes memory data)
        public returns (bool, bytes memory)
    {
        return target.delegatecall(data);
    }

    // 4. staticcall (只读调用,不修改状态)
    function staticCallExample(address target, bytes memory data)
        public view returns (bool, bytes memory)
    {
        return target.staticcall(data);
    }
}

调用类型对比:

CALL:
- msg.sender = 调用者合约
- 使用被调用合约的存储
- 可修改状态
- 可转账

DELEGATECALL:
- msg.sender = 原始调用者
- 使用调用者合约的存储
- 代理模式核心
- 不可转账

STATICCALL:
- 只读调用
- 不可修改状态
- 用于view/pure函数
- Gas优化

代理模式示例:

// 代理合约
contract Proxy {
    address public implementation;

    constructor(address _impl) {
        implementation = _impl;
    }

    fallback() external payable {
        address impl = implementation;
        assembly {
            // 复制calldata
            calldatacopy(0, 0, calldatasize())

            // delegatecall到实现合约
            let result := delegatecall(gas(), impl, 0, calldatasize(), 0, 0)

            // 复制返回数据
            returndatacopy(0, 0, returndatasize())

            // 返回或回滚
            switch result
            case 0 { revert(0, returndatasize()) }
            default { return(0, returndatasize()) }
        }
    }
}

// 实现合约
contract Implementation {
    uint256 public value;

    function setValue(uint256 _value) public {
        value = _value;  // 实际存储在Proxy合约中
    }
}

2.7.3 合约销毁

contract SelfDestructExample {
    address payable owner;

    constructor() {
        owner = payable(msg.sender);
    }

    function destroy() public {
        require(msg.sender == owner);
        selfdestruct(owner);  // 销毁合约,余额转给owner
    }
}

销毁效果:
1. 合约代码被删除
2. 存储被清空
3. 余额转移到指定地址
4. 退还部分Gas(24,000 Gas)

注意:
- EIP-6049计划弃用selfdestruct
- 新标准下仅清空余额,不删除代码

2.8 事件与日志

2.8.1 事件机制

事件是智能合约与外部世界通信的主要方式:

contract EventExample {
    // 定义事件
    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(address indexed owner, address indexed spender, uint256 value);

    // 触发事件
    function transfer(address to, uint256 amount) public {
        // ... 转账逻辑
        emit Transfer(msg.sender, to, amount);
    }
}

事件特点:
1. 存储在交易收据的日志中
2. 不可从合约内部访问
3. Gas成本远低于存储
4. 支持索引参数(最多3个)
5. 前端可订阅和过滤

2.8.2 日志结构

Log {
    Address: 合约地址
    Topics: [
        Topic0: Keccak256(事件签名)
        Topic1: 第1个indexed参数
        Topic2: 第2个indexed参数
        Topic3: 第3个indexed参数
    ]
    Data: 非indexed参数(ABI编码)
}

示例:
Transfer事件:
Topics[0] = Keccak256("Transfer(address,address,uint256)")
         = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef
Topics[1] = from地址(填充到32字节)
Topics[2] = to地址
Data = amount(32字节)

2.8.3 布隆过滤器

以太坊使用布隆过滤器加速日志查询:

每个区块头包含:
LogsBloom: 256字节(2048位)布隆过滤器

工作原理:
1. 对每个日志的地址和Topics进行哈希
2. 设置布隆过滤器的对应位
3. 查询时快速判断日志可能存在的区块
4. 减少全链扫描

优势:
- 空间高效(每个区块仅256字节)
- 快速过滤不相关区块
- 允许假阳性,但无假阴性

查询流程:
1. 计算目标事件的布隆位
2. 扫描区块头,过滤不匹配的区块
3. 仅在匹配区块中查找完整日志

2.9 以太坊的网络层

2.9.1 DevP2P协议栈

以太坊使用DevP2P协议进行节点通信:

协议层次:
┌─────────────────────────┐
│  Application Layer      │
│  (eth, snap, les)       │
├─────────────────────────┤
│  RLPx Layer             │
│  (加密、认证、多路复用)    │
├─────────────────────────┤
│  Node Discovery         │
│  (Kademlia DHT)         │
├─────────────────────────┤
│  Transport Layer        │
│  (TCP/UDP)              │
└─────────────────────────┘

主要子协议:
- eth: 以太坊主协议
- snap: 快照同步
- les: 轻客户端
- whisper: 消息协议(已弃用)

2.9.2 节点发现

使用Kademlia DHT进行节点发现:

节点ID:
NodeID = Keccak256(PublicKey)

距离度量:
distance(A, B) = XOR(NodeID_A, NodeID_B)

发现流程:
1. Ping:探测节点是否在线
2. Pong:响应Ping
3. FindNode:查询邻近节点
4. Neighbors:返回已知节点列表

DHT特性:
- 每个节点维护K桶(K-buckets)
- 存储距离相近的节点
- 对数复杂度查找

2.9.3 区块同步

Full Sync(全同步):

从创世区块开始:
1. 下载所有区块
2. 执行所有交易
3. 重建完整状态
4. 验证每个状态转换

优点:
- 最高安全性
- 完整验证
- 可追溯所有历史

缺点:
- 极慢(数周)
- 存储需求大(>1TB)
- 不适合普通用户

Fast Sync(快速同步):

下载区块头和收据:
1. 下载所有区块头
2. 下载最近N个区块的完整数据
3. 下载最新状态的快照
4. 验证状态一致性

优点:
- 快速(数小时)
- 存储需求适中
- 适合全节点

缺点:
- 信任最新状态
- 无法验证早期历史

Snap Sync(快照同步):

下载状态快照:
1. 获取最新状态根
2. 按账户哈希范围下载状态
3. 并行下载多个范围
4. 验证Merkle证明

优点:
- 最快(1-2小时)
- 高效并行化
- 当前默认方式

缺点:
- 网络开销大
- 需要较好带宽

Warp Sync(扭曲同步,Geth Archive):

仅下载检查点:
1. 信任硬编码检查点
2. 跳过中间验证
3. 仅同步最新数据

优点:
- 极快
- 资源需求低

缺点:
- 安全性最低
- 需要信任检查点

2.10 以太坊2.0与PoS

2.10.1 从PoW到PoS的转变

2022年9月15日,以太坊完成"The Merge",从PoW切换到PoS:

PoW的问题:

1. 能耗高:
   - 年耗电量约100 TWh(相当于荷兰)
   - 碳排放严重
   - 硬件浪费

2. 中心化风险:
   - 矿池集中(前5个矿池>50%算力)
   - ASIC专业化
   - 电费驱动地理集中

3. 安全成本:
   - 51%攻击成本高但可行
   - 需持续发行新币激励矿工

PoS的优势:

1. 能耗降低99.95%:
   - 无需算力竞争
   - 仅需普通服务器

2. 降低准入门槛:
   - 32 ETH即可质押
   - 无需专业硬件

3. 经济安全性:
   - 攻击者损失质押资产
   - Slashing惩罚机制

4. 支持分片:
   - PoW难以实现分片
   - PoS天然支持

2.10.2 PoS共识机制

Casper FFG(Friendly Finality Gadget):

核心概念:
1. Validator(验证者):质押32 ETH的节点
2. Epoch:32个Slot(约6.4分钟)
3. Slot:12秒,一个出块时间
4. Committee:随机选择的验证者组

出块流程:
Slot 0:  Validator_A提议区块
         Committee投票证明
Slot 1:  Validator_B提议区块
         Committee投票证明
...
Slot 31: Validator_X提议区块
         Committee投票证明

当前Epoch结束,新Epoch开始

LMD GHOST(Latest Message Driven GHOST):

分叉选择规则:
1. 每个验证者投票给最新看到的区块
2. 选择累计权重最大的分支
3. 权重 = 投票的验证者质押量

示例:
      A (100票)
     / \
    B   C (150票)
    |   |
    D   E

选择分支C,因为权重更大

最终性(Finality):

检查点(Checkpoint):
- 每个Epoch的第一个Slot的区块

状态:
1. Justified(合理化):
   - 2/3验证者投票支持

2. Finalized(最终化):
   - 连续两个Epoch都是Justified
   - 之前的检查点变为Finalized

时间线:
Epoch N:   区块A成为检查点
           2/3验证者投票 → A变为Justified
Epoch N+1: 区块B成为检查点
           2/3验证者投票 → B变为Justified, A变为Finalized

最终化后的区块不可逆转

2.10.3 质押与奖惩

质押要求:

最低质押:32 ETH
激活时间:约24小时(排队机制)
提款:需等待提款队列

验证者职责:
1. 提议区块(每个Epoch约1次机会)
2. 证明区块(每个Epoch 1次)
3. 参与同步委员会(随机选择,27小时周期)

奖励计算:
基础奖励 = 有效余额 × 基础奖励系数 ÷ √(总质押量)

年化收益率(APR):
APR ≈ 64 ÷ √(总质押ETH数量)

示例:
总质押:30M ETH
APR ≈ 64 ÷ √30,000,000 ≈ 64 ÷ 5,477 ≈ 1.17%

总质押:10M ETH
APR ≈ 64 ÷ √10,000,000 ≈ 64 ÷ 3,162 ≈ 2.02%

Slashing惩罚:

严重违规:
1. 双重提议:同一Slot提议两个区块
2. 双重投票:为同一Epoch投两次票
3. 环绕投票:投票包围之前的投票

惩罚力度:
基础惩罚:约1 ETH
相关惩罚:同时被Slash的验证者越多,惩罚越重
最高可损失全部32 ETH

示例:
如果1%的验证者同时被Slash:
单个验证者损失 ≈ 1 + (32 × 1%) = 1.32 ETH

如果33%同时被Slash:
单个验证者损失 ≈ 1 + (32 × 33%) = 11.56 ETH

离线惩罚:

小额扣款:
- 未能及时证明:扣除基础奖励
- 累计离线:逐渐增加惩罚
- 长期离线:强制退出

激励在线率:
网络整体在线率 > 66.7% → 正收益
网络整体在线率 < 66.7% → 负收益

2.11 扩展性解决方案

2.11.1 Layer 2方案

Rollup技术:

核心思想:
1. 链下执行交易
2. 链上发布数据
3. 继承以太坊安全性

两种类型:
┌───────────────┬──────────────────┬──────────────────┐
│   特性        │  Optimistic      │  ZK-Rollup       │
├───────────────┼──────────────────┼──────────────────┤
│ 验证方式      │ 欺诈证明         │  零知识证明       │
│ 提款时间      │ 7天              │  几分钟           │
│ TPS           │ 1000-4000        │  2000-10000       │
│ Gas成本       │ 较低             │  中等             │
│ EVM兼容性     │ 高               │  中等             │
│ 代表项目      │ Optimism, Arbitrum│ zkSync, StarkNet│
└───────────────┴──────────────────┴──────────────────┘

Optimistic Rollup:

工作流程:
1. Sequencer(排序器)收集交易
2. 批量执行,生成状态根
3. 提交状态根和交易数据到L1
4. 7天挑战期
5. 如有欺诈,提交欺诈证明

欺诈证明:
if submitted_state != computed_state:
    revert_transaction()
    slash_sequencer()
    reward_challenger()

优势:
- EVM等效,易于移植
- 开发成本低

劣势:
- 提款延迟长
- 需要诚实验证者监控

ZK-Rollup:

工作流程:
1. Sequencer执行交易
2. 生成零知识证明(zk-SNARK或zk-STARK)
3. 提交状态根和证明到L1
4. L1验证证明(快速)
5. 立即最终化

零知识证明特性:
- 证明"我知道X,但不告诉你X"
- 验证时间短(毫秒级)
- 数学保证正确性

优势:
- 提款快
- 安全性高(密码学保证)

劣势:
- 生成证明成本高
- EVM兼容性差(需重写合约)

State Channels(状态通道):

原理:
1. 双方锁定资金到链上合约
2. 链下进行无限次交易
3. 仅最终状态上链

示例:支付通道
Alice和Bob各锁定10 ETH
链下交易:
  Alice → Bob: 1 ETH (Alice:9, Bob:11)
  Bob → Alice: 2 ETH (Alice:11, Bob:9)
  ...
关闭通道时才上链最终状态

优势:
- 即时确认
- 几乎无Gas成本
- 高隐私性

劣势:
- 仅限参与方
- 需要在线
- 流动性锁定

Plasma:

子链结构:
Main Chain (Ethereum)
    ↓
  Plasma Chain
    ↓ ↓ ↓
Child Chains

特点:
- 独立的侧链
- 定期向主链提交Merkle根
- 退出机制保证安全

问题:
- 数据可用性假设
- 大规模退出拥堵
- 已被Rollup取代

2.11.2 数据可用性采样

未来的分片方案依赖数据可用性采样(DAS):

问题:
如何确保区块数据可用,而不下载全部数据?

解决方案:
1. 将区块数据编码为纠删码(Reed-Solomon)
2. 分割为N个片段
3. 仅需任意50%片段即可重建
4. 轻节点随机采样几个片段
5. 高概率保证数据可用

数学原理:
采样30个片段:
数据不可用但采样通过的概率 < 10^-9

应用:
- 支持大区块(>1MB)
- 轻节点也能验证
- 为分片铺路

2.12 以太坊与比特币深度对比

2.12.1 设计哲学差异

比特币:
- 极简主义
- 单一功能(货币)
- 保守升级
- 安全优先

以太坊:
- 功能丰富
- 通用平台
- 快速迭代
- 创新优先

2.12.2 技术架构对比

账本模型:
比特币:UTXO
- 并发友好
- 隐私性好
- 状态简单

以太坊:Account
- 编程友好
- 状态复杂
- 并发受限

智能合约:
比特币:Script(非图灵完备)
- 简单安全
- 功能有限
- 难以表达复杂逻辑

以太坊:EVM(图灵完备)
- 功能强大
- 需Gas限制
- 支持任意计算

共识机制:
比特币:PoW (SHA-256)
- 成熟稳定
- 能耗高
- 难以升级

以太坊:PoS (Casper)
- 节能
- 更安全(经济惩罚)
- 支持分片

2.12.3 性能对比

指标           | 比特币      | 以太坊(PoS)
--------------|------------|-------------
出块时间       | ~10分钟    | 12秒
TPS           | ~7         | ~30
最终性时间     | ~60分钟    | ~12.8分钟(2 Epochs)
区块大小       | ~1-4MB     | 动态(Gas限制)
年发行率       | <1.8%      | ~0.5%(通缩)
能耗          | ~100 TWh/年| ~0.01 TWh/年

2.13 本章总结

本章系统介绍了以太坊的架构和核心概念:

账户模型:

  • 两种账户类型:EOA和合约账户
  • 直观的余额记录方式
  • 天然支持智能合约

世界状态:

  • Merkle Patricia Trie存储结构
  • 状态树、存储树、交易树、收据树
  • 高效的状态验证和历史追溯

Gas机制:

  • 防止无限循环和资源滥用
  • EIP-1559引入基础费用和优先费
  • 通缩机制(销毁Base Fee)

EVM:

  • 基于栈的虚拟机
  • 140多个操作码
  • 图灵完备的执行环境

交易生命周期:

  • 创建、签名、广播、打包、执行、确认
  • Nonce机制保证顺序
  • 收据和事件记录结果

PoS共识:

  • 从PoW到PoS的历史性转变
  • Casper FFG + LMD GHOST
  • 质押、奖励、Slashing机制

扩展性方案:

  • Layer 2:Rollup、State Channel
  • Optimistic vs ZK-Rollup
  • 数据可用性采样

以太坊的创新:

以太坊在比特币的基础上实现了重大创新:

  1. 从货币到平台的飞跃
  2. 智能合约开启DeFi、NFT、DAO等应用
  3. 账户模型更适合复杂状态管理
  4. Gas机制平衡计算资源
  5. PoS提升可持续性

挑战与未来:

尽管以太坊功能强大,仍面临挑战:

  • 扩展性瓶颈(TPS有限)
  • 高Gas费用(网络拥堵时)
  • 状态膨胀(历史数据不断增长)
  • MEV问题(矿工可提取价值)

未来路线图致力于解决这些问题:

  • Danksharding:大规模数据可用性
  • PBS:提议者-构建者分离
  • Verkle树:减少状态大小
  • 无状态客户端:降低节点要求

下一章将深入智能合约开发,讲解Solidity编程语言、常见设计模式、安全最佳实践以及DApp开发全流程。

Prev
第一章:比特币原理与实现
Next
Solidity智能合约开发基础