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

  • Helius
  • 发布于 2023-12-13 19:41
  • 阅读 58

本文详细介绍了Solana区块链中的交易费用机制,特别是优先级费用的概念、实现方法及最佳实践,帮助开发者和用户优化其在Solana上的交易处理。

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。

当用户将签名交易发送到网络时,他们使用像 Helius 这样的 RPC 提供者。Helius 的 RPC 接收交易并检查当前 领导者调度。在 Solana 上,只有特定的验证者负责在特定时间向分类账追加条目。领导者 负责为其当前 时隙 生成区块,并被分配四个连续的时隙。签名交易被发送到当前领导者及接下来的两个领导者。

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

当交易被执行时,通过 Turbine 进行传播,其费用也相应支付。

Solana 上交易费用的工作原理

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

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

Solana 上的交易费用如何运作

Solana 借助基于通货膨胀的协议激励来确保网络在短期内的安全。网络设定了一个全球通货膨胀率,以 奖励验证者。在长期方面,Solana 则依赖于交易费用来维持安全性。每笔交易费用的一定比例(最初设定为 50%)被销毁,其余部分发送给当前的领导者。Solana 销毁费用以增强 SOL 的价值,同时阻止恶意验证者审查交易。

交易费用基于每签名固定设置的基本费用和在交易过程中使用的计算资源(以计算单位(CU)计)来计算。此基本费用范围从 50% 到 1000% 的目标型 lamports 每签名。默认每签名目标 当前设定为 10,000。每交易被分配的最大 CU 预算称为 计算预算。超出此预算将导致运行时中止交易并返回错误。每笔交易的最大预算为 140万 CU,而区块空间限制为 4800万 CU

什么是优先费用?

由于这些限制,计算量重的交易可能会占用区块空间,从而延迟其他交易。Solana 引入了一个 可选的 费用,以允许交易在领导者的队列中对其他交易进行优先排序,称为优先费用。支付此费用实际上会增强你的交易,从而导致更快的执行时间。这对于时间敏感或高价值交易非常有用。交易的费用优先级由其请求的计算单位数量决定。请求的计算单位越多,交易需要支付的费用就越高,以保持其在交易队列中的优先级。对更多计算单位收取更多费用可防止计算量重的交易垃圾邮件。

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

计算预算

computeBudget 指定交易可以消耗的最大计算单位数量、交易可能执行的不同操作所涉及的成本,以及交易必须遵守的操作范围。以下操作会产生计算成本:

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

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

当前计算预算可以在 此处 找到。

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

交易的优先费用是通过设置 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,认识到去中心化应用(dApps)可以设置优先交易费用。但是,他们不鼓励这样做,认为这往往会给最终用户带来不必要的复杂性。相反,他们敦促 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/