优先级费用:理解Solana的交易费用机制

  • Helius
  • 发布于 2023-12-13 22:25
  • 阅读 59

本文介绍了Solana区块链的交易费用机制,特别是优先费用的概念,包括交易的生命周期、费用的计算方法以及如何通过编程实现优先费用。优先费用是一种可选的额外费用,可以加速交易处理,尤其适用于时间敏感或高价值的交易。文章还提供了最佳实践和最新的Helius优先费用API的使用方法。

10分钟阅读

2023年12月11日

这篇文章讲的是什么?

Solana 是 快速 的。然而,即使在可用的最快区块链上,用户仍然希望对重要事务进行优化的交易处理。优先费用是确保用户的交易被放在执行排序队列前面的方式。这些是用户可以选择添加到其交易中的额外费用。

本文简要探讨了在 Solana 上交易处理的细微差别。它涵盖了交易、它们的生命周期以及交易费用是如何运作的。接着,文章探讨了优先费用,如何以编程方式实现它们,以及最佳实践。

交易及其生命周期

交易 用于调用 Solana 程序并进行状态更改。它们是指令的捆绑(即单个程序调用的指令),告诉验证者在什么账户上执行什么操作,以及它们是否具有必要的权限。

Solana 上交易的一般生命周期如下:

  • 用户对他们想要执行的操作有一个明确的目标。例如,Alice想给Bob发送 10 SOL。
  • 用户生成一个他们所需操作的交易。例如,Alice创建了一个包含将 10 SOL 从她的账户转移到Bob账户的指令的交易。Alice还包括了一个最近的区块哈希,并使用她的私钥对交易进行签名。
  • 用户将交易发送到网络。然后,他们会收到交易是否已成功添加的信息。例如,Alice发送了一个具有 确认 承诺状态的交易。当交易被确认时,她收到了交易签名。Alice可以在区块浏览器(如 X-ray)上使用该交易签名,以查看她是否成功地给Bob发送了 10 SOL。她的账户被扣除了 10 SOL,而Bob的账户则增加了 10 SOL。

当用户将签名交易发送到网络时,他们会使用一个 RPC 提供者,例如 Helius。Helius 的 RPC 接收交易并检查当前的 领导者日程安排。在 Solana 上,只有特定的验证者在特定时间负责将条目追加到分类帐中。 领导者 负责为其当前的 生成区块,并被分配四个连续的槽。签名交易被发送到当前的领导者和接下来的两个领导者。

当前的领导者验证签名交易,并在调度交易执行之前执行其他预处理步骤。与以太坊等其他 L1 区块链不同,Solana 没有全局交易队列。大多数验证者使用 Solana Labs 提供的调度器实现。但是,运行 Jito 验证者客户端的验证者使用伪内存池(即 MempoolStream)来对交易进行排序。默认调度器是多线程的,每个线程维护一个等待执行的交易队列。交易通过结合先进先出(FIFO)和优先费用的方式按顺序组合成区块。需要注意的是,这种排序本质上是非确定性的,因为交易被随机分配到执行线程中。

当交易被执行时,它通过 Turbine 传播,并相应地支付费用。

Solana 上的交易费用是如何运作的

交易费用是为处理 Solana 上的交易而支付的小额费用。当前的 领导者 处理通过网络发送的交易以生成分类帐条目。当交易被确认作为状态的一部分时,会支付费用以支持 Solana 的经济设计。交易费用从根本上使 Solana 受益。它们:

  • 为验证者提供补偿
  • 通过引入实时交易成本来减少网络空间
  • 通过协议捕获的最低费用为网络提供长期经济稳定

Solana 上的交易费用如何运作

Solana 在短期内依赖基于通胀的协议奖励来确保网络安全。网络具有预定的全球通胀率以 奖励验证者。在长期内,Solana 依靠交易费用来维持安全性。每笔交易费用的固定部分(最初设置为 50%)会被销毁,其余部分发送给当前的领导者。Solana 销毁费用以巩固 SOL 的价值,同时阻止恶意验证者对交易进行审查。

交易费用是基于每个签名的静态基础费用和在交易过程中使用的计算资源(以计算单位 CU 衡量)进行计算的。该基础费用可以在 每个签名的目标 lamports 的 50% 到 1000% 范围内。每个签名的默认目标 目前设置为 10,000。每笔交易被分配一个最大计算单位预算,称为 Compute Budget。超出此预算将导致运行时停止交易并返回错误。每笔交易的最大预算为 140万 CU,而区块空间限制为 4800万 CU

什么是优先费用?

由于这些限制,高计算计价的交易可能会填满区块空间,从而延迟其他交易。Solana 引入了一种 可选 费用,允许交易在领导者的队列中优先于其他交易,这被称为优先费用。支付此费用有效地提升了你的交易,从而实现更快的执行时间。这对于时间敏感或高价值的交易非常有用。交易的费用优先级由请求的计算单位数量决定。请求的计算单位越多,所需支付的费用就越高,以维持其在交易队列中的优先级。对更多计算单位收取更高费用可以防止高计算要求交易的垃圾传播。

