接第二条链的真实工时,从来不是 PM 在需求文档里写的”2 周加适配层”。实际 8-12 周,上线后前 3 个月故障率是单链版本的 5 倍。
为什么?因为这 3 个坑,我一个不落全踩了。
在交易所做了两年 Web3 后端,Solana 和 EVM 的基建都从头搭过。说实话,入行之前我以为多链就是写个适配层——定义一套接口,链 A 实现一下,链 B 实现一下,完事。
后来发现,这是整个行业最贵的妄念。
坑一:Solana 的 confirmed,不是你以为的 confirmed
凌晨 3 点,用户群炸了。
“充了 20 分钟还没到账,钱去哪了?”
查了一下所有节点状态——都是 confirmed。系统按正常流程完成了入金,用户卖掉了他的 BTC。然后我们意识到一个让人后背发凉的事实:Solana 的 confirmed 不等于 finalized。用户的钱在链上其实还没真正不可逆确认,但我们的系统已经执行完了整个交易流程。
说白了——我们把还没到账的钱,当成到账的钱卖了。
为什么这个问题这么隐蔽?
EVM 开发者在”交易确认”这件事上的心智模型非常简单:等 N 个区块确认,N 越大越安全。绝大多数交易所跑 EVM 都是 6-12 个区块确认后入账,这套逻辑运行多年,没出过问题。
Solana 不一样。它有三层状态:
- processed:交易被 leader 节点处理了,但不代表被接受
- confirmed:交易被 2/3 的 validators 投票过了,大概率会进入下一个区块
- finalized:交易经过了 32 个 slot(约 12.8 秒内 2/3 的 validators 投票),不可逆
绝大多数 Solana RPC 的默认返回就是 confirmed。因为 finalized 要多等几个 slot,用户体验差——对 DApp 来说无所谓,但对交易所是致命的。confirmed 状态下区块仍然有可能被 fork 掉(虽然概率低),在极端网络条件下这个概率会被放大。
更隐蔽的是:不同 RPC 提供商返回的确认状态不一致。我当时用的 RPC 在 90% 的情况下返回 confirmed 时区块已经是 finalized 了,导致我们用 confirmed 跑了几个月都没出事。然后就遇到了那 10%。
修的是什么
Solana 的入金确认策略从 confirmed 改成了 finalized + 业务层兜底窗口。业务层的延迟窗口是额外的一道保险——即使 finalized,交易所也额外等 3-5 秒再放款。
这不是性能优化,这是资金安全底线。
写完这个修复之后我专门看了一下市面上几个主流跨链桥和交易所的文档。老实说,大部分都没有明确标注自己用的是 confirmed 还是 finalized。后来跟一个 Solana RPC 提供商的工程师聊,他说”半年内至少有 3 个我们对接的交易所遇到过这个问题”。
你不是第一个踩的,你可能只是还没发现自己在踩。
坑二:EVM nonce — 高频场景下,优雅的设计变成了灾难
EVM 的 nonce 设计本身没有问题——每笔交易带一个递增的整数,保证交易顺序执行,逻辑清晰。
但这是对单用户场景设计的。交易所的提币场景是天然并发的:几十个用户同时在提币,需要同时发几十笔交易。nonce 的”顺序执行”在这里变成了瓶颈。
最严重的一次是在提币高峰期。有一笔交易的 gas price 被设得略低于当时网络价格(base fee 刚跳了一波),这笔交易在 mempool 里 pending。接下来 40 多笔提币交易全部卡死——每一笔都在等它前面的 nonce 被确认。
用户端看到的就是”待处理”。“待处理”了 20 分钟。
客服被打爆。
为什么”重发一笔”不是银弹
表面上这问题不复杂——发一笔同 nonce 但 gas 更高的交易替换掉 pending 那笔不就行了?
实际执行没那么干净:
- 替换交易不一定被接受。部分节点的 mempool 策略不同,有的会拒绝替换(除非 gas 高出一个阈值),有的根本不会把替换交易广播出去。
- nonce gap 连锁反应。在你替换的时间里,后面排队的 nonce 队列已经乱了。需要手动重建整个 nonce 队列,而重建的时候又有新的提币请求进来。
- 生产环境的并发。提币服务通常是多实例的,多个实例同时抢 nonce,你刚修好一个 nonce 的替换,另一个实例已经又发了一笔同一 nonce 的交易出去。
那 20 分钟里我做的事:先 bump gas 替换 pending 交易 → 发现替换没有被所有节点接受 → 手动从 mempool 里清掉 → 重建 nonce 队列 → 把攒的 40 多笔提币按新 nonce 重新发出去。
手忙脚乱,但总算没丢一笔资金。
后来怎么解决的
现在的方案是三件套:
- nonce 预分配:提币服务启动时从链上查询当前 nonce,集中管理一个 nonce 池,不依赖服务实例自己取。
- 动态 gas bumping:交易发出后持续监测 pending 状态,超过 30 秒自动发替换交易(gas 加 1.5 倍),超过 2 分钟强制替换。
- nonce 队列重建:如果某个 nonce 持续 pending 超过阈值(5 分钟),放弃该 nonce,把后续交易按新 nonce 重新编排。
这里有个有意思的对照:Solana 没有 nonce 概念,用的是 recent blockhash + 交易间的依赖声明。这听起来更灵活,但它引出了另一个完全不同的坑——交易过期。Solana 的 blockhash 有效期只有约 150 个 slot(大概 1 分钟),过了这个窗口交易就直接作废。高频提币场景下,blockhash 过期的概率不低。
所以别觉得”某某链没有 nonce 就好了”。每条链都有自己的坑,而且通常坑的形态不一样。
坑三:手续费估算 — 你以为是个简单公式,其实是个世界模型
讲个细节。PM 提需求的时候说:“不就是算个 gas 告诉用户要花多少钱吗?把这模块抽象一下,EVM 和 Solana 用同一套接口。”
逻辑上没毛病。工程上是个定时炸弹。
两个完全不同的定价系统
EVM 的 gas 模型是竞拍型。gas price × gas limit,你出价高矿工先打包。base fee(EIP-1559 引入)加上你付的 priority fee(小费),最终价格 = 网络拥堵程度 × 你自己愿意出多少。
Solana 完全不是这个逻辑。它是定价 + 小费型:
- compute unit limit:类似 gas limit,你声明这笔交易需要多少计算资源
- compute unit price:每个 compute unit 你愿意付多少钱(单位是 lamports,1 lamport = 0.000000001 SOL)
- prioritization fee:给 validators 的额外小费,直接通过
setComputeUnitPrice或getRecentPrioritizationFees获取
没有 mempool。没有竞拍。你定好价,交给 leader 排。
这就导致两套系统的预测逻辑完全是两个东西:
- EVM 的 gas 预估靠采样 mempool + 最近 N 个块的 gas 分布,预测”我出多少 gas 能在 X 秒内被打包”。
- Solana 的手续费预估靠最近 N 个 slot 的 compute unit price 分布 + 优先费指令,预测”我设多少 compute unit price 和优先费能过”。
两套逻辑用同一套接口?相当于用同一个公式去算两种不同的物理定律。
一个人的双线崩溃
团队里维护手续费估算的人只有一个。每次某条链出现 gas spike——NFT mint 潮、空投领取、土狗冲榜——估算就会偏。
最严重的一次发生在 Solana 上一波 meme 潮。优先费飙升了 20 倍,估算代码用的还是 2 分钟前采样的基础价。结果:大量用户交易排队超时,提币失败率从正常的 0.3% 飙到 18%。
用户看到的是”交易失败,请重试”。重试还是失败。再重试,手续费被前置扣掉了但交易没确认,需要人工退手续费——又一个工单。
那次之后把手续费模块拆了。EVM 一个估费服务,Solana 一个估费服务,共用同一套”采样→预测→降级”的流程框架,但模型和参数完全独立。
拆完发现代码量没减少反而增加了,但故障停了。说白了:真实世界的复杂度不会因为你用了漂亮抽象就消失。它只是换个地方爆发。
三条链对三个词给出了三套定义
回看这两年踩的坑,我发现一条规律:
EVM、Solana 和其他任何链,对”交易确认""交易顺序""交易成本”这三个词的定义,从根本上就不是一个东西。 而多链扩展真正的难度,不在于写适配代码,在于你要抛弃在第一条链上建立的所有”常识”,重新学习另一条链的底层假设。
坑一和坑二是一个东西的两面——EVM 开发者习惯了”区块确认 = 安全”的心智模型,到了 Solana 就栽在 confirmed 上。反过来,你让一个只做 Solana 的人去做 EVM,他大概率会在 nonce 管理和 gas 竞拍上吃苦头。
我听过同行最让我认同的一句话:“多链不是把同一个东西做 N 遍,是把 N 个不同的东西各做一遍,然后试图让它们看起来一样。”
在交易所这种容错为零的场景下,每一次认知错误都会直接变成资金事故。这就是为什么 99% 的公司低估了多链的工程量——不是人不够,是他们以为自己在做”适配”,其实他们需要做的是”重新建造”。
如果你也在做多链产品——钱包、DEX、交易所——欢迎分享你踩过的坑。这种经验,文档里没有,测试也测不出来。