如何在 Solana 上进行交易

如何在 Solana 上进行交易

Solana 近期经历了前所未有的交易量,导致交易失败或丢失的比例很高。该网络每秒的交易量(TPS)约为2000-3000 笔,其中大约 800-900 笔是非投票交易。Quinn (网络层的 Rust 实现- QUIC)在高需求场景下有效处理垃圾邮件方面存在局限性,这可能导致区块领导者必须有选择地断开连接。在所有失败的交易中,大约 8% 是由实际用户发起的,其余的是机器人的任意交易

了解事务如何在 Solana 上提交和处理对于处理失败的事务至关重要。本文深入探讨了事务失败的可能原因,并推荐了提高事务吞吐量的最佳实践。本文假设您对Solana 的编程模型以及创建和发送交易有基本的了解。

交易

程序执行从提交到集群的事务开始。一笔交易包含:

  • 打算读取或写入的所有帐户的数组
  • 一条或多条指令(即最小执行单元)
  • 最近的区块哈希
  • 一个或多个签名

运行时将按顺序和原子地处理事务中包含的每条指令。如果指令的任何部分失败,则整个事务将失败。

什么是区块哈希?

“blockhash”是插槽的最新历史证明(PoH)哈希。由于 Solana 依赖PoH机制,因此交易的最近区块哈希可以被视为时间戳。 Blockhash 可以防止重复并为交易提供生命周期。如果一笔交易的区块哈希太旧,它将被拒绝。最大区块哈希年龄为 150 个区块或约 1 分 19 秒。

交易如何提交?

Solana 由一组验证器维护,这些验证器验证添加到分类账中的交易。从该组中选择一个领导验证者将条目附加到分类账中。账本上的条目可以是一个刻度交易的条目。分类账包含包含客户签署的交易的条目列表。创世区块在概念上可以追溯到账本。尽管如此,实际验证者的分类账可能只包含较新的块以减少存储,因为设计时不需要旧的块来验证未来的块。

领导者验证器每个只能生成一个,而 blockhash 是用于标识每个块的唯一标识符。它是一个块的所有条目的哈希值,包括最后一个块的哈希值。领导者时间表在每个 epoch 之前确定,通常是在两天前左右,以决定在任何给定时间哪个验证者将充当当前领导者。当事务启动时,它会被转发到当前和下一个领导者验证器。

‍交易可以通过以下方式提交给领导者: 

  1. RPC 服务器:RPC 提供者可以通过sendTransaction JSON-RPC 方法提交交易。接收 RPC 节点将尝试每两秒将其作为UDP 数据包发送给当前和下一个领导者,直到事务完成或事务的块哈希过期(150 个块或约 1 分 19 秒后)。在此之前,除了客户端和中继 RPC 节点所知之外,不存在任何事务记录。 
  2. TPU 客户端TPU 客户端只需提交交易。客户端软件需要处理重播和领导者转发。

要使用sendTransaction方法,您需要传递编码为字符串的交易对象。其他可选参数包括:

  1. 编码:交易数据使用的编码是base58或base64。 
  2. skipPreflight:预检检查包括验证交易签名并根据预检承诺指定的银行槽模拟交易。如果预检检查失败,将返回错误。此功能的默认设置为false,这意味着不会跳过预检检查。
  3. preflightCommitment:它指定执行预检检查时使用的承诺级别。承诺级别默认设置为最终确定,但可以通过指定字符串进行更改。建议指定相同的承诺和预检承诺,以避免混淆行为。
  4. maxRetriesmaxRetries参数确定 RPC 节点需要重试向领导者发送事务的最大次数。如果未提供此参数,RPC 节点将重试交易,直到交易完成或区块哈希过期。 
  5. minContextSlotminContextSlot参数指定执行预检事务检查的最小槽位。

交易如何处理?

验证器的交易处理单元(TPU)接收交易,验证签名,执行它,并与网络中的其他验证器共享它。

TPU 分五个不同的阶段处理交易:

资料来源:JitoLabs 的交易处理单元概述
资料来源: JitoLabs的交易处理单元概述
  1. 获取阶段

Fetch Stage负责接收事务。它将根据三个端口对传入交易进行分类:

  • tpu:处理常规交易,例如代币转移、NFT 铸币和程序指令
  • tpu_vote:专注于投票交易
  • tpu_forwards:如果当前领导者无法处理所有事务,则将未处理的数据包转发给下一个领导者

