一切看起来都正确——但交易失败。第一部分

一切看起来都正确——但交易失败。第一部分

由英语翻译

在加密货币用户中,有一种广泛的看法:如果你输入了正确的地址、选择了正确的网络,并且拥有足够的本地代币支付手续费,交易就会成功。在绝大多数情况下,这种看法是成立的。

但每条区块链都有其自身的隐藏规则——这些规则只有在非常特定的情形下才会显现。而当它们显现时,即便从你的角度看所有操作都正确,交易也可能失败。

这正是我们在为客户提供兑换服务Rabbit.io时遇到的问题:客户将 LTC 兑换成 SOL。金额非常小——不到 0.0003 SOL,按当前价格不到三美分。在我之前的多篇文章中,我提到过我们不对兑换设置下限。是的,我们经常处理极小金额的兑换。

当我们尝试发送客户要求的 SOL 时,遇到了问题。地址是正确的。网络是 Solana 主网。我们的余额充足。手续费已计入。可交易一次又一次地失败。

解释出乎意料——即便对我们而言也是如此。当我们把原因告诉客户时,他起初也不相信。这件事让我思考:在不同区块链上还有多少类似的隐藏陷阱?

于是我决定调查并汇总最有趣的例子——那些即便对我这种每日处理大量加密兑换的人也可能始料未及的案例。

1. Solana。支付“租金”——即便是原生 SOL

为什么会令人惊讶

很多人听说过,在 Solana 上,为了在一个以前从未持有过某种 SPL 代币(例如 USDT、USDC 等)的地址接收该代币,该地址需要有少量 SOL。严谨地说,这并不完全准确,但其中有一定道理。

每个代币都存储在一个单独的关联代币账户(ATA)中,创建该账户大约需要 0.002 SOL。不过,即便接收方的 SOL 为零,仍然可以接收代币——因为是发送方为创建 ATA 支付费用,而不是接收方。

更鲜为人知的是,类似的要求有时也适用于原生 SOL 的转账。我们在尝试发送客户要求的极小金额时,就遇到了这种情况。

机制如何运作

在 Solana 上,一个账户不仅仅是表示地址的一串字符。它是区块链当前状态中存储的实际记录。该记录占用验证节点的磁盘空间——而存储不是免费的。

这个机制被称为租金豁免。要使账户存在,其余额必须高于某个最小值——实际上相当于两年存储租金的成本。

该值不是永远固定的,但目前,对于一个基本的系统账户(普通钱包),最低为 890,880 lamports,即 0.00089088 SOL。

如果一个地址在链上尚不存在(即其余额为零),第一次尝试以低于此阈值的金额创建账户的交易将被拒绝。

这正是我们遇到的情况。我们向一个全新地址发送了略低于 0.0003 SOL 的金额——大约是所需阈值的三分之一。我们的客户不相信这样的规则存在。公平地说,我们也很难在 Solana 的官方文档中找到明确表述。唯一的间接确认来自 RPC 方法 getMinimumBalanceForRentExemption 的说明,暗示了这一规则——但并未明确写出。

这里有一个重要的细节。我们之前多次向客户发送过如此小的金额而没有任何问题。基于实践经验,我们知道 Solana 允许任意小额的转账。

这确实是真的——但前提是接收账户已存在。如果这是第二次、第三次或第一百次入账,该地址甚至可以接收 1 lamport。但当账户第一次被创建时,就会进行租金豁免检查。

2. XRP Ledger 与 Stellar。地址存在——但账户不存在

我们在 Solana 遇到的问题实际上是更广泛模式的一个特例。在若干网络中,作为加密对象的地址与分布式账本中的账户条目是两回事。没有最低初始余额,账户根本不会存在。

在 XRP Ledger 上,每个新账户必须收到至少 1 XRP 的最低准备金。网络早期,这一要求要高得多——在启动时为 200 XRP,后来降到 50 XRP、20 XRP、10 XRP。随着 XRP 价格随时间上涨,准备金逐步降至目前的 1 XRP。

这笔准备金会永久锁定在账户中。它不能被花费。它不能用于支付交易费用。一些 XRP Ledger 浏览器甚至会将总余额与被锁定的准备金分别显示,以便钱包持有人清晰看到实际可花费的 XRP 数量。

Stellar 网络遵循类似逻辑。在账本中,地址在收到最低余额之前并不存在。Stellar 账户至少需要 1 XLM 才能被激活并保持可用状态。

你可能还记得我曾写过一篇关于 一个错误把 1,000 USDC 变成 1 XLM 的文章。该文描述的情况正是这一机制的例子:即便发送方正确签名,代币交易也无法记入接收方,除非满足某些先决条件。

XRP Ledger 与 Stellar 的一个显著特征是:除非账户明确选择与特定资产交互,否则该账户不能接收该资产。这是开发者的刻意设计,并有其优点。