优先费用是交易的计算预算与其计算单位价格(以微 lamports 计)之积:priorityFees = computeBudget * computeUnitPrice

计算预算

computeBudget 指定交易可以消耗的计算单位的最大数量、交易可能执行的不同操作所需的成本以及交易必须遵循的操作限制。以下操作会产生计算费用:

  • 执行 SBF 指令
  • 在程序之间传递数据
  • 调用系统调用(例如,日志记录、创建程序地址、CPI)

对于 跨程序调用(CPI),被调用程序在调用(即父)程序的计算预算内操作。如果被调用程序耗尽所有剩余计算预算或超过设置限制,则会导致整个程序调用链失败。这包括启动该过程的初始交易的执行。

当前的计算预算可以在 这里

如何以编程方式实现优先费用

交易的优先费用通过设置 SetComputeUnitPrice 指令 和可选的 SetComputeUnitLimit 指令 来设置。如果未提供 SetComputeUnitPrice 指令,交易将默认具有最低优先级,因为未提供额外费用。如果未提供 SetComputeUnitLimit 指令,限制会被计算为交易中的指令数量与 默认计算单位限制 的乘积。运行时使用计算单位价格和计算单位限制来计算优先费用,用于优先处理给定的交易。

我们必须将这些指令添加到我们所需的交易中以编程方式添加优先费用。在 Javascript 中,它是以下形式:

代码

import {
  Keypair,
  Connection,
  PublicKey,
  Transaction,
  SystemProgram,
  LAMPORTS_PER_SOL,
  sendAndConfirmTransaction,
  ComputeBudgetProgram,
} from "@solana/web3.js";

async function main() {
  // 初始化 RPC 客户端
  const clusterUrl = "http://127.0.0.1:8899";
  const connection = new Connection(clusterUrl, "confirmed");

  // 初始化新的发送者和接收者密钥对
  const fromKeypair = Keypair.generate();
  const toPubkey = new PublicKey(Keypair.generate().publicKey);

  // 向 from_keypair 空投 SOL
  const airdropAmount = 100 * LAMPORTS_PER_SOL;
  try {
    const signature = await connection.requestAirdrop(
      fromKeypair.publicKey,
      airdropAmount
    );
    console.log("请求空投。签名:", signature);
    await connection.confirmTransaction({
      signature,
      confirmation: "confirmed",
    });
  } catch (e) {
    console.error("请求空投失败:", e);
    return;
  }

  // 检查空投是否成功
  const balance = await connection.getBalance(fromKeypair.publicKey);
  if (balance < airdropAmount) {
    console.error(
      "空投未成功。目前余额不足"
    );
    return;
  }

  // 向 toPubkey 空投 SOL
  const airdropAmountTo = 100 * LAMPORTS_PER_SOL; // 1 SOL 的 lamports
  try {
    const signature = await connection.requestAirdrop(toPubkey, airdropAmount);
    console.log("请求空投。签名:", signature);
    await connection.confirmTransaction({
      signature,
      confirmation: "confirmed",
    });
  } catch (e) {
    console.error("请求空投失败:", e);
    return;
  }

  // 检查空投是否成功
  const balanceTo = await connection.getBalance(toPubkey);
  if (balance < airdropAmount) {
    console.error(
      "空投未成功。目前余额不足"
    );
    return;
  }

  console.log(`账户余额: ${balance / LAMPORTS_PER_SOL} SOL`);

  // 创建优先费用指令
  const computePriceIx = ComputeBudgetProgram.setComputeUnitPrice({
    microLamports: 1,
  });

  const computeLimitIx = ComputeBudgetProgram.setComputeUnitLimit({
    units: 200_000,
  });

  // 创建转账指令
  const transferIx = SystemProgram.transfer({
    fromPubkey: fromKeypair.publicKey,
    toPubkey,
    lamports: 100_000,
  });

  // 创建包含优先费用的交易
  const transaction = new Transaction().add(
    computePriceIx,
    computeLimitIx,
    transferIx
  );

  // 获取最近的区块哈希并签名交易
  transaction.recentBlockhash = (
    await connection.getLatestBlockhash()
  ).blockhash;
  transaction.sign(fromKeypair);

  // 发送交易
  try {
    const txid = await sendAndConfirmTransaction(connection, transaction, [
      fromKeypair,
    ]);
    console.log("交易成功发送,签名:", txid);
  } catch (e) {
    console.error("发送交易失败:", e);
  }
}

main();