数据包被分成 128 个组并转发到 SigVerify 阶段。

  1. 信号验证阶段

SigVerify阶段验证数据包上的签名,并在验证失败时修剪它们。投票和常规数据包在两个独立的管道中运行。从软件的角度来看,它收到的数据包包含一些元数据,但仍不清楚这些数据包是否是交易。

如果您安装了 GPU,它将用于签名验证。此外,还有一个逻辑可以在流量较高的情况下处理过多的数据包,即利用 IP 地址丢弃数据包。

  1. 银行阶段

该阶段负责过滤和处理交易。目前,它由 6 个独立的工作线程组成,其中 2 个是投票线程,4 个是非投票线程。常规事务被添加到非投票线程中。每个线程都有一个本地缓冲区,可以在优先级队列中容纳最多 64 个不冲突的事务。然后,这些交易将在Sealevel的支持下并行处理。您可以参考此视频来了解有关银行阶段的更多信息。

  1. 历史证明服务

PoH 服务模块记录蜱虫的经过。每个tick代表一个时间单位,一个slot中有64个tick。哈希值会重复生成,直到从银行阶段收到记录为止:

next_hash = 哈希(prev_hash, 哈希(transaction_ids))

然后这些记录被转换为条目并通过广播阶段广播到网络。

  1. 广播舞台

来自 PoH 服务的条目被转换为碎片,代表块的最小单位,然后使用称为Turbine的块传播技术发送到网络的其余部分。在较高的层次上,Turbine 将一个块划分为更小的块,并通过节点的层次结构分布它们。节点不必与每个其他节点联系。他们只需要与少数人沟通。您可以参考这篇文章来了解有关Turbine及其工作原理的更多信息。 

交易失败怎么办?

排除指令错误或自定义程序错误导致的失败,交易失败的可能原因有:

网络掉线

网络层可以在领导者处理交易之前丢弃该交易。UDP 数据包丢失是发生这种情况的最简单原因。另一个原因与TPU 的获取阶段有关。当网络处于高负载时,验证器可能会因需要处理的交易数量而不堪重负。验证器可以将额外的交易转发到下一个验证器的tpu_forward端口。但是,可以转发的数据量是有限的,并且每次转发仅限于验证器之间的一跳。这意味着tpu_forwards端口上收到的交易不会转发到其他验证器。如果未完成的重播队列大小超过10,000 个事务,则新提交的事务将被丢弃。

陈旧/不正确的区块哈希 

每笔交易都有一个“最近的区块哈希”,用作历史证明(PoH)时钟的时间戳。该区块哈希可帮助验证者避免两次处理同一交易,并跟踪交易的处理时间和顺序。验证器将由于处理过程中的块哈希无效而拒绝交易。

  • 区块哈希过期

一旦交易不再被认为是足够“最新”的,它的区块哈希就会过期。为了处理交易,Solana 验证器会搜索区块中相应区块哈希的槽号。如果验证器找不到区块哈希的槽号,或者查找到的槽号比正在处理的块的槽号低151 个槽以上,则交易将被拒绝。默认情况下,如果 Solana 事务在一定时间(约1 分 19 秒)内未提交到区块,则会过期。

  • 滞后的 RPC 节点
来源:Solana 通过 RPC 池删除的交易
来源:Solana 通过 RPC 池删除的交易

当您通过 RPC 提交交易时,RPC 池可能会领先于其他交易。当池中的节点需要协同工作时,这可能会导致问题。例如,如果从池的高级部分查询交易的最近块哈希并将其提交到池的滞后部分,则节点将无法识别高级块哈希并会拒绝该交易。您可以通过在 sendTransaction 上启用预检检查来在事务提交时检测到这一点。

  • 临时网络分叉
资料来源:由于 Solana 的少数分叉(处理后),交易被丢弃
资料来源:由于 Solana 的少数分叉(处理后),交易被丢弃

临时网络分叉也可能导致交易丢失。如果验证者在银行阶段重放其区块的速度很慢,它可能会创建少数派分叉。当客户端构建交易时,交易可能会引用仅存在于少数分叉上的最近的Blockhash 。提交事务后,集群可以在处理事务之前切换出少数派分叉。在这种情况下,交易会被删除,因为找不到区块哈希。

如何进行土地交易?

要诊断确认问题,了解交易过期非常重要。请按照以下步骤增加交易成功的机会:

