如何在 Solana 上完成交易
Solana 最近经历了前所未有的交易量,导致高比例的交易失败或丢弃。该网络的每秒交易量(TPS)约为 2000-3000,其中大约有 800-900 笔是非投票交易。Quinn(QUIC 网络层的 rust 实现)在高需求场景下有效处理垃圾邮件方面存在限制,可能导致区块领导者需要有选择性地断开连接。在所有失败的交易中,大约有 8%是由实际用户发起的,其余是机器人发起的任意交易 。
了解 Solana 上交易是如何提交和处理的对于处理失败交易至关重要。本文深入探讨了交易失败的可能原因,并推荐了增加交易吞吐量的最佳实践。本文假定你对 Solana 的编程模型和创建以及发送交易有基本了解。
运行时将按顺序原子地处理交易中包含的每个指令。如果指令的任何部分失败,整个交易将失败。
什么是区块哈希?
"区块哈希"是一个 slot 的最新历史证明(PoH)哈希。由于 Solana 依赖于 PoH 作为可信时钟,一个交易的 recent blockhash 可以被视为时间戳。区块哈希可以防止重复并为交易提供生命周期。如果一个交易的区块哈希太旧,它将被拒绝。最大的区块哈希年龄为 150 个块或约 1 分钟 19 秒。
Solana 由一组验证者维护,他们验证添加到账本的交易。从这组中选择一个领导验证者来将条目附加到账本。账本上的条目可以是一个 tick 或一个交易条目 。账本保存了一个包含客户端签名的交易条目列表。 创世块在概念上追溯到账本。但实际验证者的账本可能只有更新的块,以减少存储,因为在设计上旧块不需要用来验证未来的块。
领导验证者每个 slot 只能生成一个 block,区块哈希是用于标识每个块的唯一标识符。它是一个块的所有条目的哈希,包括上一个块的哈希。 领导计划在每个纪元之前确定,通常在大约两天之前,以决定哪个验证者将在任何给定时间充当当前领导者。当发起交易时,它将被转发给当前和下一个领导验证者。
交易可以通过以下方式提交给领导者:
要使用 sendTransaction 方法,你需要将交易对象编码为字符串进行传递。其他可选参数包括:
验证者的交易处理单元(TPU) 接收交易,验证签名,执行交易,并与网络中的其他验证者共享。
TPU 在五个不同的阶段处理交易:
来源:JitoLabs 的交易处理单元概述
获取阶段负责接收交易。它将根据三个端口对传入的交易进行分类:
数据包被分批成 128 个一组,并转发到 SigVerify 阶段。
SigVerify 阶段验证数据包上的签名,并在验证失败时修剪它们。投票和常规数据包在两个单独的流水线上运行。从软件的角度来看,它接收到的数据包包含一些元数据,但目前还不清楚这些数据包是否是交易。
如果你安装了 GPU,则将用于签名验证。此外,在高流量情况下,有一种处理过多数据包的逻辑,利用 IP 地址丢弃数据包。
此阶段负责过滤和处理交易。目前,它由 6 个独立的工作线程组成,其中 2 个是投票线程,4 个是非投票线程。常规交易被添加到非投票线程。每个线程都有一个本地缓冲区,可以容纳最多 64 个不冲突的交易,存储在优先级队列中。这些交易然后并行处理,由 Sealevel 实现。你可以参考此视频了解更多关于 Banking 阶段的信息。
PoH 服务模块记录 ticks 的传递。每个 tick 代表一个时间单位,一个 slot 中有 64 个 tick。哈希重复生成,直到从 Banking 阶段收到记录:
next_hash = hash(prev_hash, hash(transaction_ids))
然后将这些记录转换为 entries,通过广播阶段广播到网络。
来自 PoH 服务的 entries 被转换为 shreds,代表一个块的最小单位,然后使用名为 Turbine 的块传播技术发送到网络的其余部分。在高层次上,Turbine 将一个块分成更小的部分,并通过节点的分层结构进行分发。节点不必与每个其他节点联系。它们只需要与少数节点通信。你可以参考此文章了解更多关于 Turbine 及其工作原理的信息。
除了由于不正确的指令或自定义程序错误导致的失败外,交易失败的可能原因有:
网络层可能在领导者处理之前丢弃交易。UDP 数据包丢失是可能发生这种情况的最简单原因。另一个原因与 TPU 的 fetch 阶段有关。当网络负载较重时,验证者可能会被需要处理的交易数量压垮。验证者可以将额外的交易转发到下一个验证者的 tpu_forward 端口。然而,可以转发的数据量是有限的,每次转发在验证者之间限制为一跳。这意味着在 tpu_forwards 端口接收的交易不会被转发到其他验证者。如果未处理的重播队列大小超过10,000 笔交易,新提交的交易将被丢弃。
每个交易都有一个作为历史证明(PoH)时钟时间戳的“最近区块哈希”。这个区块哈希帮助验证者避免处理相同的交易两次,并跟踪交易何时以及以何种顺序被处理。验证者将由于无效的区块哈希在处理过程中拒绝交易。
交易的区块哈希一旦不再被认为是足够“最近”,就会过期。为了处理交易,Solana 验证者会在一个块中搜索相应区块哈希的 slot 号。如果验证者找不到区块哈希的 slot 号,或者查找到的 slot 号比正在处理的块的 slot 号低151 个 slot 以上,交易将被拒绝。默认情况下,如果 Solana 交易在一定时间内(~1 分钟 19 秒)未提交到一个块中,则会过期。
通过 RPC 提交交易时,RPC 池可能领先于其他部分。当池内的节点需要共同工作时,可能会出现问题。例如,如果从池的高级部分查询交易的 recentBlockhash 并将其提交到池的滞后部分,则节点将无法识别高级区块哈希并拒绝交易。你可以通过在 sendTransaction 上启用预检查来在提交交易时检测到这一点。
临时网络分叉也可能导致交易丢失。如果验证者在 Banking 阶段缓慢重播其块,可能会创建一个少数分叉。当客户端构建交易时,交易可能引用仅存在于少数分叉上的 recentBlockhash。在提交交易后,集群可能会在交易处理之前切换到其少数分叉。在这种情况下,由于未找到区块哈希,交易将被丢弃。
为了诊断确认问题,了解交易过期很重要。按照以下步骤增加成功交易的机会:
交易有限的时间被验证者处理。如果与交易相关的区块哈希在验证者处理之前过期,交易将被取消。为确保你的交易成功,重要的是使用最新的区块哈希发送交易。如果区块哈希在验证者处理你的交易之前过期,你可以使用新的区块哈希重新尝试交易,以确保成功处理。这可以通过两种方式完成:
获取最新区块哈希的推荐 RPC API 方法是 getLatestBlockhash。默认情况下,此方法使用已最终化的承诺级别返回最近已最终化区块的区块哈希。此承诺级别表示该区块至少有 31 个已确认的区块添加到其上方。这消除了使用属于已删除分叉的区块哈希的风险。然而,最近已确认和已最终化的区块之间通常至少有 32 个槽的差异。这种权衡减少了交易过期约 13 秒,这在不稳定的集群条件下可能会更多。
你可以通过将承诺参数设置为不同级别来覆盖区块哈希的承诺。对于 RPC 请求,已确认的承诺级别是推荐的,因为它通常只落后于已处理的承诺级别几个槽,并且很少有可能属于已删除分叉。尽管已处理的承诺级别相对于其他承诺级别获取了最新的区块哈希,但不建议使用,因为由于 Solana 协议中的分叉,集群中大约有 5%的区块未最终化。如果你的交易使用属于已删除分叉的区块哈希,它将不被最终化区块链中的任何区块视为最新。
添加一个脚本,使用getLatestBlockhash方法频繁(每 60 秒)获取并存储最新的区块哈希。因此,每当用户触发交易时,应用程序都有一个新鲜的区块哈希准备就绪。钱包也应频繁轮询新的区块哈希,并在签署交易之前替换交易的最新区块哈希,以确保其尽可能最新。
在提交交易之前,执行以下预检查:
如果用于模拟的区块比你的交易区块哈希使用的区块旧,则模拟将失败,并显示可怕的“未找到区块哈希”错误。
如果你确信你的交易签名已验证且没有其他错误,你可以跳过预检查。即使使用skipPreflight参数,也始终将preflightCommitment参数设置为用于获取你的交易区块哈希的相同承诺级别,用于sendTransaction和 simulateTransaction 请求。
当交易在网络上确认时,它会消耗一些区块中可用的总计算单位(CU)。目前,区块上的总计算限制为 48M CU。开发人员可以为其交易指定计算单位预算。如果他们没有设置预算,则使用默认值为 1,400,000,这比大多数交易所需的要高。许多交易不使用整个 CU 预算,因为请求比必要的更高的预算没有惩罚。然而,一开始请求太多的计算单位会使调度交易变得更加困难,因为调度程序在交易执行之前不知道区块中剩余多少计算。为避免这种情况,开发人员应设置更好范围的 CU 请求,以匹配交易需求。你可以参考这篇指南来优化计算单位预算。在即将推出的 Solana 客户端 v1.18 更新中, 需要更少计算单位的交易将获得更高优先级 。
优化你的计算单位(CU)使用具有以下好处:
优先费用可以添加到基本交易费用之上,以使验证者优先处理交易。这些费用以每计算单位的微拉姆波(例如,少量的 SOL)定价。它们被添加到交易中,使其在网络中的区块中更具经济吸引力,以便验证者节点将其包含在区块中。
然而,重要的是要注意,支付优先费用的金额应该有限。支付超过典型费用的金额不会增加你的交易成功的概率。因此,建议动态计算优先费用,以确保你支付适当的金额以保持竞争力,同时避免过度支付。这种集成很简单,你可以参考官方文档关于优先费用,或使用现成的 Helius API。
在网络拥塞的情况下,在你的代码中实现自定义逻辑来处理交易失败并手动重试它们。为此,当使用 sendTransaction 提交交易时,将maxRetries参数设置为0。你可以使用不同的方法重试交易:
共享的 RPC 有限制,限制每秒可以发送多少交易。可以使用专用节点来避免此限制。它们允许更强大的重试机制,因为你可以每秒发送更多的交易,无论是尝试在链上执行其他操作还是同时确保特定交易通过。它们提供以下好处:
更快的 RPC 速度:如果你想要最小化将交易提交给验证者所需的时间,可以在服务器附近设置一个专用节点。要进行测试,请运行节点的 ping <ip-address>。
无速率限制:Helius 的专用节点可以处理高数量的请求每秒,增加成功交易的可能性。它们的性能在很大程度上取决于它们可以处理多少流量而不会崩溃。重要的是要注意每秒请求(RPS)和每秒交易(TPS)是不同的度量标准。专用节点允许你发送交易;它不会协助确认过程。
如果你有一个带有 Yellowstone gRPC 插件的专用节点,你可以利用 Atlas Transaction Sender。即使没有抵押连接,它也可以提供更好的交易落地成功率。该软件包旨在仅使用最少的依赖项向 Solana 领导者发送交易。它监听区块更新并跟踪它们,同时维护一个连接缓存以重试交易。默认情况下,它会缓存到四个领导者的连接并向它们发送交易。请注意,此服务在将交易发送到领导者之前不处理预检查或验证区块哈希。
领导者的网络带宽容量有限。为了有效使用它,需要进行抵押加权,以避免盲目接受交易,而不考虑其来源。Solana 作为权益证明网络运作,这使得将抵押加权扩展到改善交易服务质量变得自然。这意味着具有 0.5%抵押的节点可以向领导者发送至少 0.5%的数据包。而网络的其余部分 - 以及任何其他抵押的组合 - 将无法完全淘汰它们。
Helius 提供 Atlas,我们的抵押加权服务,可以帮助你完成交易。要了解更多信息,请在 Discord 上联系我们。
持久性 nonce 允许创建和签署可以在将来的任何时间点提交的交易。它们用于情况如需要更多时间生成交易签名的托管服务。如果你的交易不是时间敏感的,可以使用此方法规避交易的 recentBlockhash 的短生命周期。
要开始使用持久性交易,你需要提交一个调用指令以在链上创建特殊的“nonce”账户并在其中存储“持久性区块哈希”的交易。Nonce 账户存储 nonce 的值。只要 nonce 账户尚未被使用,你可以按照以下两个规则创建持久性交易:
通过参考这篇文章 ,了解如何通过 CLI 和 Web3.js 实现持久性 nonce。
总之,在 Solana 网络上成功完成交易,尤其是在高流量时期,需要对网络架构和交易处理机制有细致的理解。通过掌握诸如区块哈希在交易唯一性和及时性中的作用、通过 RPC 服务器或 TPU 客户端提交交易的过程以及设置正确参数(如skipPreflight、preflightCommitment和maxRetries)等核心概念,用户可以显著提升交易性能。实施自定义重试机制并利用专用节点或抵押连接可以帮助提高成功率。
此外,了解网络当前的限制以及 Solana 基金会正在努力解决这些问题的努力至关重要,正如即将推出的 v1.18 客户端版本所示。随着网络的发展和扩展,保持信息灵通和适应性将是有效与其互动的关键。
如果你需要任何帮助或支持,请随时在 Discord 上联系我们。请务必在下方输入你的电子邮件地址,以便你不会错过 Solana 的最新动态。准备深入了解吗?探索 Helius 博客上的最新文章,继续你的 Solana 之旅。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!