开发者指南:技术栈全景与零资金学习路线

这一篇写给一类很具体的读者:你会写代码,可能写了很多年后端、前端或数据,但你没碰过链。你打开任何一份"Web3 入门",扑面而来的是一堆你不熟悉的名词——RPC、Rollup、Sequencer、DA、Prover、MEV——每一个单独看都能查到定义,但拼不成一张图。更糟的是,这个行业的技术文档和营销材料混在一起写,你分不清哪句是工程事实,哪句是融资话术。

本篇要做三件事。第一,给你一张完整的分层地图,从用户手指点到的钱包一路下到共识和数据可用性,每一层讲清"是什么、代表工具、为什么存在"。第二,给你一条不花一分钱、全程测试网的十步实践路线,每一步都有可验证的产出物。第三,也是我认为最重要的一节——技术诚实清单:把这个行业最常被营销话术扭曲的十四个概念逐条拆开,告诉你真实的工程约束是什么,以及为什么白皮书里那句话在技术上不成立。

全篇不提供任何可以直接用于攻击真实系统的操作细节。安全章节只讲原理、防御写法,以及已经公开多年的历史事故机制。


一、完整技术栈地图:从你的手指到共识

0. 先建立一个正确的心智模型

传统 Web 开发的心智模型是:客户端 → API → 服务器 → 数据库。你控制服务器,你控制数据库,你想改哪条记录就改哪条,出了错回滚就是。

链上开发的心智模型完全不同:客户端 → 节点 → 一台全球共享的、所有人都能读、写入要付费、写完不能撤销的状态机

这句话里每个定语都对应一整类工程约束(作者判断,但这是链上开发者近乎普遍的共识):

如果你只记住一句话:链是一台你租不到 root 权限、按字节收费、且不许 rollback 的公共计算机。 整个技术栈的复杂度,本质上都是在绕开这四条约束。

阿链解释 我知道你在想什么——"这不就是一个特别贵、特别慢的数据库吗?" 从工程指标看,是的,而且差得离谱:以太坊主网每秒十几笔交易,一次状态写入的成本大约是云数据库的几百万倍。如果你的需求是"存数据",链是史上最差的选择,没有之一。 但链解决的从来不是"存数据",而是"在没有可信第三方的情况下,让互不信任的多方对同一份状态达成一致,并且这份状态的历史无法被任何单方篡改"。这个问题传统架构解决不了——不是性能不够,是根本没有对应的原语。你可以做主从复制、做多活、做拜占庭容错,但只要机器都在你手里,用户就只能选择信任你。 所以判断一个需求该不该上链,只问一句:这件事里有没有"参与方互不信任、又必须共享同一份账本"的成分? 有,链可能值得付出那几百万倍的成本;没有,用 Postgres。

1. 钱包(Wallet):用户的身份与签名器

是什么。 钱包不保管币——币在链上,从来没离开过链。钱包保管的是私钥,以及用私钥对交易做签名的能力。你可以把它理解成一个"只做签名的硬件安全模块 + 一个只读的链上余额查询器"。

代表工具。 浏览器插件钱包:MetaMask(EVM 生态事实标准)、Rabby(对交易模拟和风险提示做得更细)、Phantom(Solana 起家,现已多链)。多签/合约钱包:Safe(原 Gnosis Safe,机构和 DAO 金库的默认选择)。嵌入式钱包:Privy、Dynamic、Turnkey、Coinbase Embedded Wallet——它们让用户用邮箱或 Google 登录,把密钥分片托管,用户全程不知道"助记词"三个字。

为什么存在。 因为链只认签名,不认账号密码。链上不存在"忘记密码"的服务器端流程——没有服务器可以帮你重置。钱包这一层的全部工程复杂度,都来自一个矛盾:私钥必须绝对私有(否则资产丢失),又必须频繁使用(每笔交易都要签)。硬件钱包、MPC 分片、账户抽象(ERC-4337 / EIP-7702)、Passkey 登录,都是在这个矛盾的不同位置上做取舍。

对开发者的直接影响:你的应用不持有用户身份。用户带着自己的地址来,你只能请求签名,无法代替用户执行任何动作。这从根上改变了权限设计——传统应用的"我的服务器判断你有没有权限",在这里变成"合约根据 msg.sender 判断"。

2. 前端(dApp Frontend):唯一没有被去中心化的一层

是什么。 就是普通的 Web 前端,React / Vue / Svelte,加上一套连接钱包和读写合约的库。

代表工具。 wagmi(React hooks 封装,EVM 生态主流)、RainbowKit / ConnectKit / Web3Modal(连钱包的 UI 组件)、Solana 的 @solana/wallet-adapter

为什么存在。 因为用户不会手写 calldata。但这一层有个必须诚实说清的事实(事实):绝大多数所谓"去中心化应用"的前端,托管在 Vercel、Cloudflare、AWS 上,域名在 GoDaddy 或 Namecheap 上。 合约可能确实无法篡改,但用户 99% 的情况下是通过一个中心化网站访问它的。历史上多次出现的"前端劫持"事故——攻击者拿下 DNS 或 CDN,把前端换成把用户资产转走的版本,合约一行代码没改,用户照样损失(这是公开的事故模式,如 2021 年前后多起 DNS 劫持事件)。

行业的应对是把前端构建产物上传到 IPFS/Arweave,用 ENS 域名解析到内容哈希(IPFS 的 contenthash 记录),这样前端本身也不可篡改。但这么做的项目是少数,因为它牺牲了灰度发布、A/B 测试和热修复能力(作者判断)。

3. SDK / 客户端库:把 JSON-RPC 包装成人能写的代码

是什么。 一层把"给节点发 JSON-RPC 请求"包装成类型安全函数调用的库,同时负责 ABI 编解码、单位换算、交易构造、签名协调。

代表工具。 viem(TypeScript,类型推导做到从 ABI 反推出参数类型,目前的主流选择)、ethers.js(历史最悠久、文档最全、老项目遍地都是)、web3.js(更老,已逐步退场)、Alloy(Rust 生态)、web3.py(Python)、@solana/web3.js

为什么存在。 因为原始的 JSON-RPC 接口极其反人类。举个例子:查一个 ERC-20 余额,本质是往节点发一个 eth_call,data 字段是 balanceOf(address) 的函数选择器(keccak256 哈希的前 4 字节)拼上左填充到 32 字节的地址,返回一个 32 字节的十六进制串,你还得知道这个 token 的 decimals 是几才能除出人类可读的数字。SDK 把这一整串变成一行 readContract

踩坑点。 ABI 编解码错误是新手最常见的 bug 来源,而且报错信息极其不友好——参数类型写错,链上不会告诉你"类型错了",它只会 revert,或者更糟:静默返回一个错误的值(比如调用了一个不存在的函数,某些情况下会命中 fallback)。

4. RPC 服务:你和链之间的那根网线

是什么。 一个对外提供 JSON-RPC 接口的节点服务。你的前端不直接连区块链网络,它连的是某个 RPC 端点(一个 https URL)。

代表工具。 Alchemy、Infura、QuickNode、Ankr、dRPC、Chainstack;公共免费端点(各链官方或社区维护,限流严重);去中心化 RPC 网络(Pocket Network / POKT)。

为什么存在。 因为跑一个全节点需要几百 GB 到几 TB 的磁盘、持续的带宽和运维(事实,具体数字随链和节点类型变化很大)。没有开发者愿意为了查个余额先同步两天。

这里有一个必须点名的中心化事实(事实): 以太坊生态相当比例的读请求,长期集中在少数几家 RPC 服务商手上,Infura 和 Alchemy 是其中最大的两家。历史上 Infura 出现过服务中断,导致大量钱包和交易所同时"看不见链"(2020 年 11 月的 Infura 中断是最著名的一次)。链本身在正常出块,但用户觉得链挂了——因为他们的窗户被关上了。

这也是"去中心化不是二元属性"的第一个具体例证:共识层可能确实由几十万个节点维护,但访问层可能只由几家公司提供。

风险猫提醒 你的 RPC 服务商能看见什么?——你所有请求的来源 IP、你查询的每一个地址、你发出的每一笔交易的原始内容和时间戳。 这意味着:如果你用同一个 API Key 查了你的主钱包余额和你以为匿名的小号余额,服务商侧的日志里,这两个地址就被关联在一起了。链上分析公司做地址聚类,RPC 日志是极有价值的辅助数据源(作者判断,各家隐私政策不同,但技术上完全可行)。 另外,把 RPC API Key 硬编码进前端 JS 是行业普遍做法,也意味着它是公开的——别人可以拿去刷你的配额。生产环境请走自己的后端代理并加限流。

5. 节点(Node):真正保存和验证账本的那台机器

是什么。 运行区块链协议软件、保存账本副本、独立验证每一笔交易和每一个区块的程序。以太坊合并后,一个完整节点实际上是两个程序:执行客户端(Execution Client)+ 共识客户端(Consensus Client),两者通过 Engine API 通信。

