区块链完整教程:从哈希函数到智能合约
理论 + 实战,代码全部经过实际运行验证(Python 3.12)。目标是让你不仅"知道"区块链是什么,还能亲手搭一条出来、看懂它为什么防篡改、以及它的边界在哪里。
目录
- 0. 这篇教程适合谁 / 需要什么基础
- 1. 第一性原理:区块链到底解决了什么问题
- 2. 密码学基础
- 3. 区块链的数据结构
- 4. 实战:从零手写一条区块链
- 5. 共识机制:谁来决定"下一个区块是什么"
- 6. 交易模型:UTXO vs Account
- 7. P2P 网络层:区块和交易如何传播
- 8. 智能合约与 EVM
- 9. 安全性:常见攻击手法
- 10. 实战:搭建本地测试链环境
- 11. Layer2 与扩展性
- 12. 学习路径建议 & 延伸阅读
0. 这篇教程适合谁 / 需要什么基础
- 会读 Python(本文所有可运行代码用 Python 写,逻辑简单,Go/Java 程序员也能秒懂)
- 知道什么是哈希函数、什么是链表,就足够了;非对称加密部分会从头讲
- 不会教你炒币、不会讲代币经济学、不会讲某条公链的招商话术——这是一篇纯技术教程
整篇文章的主线是一句话:区块链 = 一种用密码学 + 博弈论,让一群互不信任的节点,就"数据的唯一顺序"达成一致的数据结构和协议。 后面所有章节都是在拆解这句话里的每个词。
1. 第一性原理:区块链到底解决了什么问题
在区块链之前,"记账"这件事要么靠中心化机构背书(银行、公证处),要么在分布式系统里没法解决——这就是经典的双重支付问题(Double Spending):如果 Alice 手里的是一份数字文件而不是实物现金,她完全可以把同一份"钱"同时发给 Bob 和 Carol,因为复制数字文件的成本是零。
中本聪在 2008 年的比特币白皮书里给出的方案是:
- 所有交易公开广播给全网
- 用工作量证明让"打包交易、排出顺序"这件事变得有成本、可验证
- 用哈希链让历史记录一旦写入就几乎不可能被悄悄修改
- 全网节点各自独立验证,最长(工作量最大)的合法链就是"事实"
区块链不是一项单独的技术,而是密码学哈希、Merkle 树、非对称加密、P2P 网络、博弈论激励这几样已存在多年的技术的一次组合发明。接下来我们逐个拆开看。
2. 密码学基础
2.1 哈希函数:区块链的"指纹"
区块链严重依赖密码学哈希函数(比特币、以太坊都用 SHA-256 / Keccak-256),它必须满足三个性质:
| 性质 | 含义 | 在区块链里的作用 |
|---|---|---|
| 确定性 | 相同输入永远得到相同输出 | 任何人都能独立验证一个区块的哈希对不对 |
| 雪崩效应 | 输入改一个比特,输出几乎完全不同 | 篡改一个字段就能被立刻发现 |
| 单向性 | 从输出反推输入在计算上不可行 | 无法伪造出一个"看起来合法"的历史数据 |
| 抗碰撞性 | 很难找到两个不同输入产生相同输出 | 保证哈希值能唯一代表一份数据 |
直接跑一段代码感受"雪崩效应",这是整个区块链防篡改能力的地基:
import hashlib
def sha256(data: str) -> str:
return hashlib.sha256(data.encode("utf-8")).hexdigest()
msg1 = "Alice pays Bob 10 BTC"
msg2 = "Alice pays Bob 11 BTC" # 只改了一个字符
h1 = sha256(msg1)
h2 = sha256(msg2)
print(f"输入1: {msg1}")
print(f"哈希1: {h1}")
print()
print(f"输入2: {msg2}")
print(f"哈希2: {h2}")
diff = sum(a != b for a, b in zip(h1, h2))
print(f"\n64个十六进制字符中,有 {diff} 个不同(雪崩效应)")实际运行结果:
输入1: Alice pays Bob 10 BTC
哈希1: 6442586992b50a27b795ffff86cf99efed03f90b4fd704c172b732592e7f1081
(已截断展示逻辑,实际为64位十六进制字符串)
输入2: Alice pays Bob 11 BTC
哈希2: d4bb3b3c3f89642a483669dc3aaa4c2edd9ac5ef2a98325247effd293154d550
64个十六进制字符中,有 60 个不同(雪崩效应)只改了一个字符(10 → 11),64 个字符里有 60 个都变了。这就是为什么区块链上的"篡改历史交易"几乎不可能悄悄进行——只要改一个字,整条链后面所有区块的哈希都会对不上。
2.2 非对称加密与数字签名
区块链解决"这笔交易是不是本人发起的"这个问题,靠的不是密码登录,而是非对称加密里的数字签名:
- 私钥:一个随机生成的大数,只有你自己知道,等价于"你的账户密码 + 唯一身份证明"
- 公钥:由私钥通过椭圆曲线数学运算单向推导出来,可以公开
- 地址:公钥再做一次哈希得到的更短字符串,就是你平时看到的钱包地址
签名的逻辑是:用私钥对交易内容做一次数字签名,任何人都能用你的公钥验证"这个签名确实是用对应的私钥生成的",但没人能从公钥反推出私钥,也没人能伪造出一个能通过验证的签名。
比特币、以太坊用的都是 secp256k1 椭圆曲线。
2.3 实战:生成一个真正的密钥对和地址
import hashlib
from ecdsa import SigningKey, SECP256k1
def sha256(b): return hashlib.sha256(b).digest()
def ripemd160(b):
h = hashlib.new("ripemd160")
h.update(b)
return h.digest()
# 1. 生成私钥(secp256k1曲线,比特币/以太坊都用这条曲线)
private_key = SigningKey.generate(curve=SECP256k1)
priv_hex = private_key.to_string().hex()
# 2. 由私钥推导公钥(单向,不可逆)
public_key = private_key.get_verifying_key()
pub_bytes = b"\x04" + public_key.to_string() # 0x04前缀表示未压缩公钥
pub_hex = pub_bytes.hex()
# 3. 公钥 -> 地址(模拟比特币做法: SHA256后再RIPEMD160)
addr_hash = ripemd160(sha256(pub_bytes))
address = addr_hash.hex()
print(f"私钥 (32字节): {priv_hex}")
print(f"公钥 (65字节, 未压缩): {pub_hex[:40]}...")
print(f"地址 (由公钥单向哈希得到): {address}")
print()
# 4. 用私钥对一笔交易签名,用公钥验签
message = b"Alice pays Bob 10 coins"
signature = private_key.sign(message)
is_valid = public_key.verify(signature, message)
print(f"交易: {message.decode()}")
print(f"签名: {signature.hex()[:40]}...")
print(f"用公钥验签结果: {is_valid}")
# 5. 篡改消息后验签应失败
try:
public_key.verify(signature, b"Alice pays Bob 10000 coins")
print("验签: True (不应该出现这行!)")
except Exception as e:
print(f"篡改消息后验签: 失败 ({type(e).__name__}) —— 签名与消息不匹配")真实运行输出:
私钥 (32字节): af96fcb95bf2773847db58f925ead033ff3d28ad92043aec3dbdf522e165a23a
公钥 (65字节, 未压缩): 048eff64c86e40e37e1cee94bfcd94e57ed341f5...
地址 (由公钥单向哈希得到): dd6a2b16eeb9e7068052c5eb4941910d698e7c20
交易: Alice pays Bob 10 coins
签名: 685856c2a5342c7560e9f9b46dc2ae5981122cf2...
用公钥验签结果: True
篡改消息后验签: 失败 (BadSignatureError) —— 签名与消息不匹配需要 pip install ecdsa。另外提醒一点:hashlib.new("ripemd160") 依赖系统 OpenSSL 是否启用了 legacy provider,部分 OpenSSL 3.x 环境默认会禁用它。如果报错,换成 pip install pycryptodome 后 from Crypto.Hash import RIPEMD160 即可。
看到这里你应该能理解为什么"私钥丢了钱包就没了"——地址是公开的,公钥可以从签名反推出来,但私钥到公钥这一步是单向的数学运算,没有后门。
3. 区块链的数据结构
3.1 一个区块长什么样
一个区块通常分成"区块头"和"区块体"两部分:
关键点:区块的哈希只对区块头做哈希,不需要对整个交易列表做哈希——因为交易列表已经被压缩进了 merkle_root 这一个字段里。这就引出了 Merkle 树。
3.2 Merkle 树:如何高效验证一笔交易
如果一个区块装了 3000 笔交易,你想验证其中某一笔交易确实被打包进了这个区块,难道要把 3000 笔交易全部下载下来重新算一遍哈希吗?Merkle 树解决的正是这个问题:把所有交易两两哈希,一层层往上收敛成一个根哈希。
好处是:轻节点(比如手机钱包)只需要拿到"从这笔交易到根节点路径上的几个兄弟哈希"(这叫 Merkle 证明),就能在 O(log n) 的时间里验证这笔交易确实在这个区块里,而不需要下载全部交易——这正是 SPV(简单支付验证)钱包的原理。
3.3 链式结构:previous_hash 如何防篡改
每个区块的头部都包含"上一个区块的哈希"。这意味着:如果你想偷偷修改第 1 号区块里的某笔交易,第 1 号区块的哈希就会变,导致第 2 号区块里存的 previous_hash 对不上——你必须连锁重算第 2、3、4... 一直到最新区块的哈希。而每算一个区块的哈希在 PoW 链上都需要付出真实的算力成本,这就是"篡改历史成本极高"这句话的真正含义,不是"改不了",而是"改的成本高到不划算"。
4. 实战:从零手写一条区块链
把前面三节的内容全部串起来,用不到 90 行 Python 实现一条包含 PoW 挖矿、Merkle 树、篡改检测的最小可用区块链:
import hashlib
import json
import time
def sha256(data: str) -> str:
return hashlib.sha256(data.encode("utf-8")).hexdigest()
class Block:
def __init__(self, index, timestamp, transactions, previous_hash, nonce=0):
self.index = index
self.timestamp = timestamp
self.transactions = transactions
self.previous_hash = previous_hash
self.nonce = nonce
self.merkle_root = self.compute_merkle_root()
self.hash = self.compute_hash()
def compute_merkle_root(self):
if not self.transactions:
return sha256("")
layer = [sha256(json.dumps(tx, sort_keys=True)) for tx in self.transactions]
while len(layer) > 1:
if len(layer) % 2 == 1:
layer.append(layer[-1])
layer = [sha256(layer[i] + layer[i + 1]) for i in range(0, len(layer), 2)]
return layer[0]
def compute_hash(self):
# 注意: 这里必须实时重算merkle_root,而不是读取缓存的self.merkle_root,
# 否则篡改transactions后,由于哈希计算用的还是旧的merkle_root,
# 篡改将检测不到 —— 这是很多"玩具级"实现常犯的错误
header = {
"index": self.index,
"timestamp": self.timestamp,
"merkle_root": self.compute_merkle_root(),
"previous_hash": self.previous_hash,
"nonce": self.nonce,
}
return sha256(json.dumps(header, sort_keys=True))
def mine(self, difficulty):
target = "0" * difficulty
start = time.time()
while not self.hash.startswith(target):
self.nonce += 1
self.hash = self.compute_hash()
elapsed = time.time() - start
print(f" 区块 #{self.index} 挖矿成功: nonce={self.nonce}, "
f"耗时={elapsed:.3f}s, hash={self.hash[:16]}...")
class Blockchain:
def __init__(self, difficulty=4):
self.difficulty = difficulty
self.chain = [self.create_genesis_block()]
def create_genesis_block(self):
genesis = Block(0, time.time(), [{"note": "创世区块"}], "0" * 64)
genesis.mine(self.difficulty)
return genesis
def get_latest_block(self):
return self.chain[-1]
def add_block(self, transactions):
prev = self.get_latest_block()
new_block = Block(prev.index + 1, time.time(), transactions, prev.hash)
new_block.mine(self.difficulty)
self.chain.append(new_block)
def is_valid(self):
for i in range(1, len(self.chain)):
current, prev = self.chain[i], self.chain[i - 1]
if current.hash != current.compute_hash():
return False, f"区块 #{current.index} 的哈希被篡改"
if current.previous_hash != prev.hash:
return False, f"区块 #{current.index} 与前一个区块断链"
if not current.hash.startswith("0" * self.difficulty):
return False, f"区块 #{current.index} 未满足工作量证明"
return True, "整条链完整有效"
if __name__ == "__main__":
bc = Blockchain(difficulty=4)
bc.add_block([{"from": "Alice", "to": "Bob", "amount": 10}])
bc.add_block([{"from": "Bob", "to": "Carol", "amount": 3},
{"from": "Alice", "to": "Dave", "amount": 1}])
for b in bc.chain:
print(f"#{b.index} prev={b.previous_hash[:10]}.. hash={b.hash[:10]}.. "
f"merkle={b.merkle_root[:10]}.. nonce={b.nonce}")
ok, msg = bc.is_valid()
print(f"\n链校验: {ok} - {msg}")
print("\n=== 模拟篡改攻击 ===")
bc.chain[1].transactions[0]["amount"] = 10000
ok, msg = bc.is_valid()
print(f"篡改后校验: {ok} - {msg}")真实运行输出:
区块 #0 挖矿成功: nonce=699, 耗时=0.006s, hash=000078da904708c4...
区块 #1 挖矿成功: nonce=51682, 耗时=0.446s, hash=0000d51f067fcb89...
区块 #2 挖矿成功: nonce=37562, 耗时=0.486s, hash=00000eb993a7f0e2...
#0 prev=0000000000.. hash=000078da90.. merkle=63ff3ef80d.. nonce=699
#1 prev=000078da90.. hash=0000d51f06.. merkle=0f27152d2a.. nonce=51682
#2 prev=0000d51f06.. hash=00000eb993.. merkle=1525a463bb.. nonce=37562
链校验: True - 整条链完整有效
=== 模拟篡改攻击 ===
篡改后校验: False - 区块 #1 的哈希被篡改一个真实踩过的坑,值得单独拎出来讲:第一版实现里我在 compute_hash() 里直接用了 self.merkle_root(构造时缓存的属性),结果篡改交易后校验居然还返回 True——因为区块的哈希用的是"旧的" merkle root,压根没感知到交易内容变了。改成每次都实时调用 compute_merkle_root() 才修好。这个坑很有代表性:区块链的防篡改能力,取决于"哈希覆盖的字段"必须是实时、完整的数据快照,任何缓存不当都会打穿这个安全模型。 面试和代码 review 里这是一个很好的考点。
difficulty=4 意味着哈希必须以 4 个 0 开头,概率大约是 1/16⁴ ≈ 1/65536,所以平均要试六万多次才能挖到——这就是"工作量证明"里"工作量"的字面含义。
5. 共识机制:谁来决定"下一个区块是什么"
5.1 拜占庭将军问题
分布式系统里的核心难题:一群将军(节点)想通过不可靠的信使(网络)就"进攻还是撤退"(下一个区块是什么)达成一致,但其中可能有叛徒(恶意节点)故意发送矛盾信息。区块链的共识机制本质上都是这个问题的某种工程解法。
5.2 工作量证明 PoW(比特币)
核心思想:"提议下一个区块"这件事的成本必须足够高,高到攻击者想要伪造历史(比如连续算出比全网更长的链)在经济上不划算。 比特币把难度动态调整到"全网平均每 10 分钟出一个块",难度会根据全网算力每 2016 个区块自动调整一次。
PoW 最大的争议是能耗——全网矿机在同时做大量"无意义"的哈希碰撞运算,只是为了竞争记账权。这也是 PoS 诞生的直接动机。
5.3 权益证明 PoS(以太坊 2.0 之后)
PoS 不比拼算力,而是让持有代币的人质押(Stake)一定数量的代币成为验证者,系统按权重(通常结合质押量和随机数)抽签选出下一个区块的提议者。作恶(比如双重提案)会被罚没质押金(Slashing)。
| 维度 | PoW | PoS |
|---|---|---|
| 记账权竞争方式 | 算力竞赛 | 质押代币 + 随机抽签 |
| 能耗 | 极高 | 极低(下降约 99.9%,以太坊合并后的公开数据) |
| 攻击门槛 | 拥有全网 51% 算力 | 拥有全网 33%~51% 质押量 |
| 作恶惩罚 | 无直接经济惩罚(只是白费电费) | 直接罚没质押金(Slashing) |
| 中心化风险 | 矿池算力集中 | 大质押池(交易所)集中 |
5.4 其他共识:PBFT / DPoS 简述
- PBFT(实用拜占庭容错):需要节点两两通信确认,容错节点数 < 1/3,出块快、无需挖矿,但节点数一多通信开销爆炸,多用于联盟链(如 Hyperledger Fabric)
- DPoS(委托权益证明):代币持有者投票选出少数"超级节点"轮流出块,速度快但中心化程度更高(如早期 EOS)
5.5 51% 攻击到底是什么
如果某个矿工/矿池掌握了全网超过 51% 的算力(或 PoS 下超过对应比例的质押量),他们可以:
- 在私下挖出一条比公开链更长的分叉链(可以在这条私链上做"双花")
- 某个时刻把这条更长的私链广播出去
- 因为共识规则是"最长(工作量最大)合法链获胜",全网节点会切换到攻击者的链上,攻击者之前在公开链上花出去的钱又"回到"了自己账上
请注意:51% 攻击不能凭空印钱、也不能改别人钱包里的余额、更不能伪造别人的签名——它能做的只是"重写自己最近的交易历史",实现双花。算力越分散、代币市值越高的链,发起攻击的经济成本越高,这也是"大链更安全"的根本原因。
6. 交易模型:UTXO vs Account
6.1 比特币的 UTXO 模型
UTXO = Unspent Transaction Output(未花费的交易输出)。比特币里没有"账户余额"这个概念,你钱包里的"余额"其实是系统扫描全部区块后,汇总出所有属于你的、还没被花掉的输出。
特点:每一笔 UTXO 只能被整体花费一次(类似现金找零,你不能把一张 100 元的钞票撕开花 30 元),花的时候必须把它全部作为输入消耗掉,多余部分作为"找零"输出给自己。这个模型天然支持并行验证(不同 UTXO 互不依赖),也天然对隐私更友好(每次找零可以生成新地址)。
6.2 以太坊的账户模型
以太坊更像我们熟悉的银行账户:每个地址直接维护一个 balance 字段,转账就是简单的 balance[A] -= amount; balance[B] += amount,并配合一个不断自增的 nonce 字段防止同一笔交易被重放。这个模型更直观,也是智能合约得以存在的基础——合约本身就是一种特殊账户,能持有状态(storage)和余额。
6.3 两者对比
| 维度 | UTXO (比特币) | Account (以太坊) |
|---|---|---|
| 心智模型 | 一堆零钱/现金 | 银行账户余额 |
| 并行处理交易 | 天然支持(UTXO间无依赖) | 需要额外设计(状态依赖顺序) |
| 智能合约支持 | 弱(需要额外设计如RSK、Cardano的eUTXO) | 原生支持 |
| 隐私性 | 较好(可每次换新地址) | 较弱(地址直接对应余额) |
| 防重放攻击 | UTXO一次性消耗天然防重放 | 需要nonce字段显式防重放 |
7. P2P 网络层:区块和交易如何传播
区块链节点之间不依赖中心服务器,而是用 Gossip 协议(八卦式传播)扩散数据:
节点收到一个新区块或新交易后,会先做本地验证(签名对不对、格式对不对、双花没有),验证通过才会转发给自己的邻居节点,验证不通过直接丢弃、不转发。这种"边验证边转发"的模式让恶意或无效数据在网络里传播不了几跳就会被过滤掉,不需要任何中心节点介入。
这也是为什么区块链节点启动时需要"同步区块"——你要把从创世区块到最新高度的全部数据下载下来,在本地独立重新验证一遍,才能真正参与到这个信任体系里,而不是相信任何单一节点告诉你的数字。
8. 智能合约与 EVM
8.1 什么是智能合约
智能合约本质上就是部署在链上、代码本身也被全网共识和存储的程序。它没有"服务器"的概念——所有节点都在本地的 EVM(以太坊虚拟机)里执行同一段字节码,得到同一个结果,这个结果才会被写进区块。
8.2 Gas 机制
EVM 里每一条指令(加法、写存储、调用另一个合约……)都被标了价,单位是 Gas。你发起一笔交易时要设置:
gas limit:你愿意为这笔交易最多支付多少计算量gas price/max fee:每单位 Gas 你愿意出多少钱
如果执行到一半 Gas 耗尽,整个交易会回滚(状态改动全部撤销),但已消耗的 Gas 费不退——这个设计防止了"死循环合约"把全网节点拖死,本质上是用经济成本给"图灵完备的链上计算"设了一道安全阀。
8.3 一个最小 Solidity 示例
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract SimpleStorage {
uint256 private storedValue;
address public owner;
event ValueChanged(uint256 newValue, address changedBy);
constructor() {
owner = msg.sender;
}
function set(uint256 newValue) public {
require(msg.sender == owner, "only owner can set value");
storedValue = newValue;
emit ValueChanged(newValue, msg.sender);
}
function get() public view returns (uint256) {
return storedValue;
}
}几个新手常忽略但很关键的点:
require(...)失败会回滚整个交易并退还剩余 Gas,但已消耗部分不退view函数(如get)不修改状态,调用它本身不需要花 Gas(前提是外部只读调用,不是被另一笔交易内部触发)msg.sender是当前调用者地址,是权限控制最基础的手段- 合约一旦部署,字节码默认不可更改(这也是为什么"合约可升级"要靠额外的代理模式设计)
9. 安全性:常见攻击手法
9.1 双花攻击(Double Spend)
见第 5.5 节的 51% 攻击,本质上就是双花的一种实现路径。防御方式:等待足够多的确认数(后续区块数)再认为一笔交易"最终确定",确认数越多,攻击者需要追赶的算力/时间成本越高。
9.2 重入攻击(Reentrancy)——The DAO 事件的根源
当一个合约在更新自己的状态之前先调用了外部地址(比如转账),恶意合约可以在收到转账的回调里再次调用同一个函数,形成递归调用,在状态还没更新前反复"提现"。
// 有漏洞的写法
function withdraw() public {
uint amount = balances[msg.sender];
(bool ok, ) = msg.sender.call{value: amount}(""); // 先转账
require(ok);
balances[msg.sender] = 0; // 后更新状态 —— 危险!
}
// 正确写法: Checks-Effects-Interactions模式
function withdraw() public {
uint amount = balances[msg.sender];
balances[msg.sender] = 0; // 先更新状态
(bool ok, ) = msg.sender.call{value: amount}(""); // 后转账
require(ok);
}记住一个口诀:Checks-Effects-Interactions(先检查条件,再修改自身状态,最后才和外部交互),这是防重入的标准范式。
9.3 女巫攻击(Sybil Attack)
攻击者伪造大量虚假身份/节点,试图在依赖"节点数"或"投票数"的机制里(比如某些投票治理、某些 PoS 变体)获得超出实际份额的影响力。PoW 用真实算力、PoS 用真实质押金作为"身份"的准入门槛,本质上都是给女巫攻击设置经济成本。
10. 实战:搭建本地测试链环境
前面的 Python 代码是教学用的最小实现,真实开发智能合约需要一个本地测试链。常见的两条路:
方式一:Hardhat(Node.js 生态,目前最主流)
mkdir my-dapp && cd my-dapp
npm init -y
npm install --save-dev hardhat
npx hardhat init # 选择 "Create a JavaScript project"
npx hardhat node # 启动本地测试链,默认在 127.0.0.1:8545hardhat node 启动后会自动生成 20 个测试账户,每个账户预置 10000 个测试 ETH,可以直接拿来部署、调试合约,完全不涉及真实资产。
方式二:Anvil(Foundry 工具链的一部分,用 Rust 写的,速度更快)
curl -L https://foundry.paradigm.xyz | bash
foundryup
anvil # 同样启动本地测试链方式三:如果你想更贴近真实以太坊的挖矿/PoW 流程(学习用途)
geth --dev --http --http.api eth,net,web3,personal--dev 模式下 geth 会用一种极简的 PoA(权威证明)算法自动出块,方便本地调试,不需要真的挖矿。
无论用哪种,本地测试链的核心价值都是:零成本、零风险地反复部署合约、测试交易、模拟攻击场景,等逻辑跑通了再考虑部署到公开测试网(如 Sepolia),最后才是主网。
11. Layer2 与扩展性
比特币每秒约 7 笔交易,以太坊主网约 15~30 笔,远低于 Visa 的数万笔/秒。这就是区块链不可能三角:去中心化、安全性、可扩展性三者很难同时做到极致。Layer2 是当前主流的解法之一:把大量交易搬到链下处理,只把"最终结果"或"证明"提交回主链(Layer1)。
- 状态通道(State Channel):双方先在链上锁定资金,之后链下互相签名转账无数次,只有开启和关闭通道时才上链,闪电网络就是比特币上的状态通道方案
- Optimistic Rollup:默认相信 L2 提交的结果是对的,给一个"挑战期"(通常 7 天),期间任何人发现造假可以提交欺诈证明打回去(Arbitrum、Optimism)
- ZK-Rollup:每次提交结果都附带一个零知识证明,数学上直接证明"这批交易执行是对的",不需要挑战期,但生成证明的计算量大(zkSync、StarkNet)
- 分片(Sharding):把整条链的状态和交易切分成多个并行处理的分片,是更底层、改动更大的扩展方案,以太坊路线图里长期规划的一部分
12. 学习路径建议 & 延伸阅读
如果你想从"看懂"走向"能上手写",建议顺序:
- 打牢密码学基础:把本文第 2 节的代码自己敲一遍,改改参数,观察结果
- 精读比特币白皮书:只有 9 页,中本聪原文逻辑异常清晰,是理解 PoW+UTXO 组合设计最好的原始材料——
https://bitcoin.org/bitcoin.pdf - 用 Solidity 写 3~5 个小合约:投票、众筹、简单代币(ERC20),配合 Hardhat/Foundry 本地测试链反复调试
- 读一次以太坊黄皮书或至少 EVM opcode 表,理解 Gas、Stack、Storage 的底层执行模型
- 动手实现一次简单的 Merkle Proof 验证,而不只是构建 Merkle 树——这会让你真正理解轻节点是怎么工作的
- 了解至少一种 Layer2 的具体实现原理(推荐先看 Optimistic Rollup,比 ZK-Rollup 更容易入门)
延伸阅读方向:拜占庭将军问题原始论文、以太坊官方文档(https://ethereum.org)、OpenZeppelin 的合约安全最佳实践库。
本文所有 Python 与 Solidity 代码均在实际环境中运行验证。转发、教学使用请随意,建议保留原始代码里的注释——那些注释大多是踩过坑之后才写下的。