太长了

  • 获取承诺“已确认”或“最终确定”的最新区块哈希
  • 将skipPreflight设置为true
  • 优化请求的计算单元数量
  • 动态添加和计算优先费
  • 将maxRetries设置为0,并添加用于发送事务的自定义重试逻辑。
  • 使用专用节点发送大量交易
  • 探索质押连接
  • 如果交易对时间不敏感,请使用持久随机数

区块哈希

验证器处理交易的时间有限。如果与交易关联的区块哈希在验证器处理它之前过期,则交易将被取消。为了确保您的交易顺利进行,重要的是您必须使用最近的区块哈希来发送它。如果区块哈希在验证器处理您的交易之前过期,您可以使用新的区块哈希重新尝试该交易,以确保交易成功处理。这可以通过两种方式完成: 

  1. 设定新的承诺级别:

用于获取最新区块哈希的推荐 RPC API 方法是getLatestBlockhash。默认情况下,此方法使用最终确定的 承诺级别来返回最近最终确定的块的块哈希。此承诺级别表明该区块上面至少添加了 31 个已确认的区块。这消除了使用属于已删除分叉的区块哈希的风险。然而,最近确认的区块和最终确定的区块之间通常至少存在 32 个时隙的差异。这种权衡将事务的过期时间减少了大约 13 秒,在不稳定的集群条件下,这个时间可能会更长。 

您可以通过将承诺参数设置为不同的级别来覆盖块哈希的承诺。建议对 RPC 请求使用确认的承诺级别,因为它通常仅落后于已处理的承诺级别几个槽,并且属于丢弃分叉的可能性很小。尽管与其他承诺级别相比,已处理的承诺级别获取最新的块哈希,但不建议这样做,因为由于 Solana 协议中的分叉,大约 5% 的块未由集群最终确定。如果您的交易使用属于已删除分叉的区块哈希,则最终区块链中的任何区块都不会将其视为最新的。

  1. 经常轮询最近新的区块哈希:

添加脚本以频繁(每 60 秒)使用getLatestBlockhash方法获取并存储最新的 blockhash。因此,每当用户触发交易时,应用程序就会准备好一个新的区块哈希。钱包还应该经常轮询新的区块哈希,并在签署交易之前替换交易的最新区块哈希,以确保它尽可能是最新的。

跳过预检

在提交交易之前,会执行以下预检检查:

  • 交易的签名已得到验证。
  • 该交易是根据预检承诺指定的银行时段进行模拟的。如果失败,则返回错误。 

如果为模拟选择的块比用于交易的块哈希的块旧,则模拟将失败并出现可怕的“未找到块哈希”错误。 

如果您确信您的交易签名已通过验证并且没有其他错误,则可以跳过预检检查。即使您使用了skipPreflight参数,也始终将preflightCommitment参数设置为用于为sendTransactionsimulateTransaction请求获取交易的块哈希的相同承诺级别。

计算单元

当一笔交易在网络上得到确认时,它会用掉区块中可用的总计算单元 (CU) 的一部分。目前,块上的总计算限制为48M CU。开发人员可以为其交易指定计算单元预算。如果他们不设置预算,则使用默认值 1,400,000,这高于大多数交易所需的值。许多交易不会使用整个 CU 预算,因为请求高于必要预算的预算不会受到处罚。但是,预先请求太多计算单元可能会导致高效调度事务变得更加困难,因为调度程序在事务执行之前不知道块中还剩下多少计算。为了避免这种情况,开发人员应该设置范围更广的、符合事务要求的 CU 请求。您可以参考本指南来优化计算单元预算。在即将到来的 Solana 客户端 v1.18 更新中,需要较少计算单元的事务将获得更高的优先级

优化计算单元 (CU) 使用具有以下优势:

  • 较小的交易更有可能包含在区块中。
  • 更便宜的指令使您的程序更具可组合性。
  • 降低整体区块使用率,使更多交易能够包含在区块中。

实行优先收费

可以在基本交易费的基础上添加优先费,以使验证者对交易进行优先排序。这些费用以每个计算单元的微型灯(例如少量的 SOL)定价。它们被添加到交易中,以使其在经济上对验证器节点具有吸引力,以将其包含在网络上的块中。

然而,值得注意的是,优先权费用的支付金额是有限制的。支付超过一般费用并不会增加交易成功的可能性。因此,建议动态计算优先费,以确保您支付适当的金额以保持竞争力,同时避免支付过高。这种集成非常简单,您可以参考有关优先费的官方文档或使用现成的Helius API