代表工具。 执行客户端:Geth(Go,历史份额最高)、Nethermind(C#)、Erigon(Go,存储优化)、Besu(Java)、Reth(Rust,较新)。共识客户端:Prysm、Lighthouse、Teku、Nimbus、Lodestar。

为什么存在。 因为"去信任"的物理基础就是你自己能独立验证。如果你只能问别人"账本是什么样",你就还是在信任别人。节点的存在意义不是提供服务,而是提供验证能力

关于节点类型(这个区分非常重要,也最常被混淆):

客户端多样性是一个真实的系统性风险(行业共识): 如果某个执行客户端占据了绝对多数份额,而它出现了一个共识 bug,那么大多数节点会一起接受一个错误的区块,少数派客户端反而会被判定为"分叉"。以太坊社区长期把"没有任何客户端超过某个份额阈值"当作明确的安全目标,这本身就说明去中心化不只是节点数量问题,还是软件多样性问题

6. 共识层(Consensus):谁有权写下一页

是什么。 决定"下一个区块由谁产生、什么样的链是正确的链"的规则集合。

代表机制。 工作量证明 PoW(比特币);权益证明 PoS 的多种变体——以太坊的 Gasper(Casper FFG 提供最终性 + LMD GHOST 提供分叉选择)、Tendermint / CometBFT(Cosmos 系,即时最终性)、Solana 的 PoH + Tower BFT、Avalanche 的重复子采样投票。

为什么存在。 因为在一个开放网络里,"谁说了算"这个问题没有天然答案。共识机制的作用是把这个问题转化成一个有成本、可验证、且作弊不划算的博弈:PoW 让你付电费,PoS 让你抵押资产并接受罚没(slashing)。

开发者需要知道的三个直接影响:

  1. 区块时间决定你的 UI 反馈延迟。 以太坊约 12 秒一个 slot,Solana 亚秒级,比特币约 10 分钟。你的"交易确认中"转圈要转多久,由这一层决定。
  2. 最终性模型决定你什么时候敢发货。 BFT 类共识(Tendermint)出块即最终;以太坊需要等约两个 epoch(十几分钟)才达到经济最终性;比特币是概率最终性,永远没有绝对确定的那一刻。详见第三节。
  3. 重组(reorg)会真实发生。 你监听到的事件可能在几个区块后消失。任何"收到事件就发货"的后端,如果不处理重组,就是一个等待被触发的资损 bug。

7. 执行层(Execution):链上的那台虚拟机

是什么。 真正执行智能合约字节码、改变状态的虚拟机与状态数据库。

代表实现。 EVM(以太坊虚拟机,256 位字长的栈式虚拟机,被数百条链复制,形成"EVM 兼容"这个巨大的护城河);SVM(Solana,基于 eBPF,支持并行执行,要求交易预先声明会访问哪些账户);MoveVM(Aptos / Sui,资源类型系统在语言层面禁止资产被复制或凭空销毁);CosmWasm(WASM 字节码);以及正在被讨论的基于 RISC-V 的下一代执行环境(尚在研究/提案阶段,存在争议)。

为什么存在。 因为需要一个完全确定性的执行环境:同一份输入,在全球任何一台机器上执行,必须得到逐字节相同的结果。这条要求排除了浮点数(不同硬件的舍入可能不同)、系统时间、随机数、网络 IO、文件系统——所有你在正常编程里习以为常的东西。

这解释了链上开发最反直觉的几件事: - 合约里没有 Math.random(),因为随机数不确定。想要随机,要么用外部预言机(Chainlink VRF),要么用未来区块的信息(有被操纵风险)。 - 合约里没有 fetch(),合约不能主动访问外部世界。它是一个纯函数式的状态转换器,只能被动接受输入。这是预言机存在的根本原因。 - 合约里没有定时器。"每天凌晨自动结算"这种需求,链上做不到,必须有人(或某个 Keeper 网络)在那个时刻发一笔交易来"戳"它一下。

EVM 的并行化困境(事实): 经典 EVM 是单线程串行执行的,因为它无法预知一笔交易会读写哪些存储槽。Solana 用"交易必须预先声明访问的账户列表"换取了并行执行能力,代价是编程模型复杂得多。近年 EVM 生态出现了乐观并行执行(先并行跑、冲突了再串行重跑)的实现(Block-STM 系思路),但这是工程优化,不改变语义。

8. 数据可用性(Data Availability, DA):Rollup 的地基

是什么。 保证"某笔数据确实被发布出来了、任何想要的人都能拿到"的机制。注意它证明的不是"数据正确",而是"数据可取"。

代表方案。 以太坊 EIP-4844 的 blob(2024 年 3 月坎昆升级上线,为 L2 数据开设的临时通道,约 18 天后节点可丢弃);专职 DA 链 Celestia;再质押型 EigenDA;Avail;以及以太坊路线图中的 PeerDAS(数据可用性采样)。

为什么存在。 这是整个 Rollup 架构里最容易被跳过、但最要命的一环。Rollup 把交易执行搬到了 L2,只把结果提交回 L1。问题来了:如果 L2 的排序器把交易数据藏起来不公布,会发生什么?

答案是:没有人能重建 L2 的状态,没有人能构造欺诈证明(Optimistic 路线),没有人能自己算出"我的余额应该是多少"来强制提款。你的资产在数学上还在,在实践中你取不出来。这被称为数据扣留攻击(data withholding)

所以一条 Rollup 的安全性,等于 min(执行正确性保证, 数据可用性保证)如果一条链把数据发布在自己控制的服务器上(这类方案通常叫 Validium 或 Optimium),那它的安全性上限就是那个服务器运营方的诚实度,不管它的零知识证明多么精妙。 这一点在营销材料里几乎从不被强调(作者判断)。

9. 智能合约(Smart Contracts):你实际要写的那部分

是什么。 部署在链上、由交易触发执行、能持有和转移资产的程序。

代表工具。 语言:Solidity(绝对主流)、Vyper(Python 风格,刻意做减法以减少攻击面)、Rust(Solana 的 Anchor 框架、CosmWasm、Arbitrum Stylus)、Move(Aptos / Sui)。开发框架:Foundry(Rust 写的工具链,测试用 Solidity 写,速度快,目前新项目主流)、Hardhat(JS/TS 生态,插件丰富)、Remix(浏览器 IDE,零安装,教学首选)。库:OpenZeppelin Contracts(ERC 标准的事实参考实现,被审计次数最多)、Solady(极致 gas 优化版本,代价是可读性)。

为什么存在。 因为如果链只能转账,它就只是账本;能跑图灵完备的程序,它才是平台。

给传统程序员的三条"世界观校准":

  1. 代码量和风险不成正比,而是和"能碰到多少钱"成正比。 一个 200 行的合约管着 5 亿美元,是这个行业的常态。你写的每一行都在生产环境,且第一天就有全球最强的一批攻击者在读它。
  2. 没有 try-catch 的常规语义。 revert 会回滚整笔交易的全部状态变更(这其实是个很好的特性,天然原子性),但也意味着一个下游调用失败可能让整笔业务失败。低级调用(call)不会自动 revert,返回一个 bool——忘记检查这个 bool 是历史上多次事故的直接原因(公开事故模式)。
  3. 升级不是理所当然的。 见第三节的"合约可升级性"。

10. 索引器(Indexer):把链上数据变成能查询的数据

是什么。 持续读取区块和事件日志,把它们转换成结构化数据库(通常是 Postgres),对外提供 GraphQL 或 SQL 查询的服务。

代表工具。 The Graph(去中心化子图网络)、Ponder(TypeScript,本地优先,开发体验好)、Subsquid、Envio、Goldsky;分析侧的 Dune Analytics(SQL 查链上数据,社区看板生态庞大)、Flipside、Allium。

为什么存在。 因为链不是数据库,它没有索引。链只支持两类查询:按 key 读状态("这个地址余额多少"),和按区块范围过滤日志(eth_getLogs)。它不支持"列出该用户所有历史交易"、"按 TVL 排序所有池子"、"过去 7 天成交额"。这些全都要靠链下重建。

这一层是"去中心化应用"叙事里另一处安静的中心化(作者判断): 一个前端漂亮、合约无懈可击的 DeFi 应用,它页面上显示的所有数字——你的持仓、你的收益率、历史图表——大概率来自一个跑在某台云服务器上的索引服务。索引挂了,应用就白屏了;索引算错了,你看到的数字就是错的。合约还在正常运行,但用户体验上"应用挂了"。

踩坑点。 索引器必须处理重组:链回滚了,你的数据库也得跟着回滚。成熟的索引框架内置了这个能力,自己手写的 while(true) { getLogs(); } 脚本通常没有——这是自建索引最常见的数据污染来源。

11. 区块浏览器(Block Explorer):链上的浏览器和调试器

是什么。 把区块、交易、地址、合约以人类可读方式呈现的网站,同时提供合约源码验证、交易解码、内部调用追踪。

代表工具。 Etherscan(EVM 生态事实标准,几乎每条 EVM 链都有对应部署)、Blockscout(开源,可自建,很多新链的默认浏览器)、Otterscan(本地跑,配合自己的 Erigon 节点,隐私最好)、Tenderly(偏调试和模拟,能重放交易并单步查看状态变化)。

为什么存在。 对开发者:它是你唯一的生产环境调试器。交易失败了,你在浏览器上看 revert reason、看 trace、看每一步的 gas 消耗。对用户:合约源码验证是最重要的功能——它证明"这个地址上部署的字节码,确实是由这份公开源码编译出来的"。没有验证的合约,用户面对的就是一坨看不懂的十六进制。

诚实提示(事实): Etherscan 是一家私营公司。整个行业对"合约是否可信"的判断,高度依赖它的验证服务和标签数据库。它标一个地址是"钓鱼合约",钱包就会弹警告;它没标,就没有。这是又一处基础设施级的单点依赖。

12. 预言机(Oracle):把外部世界搬上链

是什么。 把链外数据(价格、天气、比赛结果、随机数)写入链上,供合约读取的服务。

代表项目。 Chainlink(推送模式,节点网络聚合后定期或在偏离阈值时更新,DeFi 采用最广)、Pyth(拉取模式,用户在交易中自带最新签名价格,延迟低)、RedStone、Chronicle、API3(第一方预言机,数据源自己上链)、UMA(乐观预言机,先声明后挑战)。

为什么存在。 因为执行层的确定性要求(见第 7 节)导致合约根本不能主动访问外部数据。而 DeFi 的几乎所有核心逻辑——抵押率、清算线、永续合约的标记价格——都建立在"外部价格"之上。

为什么这一层是风险最集中的地方之一(行业共识): 预言机把一个链下的、可能被操纵的数字,变成了链上合约无条件信任的输入。历史上多起借贷协议被"操纵价格再借空"的事故,攻击的都不是合约代码,而是价格来源。防御的核心思路是:使用流动性足够深的市场作为数据源、多源聚合、时间加权(TWAP)而非瞬时价、设置偏离熔断。详见第三节。

13. 跨链桥与互操作(Bridge / Interop)

是什么。 让资产或消息在不同链之间流动的协议。主流机制是"在 A 链锁定,在 B 链铸造等量凭证"。

代表项目。 LayerZero、Wormhole、Axelar、Chainlink CCIP、Across、Hyperlane;Cosmos 生态的 IBC(基于轻客户端验证,信任假设最干净);各 Rollup 的原生桥(安全性继承自 L1,但 Optimistic 桥有 7 天挑战期)。

为什么存在。 因为链之间天然互不通信。链 A 上的合约看不到链 B 的状态,也验证不了链 B 的事件——除非有人把链 B 的共识规则实现成链 A 上的一个合约(这就是轻客户端桥,成本极高但信任假设最小)。

必须诚实说的(事实): 跨链桥是这个行业被盗金额最集中的环节。公开的重大事故包括 Poly Network(2021 年 8 月,约 6.1 亿美元,后归还)、Wormhole(2022 年 2 月,约 3.2 亿美元)、Ronin(2022 年 3 月,约 6.2 亿美元,且 6 天后才被发现)。这些事故的共性不是"代码写得烂",而是架构上把安全性交给了一个小规模的多签或验证人集合——攻破 5 个私钥里的 3 个,就能凭空铸造资产。详见第三节。

14. Rollup 框架:一键造一条链

是什么。 把"部署一条 Rollup"从一个需要几十人团队的工程,变成一份配置文件的工具集。

代表方案。 OP Stack(Optimism 开源,Base、Zora 等大量链在用,组成"超级链"愿景)、Arbitrum Orbit、ZK Stack(zkSync)、Polygon CDK、Starknet 的 Madara;以及 RaaS(Rollup as a Service)服务商 Caldera、Conduit、Gelato——它们提供托管式的一键部署。

为什么存在。 因为一旦"造链"的边际成本趋近于零,链就从稀缺资源变成了应用的一个部署选项——像今天开一个 Kubernetes 集群一样。

随之而来的问题(作者判断): 链的数量爆炸,流动性和用户被切碎。一个用户的资产分散在七条链上,每条链上都有一点点,每次跨链都要付费和等待。这就是所谓"碎片化"问题,也是链抽象(Chain Abstraction)、意图(Intent)架构、共享排序器等方向试图解决的。截至目前,这个问题尚未有公认的解法

15. Sequencer(排序器):Rollup 里那个说了算的角色

是什么。 决定 L2 上交易顺序、打包出块、并把数据提交回 L1 的组件。

代表形态。 中心化单排序器(目前几乎所有主流 Rollup 的现状,事实);共享排序器网络(Espresso、Astria,多条链共用一个去中心化排序层);Based Rollup(直接把排序权交给 L1 的提议者,牺牲一部分性能换取最强的抗审查性)。

为什么存在。 因为总得有人决定顺序。而顺序在金融场景里等于钱——谁先成交、谁被夹在中间,全由排序决定。

这里是整个 Rollup 叙事最大的诚实缺口(行业共识): 主流 L2 目前基本都由项目方运行单一排序器。这个排序器可以:审查你的交易(不打包)、重排序交易(提取 MEV)、宕机导致整条链停摆(历史上多条 L2 都发生过数小时级别的停摆)。它不能做的是:偷你的钱——因为状态转换的正确性由证明或挑战机制保证,且大多数 Rollup 设计了"强制包含"(force inclusion)通道,允许用户直接通过 L1 提交交易绕过排序器(不同链的这条通道成熟度差异很大,存在争议)。详见第三节。

16. Prover(证明器):把"我算对了"变成一张数学收据

是什么。 为一批计算生成简洁密码学证明的系统,使得验证者用极小的代价就能确认"这批计算被正确执行了",而无需重新执行。

代表项目/技术。 通用 zkVM:SP1(Succinct)、RISC Zero、Jolt;专用电路:zkSync 的 Boojum、Scroll 与 Polygon zkEVM 的 zkEVM 电路;证明市场:把证明生成外包给一个竞价的算力网络。

为什么存在。 因为验证比执行便宜好几个数量级——这是零知识证明系统最实用的性质(在 Rollup 场景里,"零知识"其实主要用的是简洁性而不是隐私性,这是一个常见的命名误导)。

工程现实(事实): 生成证明的计算开销远大于原始执行,通常是几个数量级,需要专门的 GPU 集群。这带来两个后果:一是成本,证明费用要摊到用户手续费里;二是中心化,能负担证明基础设施的实体不多,很多"去中心化 ZK 链"的证明生成实际由项目方一家承担(作者判断,各链情况不同)。

17. 去中心化存储

是什么。 存放链上放不下的东西——图片、视频、大 JSON、前端构建产物。

代表方案。 IPFS(内容寻址协议,注意它本身不保证持久性,没人 pin 的内容会消失);Filecoin(给 IPFS 加上经济激励与存储证明);Arweave(一次付费、承诺永久存储的模型);Walrus(Sui 生态);Irys;托管型 pinning 服务 Pinata、web3.storage。

为什么存在。 因为链上存储的成本高到荒谬。一张几百 KB 的图片直接上链,成本可能是几千美元级别(随 gas 价格波动,事实)。所以主流做法是:大文件存链下,链上只存一个内容哈希(CID)

踩坑点(很常见): 很多 NFT 的 tokenURI 指向的是一个中心化 HTTP 地址(甚至是项目方的 AWS S3)。项目方跑路或不续费,NFT 图片就变成 404——你花大价钱买的"永久数字资产",链上只剩一个死链接。检查一个 NFT 项目的元数据是否真的存在 IPFS/Arweave 上,是最基础的尽调动作之一。

18. 身份(Identity)

是什么。 把一串 42 位的十六进制地址,与人类可读的名字、可验证的属性、跨应用的身份关联起来的一层。

代表方案。 ENS(以太坊域名,vitalik.eth 这种,也可以反解析为头像和社交账号);SIWE / EIP-4361(Sign-In with Ethereum,用签名代替密码登录);EAS(Ethereum Attestation Service,链上/链下的通用背书原语);DID 与 Verifiable Credentials(W3C 标准);人格证明方向的 Worldcoin、BrightID、Proof of Humanity。

为什么存在。 因为地址天然是匿名且可无限生成的,这导致两个问题:一是用户体验(没人记得住十六进制),二是女巫攻击(一个人可以伪装成一万个人,任何"每人限一份"的机制都会被薅穿)。

技术难点(行业共识): 隐私和可验证性天然冲突。你要证明"我是唯一的真人",最简单的办法是交出生物特征——但这就把隐私交出去了。零知识证明理论上能做到"证明属性而不暴露数据",但需要一个可信的发证方,问题只是被推到了上游。这一层截至目前没有令人满意的解法(作者判断)。

19. 安全监控与响应

是什么。 在合约部署之后,持续监控链上行为、检测异常、并在必要时执行应急响应(暂停、限流、转移资产)的一整套体系。

代表工具。 Forta(去中心化检测机器人网络)、OpenZeppelin Defender(监控 + 自动化运维 + 多签操作)、Tenderly Alerts、Hexagate、Chaos Labs(风险参数模拟);以及 Immunefi 这类白帽赏金平台。

为什么存在。 因为审计只是一个时间点的快照,而攻击发生在部署之后的任意时刻。真实的攻击往往有几分钟到几小时的窗口期——攻击者先做小额试探、再放大规模。能在试探阶段就报警并暂停,就能把损失从千万级压到零(公开的事故复盘中多次出现这种对比)。

工程要求。 你需要事先设计好:熔断开关(pause)由谁控制、多长时间能执行(多签凑齐签名要多久?)、暂停会不会反而伤害用户(比如让人无法平仓)。"有暂停功能"和"能在 8 分钟内真的按下去"是两回事(作者判断)。

20. MEV 基础设施

是什么。 围绕"交易排序权的经济价值"形成的一整套市场化基础设施。MEV = Maximal Extractable Value,即通过重排、插入、审查交易能提取的最大价值。

代表组件。 Flashbots(把 MEV 从公开的 gas 竞价战搬到私有拍卖)、MEV-Boost(以太坊上分离提议者与构建者的中间件,PBS 的过渡实现)、区块构建者(builder)与中继(relay)、私有交易池 / RPC(Flashbots Protect、MEV Blocker,让交易不进公共内存池从而避免被夹)、订单流拍卖(OFA)。

为什么存在。 因为在公开内存池里,你的交易在被打包前是所有人可见的。一笔大额兑换会被机器人观察到,机器人可以在你前面买、在你后面卖,赚走价差——这就是"三明治攻击"。这不是漏洞,是公开内存池 + 可自由排序的必然结果。

开发者的必修课(作者判断): 任何涉及价格的交易都必须有滑点保护截止时间参数。这不是可选优化,是基本安全要求。把 amountOutMin 设成 0 等于对全世界说"随便夹我"。

21. 法币出入金(On/Off Ramp)

是什么。 把银行体系里的钱换成链上资产(入金)和反向操作(出金)的通道。

代表服务。 中心化交易所(Coinbase、Binance 等)、嵌入式支付服务商(MoonPay、Transak、Ramp Network、Stripe 的加密支付)、P2P 场外市场。

为什么存在。 因为链上没有法币。链是一个封闭的价值系统,所有法币进出必须经过银行体系——而银行体系有 KYC、AML、制裁筛查、地区限制。

这是整个"无许可"叙事最硬的边界(事实): 你的智能合约可以完全无许可,任何人都能调用;但绝大多数用户要用它,必须先通过一个需要提交身份证件的中心化机构。协议层的无许可,不等于用户旅程的无许可。 对开发者的实际影响:如果你的应用面向普通用户,出入金体验会是你转化漏斗里最陡的那一段,而且这段你控制不了。

22. 托管(Custody)

是什么。 私钥的保管方案。

代表形态。 自托管(用户自己持有助记词,硬件钱包);多签(Safe,M-of-N 签名,DAO 金库标配);MPC / 门限签名(Fireblocks、Copper、Turnkey,私钥从不完整存在于任何一台机器上);合格托管机构(Coinbase Custody、Anchorage、BitGo,面向机构与合规要求)。

为什么存在。 因为"自己保管私钥"这件事,个人做起来风险极高(丢失、被盗、继承问题),机构做起来则有法律和审计要求。

必须点破的一点(作者判断): 行业口号是"Not your keys, not your coins",但同时,用户自托管的资产丢失是不可逆的、没有保险的、没有客服的。自托管把风险从"机构可能作恶"转移到了"我可能犯错",而不是消除了风险。 对大多数普通用户来说,后者的实际发生概率可能更高。这是一个取舍,不是一个道德立场。

23. 合规工具

是什么。 链上分析、地址风险评分、制裁名单筛查、旅行规则(Travel Rule)报文交换的一整套软件。

代表公司。 Chainalysis、TRM Labs、Elliptic、Merkle Science;Travel Rule 方案商 Notabene、Sygna。

为什么存在。 因为链上数据是公开且永久的,这对隐私是坏消息,对取证是好消息。执法机构和金融机构需要知道一笔资金是否来自被制裁地址、混币器或已知盗窃事件。

开发者需要知道的: 如果你的产品接触法币或面向机构,你几乎一定会被要求接入某种地址筛查。这带来一个技术伦理问题——在合约层面做地址黑名单(很多中心化稳定币合约都有 freeze 功能,事实)意味着这个"去中心化资产"实际上可以被单方冻结。这不是阴谋,是白纸黑字写在合约里的公开功能,但很多用户从不知道。

投研姐视角 我看这张技术栈图的方式和工程师不一样。工程师问"这层怎么实现",我问三个问题: 第一,这层能不能收到钱? 能持续收到真实付费的层不多:RPC 服务(开发者付费,是这个行业少见的健康 SaaS 生意)、出入金(按笔抽成,现金流最实)、托管(按 AUM 收费)、区块空间本身(gas 费)。而大量中间件层——索引、身份、部分预言机——至今主要靠代币补贴和融资维生(作者判断)。 第二,这层的竞争格局是不是赢家通吃? 有强网络效应的层会收敛到一两家:预言机(数据源和集成越多越可信,Chainlink 的位置就是这么来的)、区块浏览器、L1 本身。而没有网络效应的层会被卷成红海:RPC 供应商的价格战、RaaS 服务商的同质化。 第三,也是最关键的——这层的价值会不会被上下游吃掉? Rollup 框架开源意味着造链不值钱,值钱的是排序权和用户;DA 层如果以太坊自己的 blob 容量持续扩大,独立 DA 链的定价空间就会被压缩。 一个反直觉的结论:技术上最难的层,商业上未必最值钱。 ZK 证明是这个行业智力密度最高的方向之一,但它目前的商业模式是"给 Rollup 打工,赚证明费",而 Rollup 的利润本身就薄。而技术含量远低于它的出入金业务,是这个行业最赚钱的生意之一。

分层总表

层级 是什么 代表工具 / 项目 存在的根本原因 中心化风险
钱包 私钥保管与签名 MetaMask、Rabby、Safe、Privy 链只认签名,没有账号密码 嵌入式钱包托管密钥分片
前端 dApp 的界面 React + wagmi、RainbowKit 用户不会手写 calldata 高:域名 + CDN 几乎都中心化
SDK RPC 的类型安全封装 viem、ethers.js、web3.py 原始 JSON-RPC 反人类
RPC 与链通信的端点 Alchemy、Infura、QuickNode 自跑节点门槛高 高:少数服务商承载大量请求
节点 保存并验证账本 Geth、Reth、Lighthouse 去信任的物理基础 客户端份额过度集中是系统风险
共识 决定谁写下一块 PoS/Gasper、CometBFT、PoH 开放网络需要抗女巫的记账权分配 质押集中于少数流动性质押协议
执行 跑合约的虚拟机 EVM、SVM、MoveVM 需要完全确定性的执行环境 低(但客户端多样性同上)
数据可用性 保证数据可取 blob、Celestia、EigenDA、Avail 无数据则无法重建状态、无法证伪 Validium 类方案依赖运营方
智能合约 链上业务逻辑 Solidity、Foundry、OpenZeppelin 让链从账本变成平台 升级权限 / Admin Key
索引器 把链上数据变可查 The Graph、Ponder、Dune 链没有索引,只能按 key 读 高:应用显示的数字来自链下
区块浏览器 查看与验证 Etherscan、Blockscout、Tenderly 生产环境唯一的调试器 高:源码验证与标签高度依赖单一服务
预言机 外部数据上链 Chainlink、Pyth、RedStone 合约不能主动访问外部世界 中:节点集合与数据源集中
跨链桥 资产与消息跨链 LayerZero、Wormhole、IBC 链之间天然互不通信 极高:历史被盗最集中
Rollup 框架 一键造链 OP Stack、Orbit、ZK Stack、CDK 把造链变成部署选项 造链易,但排序权仍在项目方
Sequencer 排序与打包 单排序器、Espresso、Based Rollup 总得有人决定交易顺序 极高:主流 L2 均为单排序器
Prover 生成有效性证明 SP1、RISC Zero、Boojum 验证远比重新执行便宜 高:证明算力昂贵且集中
去中心化存储 存放大文件 IPFS、Arweave、Filecoin 链上存储成本高到荒谬 IPFS 不保证持久,pin 服务中心化
身份 名字、属性、人格证明 ENS、SIWE、EAS、DID 地址匿名且可无限生成 人格证明依赖可信发证方
安全监控 部署后的持续防守 Forta、OZ Defender、Tenderly 审计只是时间点快照 熔断权限本身即中心化点
MEV 基础设施 排序权的市场化 Flashbots、MEV-Boost、私有 RPC 公开内存池必然产生抢先交易 构建者与中继高度集中
法币出入金 法币与链上资产互换 Coinbase、MoonPay、Transak 链上没有法币 极高:必经 KYC 与银行体系
托管 私钥保管方案 硬件钱包、Safe、Fireblocks 个人保管风险高,机构有合规要求 视方案而定,本质是信任转移
合规工具 风险筛查与报文 Chainalysis、TRM、Elliptic 链上数据公开永久,利于取证 黑名单可导致资产被冻结

二、零资金 / 测试网开发路线:十步,一分钱不花

这条路线的设计原则有三条:全程测试网(测试币从水龙头免费领,没有任何真实价值,即使全部丢失也毫无损失)、每一步都有可验证的产出物(不是"看懂了",是"跑起来了、能在区块浏览器上查到")、每一步都标注常见坑(这些坑我见过太多人踩,包括我自己)。

推荐主链路:以太坊 Sepolia 测试网(目前面向应用开发者的主力测试网)。如果你想顺带体验 L2,Base Sepolia 和 Arbitrum Sepolia 都可以从 Sepolia 桥接。

工具链推荐:Foundry(合约侧)+ viem / wagmi(前端侧)+ Remix(教学与快速验证)。Foundry 的安装是一条 foundryup 命令,测试用 Solidity 写,运行速度比 JS 框架快一到两个数量级(事实)。

Builder 视角 在你开始之前,我想说一件很多教程不说的事:这十步里,真正的技术难点只有第 8 步和第 10 步。 前七步的所有内容,一个熟练的后端工程师大概两三个周末就能跑通——它们只是新的 API 和新的名词。 真正把人筛掉的是"心智模式的切换":接受"我不能改数据库"、接受"我的代码第一天就在被攻击者审计"、接受"我不能 hotfix"。这些不是知识,是习惯。养成它们的唯一办法是真的部署一个东西,然后真的被它坑一次——最好是在测试网上被坑,那不花钱。 所以我建议你把这十步当成十次"故意犯错"的机会,而不是十个要打勾的任务。每一步的"常见坑"那一段,请你主动去踩一遍:故意把滑点设成 0、故意不检查返回值、故意在测试网上部署一个有权限漏洞的合约然后自己攻击自己。在测试网上,最贵的学费也只是几分钟时间。

第 1 步:钱包连接

目标。 让一个网页能识别用户的钱包地址,并完成一次"登录"。

用什么。 MetaMask(切到 Sepolia 网络)+ 一个 Vite/Next.js 项目 + wagmi + RainbowKit。约 30 行代码就能出一个能用的连接按钮。

可验证成果。 页面上显示 0x1234...abcd,切换账户时页面同步更新,切换到非 Sepolia 网络时提示用户切链。进阶:实现一次 SIWE(Sign-In with Ethereum)登录——让用户签一条包含域名、随机 nonce 和时间戳的消息,后端验签后签发 session。

常见坑。 - 把"连上钱包"当成"用户已认证"。 前端能读到地址不代表用户拥有这个地址的私钥——地址是公开信息,任何人都可以在自己的前端里伪造。只有一次成功的签名验证才构成认证。 这是新手最常见的安全误解。 - SIWE 消息里不带 nonce 或不带域名,会导致签名可被重放到其他站点。EIP-4361 规定的字段每一个都有理由,别删。 - 忘记处理"用户拒绝签名"和"用户中途换账户"两个事件,页面会卡在一个奇怪的中间态。

第 2 步:读链上数据

目标。 不发交易、不花任何 gas,纯读取链上状态。

用什么。 viem 的 publicClient,配一个 Alchemy 或 Infura 的免费 Sepolia 端点。读四类东西:原生币余额(getBalance)、某个 ERC-20 的余额(readContractbalanceOf)、区块信息(getBlock)、某笔交易的回执(getTransactionReceipt)。

可验证成果。 一个页面,输入任意地址,显示它的 ETH 余额和你指定的几个测试 ERC-20 的余额,并且数字和 Sepolia Etherscan 上显示的完全一致。

常见坑。 - decimals 陷阱。 链上余额是整数(uint256),显示要除以 10 ** decimals。ETH 是 18 位,但 USDC 是 6 位,WBTC 是 8 位。永远从合约读 decimals,不要硬编码。 这个坑在主网上直接等于把金额算错一百万倍。 - 用 JavaScript 的 Number 处理 uint256。 JS 的安全整数上限是 2^53,而 uint256 的上限是 2^256。全程用 BigInt,任何地方都不要转成 Number 做运算,只在最终格式化显示时转成字符串。这是链上前端最高频的资损级 bug。 - 免费 RPC 限流。 你写个 for 循环查 500 个地址,第 100 个开始全是 429。加批量请求(multicall)或加退避重试。

第 3 步:发一笔测试交易

目标。 第一次改变链的状态。

用什么。 从水龙头(faucet)领 Sepolia 测试 ETH——Google Cloud、Alchemy、QuickNode 都提供,通常需要一个满足最低余额或活跃度要求的主网地址做防女巫(这是唯一可能让你觉得"卡住了"的地方,多试几家)。然后用钱包发一笔普通转账给自己的第二个地址。

可验证成果。 在 Sepolia Etherscan 上能查到这笔交易,看到 from、to、value、gas used、区块号。然后用代码复现同一件事:viem 的 walletClient.sendTransaction

常见坑。 - 不理解 nonce。 每个地址的交易有严格递增的序号。如果你连续发三笔,第一笔卡住(gas 给低了),后两笔就算 gas 给得再高也上不了链——它们在排队等前面那笔。这是所有"交易一直 pending"问题的第一大原因。解法是用相同 nonce 发一笔更高 gas 的交易覆盖它(speed up / cancel)。 - 混淆 gas limit 和 gas price。 limit 是"我最多愿意消耗多少计算单位"(用不完会退),price 是"每单位我出多少钱"(不退)。gas limit 给低了交易会 out-of-gas 失败,而且失败也要付掉已消耗的 gas——链上失败的交易同样收费,这是新手最意外的一点。 - 把测试网私钥和主网私钥用同一个。 别。测试用的私钥会出现在你的 .env、你的终端历史、你的截图里。给测试网单独生成一个全新私钥,并且这个私钥永远不要碰任何真钱。

第 4 步:部署一个最小合约

目标。 让自己的代码住进链里。

用什么。 forge init 起项目,写一个最简单的计数器合约(一个 uint256 public count,一个 increment()),forge test 跑通,forge create 部署到 Sepolia,然后在 Etherscan 上做源码验证(forge verify-contract)。

可验证成果。 Sepolia Etherscan 上你的合约地址页面显示绿色的"Contract Source Code Verified",别人点进去能看到你的源码,并且能通过 Etherscan 的 Write Contract 界面直接调用你的 increment()

常见坑。 - 构造函数参数忘了在验证时传一致。 验证失败率最高的原因。 - 合约里用了 block.timestamp 当作精确时间。 它是由出块者填写的,有一定的操纵空间(不同链的容忍窗口不同)。用它做"大致时间"没问题,用它做"精确到秒的公平判定"就有问题。 - 误以为 private 变量是私密的。 再强调一次:链上所有存储都是公开可读的,private 只影响其他合约能否通过函数访问,不影响任何人直接读存储槽(事实)。不要把任何秘密写进合约,哪怕只是"临时放一下"。 - 部署到主网。 检查你的 RPC URL 和 chainId。有人在这一步真的花了真钱,虽然一般是小额。

第 5 步:事件监听

目标。 让链下系统对链上变化做出反应——这是绝大多数实际业务的核心链路。

用什么。 在合约里 emit 一个事件(比如 event Incremented(address indexed by, uint256 newCount)),前端用 viem 的 watchContractEvent 订阅,后端用 getLogs 按区块范围拉取历史。

可验证成果。 一个终端脚本,实时打印出每一次 increment() 被调用的调用者和新值;以及一个能拉取"从合约部署至今全部事件"的历史回补脚本。

常见坑(这一步的坑最多,也最容易在生产环境造成资损)。 - 只订阅实时事件,不做历史回补。 你的服务重启 3 分钟,这 3 分钟的事件就永远丢了。正确做法是记录"已处理到的区块高度",重启时从那里 getLogs 补齐,再切到实时订阅。 - 不处理重组。 你在区块 N 收到一个事件,发货了;然后链重组了,区块 N 被替换,那笔交易根本不存在。任何触发不可逆动作(发货、打款、发消息)的逻辑,都必须等待足够的确认数。等多少?取决于链和金额,详见第三节的"重组"。 - indexed 参数用错。 只有 indexed 的参数能被高效过滤,且最多 3 个。字符串或数组被 indexed 后,日志里存的是它的哈希而不是原值——你能过滤但读不出内容。这是设计事件签名时必须提前想清楚的。 - getLogs 的区块范围太大。 多数 RPC 服务商限制单次查询的区块跨度(常见是几千到一万块)。批量分段查询,并处理超限报错。

第 6 步:Token 的基本结构

目标。 理解"代币"这个词在技术上到底指什么。

用什么。 继承 OpenZeppelin 的 ERC20,部署一个自己的测试代币。然后手写一遍(只为学习,不用于部署):balanceOf 是一个 mapping,transfer 就是减一个加一个并 emit 事件,approve / transferFrom 是一个二级授权 mapping。

可验证成果。 你的代币出现在 MetaMask 的资产列表里(手动添加合约地址),能在两个地址间转账,Etherscan 的 Token 页面能显示持有人列表。

关键认知。 走完这一步你会明白一件事:代币不是一个"东西",它只是某个合约里的一行 mapping 记录。 "我钱包里有 1000 个 XYZ"的真实含义是"XYZ 合约的 balances 映射里,我的地址对应的值是 1000 × 10^decimals"。你的钱包本身什么都没存,它只是去每个合约那里问了一遍。

理解这一点之后,很多事情会突然变得清晰:为什么代币能被合约方冻结(合约里有 blacklist 就能)、为什么"空投到钱包里的垃圾币"删不掉(那是别人合约里的记录,不在你的控制范围)、为什么授权(approve)是危险的(你给了一个合约无限期支配你余额的权力)。

常见坑。 - 无限授权。 前端为了少一次交互,默认让用户 approve 一个天文数字。用户以后忘了这回事,而那个被授权的合约如果有漏洞或被升级成恶意版本,就能随时把余额划走。历史上多起损失就是这么发生的(公开事故模式)。开发者应默认只授权本次所需额度,或使用 EIP-2612 的签名授权(permit)。 - 假设所有 ERC-20 都规矩。 现实中存在:转账时收手续费的代币(你转 100 到账 98,你的账本就对不上了)、transfer 失败时返回 false 而不 revert 的代币(不检查返回值就等于没转成功却当成功了)、可以 rebase 改变余额的代币。处理任意代币的协议必须用 SafeERC20 之类的包装,并对"实际到账金额"做二次测量。

第 7 步:拼成一个简单 DApp

目标。 把前六步串成一个完整的东西。

用什么。 一个"链上留言板"是很好的练习题:合约存留言(或只 emit 事件省 gas),前端连钱包、读历史留言、发新留言、实时刷新。加分项:接入一个索引器(Ponder 本地跑就行)来做分页和搜索。

可验证成果。 一个别人打开链接、连上钱包就能用的网站(部署到 Vercel 免费额度即可)。你能把链接发给朋友,他领点测试币就能留言。

常见坑。 - 交易状态机没做全。 用户点击后有至少五个状态:等待钱包弹窗 → 用户已签名待广播 → 已广播待确认 → 已确认 → 失败/被替换。只做"loading / done"两态的界面,用户会疯狂重复点击,然后发出五笔重复交易。 - 乐观更新不回滚。 交易发出就把 UI 改成成功状态,结果交易 revert 了,界面和链上不一致。 - 没处理"用户在另一个标签页换了链"。

第 8 步:常见安全错误的原理与防御

声明:这一节只讲原理和防御写法。所有示例都是教学级伪代码,且都是已经公开十年左右的、被写进每一本教材的经典模式。这里不提供任何可用于攻击真实系统的完整代码、具体目标或绕过手段。

目标。 建立"攻击者视角"——不是为了攻击,是为了在写每一行代码时能自动问一句"如果调用者是恶意的呢"。

8.1 重入(Reentrancy)

原理。 当合约 A 向外部地址转账或发起调用时,如果对方是一个合约,控制权会转移给对方的代码。如果 A 在转出之后才更新自己的账本,对方就可以在这个"钱已出去、账还没改"的窗口里再次调用 A。A 一看账本没变,于是再转一次。这个模式是 2016 年 The DAO 事件的核心机制(公开历史,该事件直接导致了以太坊的硬分叉)。

教学级伪代码(错误写法):

function withdraw():
    amount = balances[caller]      // 1. 读余额
    sendEther(caller, amount)      // 2. 转账 —— 控制权在这里交给了对方
    balances[caller] = 0           // 3. 才清零 —— 太晚了

防御写法(Checks-Effects-Interactions 模式):

function withdraw():
    amount = balances[caller]      // Checks: 读取与校验
    require(amount > 0)
    balances[caller] = 0           // Effects: 先改自己的状态
    sendEther(caller, amount)      // Interactions: 最后才和外部交互

三层防御,建议全用: 1. CEI 顺序:永远先改状态再做外部调用。这是最根本的,也是免费的。 2. 重入锁:OpenZeppelin 的 ReentrancyGuard,一个 modifier 的事。 3. 警惕跨函数重入和只读重入:重入不一定重入同一个函数。攻击者可能在回调里调用另一个共享同一份状态的函数;更隐蔽的是"只读重入"——在状态不一致的中间时刻,去读一个被其他协议当作价格源的 view 函数。任何被外部当作数据源的 view 函数,也必须考虑状态一致性。

8.2 整数溢出与精度

原理。 在 Solidity 0.8 之前,uint256 的加减乘不检查溢出,0 - 1 会得到 2^256 - 1(一个天文数字)。这曾经导致过公开的代币合约事故。0.8 版本之后,编译器默认插入溢出检查,溢出会直接 revert——所以这个具体问题在新代码里基本消失了(事实)。

但它没有真正消失,只是换了形态: - unchecked:为了省 gas 而手动关闭检查的代码段。省 gas 是真的,风险也是真的。只在你能严格证明不会溢出时使用。 - 除法精度损失:Solidity 没有浮点数,整数除法直接截断。(a / b) * c(a * c) / b 结果可能差很多。永远先乘后除,并注意先乘会不会溢出。 - 类型转换截断uint256 强转 uint128 会静默丢掉高位。 - 单位混用:一个 6 位精度的 USDC 数量和一个 18 位精度的 DAI 数量直接相加,是一个不会报错但完全错误的计算。这是当前更常见的"数值 bug"形态(作者判断)。

8.3 权限控制

原理。 最朴素也最常见的问题:一个本该只有管理员能调的函数,忘了加权限修饰符。攻击者不需要任何技巧,直接调用即可。这类事故在真实世界反复发生,包括一些著名的初始化函数未加保护的案例(公开事故模式)。

防御要点: 1. 默认拒绝。 每写一个 external/public 函数,第一件事是问"谁能调",明确回答"任何人"或"只有 X",并在代码里体现。 2. 初始化函数必须防重复调用。 可升级合约用 initializer 代替构造函数,如果它能被重复调用或被抢先调用,控制权就丢了。 3. 用成熟库。 OpenZeppelin 的 Ownable / AccessControl,不要自己写权限系统。 4. 权限转移要用两步式。 transferOwnership 一步完成的话,地址打错一个字符 = 合约永久失控。两步式(新地址必须主动 accept)能防住这个。 5. tx.origin 永远不要用于鉴权。 它是整条调用链的最初发起者,用它鉴权意味着用户被诱导调用任意恶意合约时,那个合约就能以用户身份操作你的合约。只用 msg.sender

8.4 预言机与价格操纵

原理。 如果一个借贷协议用某个流动性很浅的交易池的瞬时价格来判断抵押品价值,那么攻击者可以在同一笔交易内:借入大量资金 → 在那个浅池里大额买入把价格推高 → 用被高估的抵押品借出真实资产 → 归还借款。整个过程在一笔交易内完成,价格随后恢复,但协议已经产生了坏账。这类事故在 2020–2022 年多次公开发生(公开事故模式)。

注意:这里的漏洞不在代码,在架构选择——用了一个可被廉价操纵的价格源。

防御思路: 1. 不要用可被单笔交易操纵的现货价格。 尤其不要直接读某个 AMM 池的即时储备比例。 2. 用时间加权(TWAP)。 操纵一个 30 分钟的加权均价,成本比操纵一个瞬时价高几个数量级。 3. 多源聚合 + 偏离熔断。 两个独立数据源偏离超过阈值时,暂停相关操作而不是照常执行。 4. 检查数据新鲜度。 预言机返回的数据带时间戳,必须检查它是不是过期的。忘记检查 staleness 是真实事故的常见成因。 5. 压力测试你的清算逻辑。 在极端行情下,清算者有没有动力来清算?如果 gas 费高于清算奖励,就没人来,坏账就会累积。

风险猫提醒 我要说一句可能让你不舒服的话:你写的第一个 DeFi 合约,一定有漏洞。 不是"可能有",是"一定有"。我见过太多人在测试网上跑通了、审计朋友看了一眼说"还行"、于是就上主网了。 请记住这个行业的基本不对称:你是一个人在写,全球有几千个专业团队在读。 他们有自动化扫描器全天候扫描新部署的合约,有历史漏洞模式库,有比你更充足的动机——因为你合约里的钱就是他们的赏金。你需要防住所有攻击面,他们只需要找到一个。 所以我的建议不是"别写",而是:在你的合约管理真钱之前,让它在测试网上活足够久,并且明确写下"如果这里出问题,最大损失是多少"。 如果这个数字你承受不了,就先别上主网。测试网上的失败是学费,主网上的失败是别人的钱。

第 9 步:测试与审计

目标。 建立"我凭什么相信这段代码是对的"的证据链。

用什么。 - 单元测试forge test。覆盖每一个函数的正常路径和异常路径。 - 模糊测试(Fuzzing):Foundry 内置。你写一个"对任意输入都应该成立"的性质,框架自动生成上千组随机输入去撞。比如"任何时候,所有用户余额之和应等于总供应量"。 - 不变量测试(Invariant Testing):更强的版本,让框架随机调用你合约的各个函数序列,每步之后检查不变量是否被打破。这是发现"多步骤组合才触发"的逻辑漏洞的最有效手段(行业共识)。 - 主网分叉测试forge test --fork-url <主网RPC>。在真实的主网状态副本上跑你的测试,能发现所有"和真实协议交互时才出现"的问题。这个功能非常强大且免费。 - 静态分析:Slither(快速,能扫出大量常见模式)、Aderyn。 - 覆盖率forge coverage。注意行覆盖率 100% 不代表逻辑覆盖率 100%。

可验证成果。 一份测试报告:单测全绿、覆盖率报告、至少 3 条有意义的不变量、Slither 输出零高危告警(每一条中低危你都能解释为什么可以接受)。

关于审计的诚实认知(行业共识): - 审计不是"审计过了就安全了",而是"一批有经验的人在有限时间内没找到更多问题"。 - 被审计过的项目照样被黑,这在历史上反复发生。原因包括:审计范围不含某个模块、审计后又改了代码、漏洞在协议交互层面而不在单个合约内。 - 审计报告的正确读法:先看范围(Scope)和 commit hash,确认审计的是不是当前部署的版本;再看"已修复"和"已知悉但不修复"分别是哪些。很多项目会展示一份三个月前的、针对完全不同版本的报告。

第 10 步:主网部署前检查清单

即使你只是想学,走一遍这个清单也会让你对"上线"这件事的重量有正确的认识。

合约层 - [ ] 编译器版本锁定(不用 ^),优化器设置与验证时一致 - [ ] 所有 external/public 函数的调用权限都被明确写下并测试过 - [ ] 所有外部调用都遵循 CEI,涉及资金的函数都有重入保护 - [ ] 所有外部调用的返回值都被检查 - [ ] 所有价格/时间输入都检查了新鲜度与合理区间 - [ ] 所有 unchecked 块都有注释说明为什么安全 - [ ] 没有任何秘密数据写入存储 - [ ] 极端值测试:0、1、type(uint256).max、空数组、重复元素

权限与治理 - [ ] 部署者的临时权限已移交或已放弃(很多事故源于部署脚本里的临时 owner 忘了撤) - [ ] 特权操作走多签,且多签的签名人分布在不同机构/地域/设备 - [ ] 关键参数变更有时间锁(Timelock),给用户留出退出窗口 - [ ] 明确列出"合约管理员能做什么",并公开告知用户 - [ ] 升级机制(如果有)的权限归属清晰,升级流程演练过

运维与应急 - [ ] 熔断开关存在,且演练过——从发现异常到暂停成功需要几分钟? - [ ] 监控告警接入(异常大额、异常调用频率、参数偏离),告警接到人的手机上 - [ ] 事故响应流程写下来了:谁有权决定暂停、怎么联系其他签名人、公告怎么发 - [ ] 前端构建产物有内容哈希,域名和 DNS 账号开了强 MFA - [ ] 私钥管理:部署私钥不在任何 CI 环境变量里明文存放

上线策略 - [ ] 设置 TVL 上限(cap),从小额开始,逐步放开 - [ ] 白帽赏金计划已发布,联系方式在合约注释和官网上都能找到 - [ ] 至少一次独立审计,且审计的 commit 与部署的 commit 一致 - [ ] 在测试网上以完整配置运行过足够长的时间 - [ ] 文档写清楚了信任假设:用户在使用这个协议时,实际上在信任谁

Builder 视角 这个清单里最容易被跳过、也最容易出事的一条是"熔断开关演练过"。 我见过的真实情形是这样的:项目有一个 3/5 多签控制的 pause 函数,写在文档里,看起来很专业。真出事那天,凌晨三点,一个签名人在飞机上,一个签名人的硬件钱包在办公室抽屉里,一个签名人不确定这个 Safe 的界面该怎么操作。等三个签名凑齐,四十分钟过去了。 所以请把应急响应当成一个可以练习的技能,而不是一份文档。 每季度演练一次:随机挑一个时间,看从"发出告警"到"pause 交易上链"实际需要多久。这个数字,才是你真实的防御能力。


三、技术诚实清单

这一节是本篇我最想让你读的部分。每一条都是一个被营销话术反复扭曲的概念——不是说这些技术是假的,而是说它们的真实能力边界,和宣传材料给你的印象之间,存在系统性的差距。

1. 去中心化不是二元属性

概念。 "去中心化"在工程上从来不是一个开关,而是一个多维度的连续谱。至少要分开评估:谁能产生区块(出块权)、谁能决定交易顺序(排序权)、谁能修改协议代码(升级权)、谁能停止系统(紧急权)、谁提供数据访问(RPC 与索引)、谁开发和维护客户端软件(代码权)、谁持有治理代币(投票权)。这七个维度可以完全不相关:一条链可能有几十万个验证节点(出块权非常分散),同时升级权掌握在一个 5 人多签手里(治理权高度集中)。

为什么营销话术会误导。 因为宣传只会挑最好看的那一维来说。"我们有 12 万个验证者"是真的,但它只回答了出块权这一维;"我们的合约是不可变的"也可能是真的,但它没说预言机地址是可以被管理员替换的。行业里已经形成一个更诚实的框架:L2BEAT 那套"Stage 0 / 1 / 2"分级,就是在拒绝回答"它是不是去中心化的"这个问题本身,转而问"它在每个具体维度上处在哪个阶段"(行业共识)。

正确的提问方式不是"这个项目去中心化吗",而是三个具体问题:如果创始团队今晚全部消失,这个系统还能运行多久?如果他们想作恶,最坏能造成什么后果?如果我想退出,需要谁的配合? 第三个问题尤其关键——一个系统可以是高度中心化的,但只要"用户永远能在无需任何人许可的情况下取回资产",它的风险性质就完全不同。这也是判断 Rollup 安全性的核心:不是问它有没有去中心化,而是问它的逃生舱(escape hatch)是否真实可用

2. 节点数量 ≠ 实际去中心化

概念。 "我们有 N 个节点"这个数字,几乎不携带任何安全信息,因为它没有回答四个更重要的问题。第一,这些节点是谁在跑? 如果 12 万个验证者里有相当大比例属于同一个流动性质押协议或同一家交易所,那么真正的独立决策实体可能只有几十个。第二,它们跑在哪里? 大量节点集中在少数几家云服务商的少数几个可用区,是公开的行业现状(事实)——一次云服务商的区域故障或一纸监管命令,可以同时影响它们。第三,它们跑什么软件? 如果 70% 的节点跑同一个客户端实现,那么这个实现的一个共识 bug 就能让多数派一起出错。第四,它们真的在验证吗? 有些"节点"实际是轻客户端,或者干脆信任别人的结果,并不独立执行验证。

为什么营销话术会误导。 因为节点数是最容易做大也最容易展示的数字。搞一个低门槛的节点程序,发一点代币激励,几周内节点数就能上万。但这些节点如果都由同一批人用同一套脚本在同一家云上批量部署,它们在故障和攻击面前是完全相关的,而不是独立的。安全性来自独立性,不是来自数量——一万个相关的节点,抵不上一百个真正独立的节点。

更有信息量的指标是:中本聪系数(Nakamoto Coefficient,需要串通多少个实体才能攻击系统)、客户端份额分布、地理与云服务商分布、以及"独立跑节点的门槛有多高"(硬件要求越高,长期越会向机构收敛)。这些数字通常不会出现在项目首页。

3. TPS 统计口径陷阱

概念。 TPS(每秒交易数)是这个行业被滥用得最严重的指标,因为"一笔交易"这个单位在不同链上完全不可比。口径差异至少有五处: 第一,计入的是什么交易——有的链把投票、共识消息、状态同步包都算进"交易";第二,交易的复杂度——一笔简单转账和一笔跨三个池子的兑换,消耗的计算资源可能差一百倍;第三,测的是理论峰值还是实际持续值——实验室最优条件下的数字和主网真实负载下的数字往往差一个数量级;第四,是否包含最终性——"确认了"和"不可逆了"是两回事;第五,测试时的节点配置——用 32 核服务器测出来的 TPS,和"任何人用笔记本都能验证"是两个世界。

为什么营销话术会误导。 因为 TPS 是一个所有人都自以为懂的数字,且越大越好这件事符合直觉。但把 TPS 当作链的核心指标,本身就是一个范畴错误——如果只追求 TPS,一台 AWS 上的 Postgres 能轻松做到几十万,而且延迟更低、成本更低。链付出巨大代价换取的是"多方无需互信也能对状态达成一致",TPS 只是这个代价的副产品。

更诚实的比较方式是问:在保持 X 级别的去中心化和 Y 级别的最终性延迟的前提下,能达到多少吞吐? 三个变量必须一起说。任何只报一个数字的 TPS 宣传,都应该被当作营销材料而非技术指标(作者判断)。另外值得注意的是:目前绝大多数链的真实负载远低于其容量上限——瓶颈早就不是 TPS 了,是需求。

4. 最终性(Finality)

概念。 最终性回答一个问题:这笔交易什么时候才是真的不可能被撤销了? 不同共识机制给出的答案性质完全不同。概率最终性(比特币、PoW 链):从来没有"绝对确定"的时刻,只有"被推翻的概率随确认数指数下降"。等 6 个块不是因为 6 有魔力,而是因为在特定的算力假设下,6 个块之后推翻的成本已经高到不划算。经济最终性(以太坊 PoS):达到 finalized 状态后,要推翻它必须让至少三分之一的质押资产被罚没——技术上不是不可能,而是代价极其高昂。即时最终性(Tendermint/CometBFT 类):出块即最终,前提是超过三分之二的验证者诚实;如果这个假设被打破,链会直接停止而不是产生分叉。

为什么营销话术会误导。 因为宣传里说的"确认时间",通常指的是出块时间,不是最终性时间。"我们 0.4 秒确认"意味着 0.4 秒出一个块,但那个块什么时候不可逆是另一个数字,可能是几秒,也可能是十几分钟。对交易所、支付网关、跨链桥这类"收到即发货"的业务,用错这个数字直接等于资损。

给开发者的实用规则: 你的确认策略应该由金额可逆性共同决定。小额、可撤销的动作(点个赞、更新 UI)可以乐观处理;大额、不可逆的动作(打款、发货、跨链释放)必须等到该链定义的最终状态。以太坊上的做法是监听 finalized 标签而不是最新区块。不要为了体验好而缩短确认数——那是在用别人的钱买你的用户体验。

5. 重组(Reorg)

概念。 重组是指链上已经存在的若干区块被另一条更"重"的链取代,导致其中的交易被回滚或重新排序。它不是异常,是设计的一部分——在一个网络延迟不为零的分布式系统里,两个节点几乎同时出块是必然会发生的,协议必须有规则来收敛。浅层重组(1–2 个块)在多数链上是常规事件,深层重组罕见但历史上真实发生过。重组之后,你之前收到的事件可能凭空消失,或者顺序完全变了——同一笔交易可能出现在不同的区块里,甚至因为状态变化而从成功变成失败。

为什么营销话术会误导。 因为几乎没有任何项目文档会在首页告诉你"我们的链会重组"。开发者第一次遇到它通常是在生产环境:数据库里有一条订单记录,链上却查不到那笔交易了。更隐蔽的情况是:交易还在,但它在新链上的执行结果不同了——因为它前面的交易顺序变了,导致价格变了,导致滑点保护触发了 revert。

工程对策: 第一,任何链下系统都必须把"区块可能被回滚"作为一等公民来设计,而不是当异常处理。记录每条数据来源的区块号和区块哈希,检测到父哈希不连续时回滚对应记录。第二,成熟的索引框架(Ponder、Subsquid 等)内置重组处理,自己手写监听脚本时这是最容易漏掉的一块。第三,对不可逆动作,用确认数或 finalized 标签把重组窗口挡在外面。第四,注意 L2 的特殊性:L2 自己可能不重组,但如果 L1 重组,L2 的批次提交也会跟着回滚,这是很多人没想到的传导路径。

6. 数据可用性

概念。 DA 保证的是"数据被发布了、任何人都能拿到",而不是"数据是正确的"。这两件事的区别是理解 Rollup 安全性的关键。有效性证明(ZK)保证状态转换正确;欺诈证明(Optimistic)允许人举报错误。但两者都有一个前提:有人能拿到原始数据。 拿不到数据,你算不出正确状态,也就构造不了证明、发不了挑战、更没法自己算出余额去强制提款。

为什么营销话术会误导。 因为 DA 是整条链路里最抽象、最没有故事性的一环,宣传时几乎必然被略过。一条链可以宣称自己有"以太坊级别的安全性",因为它的证明确实提交到了以太坊;但如果它的交易数据只保存在项目方的服务器上(这类架构通常叫 Validium 或 Optimium),那么项目方一旦扣留数据或服务器宕机,用户的资产就处于"数学上存在、实践中取不出"的状态。这不是理论风险,是架构决定的确定性后果。

判断方法很简单,只问一句:如果这个项目的所有服务器今晚永久下线,我能不能仅凭以太坊(或其他公链)上的公开数据,重建出我的余额并把资产取出来? 能,它是 Rollup;不能,它是别的东西,不管它自称什么。这个问题的答案,比任何 TPS 数字都更能决定你的资产安全。(行业共识,L2BEAT 的分级体系正是围绕这类问题建立的。)

7. 状态膨胀(State Bloat)

概念。 区块链有两个不同的"变大"问题,经常被混为一谈。历史数据(所有区块和交易的记录)是只增不减的,但它可以被裁剪——新节点不需要从创世重放,可以用快照同步,历史数据也可以交给专门的归档服务保存。状态(当前所有账户余额、所有合约的存储)才是真正的麻烦:它必须被每个验证节点随时保持在可快速访问的介质里,因为每笔交易都要读写它。状态只会增长——每一个新地址、每一个新代币持仓、每一个 NFT,都在里面永久占一个位置。

为什么这是个长期结构性问题(行业共识)。 因为费用模型是一次性的,成本是永久的:你付一次 gas 写入一个存储槽,全世界所有节点要永久保存它。这是一个明显的经济学错配。随着状态增长,节点的内存和磁盘要求持续上升,独立运行节点的门槛随之上升,去中心化会被缓慢地、不可逆地侵蚀。这个过程慢到在任何一个季度里都不明显,所以几乎从不出现在营销材料里。

行业的应对方向包括:状态到期(超过一定时间未访问的状态被移出活跃集,需要时凭证明恢复)、Verkle 树 / 无状态客户端(让验证者不需要保存完整状态,只需验证随附的见证数据)、以及提高存储的持续性成本(租金模型,在多数链上因为影响体验而未被采纳)。这些方案都在推进中,但都还没有在主流链上完全落地(截至本文写作时)。作为开发者,你能做的是:别浪费链上存储——能存事件日志的不要存状态,能存哈希的不要存原文。

8. Sequencer 集中化

概念。 前面第 15 节讲过机制,这里讲它的实际含义。目前主流 Rollup 基本都由项目方运行单一排序器(事实)。这个角色的权力比多数用户以为的大:它决定交易顺序(因此可以提取 MEV,也可以把 MEV 收入据为己有)、它可以拒绝打包某笔交易(审查)、它宕机会让整条链停止出块(历史上多条主流 L2 都发生过数小时级停摆,公开事件)。

它不能做的是偷你的钱——因为状态转换的正确性由 ZK 证明或欺诈证明约束。这是一个真实的、重要的安全保证,不应被抹杀。

为什么营销话术会误导。 因为"L2 继承了以太坊的安全性"这句话是部分正确的,而部分正确最容易误导。它继承的是状态正确性数据可用性(如果数据确实发到了 L1),它没有继承的是活性(liveness)和抗审查性——这两项完全取决于那个单一排序器。用户听到"以太坊级安全",脑子里想的是"和主网一样安全",而真实情况是"钱偷不走,但可能几小时动不了,或者你这笔交易可能永远排不上"。

关键的缓解机制是"强制包含"(force inclusion): 允许用户绕过排序器,直接向 L1 提交一笔交易,排序器在一定时间内必须包含它,否则用户可以走逃生流程。但这条通道在不同 L2 上的成熟度差异极大——有的完整实现并测试过,有的还在路线图上,有的延迟长达数天。这是评估一条 L2 时最应该查的一件事,而它几乎从不出现在项目主页。(存在争议:各方对"多长的强制包含延迟是可接受的"没有共识。)

9. 多签升级权限与 Admin Key

概念。 绝大多数"去中心化协议"背后都有一个多签钱包,持有一组特权:升级合约实现、修改关键参数(利率、抵押率、手续费)、暂停系统、提取协议收入、更换预言机地址。其中"升级合约实现"是权力的顶点——它等价于"可以把这份代码换成任意另一份代码",包括一份把所有资金转走的代码。你审计的那个版本,随时可以变成另一个版本。

为什么营销话术会误导。 因为项目会说"由 5/9 多签控制,非常安全",但不会说清楚三件事:第一,这 9 个签名人是谁? 如果全是团队内部成员,那 5/9 和 1/1 在"团队作恶"这个威胁模型下没有区别。第二,有没有时间锁? 没有时间锁的升级权意味着变更可以在一个区块内完成,用户来不及反应;有 48 小时时间锁意味着用户至少有两天时间看到变更并退出。第三,签名人的密钥怎么保管? 9 个人如果都用同一家云服务的手机 App 签名,那它不是 9 个独立的失败点。

这不是说有 Admin Key 就是坏的——早期协议需要修 bug 的能力,冻结升级权对用户未必更好(历史上有协议因为无法升级而只能眼睁睁看着漏洞被利用)。问题在于信息不对称:用户以为自己在信任代码,实际上在信任那几个人。 正确的做法是把它明说出来:谁有权、多久生效、怎么监督。Timelock + 公开的变更提案 + 链上可查的签名人构成,是目前的最佳实践(行业共识)。

10. Oracle 风险

概念。 预言机是"链下真实"与"链上逻辑"之间唯一的接口,也因此是信任假设最集中的单点。风险至少有五种:数据源风险(源头的交易所或做市商报价被操纵,或流动性太浅);聚合逻辑风险(取中位数、加权还是均值,在极端行情下差别巨大);传输风险(节点作恶或串通,或者签名机制被攻破);新鲜度风险(在剧烈行情中,价格更新赶不上真实变化,协议按一个几分钟前的价格清算或不清算);可用性风险(预言机在最需要它的时候——极端行情、链拥堵——恰恰最容易停止更新)。

为什么营销话术会误导。 因为集成一个知名预言机会被描述成"已解决价格问题",这句话隐含了一个错误前提:预言机是可靠的基础设施,接上就完事。 实际上,"接了 Chainlink"和"正确地接了 Chainlink"是两回事——你有没有检查返回数据的时间戳?有没有处理 answer 为 0 或负数的情况?有没有为预言机停更设计降级路径?历史上多起事故的根因不是预言机本身出错,而是集成方没有检查数据的有效性(公开事故模式)。

更根本的一点: 极端行情下的预言机行为是最难测试也最关键的。你的协议在价格 5 分钟内腰斩、同时链拥堵到 gas 涨十倍、同时预言机因为源头交易所熔断而停止更新时,会发生什么?这个场景必须被明确设计,而不是被假设不会发生。

11. Bridge 信任假设

概念。 跨链桥的安全性,完全取决于"谁来证明 A 链上确实发生了那件事"。按信任假设从强到弱排列:外部验证者桥(一组独立的验证者签名确认,最常见,也是被盗最多的类型——攻破其中的签名阈值就能凭空铸造资产);乐观桥(默认放行,设挑战期,需要有诚实的挑战者在线);流动性网络(不铸造凭证,而是两边各有资金池,本质是原子换汇,信任假设小但受流动性限制);轻客户端 / 原生验证桥(在 A 链上用合约实现 B 链的共识验证,信任假设最小,IBC 是代表,但实现成本极高且不是任意两条链都能做);Rollup 原生桥(安全性继承自 L1,是最强的一类,代价是提款延迟)。

为什么营销话术会误导。 因为几乎所有桥都自称"安全"和"去中心化",但它们的信任假设可能相差几个数量级。一句最需要被记住的话是:跨链桥的安全性上限,等于它的验证机制的安全性,而不是它连接的两条链中更安全的那条,甚至也不是更弱的那条——它可能比两条链都弱。 一个连接以太坊和某条 L1 的桥,如果由 8 个验证者的 5/8 多签控制,那么这个桥的安全性就是那 5 个私钥,与以太坊几十万验证者毫无关系。

这就是为什么跨链桥是被盗金额最集中的环节(事实)——它把两条链上的巨额资产,集中在一个安全模型远弱于任何一条链的合约里。评估一座桥只需要问:要偷走里面的钱,最少需要攻破几个私钥/几个实体? 如果答案是个位数,那不管它前面有多少形容词,它的安全等级就是个位数。

12. MEV

概念。 MEV 是通过重新排序、插入或审查区块内交易所能提取的价值。它不是漏洞,是公开内存池加上自由排序权的必然经济后果。主要形态包括:套利(在两个市场的价差间赚钱,这类通常被认为对市场有益)、清算(抢先执行清算获得奖励,对协议健康有必要)、三明治攻击(在用户的兑换前后各插一笔,赚走用户的滑点,这类是纯粹的用户损失)、以及更复杂的多步骤组合。

为什么营销话术会误导(两个方向都有)。 一个方向是淡化:"MEV 是自由市场的正常现象"——这句话对套利成立,对三明治攻击就是在为损害用户辩护。另一个方向是夸大宣称解决:"我们的链没有 MEV"——只要存在排序权且顺序影响结果,MEV 就存在,区别只是它被谁拿走、以何种方式分配。有的链把 MEV 收归排序器(用户仍然承担损失,只是受益方从机器人变成了项目方),这不叫消除 MEV,叫垄断 MEV。

当前的现实是一整套市场化基础设施:私有交易池让交易不进公开内存池(有效缓解三明治,代价是把交易内容交给了另一个中心化实体);PBS 把区块构建和提议分离(缓解验证者中心化,但把中心化转移到了少数几个区块构建者身上);订单流拍卖试图把 MEV 收益返还给用户。每一种方案都是在转移信任,而不是消除它(作者判断)。对开发者的实操结论只有一条且不可妥协:涉及价格的交易必须设滑点上限和截止时间。

13. 合约可升级性

概念。 常见的升级模式是代理(Proxy):用户始终与代理合约交互,代理通过 delegatecall 把逻辑委托给一个实现合约;升级就是把指针指向新的实现。变体包括透明代理、UUPS、Beacon、以及 Diamond(多面)模式。升级能力带来的是修 bug 的能力,也带来了"代码可以变成任何东西"的能力——这两件事在技术上是同一件事,无法分开。

为什么营销话术会误导。 因为项目常常同时说两句互相矛盾的话:"我们的合约是不可篡改的"和"我们能快速响应问题修复漏洞"。这两句话不可能同时为真。 如果合约可升级,那么"不可篡改"就是错的;如果真的不可升级,那么发现漏洞时你只能眼睁睁看着。用户需要知道自己处在哪一边。

升级模式本身还有一批技术陷阱: 存储布局冲突(新实现的变量顺序变了,读出来的就是别的字段的值,这是灾难性的静默错误);实现合约未被初始化保护(可能被他人抢占);delegatecall 上下文中 msg.sender 和存储归属的语义容易搞错;不同代理模式对函数选择器冲突的处理差异。OpenZeppelin 的 Upgrades 插件会自动检查存储布局兼容性,强烈建议使用而不是自己手写代理(作者判断)。

最诚实的做法是把升级权限本身当作产品的一部分公开设计:升级由谁提议、需要几方同意、有多长的时间锁、用户在这段时间内能否无损退出。"能升级"不是问题,"能被谁在多快时间内不打招呼地升级"才是问题。

14. 形式化验证与审计的边界

概念。 这两者都是提高代码可信度的手段,但它们证明的东西都比直觉中窄得多。形式化验证是用数学方法证明"代码满足某个形式化规约"。它的能力极强——在规约覆盖的范围内,它给出的是证明而不是抽样。但它有三条硬边界:第一,它只能证明你写下的性质,你没想到要写的性质它不会替你发现;第二,规约本身可能是错的,如果你对"正确"的定义就有问题,验证通过只说明代码忠实实现了一个错误的定义;第三,它通常只覆盖单个合约的内部逻辑,跨协议交互、经济激励失衡、预言机被操纵这些系统级问题,落在它的能力范围之外。

审计则是"一组有经验的人在有限时间内的人工审查"。它的价值是真实的且不可替代——人类审计员擅长发现逻辑意图与实现的偏差、经济模型的漏洞、以及"这个设计本身就有问题"这类形式化方法碰不到的东西。但它的边界同样明确:审计的是某个 commit 的某个范围,在某个时间点。审计后改的代码不在其中;不在 scope 里的模块不在其中;审计员没想到的攻击路径不在其中。

为什么营销话术会误导。 因为"已通过 XX 审计"和"已形式化验证"被当作安全徽章展示,而徽章天然传达"这件事已经解决了"的意思。真实情况是:被顶级机构审计过的项目照样被攻破,这在历史上反复发生(事实)。原因往往不是审计做得差,而是漏洞在审计范围之外——在协议交互层面、在经济假设层面、在部署配置层面、在审计之后的代码变更里。

正确的心智模型是把安全当作一个纵深体系,而不是一个可以打勾的检查项: 良好的架构设计 + 全面的自动化测试(含不变量测试)+ 静态分析 + 多轮独立审计 + 形式化验证关键不变量 + 部署后的实时监控 + 演练过的应急响应 + 持续的白帽赏金 + 渐进式的资金上限。每一层都会漏,但它们漏的地方不一样。 只做其中一层而声称安全,才是真正危险的。

投研姐视角 上面这十四条,我建议你换个用途来读——把它们当成一份尽调问卷。 当一个项目来找你(无论是找你投资、找你集成、还是找你去上班),你把这十四条挨个问过去,会发生一件很有意思的事:能把每一条都回答清楚的团队,通常也是技术做得最扎实的团队。 反过来,如果一个团队在被问到"你们的排序器是单点吗"、"升级权限的时间锁多长"、"数据可用性发在哪"这些问题时,回答是绕开、含糊或者反问你"这重要吗",那这本身就是最有价值的信息。 我这几年形成的一个判断标准是:看一个项目的文档里,有没有专门的一节在讲自己的局限性和信任假设。 有这一节的项目,是少数,而且这个少数群体的长期存活率明显更高(作者判断,基于观察而非统计)。 因为愿意主动写下"我们目前是中心化排序器,路线图上第三季度会引入强制包含"的团队,说明他们内部真的在跟踪这件事。而通篇只有优势和愿景的文档,往往意味着这些问题在内部也没被认真讨论过。 技术诚实不只是一种美德,它是一个可观测的、有预测力的信号。


【自测 5 题 + 答案】

第 1 题。 一个 L2 项目宣称"完全继承以太坊的安全性"。你只能问一个问题来验证这句话的含金量,你问什么?为什么?

答案 问:**"交易数据发布在哪里?如果你们所有服务器今晚永久下线,我能否仅凭以太坊上的公开数据重建状态并取回资产?"** 因为这一个问题同时检验了数据可用性和逃生舱两件最关键的事。如果数据发在以太坊上(blob 或 calldata),那么即使项目方跑路,任何人都能重建 L2 状态并通过 L1 合约强制提款——这才是"继承安全性"的实质。如果数据发在项目方自己的服务器或一个独立的 DA 委员会上,那么它的安全性上限是那个委员会,与以太坊无关。 补充:即使数据发在 L1,还要追问强制包含(force inclusion)通道是否已实现并被测试过——否则你能算出余额,但发不出提款交易。

第 2 题。 你的后端监听合约的 Deposit 事件,收到就给用户账户加钱。代码逻辑完全正确,测试全绿。上主网后偶发性地出现"用户账户加了钱但链上查无此笔充值"。为什么?怎么修?

答案 **原因:链重组。** 你在区块 N 收到了事件,链随后发生重组,区块 N 被另一条分支取代,那笔交易不在新链上了(或者被换到了另一个区块、甚至因为顺序变化而执行失败)。你的数据库记住了一件链上已经不存在的事。 **修法(三层):** 1. 不要在收到事件的瞬间就入账,**等待足够的确认数**,或者在以太坊上直接监听 `finalized` 区块。 2. 数据库里为每条记录保存**区块号 + 区块哈希**,持续校验父哈希的连续性,检测到不一致时回滚受影响的记录。 3. 用成熟的索引框架(Ponder、Subsquid 等)而不是手写 `while(true)` 轮询——重组处理是它们的内置能力。 顺带:这也是为什么"确认数"不是迷信,而是把重组窗口挡在业务之外的工程手段。

第 3 题。 下面这段伪代码有什么问题?请指出漏洞类别,并写出修复后的顺序。

function claimReward():
    reward = pending[caller]
    require(reward > 0)
    sendEther(caller, reward)
    pending[caller] = 0
答案 **漏洞类别:重入(Reentrancy)。** `sendEther` 把控制权交给了调用者。如果调用者是一个合约,它可以在收款回调里再次调用 `claimReward()`。此时 `pending[caller]` 还没被清零,`require` 依然通过,于是可以重复领取。 **修复(Checks-Effects-Interactions):**
function claimReward():
    reward = pending[caller]      // Checks
    require(reward > 0)
    pending[caller] = 0           // Effects —— 先改状态
    sendEther(caller, reward)     // Interactions —— 最后交互
**再加两层:** 加 `ReentrancyGuard` 修饰符;并检查是否存在跨函数重入(攻击者是否能在回调里调用另一个共享 `pending` 状态的函数)以及只读重入(是否有 view 函数在这个中间状态被外部当作数据源)。

第 4 题。 项目 A 说"我们有 12 万个验证节点,是最去中心化的链"。请列出三个能实质性削弱这句话的追问。

答案 1. **这些节点背后有多少个独立实体?** 如果其中很大比例属于同一个流动性质押协议或同一家交易所,独立决策实体可能只有几十个。看中本聪系数,不看节点数。 2. **客户端软件的份额分布如何?** 如果某个实现占比过高,一个共识 bug 就能让多数派一起出错,节点数量在这个威胁模型下毫无帮助。 3. **升级权限归谁?** 出块权分散和治理权分散是两回事。12 万个节点可以在一个 5/9 多签决定升级协议时完全无话可说。 (加分追问:节点跑在几家云服务商的几个可用区?这些节点是真的在独立验证,还是轻客户端?)

第 5 题。 你要集成一个价格预言机来做抵押清算。除了"调用它的 latestAnswer 接口"之外,请列出至少四项必须做的额外工作。

答案 1. **检查数据新鲜度。** 读取返回的时间戳,超过可接受阈值就视为无效,触发降级逻辑而不是照常清算。忘记检查 staleness 是真实事故的常见成因。 2. **检查数值合理性。** 拒绝 0、负数、以及超出预设合理区间的值。 3. **设计预言机不可用时的降级路径。** 停止新增借贷?暂停清算?明确写下来并测试,而不是假设它永远在线。 4. **避免单一数据源。** 多源交叉验证,偏离超过阈值时熔断而非继续执行。 5. **不要用可被单笔交易操纵的现货价**(比如直接读某个浅池的即时储备),必要时用 TWAP。 6. **压力测试清算激励。** 在 gas 暴涨的极端行情下,清算奖励是否还足以吸引清算人?不足就会累积坏账。

【常见误区】

误区一:把链当数据库用。 这是传统工程师最常见的第一个错误。链的每一条特性——贵、慢、公开、不可撤销——都是为"多方无需互信"这一个目标付出的代价。如果你的场景里没有互不信任的多方,你付了代价却没买到东西。

误区二:以为 private 变量是私密的。 链上所有存储都可被任何人直接读取。private 只是编译期的可见性控制。任何秘密都不该出现在链上,包括"只放一小会儿"。

误区三:用 Number 处理 uint256。 JavaScript 的安全整数上限远小于 uint256。全程 BigInt,只在最终显示时格式化。这是链上前端最高频的资损级 bug。

误区四:硬编码 decimals 为 18。 USDC 是 6 位,WBTC 是 8 位。永远从合约读。硬编码的后果是金额差一百万倍。

误区五:把"连上钱包"当成"已认证"。 地址是公开信息,前端读到地址不证明任何事。只有一次带 nonce 和域名的签名验证才构成认证。

误区六:交易失败不用付钱。 失败的交易同样消耗 gas 并被收费。out-of-gas、revert 都是如此。

误区七:只订阅实时事件不做历史回补。 服务重启期间的事件会永久丢失。必须记录处理进度并支持从任意区块回补。

误区八:认为审计通过等于安全。 审计是"一批人在有限时间内、针对特定 commit 和特定范围的审查"。被顶级机构审计过的项目照样被攻破。安全是纵深体系,不是一个勾。

误区九:把 TPS 当作链的核心指标。 口径千差万别,且必须和去中心化程度、最终性延迟一起说才有意义。单独一个 TPS 数字是营销材料。

误区十:以为 L2 的"以太坊级安全"覆盖一切。 它继承的是状态正确性和(如果数据在 L1 上的)数据可用性;它不继承活性和抗审查性——那两项取决于排序器。

误区十一:无限授权是默认选项。 为了省一次交互而让用户 approve 天文数字额度,是把用户的长期风险换成了短期体验。默认按需授权或用 permit。

误区十二:假设所有 ERC-20 都规矩。 转账收费的、失败返回 false 不 revert 的、可 rebase 的代币都真实存在。用 SafeERC20,并测量实际到账金额。

误区十三:把滑点保护当作可选优化。 amountOutMin = 0 等于公开邀请三明治攻击。滑点上限和截止时间是基本安全要求,不是性能调优。

误区十四:以为"不可升级"总是更好,或"可升级"总是更好。 两者都是取舍:不可升级意味着漏洞无法修复,可升级意味着代码可以被换成任意版本。真正的问题是升级权归谁、需要几方同意、有多长时间锁。


【一页总结】

技术栈的本质。 链是一台"租不到 root 权限、按字节收费、不许 rollback 的公共计算机"。从钱包到共识的二十多个层次,每一层的存在都是在绕开这四条约束中的某一条。判断一个需求该不该上链,只问:这里面有没有"多方互不信任却必须共享同一份账本"的成分?

去中心化的分布是不均匀的。 共识层可能由几十万节点维护,但访问层(RPC)集中在几家公司,索引层跑在某台云服务器上,前端托管在 Vercel,排序权在项目方手里,升级权在一个多签里。"这个项目去中心化吗"是个没有答案的问题;"在哪个维度上、由谁控制、我能不能无需许可地退出"才是。

零资金学习路线的十步。 钱包连接 → 读链上数据 → 发测试交易 → 部署最小合约 → 事件监听 → Token 结构 → 简单 DApp → 安全错误原理 → 测试与审计 → 上线检查清单。全程 Sepolia 测试网,一分钱不花。真正的难点只有第 8 和第 10 步,前七步是新 API,后两步是新习惯。

四条不可妥协的工程纪律。 第一,BigInt 全程,decimals 从合约读——数值错误是最直接的资损。第二,Checks-Effects-Interactions + 重入锁——外部调用即控制权转移。第三,处理重组——任何不可逆动作都要等到最终性。第四,滑点上限与截止时间——公开内存池里没有这两项等于自愿被夹。

技术诚实清单的十四条,可以压缩成三个尽调问题。 如果团队今晚全部消失,系统还能运行多久?如果他们想作恶,最坏能造成什么后果?如果我想退出,需要谁的配合? 这三个问题穿透了几乎所有营销话术——它们不问技术多先进,只问信任交给了谁。

一个可观测的信号。 项目文档里有没有专门一节在讲自己的局限性和信任假设。有这一节的是少数,而这个少数群体的长期存活率明显更高(作者判断)。技术诚实不只是美德,它是有预测力的信号——愿意写下"我们目前是单排序器"的团队,说明内部真的在跟踪这件事。

最后一句给准备入行的工程师。 这个行业最稀缺的不是会写 Solidity 的人,而是既能把系统跑起来、又能诚实说出它哪里不安全的人。前者三个月可以速成,后者需要你在每一次"这样写会不会有问题"的自问中慢慢长出来。你的技术判断力,就是你在这个信息噪音极大的行业里最值钱的资产。

学以致用链聘 ChainHire 聚合 3500+ Web3 公开职位,每日自动更新查看工程研发在招岗位 →