Logo
新用户订阅 RPC,首月享 6.5 折优惠查看优惠
OnFinality Learn
网络与协议指南12 分钟阅读

Solana 版本化交易与 getBlock/getTransaction 解析:v0、地址查找表与解码

了解如何解析 getBlock 和 getTransaction 返回的 Solana 版本化交易(v0),包括地址查找表、版本标志以及带有可运行脚本的解码步骤。

TL;DR

深入探讨 Solana 版本化交易(v0)以及如何正确解析 getBlock/getTransaction RPC 响应,涵盖地址查找表、版本标志和可运行的解码脚本。

直接回答:从 getBlock/getTransaction 解析 Solana 版本化交易

当你在 Solana RPC 端点上调用 getBlockgetTransaction 时,收到的交易对象可能是传统交易或版本化交易(v0)。正确解析它们的关键是检查交易消息的第一个字节:如果高位(0x80)被设置,则说明是使用地址查找表(ALT)的 v0 交易。对于 v0 交易,账户列表并非完全内联;部分账户通过查找表引用,因此假设所有账户都是内联的简单解析器会产生错误结果。本文解释了该机制,并提供了一个可复现的脚本来解码这两种类型。

Solana 文档中的 版本化交易地址查找表 是主要的协议来源。Solana RPC getBlockgetTransaction 方法文档也详细描述了响应格式。

版本化交易的工作原理

Solana 交易具有一种线上格式,该格式使用 base58 编码(当使用 JSON-RPC 且 encoding: 'base64' 时使用 base64)。交易消息以包含版本标志的头部开始。传统消息以所需签名的 short-u16 数量开始,然后是只读签名账户的数量等。相比之下,v0 消息设置第一个字节的高位(0x80)以指示它们是版本化的,然后在低位编码版本号(当前为 0)。

主要区别在于 v0 消息可以包含 addressTableLookups 部分。该部分列出一个或多个查找表地址,每个地址带有指向该表的索引列表。实际的账户公钥并不内联存储在消息中;它们通过从区块链获取表账户数据来解析。这允许交易引用超过传统 32 个账户的静态限制,因为表最多可容纳 256 个账户,而每次查找仅为消息增加几个字节。

有关详细说明,请参阅 Solana 文档中的 版本化交易 - v0:地址查找表

解析 getBlock 和 getTransaction 响应

当你使用 getTransactiongetBlock 请求交易时,可以指定编码。默认是 json,如果你使用 getParsedTransactiongetParsedBlock,它会返回解析后的表示。在解析格式中,交易对象包含一个 message,其中包含 accountKeys(一个对象数组,包含 pubkeysigner/writable 标志)、instructionsaddressTableLookups(如果有)。version 字段指示它是 'legacy' 还是 '0'。

如果你请求 encoding: 'jsonParsed',RPC 会返回完全解析的交易,带有可读的指令数据。但是,如果你请求 encoding: 'base58'encoding: 'base64',你会得到原始交易数据块,必须自己解码。如果你想理解线上格式或正在构建底层工具,原始数据块是你需要解析的内容。

Solana RPC 文档中的 getTransactiongetBlock 描述了响应格式。有关社区视角,请参阅 Solana 版本化交易指南

使用可运行脚本逐步解码

下面是一个 Node.js 脚本,它获取最近的交易(你提供签名),检查版本标志,展开地址查找表,并解码指令。它使用 @solana/web3.js 库,该库为你处理底层解析。脚本打印交易的摘要,包括版本、账户密钥和指令详细信息。

要运行它,你需要安装依赖项并提供自己的 RPC URL 和最近的交易签名。该脚本是自包含的,仅使用公共库。

// decode-solana-tx.js
// 用法:node decode-solana-tx.js <RPC_URL> <TX_SIGNATURE>
// 示例:node decode-solana-tx.js https://api.mainnet-beta.solana.com <signature>

const web3 = require('@solana/web3.js');

