TRON 交易待确认、已过期或查不到:对 USDT 转账各意味着什么
TRON 每三秒出一个区块,所以链上的五分钟就是一百个区块。过了这么久仍显示为待确认的 USDT 转账,处在四个位置之一:已签名但从未广播;已被节点接受但还没进区块;在一个尚不可逆的区块里;或者在链上已经最终确定,正等收款平台入账。路上还常冒出另外两个说法:区块浏览器找不到这个哈希,以及节点说这笔交易已过期。每一个都有各自的解法,而其中一个解法如果用得太早,会让同一笔转账付两次钱。
每笔交易都带着自己的截止时间
区块间隔是协议的一个常量,3,000 毫秒。另一个数在交易里面。钱包构建转账时,会把一个 expiration 时间写进 raw_data,也就是被签名的那一部分,而每个节点都会检查它。过期时间已过的交易会被拒绝,过期时间比节点的最新区块晚了一天以上的交易也会被拒绝。节点对这两种情况的说法都是 TRANSACTION_EXPIRATION_ERROR。
第二对字段把交易绑定到一个较新的区块上。ref_block_bytes 和 ref_block_hash 指向网络仍保留的某个区块,而节点记得最近的 65,536 个区块,大约 54 小时的链。比这更早的引用,或者取自测试网的引用,得到的是 TAPOS_ERROR。两个时钟都从交易构建的那一刻开始走。
“待确认”可能指的四件事
已签名,从未广播
钱包或付款脚本可能签了一笔转账,却因为超时或在签名和发送之间崩溃,从未把它交给节点。这笔转账照样有哈希,因为哈希是从交易自己的字节算出来的。它是 raw_data 的 SHA-256,在任何节点看到任何东西之前就已经存在。您日志里的哈希只能证明这笔转账被构建过。区块浏览器对它回答“未找到”是对的:它读取的节点里,没有一个持有这些字节。
已被节点接受,尚未进区块
节点收下一笔交易时会立刻答复,并把它放在自己的待确认交易池里,直到某个区块把它打包进去。接受和打包是两件独立的事,我们自己的订单状态也把它们分开:在 mode B 里,也就是我们广播客户已签名转账的那种模式,broadcasted 的意思是有节点接受了这笔交易,对区块还什么也没说。在三秒出块的链上,这个状态很短。节点也可能接受一笔永远没有区块打包的交易,通常是因为它从那个池里被丢掉了。这样的转账,在过期时间一过就彻底作废了。
已在区块里,尚不可逆
链头最新的几个区块仍然可能被替换。TRON 在链头后面还保存着第二个指针,即最新的固化区块,它是网络视为不可逆的最新区块,节点两个都提供。两者之间的距离,就是一笔转账待在区块里、却还不是最终确定的那段时间。我们从一个公共全节点上把这两个指针读了一次,两次调用在同一时刻发出。
| 读于 2026 年 10 月 27 日 21:27 UTC | 区块号 | 区块时间,UTC |
|---|---|---|
| 最新区块 | 87,487,718 | 21:27:06 |
| 最新固化区块 | 87,487,699 | 21:26:09 |
| 间隔 | 19 个区块 | 57 秒 |
区块号的间隔和时间戳的间隔对得上,都是每个区块三秒。我们的 mode B 条目只有在固化之后才会到 confirmed,我们的 API 参考文档把这个时间定为大约一分钟,而这次读数与它相符。这就给出了 TRON 上一笔 USDT 转账要多久这个问题的惯常答案:每三秒一个区块,大约一分钟后最终确定。这只是对一个节点的一次读取。这个间隔在一天里怎么变化,以及我们自己的订单从交付到广播再到进区块各用多久,我们都没有量过。
链上已最终确定,尚未入账
交易所或支付处理方会在数过它自己定的区块数之后,才给一笔充值入账。我们不知道任何平台的区块数,也不引用任何一个。在去问平台之前,先读转账旁边写着的结果。一笔转账可以已经在区块里,却依然失败了——费用花掉了,代币没有动——而那个位置上只要不是 SUCCESS,就是失败(OUT_OF_ENERGY 是什么意思,以及USDT 转账失败的其他几种情形)。固化区块里的一笔成功转账,已经到了收款地址。剩下的是平台的记账,而它的客服可以凭哈希去追查。
已过期是什么意思,以及怎样重新发送
过了过期时间的交易进不了任何区块。每个节点都会拒绝它,所以把同样的已签名字节再广播一遍,只会再收到一次拒绝。补救办法是一笔新交易:重新构建这笔转账,带上新的过期时间和对一个较新区块的引用,然后签名。
重建不会付两次钱。没有被任何区块打包的交易从未执行过:它没有挪动代币,也没有烧掉任何东西,因为一笔转账是在区块里运行的时候才花掉它的能量和 TRX。交易一旦进了区块,就没有节点会再把它运行第二遍。把钱发出两次的唯一办法,是在原交易仍有可能落地的时候就签了一笔替代的。节点是拿自己最新区块的时间去判断过期的,这个时间可能落后于您的时钟,所以在截止前最后几秒里出的区块仍可能打包原交易,而滞后的节点或区块浏览器可能还没显示出来。构建原交易的软件手里有它,可以读到 raw_data.expiration,单位是自纪元以来的毫秒数。等最新的固化区块越过那个时刻——按我们的读数,要再多等大约一分钟——再按哈希去问节点。如果节点仍然不认识这笔交易,原交易就不可能再落地了,新的这笔就是唯一的一笔。
能量订单上的待确认与已过期
租来的能量带来第二个时钟。订单的能量一交付,订单就带上 send_before,也就是租期减去三秒:五分钟租期上是 297 秒,一小时上是 3,597 秒。窗口是从我们看到被确认的那笔代理起算的,而链上的租期在那之前一个区块外加一个轮询周期就已经开始了,所以请把窗口的最后几秒当作已经没有了(租来的能量能撑多久)。能量离开钱包之后才进区块的转账,会按每单位 100 sun 的协议价烧掉发送方的 TRX,转给持有 USDT 的地址的一笔大约是 6.5 TRX。
窗口关闭时会发生什么,取决于由谁来发。在 mode A 里,转账由您自己广播。我们盯着网络,在您的转账出现时把条目标为 sent,并发出 order.item.sent webhook,哈希放在 data.txid 里。如果到 send_before 还没有转账出现,条目就变成 expired 并照常扣费,因为能量已经交付。
在 mode B 里,您把一笔已签名的交易交给我们,能量一落地我们就把它广播出去(mode B 是怎么工作的)。条目依次经过 broadcasting、broadcasted,到 confirmed,最后一步只在固化之后才发生。我们绝不在 send_before 之后广播:一个没能及时发出的条目以 failed 收场,带着 error: broadcast_window_missed,预留全额退回。那里的窗口还受交易自身过期时间的限制,要减去广播用的余量,所以到达我们时寿命所剩无几的交易,窗口就很短。
mode B 在交易进来时也会检查过期时间。我们收到交易时,它至少要剩 180 秒的寿命,至多 23 小时 59 分钟。截止时间太远的,返回 400 invalid_request,details.reason 为 expiration_too_far;寿命太短的,返回同样的 400,只带 details.index。两种答复都发生在买任何能量之前。
按顺序该查什么
- 有没有节点接受过这笔交易。发送它的软件手里有节点的答复,或者什么也没有,因为广播根本没发生过。哈希本身说明不了什么,因为它在任何广播之前就已经存在。
- 哈希能不能找到,在区块浏览器上,或者按哈希去问节点。如果找不到、而过期时间还在前面,就把同样的已签名字节再广播一次。如果最新的固化区块已经越过过期时间、哈希仍然找不到,就构建一笔新交易并签名。
- 找到的哈希旁边的结果。
SUCCESS表示转账成功。其他任何结果都表示它运行过但失败了,费用已经花掉。 - 它的区块是否已固化。按我们的读数,最新的不可逆区块落后链头大约一分钟,所以比这更新的成功转账仍在落定。
- 收款平台自己的区块数,然后是它的客服,带上哈希。
- 在我们的订单上,看条目:mode A 里是
sent,并带着它的txid;mode B 里是confirmed。窗口先关闭时,mode A 显示expired,mode B 显示failed,带着error: broadcast_window_missed。mode B 里的expired表示节点因交易本身的问题拒绝了它,或者交易在区块里回滚了,并且要扣费。
看一个条目停在 ready、然后变成 expired,而真金白银毫无风险:沙箱里有一个转账永远不会到来的收款方 →