在这个代码片段中,我们:

  • 使用 Localhost 设置测试环境
  • 创建两个新的钱包(即 fromKeypairtoPubkey
  • 向两个钱包空投 100 SOL
  • 确保空投成功
  • 创建优先费用指令(即 computePriceIxcomputeLimitIx
  • 创建转账指令,将 100,000 lamports 从 fromKeypair 转移到 toPubkey
  • 创建一个新交易,并将所有指令添加到其中
  • 附加最新的区块哈希到交易并签名
  • 发送并确认交易成功发送

就这样 - 优先费用只是添加到交易中的指令,用于支付更快的执行时间。此代码片段的大部分配置了我们的开发环境和两个用来在它们之间转移 SOL 的钱包。我们感兴趣的是创建添加优先费用的指令并将它们附加到交易中:

代码

// 其他代码

const computePriceIx = ComputeBudgetProgram.setComputeUnitPrice({
    microLamports: 1,
});

const computeLimitIx = ComputeBudgetProgram.setComputeUnitLimit({
    units: 200_000,
});

// 其他代码

const transaction = new Transaction().add(computePriceIx, computeLimitIx, transferIx);

// 剩余代码

最佳实践

这些指令的顺序很重要,因为它们是按顺序执行的。例如,如果你有一个交易,在扩展计算限制 之前 超过了默认的计算限制(即 200k CUs),则交易将失败。假设我们有以下交易:

  • 指令 1 使用了 100k
  • 指令 2 使用了 150k
  • 指令 3 扩展了计算限制

这个交易将失败,除非指令 2 和 3 被交换。因此,建议在向交易中添加其他指令之前先添加计算限制指令。请记住,如果你希望向交易添加优先费用,你不需要使用 SetComputeLimit 指令 - 它是完全可选的。SetComputePrice 指令的位置无关紧要。

建议使用 getRecentPrioritizationFees RPC 方法 来获取最近支付的优先费用列表。这些数据可以用于估算交易的适当优先费用,以确保它们能被集群处理并减少支付的费用。或者,Helius 提供了一种新的 优先费用 API,我们将在以下部分中介绍。

交易还应请求执行所需的最少计算单位,以减少这些费用。请注意,当请求的计算单位数量超过交易使用的总单位时,费用不会调整。

一些钱包提供商,如 Phantom,认识到 dApp 可以设置优先交易费用。但是,他们不鼓励这样做,因为这往往会给最终用户带来不必要的复杂性。相反,他们鼓励 dApp 开发者让 Phantom 代表用户应用优先费用。例如,Solfare 通过自动检测 Solana 是否处于负载状态,并略微提高费用以优先处理你的交易,来解决此问题。

Helius 优先费用 API

使用 getRecentPrioritizationFees RPC 方法计算优先费用在基于槽的基础上是直观的。然而,由于网络条件的不断变化以及 getRecentPrioritizationFees 响应(即返回过去 150 个区块的值列表,仅有助于评估设置费用的最低值),这可能是困难的。

Helius 优先费用 API 引入了一种新方法 getPriorityFeeEstimate,简化了响应为单个值,考虑到全球和地方费用市场。该方法使用一组预定义的百分位数来确定估算。这些百分位数或级别范围从 NONE(第 0 百分位)到 UNSAFE_MAX(第 100 百分位,并标记为不安全,以防止用户意外耗尽其资金)。用户也可以选择接收所有优先级别,并通过 lookbackSlots 调整此计算中使用的范围。因此,与 getRecentPrioritizationFees 类似,用户可以在估算计算中回溯一定数量的槽。

例如,我们可以通过以下脚本计算 Jupiter v6 程序的 所有 优先费用级别:

代码

const url = `https://mainnet.helius-rpc.com/?api-key=`;

const getRecentPrioritizationFees = async () => {
  const response = await fetch(url, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      jsonrpc: "2.0",
      id: 1,
      method: "getPriorityFeeEstimate",
      params: [{
        "accountKeys": ["JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4"],
        "options": {
          "includeAllPriorityFeeLevels": true,
        }
      }]
    }),
  });
  const data = await response.json();
  console.log("费用: ", data);
};

getRecentPrioritizationFees();

该终端点正在积极开发中。有关更多信息,请访问 Helius 文档

结论

在 Solana 交易的世界中航行揭示了一个精密的系统,平衡了网络效率与经济激励。了解交易如何运作,以及它们的费用和优先费用,使开发者和用户能够做出更明智的决策,以优化他们在 Solana 上的互动。以编程方式实现优先费用的能力为高价值和时间敏感的交易开辟了新的途径。这为 Solana 上的操作提供了更大的灵活性和效率。

如果你读到这里,谢谢你,匿名者!确保在下面输入你的电子邮件地址,以便你不会错过有关 Solana 新动态的更新。准备深入了解吗?加入我们的 Discord,开始在最强大的区块链上构建未来。

额外资源/进一步阅读

  • 原文链接: helius.dev/blog/priority...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,在这里修改,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
Helius
Helius
https://www.helius.dev/