实施稳健的重试逻辑

如果出现网络拥塞,请在代码中实现自定义逻辑来处理事务失败并手动重试。为此,请在使用sendTransaction提交交易时将maxRetries参数设置为0 。您可以使用不同的方法来重试事务:

  • 轮询具有不同承诺级别的交易状态,并持续使用相同的签名交易,直到使用指数退避机制得到确认,以避免垃圾邮件。或者,您可以以恒定的时间间隔提交事务,直到发生超时。
  • 存储来自getLatestBlockhash 方法的lastValidBlockHeight。然后,轮询集群的块高度,并在当前块高度高于lastValidBlockHeight后手动重试事务。通过getLatestBlockhash进行轮询时,建议您指定您的预期承诺级别。通过将承诺设置为已确认(投票)或最终确定(确认后约 30 个区块),您可以避免从少数分叉轮询区块哈希。

专用节点

共享 RPC 对每秒可以发送的事务数量有限制。可以使用专用节点来避免这种限制。它们允许更强大的重试机制,因为您可以每秒发送更多交易,无论是尝试在链上执行其他操作还是确保特定交易同时进行。它们具有以下优点:

  • 更快的 RPC 速度:如果您想最大程度地减少向验证器提交交易所需的时间,您可以在服务器附近设置一个专用节点。要进行测试,请运行节点的 ping <ip-address> 。
  • 无速率限制Helius 的专用节点每秒可以处理大量请求,从而增加交易成功的可能性。它们的性能很大程度上取决于它们能够处理多少流量而不会崩溃。需要注意的是,每秒请求数 (RPS) 和每秒事务数 (TPS) 是不同的指标。专用节点允许您发送交易;它不协助确认过程。

如果您有一个带有Yellowstone gRPC 插件的专用节点,您可以利用Atlas Transaction Sender。即使没有质押连接,也能提供更好的交易落地成功率。该包旨在仅使用所需的最少依赖项向 Solana 领导者发送交易。它监听块更新并跟踪它们,同时维护连接缓存以重试事务。默认情况下,它缓存与四个领导者的连接并向他们发送交易。请注意,此服务在将块哈希发送给领导者之前不处理飞行前检查或验证块哈希。

质押连接

领导者的网络带宽容量是有限的。为了有效地使用它,权益加权是必要的,以避免在不考虑交易来源的情况下以先到先得的方式盲目接受交易。 Solana 作为权益证明网络运​​行,这使得扩大权益加权的使用以提高交易服务质量变得很自然。这意味着拥有 0.5% 权益的节点可以向领导者发送至少 0.5% 的数据包。而网络的其余部分——以及剩余股份的任何组合——将无法将它们完全洗掉。

Helius 提供我们的权益加权服务 Atlas,可以帮助您进行交易。要了解更多信息,请通过Discord联系我们。

持久随机数

持久随机数允许创建和签署可以在未来任何时候提交的交易。它们用于托管服务等需要更多时间来生成交易签名的情况。如果您的交易对时间不敏感,您可以使用此方法来规避交易的previousBlockhash的短暂生命周期。

要开始使用持久交易,您需要提交一个交易,该交易调用指令以在链上创建一个特殊的“nonce”帐户并在其中存储“持久区块哈希”。 Nonce 账户存储 nonce 的值。只要nonce账户没有被使用过,就可以按照以下两条规则创建持久交易:

  • 指令列表必须以“预先随机数”系统指令开头,该指令加载您的链上随机数帐户。
  • 交易的区块哈希必须等于链上随机数账户中存储的持久区块哈希。

参考本文,了解如何通过 CLI 和 Web3.js 实现持久随机

结论

总之,要在 Solana 网络上成功落地交易,尤其是在高流量期间,需要对网络架构和交易处理机制有细致入微的了解。通过掌握区块哈希在交易唯一性和及时性中的作用、通过 RPC 服务器或 TPU 客户端提交交易的过程以及设置正确参数(如skipPreflightpreflightCommitmentmaxRetries )的重要性等核心概念,用户可以显着提升交易性能。实施自定义重试机制并利用专用节点或质押连接有助于提高成功率。

此外,认识到网络当前的局限性以及 Solana 基金会为解决这些问题所做的持续努力至关重要,正如即将发布的 v1.18 客户端版本所示。随着网络的发展和扩展,保持信息灵通和适应能力将是与其有效交互的关键。