在其他网络上,大额持有人和名人地址经常被新创建的垃圾代币淹没,给人一种地址持有人购买了这些代币的错觉。在 XRP Ledger 和 Stellar 上,这类垃圾邮件不可能发生——因为接收某种资产需要事先授权。

3. 闪电网络。流动性存在——但没有路由

闪电网络是一种支付通道网络,旨在在不将每笔转账记录到区块链上的情况下发送比特币。与常规链上交易不同,闪电支付不会被网络中的所有节点验证,也不会由矿工在区块中确认。相反,它通过一连串中继节点传递。

这正是出现一整类非直观错误的地方。

路由如何工作

为了让付款从 Alice 经由中间节点 Bob 和 Carol 到达 David,路径的每一段在所需方向上都必须有足够的流动性。

网络知道每个通道的总容量,但不知道余额如何在双方之间分配。这可能导致如下情况:

  • David 在他与 Carol 的通道中有足够的入向流动性。
  • 但在 Bob 与 Carol 的通道中,几乎整个余额恰好在 Carol 一方。Bob 无法向她的方向转发付款。

Routing in the Lightning Network

结果,从 Alice 到 David 的付款会因“未找到路由”而失败——尽管 Alice 有足够的资金,David 有可接收付款的活动通道,并且在网络图上形式上存在一条路由。

隐藏的费用上限陷阱

还有另一个特别棘手的细节。在 LND(最广泛使用的闪电节点实现之一)中,默认的路由费用上限被设置为 0 satoshi。

这意味着节点只会尝试寻找完全免费的路由——而在真实网络中,这几乎不存在。未曾更改此默认设置的用户可能会反复看到“未找到路由”错误,尽管如果他们允许支付少量路由费用,付款可能会成功。

这并非缺陷。相反,LND 的开发者刻意选择不替用户决定他们愿意支付的最高费用。如果默认设置了正值上限,用户可能会在不知情的情况下发现路由费用被扣除。

导致“未找到路由”错误的其他原因:

  • 收款方的节点离线,且其通道不活跃。
  • 发票已过期。闪电发票的有效期有限,通常约为一小时。
  • 金额过大。大额付款比小额更难路由。
  • 先前的尝试“卡住”了流动性。带有时间锁的 HTLC 合约可能会将通道容量暂时锁定,直到其到期。

在通过闪电网络兑换比特币时,我们会遇到所有这些问题。与传统区块链交易相比,它们可能显得不寻常,但每个问题都有解决办法。这就是为什么在Rabbit.io,你可以通过闪电网络发送和接收比特币,而无需担心这些陷阱。

4. USDT 与 USDC。架构不同的黑名单

USDT 和 USDC 是中心化管理的稳定币。它们的智能合约包含黑名单功能:发行方(Tether 和 Circle)可以随时将地址加入黑名单,此后该地址上的代币操作将变得不可能。

这是众所周知的事实。但较少人理解的是,USDT 与 USDC 的限制机制根本不同——这种差异有重要的实际影响。

USDC(Circle):双向阻断

在 USDC 合约中,黑名单检查适用于发送方和接收方。如果接收方地址在黑名单中,智能合约会拒绝交易。发送方只是损失少量 gas,而 USDC 仍留在其钱包中。

从发送方角度看,这是一种更安全的模型:资金不会消失到被冻结的地址中。

USDT(Tether):仅阻止发送方

在 USDT 合约(TetherToken)中,transfer 函数包含如下检查:require(!isBlackListed[msg.sender])。这意味着仅对发送方进行黑名单检查。接收方地址在转账时不会被验证。

在实践中,这会导致以下后果:

  • 你可以成功地将 USDT 发送到被列入黑名单的地址。
  • 交易会成功通过且不会报错。
  • 区块链会记录该转账。
  • 但这些资金随后会被冻结。

接收方或任何其他人随后都无法移动这些代币——被列入黑名单的地址禁止发起出账。只有发行方 Tether 能够介入,使用 destroyBlackFunds 函数将被列入黑名单地址上的代币实际上清除掉。

在这种情况下,有人可能会认为让构造正确的交易直接被拒绝更好。USDC 合约中实现的方法看起来比 USDT 模型更透明、更公平:USDT 的交易虽然成功,但随后会显而易见地变得无意义。

我的建议:

  • 在使用 USDT 时,发送前检查对方地址是否在制裁或黑名单数据中。昨天“干净”的地址今天可能不再干净——其状态可能在最糟糕的时刻发生变化。
  • 如果可能,考虑在 Liquid 区块链上使用 USDT,在那里 Tether 无权冻结单个地址。

话虽如此,即便是 USDT 转账有时也会直接失败——即便所有交易数据看起来都正确。在本文的第二部分中,我将讨论此类案例,以及比特币、以太坊和 Tron 网络上一些鲜为人知的交易限制。

第二部分将在一周后在此发布。