async function main() {
  const rpcUrl = process.argv[2];
  const signature = process.argv[3];
  if (!rpcUrl || !signature) {
    console.error('请提供 RPC URL 和交易签名。');
    process.exit(1);
  }

  const connection = new web3.Connection(rpcUrl, 'confirmed');
  const tx = await connection.getParsedTransaction(signature, {
    maxSupportedTransactionVersion: 0, // 允许 v0
  });

  if (!tx) {
    console.error('未找到交易。');
    process.exit(1);
  }

  console.log('交易版本:', tx.version);
  console.log('槽位:', tx.slot);
  console.log('区块时间:', tx.blockTime);

  const meta = tx.meta;
  if (meta) {
    console.log('费用:', meta.fee);
    console.log('日志:', meta.logMessages);
  }

  const message = tx.transaction.message;
  console.log('账户密钥:');
  message.accountKeys.forEach((key, i) => {
    console.log(`  [${i}] ${key.pubkey.toString()} (签名者:${key.signer},可写:${key.writable})`);
  });

  if (message.addressTableLookups && message.addressTableLookups.length > 0) {
    console.log('地址查找表:');
    for (const lookup of message.addressTableLookups) {
      console.log(`  表:${lookup.accountKey.toString()}`);
      console.log(`    可写索引:${lookup.writableIndexes.join(', ')}`);
      console.log(`    只读索引:${lookup.readonlyIndexes.join(', ')}`);
    }
  }

  console.log('指令:');
  for (const ix of message.instructions) {
    console.log(`  程序:${ix.programId.toString()}`);
    console.log(`    账户:${ix.accounts.map(a => a.toString()).join(', ')}`);
    console.log(`    数据:${ix.data}`);
  }
}

main().catch(err => {
  console.error(err);
  process.exit(1);
});

预期输出与验证

当你使用有效的 v0 交易签名运行脚本时,你会看到类似以下的输出(实际值会有所不同):

脚本打印交易版本、槽位、费用、账户密钥、地址查找表(如果有)以及解码后的指令。要验证正确性,你可以使用区块浏览器(如 Solscan 或 Solana Explorer)交叉检查账户密钥和指令数据。

如果交易是传统的,version 字段将是 'legacy',并且不会有 addressTableLookups。脚本处理这两种情况。

交易版本:0
槽位:123456789
区块时间:1699999999
费用:5000
日志:[...]
账户密钥:
  [0] 11111111111111111111111111111111 (签名者:true,可写:true)
  [1] ...
地址查找表:
  表:8mU... (签名者:false,可写:false)
    可写索引:1, 2
    只读索引:0
指令:
  程序:11111111111111111111111111111111
    账户:11111111111111111111111111111111, ...
    数据:3Bxs...

常见失败与修复

在解析 Solana 交易时,你可能会遇到几个常见问题。下表将症状映射到原因和修复方法。

  • 交易无法解析:如果你使用的库不支持 v0(例如,旧版本的 @solana/web3.js),你会收到错误。修复:升级到支持 maxSupportedTransactionVersion: 0 的版本。
  • 账户列表看起来错误:如果你将 v0 交易解析为传统交易,账户列表将不完整或不正确,因为它不包含查找表账户。修复:始终检查版本标志并展开地址查找表。
  • ALT 被禁用:某些 RPC 端点或库可能默认禁用地址查找表。修复:在请求中显式设置 maxSupportedTransactionVersion: 0
  • base58 与 base64:如果你请求 encoding: 'base58',你会得到 base58 字符串;如果你请求 encoding: 'base64',你会得到 base64 字符串。确保你的解码器与编码匹配。
  • 缺少查找表数据:要展开地址查找表,你需要获取表的账户数据。如果表已关闭或不可用,解码将失败。修复:确保表仍然存在于链上。

权衡与限制

带有地址查找表的版本化交易提供了显著的好处:它们允许每笔交易有更多账户,减少交易大小,并支持更复杂的指令。然而,它们引入了对查找表存在的依赖,并且如果你手动解码,需要额外的 RPC 调用来获取表数据。

传统格式更简单且自包含,但仅限于 32 个账户。对于大多数现代 Solana 应用程序,v0 是标准,你应该设计工具来处理两者。

使用公共 RPC 端点时,你可能会遇到速率限制或延迟问题。对于生产环境,请考虑使用像 OnFinality 这样的提供商的专用端点。请参阅我们的 RPC 定价API 服务 页面了解选项。

后续步骤与进一步阅读

既然你了解了如何解析版本化交易,你可以应用这些知识来构建更健壮的 Solana 应用程序。有关更多 Solana RPC 主题,请查看我们的指南:查询历史数据WebSocket 订阅超时与重试

如果你正在处理多个 RPC 调用,请参阅我们的 JSON-RPC 批处理最佳实践。有关 Solana RPC 方法的完整列表,请参阅 Solana JSON-RPC 方法(RPC 助手)

探索 OnFinality 学习中心 获取更多教程,别忘了查看我们的 Solana 网络页面 了解端点详细信息。

永远不用担心基础设施

OnFinality 消除了 DevOps 的繁重工作,让您能够更聪明、更快地构建。

开始