在网页上转账,点了两次按钮通常只扣一次钱,因为后端认得这是同一个请求。链上不是这样。区块链只按到达顺序处理收到的交易,它对你是想提交一次还是两次这件事没有概念。同一笔操作被发送两遍,多数协议就会老老实实执行两遍,于是出现双份借款、双份兑换、双份存入。理解这套缺失,是避免事故的第一步。
传统服务靠请求标识做去重:每个请求带一个唯一的号,服务端见过这个号就忽略后来的重复。链上交易的标识是它的哈希,哈希由发送方地址、nonce、目标、数据等字段一起决定。这带来一个微妙的结果:只要 nonce 变了,内容完全相同的两笔交易就是两笔不同的交易,链和协议都无法识别它们出自同一个意图。前端为了让你快速重试失败的交易,常常会主动递增 nonce,这恰恰制造了内容相同、身份不同的重复操作。
一些协议开始在合约层补这个缺口。做法通常是给关键操作附带一个用户自选的标识字段,合约维护一张已处理标识的记录,同一个标识第二次进来直接拒绝或者返回首次结果。这类机制的效果取决于记录的保存方式:如果只在单个合约生命周期内有效,那协议升级或者换部署之后去重就失效了。判断你用的协议有没有幂等保护,最直接的测试是看它的接口里是否接受一个外部标识参数,以及文档是否说明重复提交的行为。
也有协议从另一个方向解决:让重复执行本身无害。这被称作自然幂等,典型设计是把操作定义成设定绝对状态而不是施加增量。比如一个功能是不把仓位调整到目标值,而不是给仓位加上一个增量,那么同一笔指令执行两遍,结果和一遍相同。相对的,把数量加到池子、把利息累到余额这类增量语义,天生不耐重复。读接口文档时分辨这两种语义,比记住哪个协议有去重表更重要。
用户端能做的防护,靠习惯而不是靠运气。第一条是提交后不要立刻凭感觉重试:先去区块浏览器按你的地址查这笔交易的真实状态,确认它是失败、待处理还是已被打包,再决定动作。第二条是控制重试的方式,用钱包自带的替换或者加速功能提高原交易的出价,而不是新建一笔内容相同的新交易,替换会沿用同一个 nonce,天然排除双份执行。第三条是对高风险操作留出确认间隔,尤其是自动化工具和脚本,脚本里的超时重试如果没有配幂等标识,最容易在节点响应慢的时候批量放大。
风险不对称这件事要说清楚。重复存入通常只是多买了,事后可以按正常流程退出,损失主要是费与滑点;重复借款则直接多出一份负债和利息,如果市场随后转向,多出来的那一份会放大清算风险;重复开市仓可能触发仓位上限而失败,也可能静默成功形成超预期敞口。三类后果都真实存在,严重程度的差别很大,所以把防重复的力气重点放在借款和开仓这两类动作上是划算的。
最后提醒一个容易被忽略的入口差异:同一个操作,从前端页面发起、从聚合器发起、从自动化机器人发起,重试策略完全不同。前端一般会等你确认,机器人可能设了三十秒超时自动补发。当你把仓位交给任何自动执行的通道之前,值得先问清楚它遇到超时和失败时是替换原交易还是新发一笔,这一句话的答案决定了你会不会在某次网络抖动里收到两份仓位。文中涉及的参数与结构均以协议官方文档和合约读数为准,本文只做机制说明与风险提示,不构成投资建议,也不构成收益承诺。

发表评论
还没有评论,来说两句吧。
评论区为展示样式,提交不会被处理。