BCSkill (Block chain skill )
区块链中文技术社区

只讨论区块链底层技术
遵守一切相关法律政策!

Solana IDL 猜测器 - 从闭源 Solana 程序中恢复指令布局

Solana 生态系统充满活力,但许多程序尚未开源。Syndica在 2024 年 2 月编制的统计数据显示,按计算单元计算,Solana 排名前 100 的程序中,近 50% 发布了其接口定义语言 (IDL)。然而,对于排名前 1000 的程序,这一数字下降到只有 20%。此外,即使已发布的 IDL 也并不总是可靠的。发布的 IDL 过时且与部署的链上程序不匹配的情况并不少见。

作为审计人员和安全研究人员,当我们发现可能导致漏洞的有趣模式时,我们通常会在其他程序中寻找类似的弱点。然而,如果没有源代码或准确的 IDL,这个过程通常仅限于基本的 GitHub 搜索,经常会发现无人维护的项目。

大多数 Solana 程序都是用 Rust 编写的,并编译为 Solana 字节码格式 (sBPF),这是一种基于 eBPF 的格式。对编译后的 Rust 进行逆向工程具有挑战性,而与 sBPF 相关的逆向工程工具链仍在开发中。这种不透明性不仅阻碍了恶意行为者,而且还减缓了白帽黑客和安全研究人员识别和负责任地披露漏洞的工作。

要分析任何闭源 Solana 程序(无论是动态还是静态),基本前提是了解如何与其交互。这意味着了解它的指令、每条指令所需的账户以及这些账户的属性(如签名者或可写状态)。

为了应对这些挑战,我们的安全研究员齐秦领导了这项工作并开发了一个名为IDL Guesser的原型工具。该工具旨在直接从闭源 Solana 程序二进制文件中自动恢复指令定义、所需帐户(包括签名者/可写标志)和参数信息。

该博客概述了 IDL Guesser 背后的方法并讨论了未来改进的潜在领域。

利用锚点模式进行逆向工程

由于 Solana 开发的大部分内容都使用了 Anchor 框架(IDL 的概念也源于此),因此 IDL Guesser 目前专注于基于 Anchor 的程序。Anchor 通过为常见任务和检查提供宏和辅助函数,大大简化了开发。至关重要的是,这会导致编译输出中出现可预测的标准化代码结构,我们可以通过模式匹配将其用于分析。

为了进行调试,Anchor CLI 甚至提供了一个anchor expand\命令,该命令会显示由其宏生成的代码。检查此扩展代码可以深入了解 Anchor 所采用的模式,从而指导我们的逆向工程工作。

入口点和调度逻辑

我们来看看一个典型的 Anchor 程序经过宏扩展后的入口点结构:

#[no_mangle]
pub unsafe extern "C" fn entrypoint(input: *mut u8) -> u64 {
    let (program_id, accounts, instruction_data) = unsafe {
        ::solana_program::entrypoint::deserialize(input)
    };
    match entry(&program_id, &accounts, &instruction_data) {
        Ok(()) => ::solana_program::entrypoint::SUCCESS,
        Err(error) => error.into(),
    }
}

pub fn entry<'info>(
    program_id: &Pubkey,
    accounts: &'info [AccountInfo<'info>],
    data: &[u8],
) -> anchor_lang::solana_program::entrypoint::ProgramResult {
    try_entry(program_id, accounts, data)
        .map_err(|e| {
            e.log();
            e.into()
        })
}

fn try_entry<'info>(
    program_id: &Pubkey,
    accounts: &'info [AccountInfo<'info>],
    data: &[u8],
) -> anchor_lang::Result<()> {
    if *program_id != ID {
        return Err(anchor_lang::error::ErrorCode::DeclaredProgramIdMismatch.into());
    }
    if data.len() < 8 {
        return Err(anchor_lang::error::ErrorCode::InstructionMissing.into());
    }
    dispatch(program_id, accounts, data)
}

fn dispatch<'info>(
    program_id: &Pubkey,
    accounts: &'info [AccountInfo<'info>],
    data: &[u8],
) -> anchor_lang::Result<()> {
    let mut ix_data: &[u8] = data;
    let sighash: [u8; 8] = {
        let mut sighash: [u8; 8] = [0; 8];
        sighash.copy_from_slice(&ix_data[..8]);
        ix_data = &ix_data[8..];
        sighash
    };
    use anchor_lang::Discriminator;
    match sighash {
        instruction::InitializeConfig::DISCRIMINATOR => {
            __private::__global::initialize_config(program_id, accounts, ix_data)
        }
        instruction::InitializePool::DISCRIMINATOR => {
            __private::__global::initialize_pool(program_id, accounts, ix_data)
        }
        // ... other instructions
    }
}

程序首先对原始输入进行反序列化。它在进入函数之前执行基本检查(例如验证program_id\并确保instruction_data\至少有 8 个字节长)dispatch\。在 中dispatch\, 的前 8 个字节instruction_data\被解释为指令鉴别符。该鉴别符确定应执行哪个特定的指令处理程序函数。

根据 Anchor 的文档,这个 8 字节鉴别器是从指令的命名空间和名称(例如global:initialize_config\)中派生出来的,方法是取其 SHA-256 哈希的前 8 个字节。IDL Guesser 不会尝试从编译后的代码中提取这些原始鉴别器字节(这可能很复杂),而是采用一种更简单的方法:它首先专注于提取指令名称,然后使用 Anchor 的标准哈希方法计算相应的鉴别器。

识别指令处理程序

我们如何找到指令名称及其对应的处理程序函数?Anchor 提供了另一种有用的模式。考虑一个典型指令处理程序的开头:

pub fn initialize_config<'info>( /* ... */ ) -> anchor_lang::Result<()> {
    // Log the instruction name - a key pattern for us!
    ::solana_program::log::sol_log("Instruction: InitializeConfig");

    // Deserialize instruction-specific parameters
    let ix = instruction::InitializeConfig::deserialize(&mut &__ix_data[..])
        .map_err(|_| /* ... */ )?;
    let instruction::InitializeConfig { /* ... parameters ... */ } = ix;

    // Process accounts via try_accounts
    let mut __bumps = /* ... */;
    let mut __reallocs = /* ... */;
    let mut __remaining_accounts: &[AccountInfo] = __accounts;
    let mut __accounts = InitializeConfig::try_accounts(
        __program_id,
        &mut __remaining_accounts,
        __ix_data,
        &mut __bumps,
        &mut __reallocs,
    )?;
    // ... 
}

Anchorsol_log\在每个处理程序的开头插入一个调用,记录指令的名称(例如“Instruction: InitializeConfig”\)以供日志解析。此日志记录提供了我们可以在编译后的二进制文件中搜索的独特签名。

img

用于记录指令名称*InitializeConfig 的*****sol_log\系统调用序列。

在汇编级别(如上所示),此日志调用通常会转换为特定的lddw\mov64\call\指令设置,sol_log\以使用指令名称字符串调用系统调用。

通过识别这些模式,IDL Guesser 可以可靠地定位指令处理程序的入口点并提取其名称。

提取账户信息

在初始日志记录和参数反序列化(通常由编译器内联)之后,处理程序通常会调用相应的try_accounts\函数。此函数负责解析和验证指令所需的帐户。

img

参数反序列化之后调用try_accounts\函数(此处为sub_662B0\)。

让我们检查一下Accounts\结构和生成的try_accounts\函数initialize_config\

// Account definition
#[derive(Accounts)]
pub struct InitializeConfig<'info> {
    #[account(init, payer = funder, space = WhirlpoolsConfig::LEN)]
    pub config: Account<'info, WhirlpoolsConfig>, // To be initialized
    #[account(mut)]
    pub funder: Signer<'info>,                   // Must be mutable and a signer
    pub system_program: Program<'info, System>,  // System program
}

// Generated try_accounts function (simplified)
fn try_accounts( /* ... */ ) -> anchor_lang::Result<Self> {
    // Check if enough accounts provided
    if __accounts.is_empty() {
        // Error 3005
        return Err(anchor_lang::error::ErrorCode::AccountNotEnoughKeys.into()); 
    }
    // Process 'config' account (index 0) - checks applied later
    let config = &__accounts[0];
    *__accounts = &__accounts[1..];

    // Process 'funder' account using Signer's try_accounts
    let funder: Signer = anchor_lang::Accounts::try_accounts(/* ... */)
        .map_err(|e| e.with_account_name("funder"))?; // Adds "funder" to error message

    // Process 'system_program' account
    let system_program: Program<System> = anchor_lang::Accounts::try_accounts(/* ... */)
        .map_err(|e| e.with_account_name("system_program"))?; // Adds "system_program"

    // Apply constraints checks
    if !config.is_writable { // Check defined indirectly via init
            // Error 2000
        return Err(anchor_lang::error::ErrorCode::ConstraintMut.into().with_account_name("config")); 
    }
    if !config.is_signer { // Check defined indirectly via init
       // Error 2002
       return Err(anchor_lang::error::ErrorCode::ConstraintSigner.into().with_account_name("config")); 
    }
    // ... other checks like rent exemption (Error 2005), owner, etc.

    if !funder.is_writable { // Check defined by #[account(mut)]
        // Error 2000
        return Err(anchor_lang::error::ErrorCode::ConstraintMut.into().with_account_name("funder")); 
    }
    // ... checks for funder.is_signer happen inside its own try_accounts

    Ok(Self { config, funder, system_program })
}

try_accounts\函数执行几个关键操作:

  1. 它会遍历预期的帐户,并尝试根据其类型(Account\Signer\、等)对其进行解析。如果在嵌套调用(如 for )Program\中解析失败,Anchor 会方便地将帐户名称(例如“funder”)附加到错误消息中。这使我们能够通过查找这些特定的错误处理模式来提取帐户名称。*try_accounts*****funder\
  2. 它根据结构中定义的属性应用约束检查(mut\signer\has_one\seeds\、 、租金豁免等) 。重要的是,每个约束违规通常映射到一个唯一的Anchor (例如,is 2000\、is 2002\)。*owner**Accounts**ErrorCode**ConstraintMut**ConstraintSigner*

img

检查需要初始化的帐户是否有足够的帐户密钥,如果失败则分支到错误3005\

img

约束检查条件跳转,导致特定的错误代码如2000\ConstraintMut\)或2002\ConstraintSigner\)。

通过分析函数的控制流图(CFG)try_accounts\,具体来说是遵循“快乐路径”(成功执行),IDL Guesser 可以拼凑出所需的账户:

  • 账户处理的顺序揭示了账户的预期顺序。
  • 与错误消息相关的字符串文字(例如“funder”)显示帐户名称。
  • 失败路径上出现的特定错误代码(如 2000、2002、2005)表示对每个帐户应用的约束(可变、签名者、可变等)。

提取参数

虽然提取指令名称和账户详细信息依赖于相对不同的模式(日志字符串、错误代码、特定函数调用),但恢复有关指令参数的信息更加困难。

Anchor 通常不会生成针对单个参数反序列化失败的详细错误消息。这意味着 Rust 源代码中定义的原始参数名称通常会在编译期间丢失。此外,负责从切片中顺序反序列化参数的代码ix_data\通常会由编译器优化和内联,这使得可靠的汇编级模式匹配非常困难。

ix_data\更有希望的未来方向可能涉及符号执行,通过分析如何使用来确定预期的字节长度以及可能确定每个参数的类型。

然而,在 IDL Guesser 原型中,采用了一种利用动态分析的替代且更简单的方法:由于交易大小限制,Solana 指令数据通常很短,因此我们可以迭代探测处理程序函数。通过稍微增加模拟输入数据的长度并观察执行跟踪中的变化(例如,通过之前失败的检查),我们可能会在新的反序列化步骤成功时推断出边界,并可能推断出参数的类型。

这种迭代反馈循环技术也用于重建账户的内部布局。具体细节这里就不展开了,感兴趣的读者可以去源代码中探索相关的实现。此外,它还可能用于验证或完善恢复的指令和账户信息。

实现细节

识别出这些模式后,实施过程涉及反汇编 sBPF 字节码并对指令序列和 CFG 执行模式匹配。我们以现有solana-sbpf\项目(参见static_analysis.rs)为基础,为 Solana 程序分析奠定了基础。

我们对基本静态分析实现进行了一些修改。原始版本倾向于在每次函数调用后过度拆分基本块。我们对此进行了调整,以创建更大、更易于管理的块。此外,我们还为系统调用(如abort\panic\)添加了特殊处理。这些更改使 CFG 更加精确,从而简化了模式匹配过程。

完整的实现是开源的,可以在IDLGuesser 存储库中获取。

当前代码处理许多常见场景,但也包括一些特殊情况的逻辑,例如UncheckedAccount\Sysvar\帐户。

这些逻辑try_accounts\通常由编译器内联,从而创建类似于init帐户的模式。然而,挑战仍然存在,特别是对于多个连续UncheckedAccount实例或更复杂的结构(如可选帐户和嵌套帐户上下文),此原型尚未完全处理。

结论

IDL Guesser 展示了一种可行的方法,利用框架的代码生成模式和简单的动态分析,从基于 Anchor 的闭源 Solana 程序中恢复基本结构信息(具有相应帐户和参数信息的指令)。虽然原型有局限性,在复杂情况下可能需要手动进行逆向工程并与链上数据进行交叉引用,但它成功地为大量程序恢复了类似 IDL 的信息。

我们发现此功能很有用,可以更广泛地分析交易数据,并有助于自动扫描与帐户限制相关的基本漏洞(例如缺少签名者检查)。通过揭示闭源程序的内部工作原理,我们希望 IDL Guesser 等工具能够为保护 Solana 生态系统做出贡献。

原文:https://www.sec3.dev/blog/idl-guesser-recovering-instruction-layouts-from-closed-source-solana-programs

Solana基础 - Solana RPC HTTP 方法

getAccountInfo

返回与提供的公钥的账户相关的所有信息,和以太坊 eth_getAccount类似

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getAccountInfo",
    "params": [
      "8TKG1ez28ZYzNgGTein6Pc97yvxWwnHecVL9ZhdZTxd5",
      {
        "encoding": "base58"
      }
    ]
  }
'

返回

{
  "jsonrpc": "2.0",
  "result": {
    "context": {
      "apiVersion": "2.2.3",
      "slot": 370321974
    },
    "value": {
      "data": [
        "",
        "base58"
      ],
      "executable": false,
      "lamports": 4994414840,
      "owner": "11111111111111111111111111111111",
      "rentEpoch": 18446744073709551615,
      "space": 0
    }
  },
  "id": 1
}

getBalance

返回所提供公钥的账户的 Lamport 余额, 和以太坊eth_getBalance类似

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getBalance",
    "params": [
      "8TKG1ez28ZYzNgGTein6Pc97yvxWwnHecVL9ZhdZTxd5"
    ]
  }
'

返回

{
  "jsonrpc": "2.0",
  "result": {
    "context": {
      "apiVersion": "2.2.3",
      "slot": 370322177
    },
    "value": 4994414840
  },
  "id": 1
}

getBlock

返回账本中已确认区块的身份和交易信息,和以太坊 eth_getBlockByNumber类似

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getBlock",
    "params": [
      358300848,
      {
        "encoding": "json",
        "maxSupportedTransactionVersion": 0,
        "transactionDetails": "full",
        "rewards": false
      }
    ]
  }
'

如果类似如下提示,这通常意味着你尝试访问的特定槽(Slot)存在问题,具体可能是该槽被跳过或者在长期存储中缺失

{
  "jsonrpc": "2.0",
  "error": {
    "code": -32009,
    "message": "Slot 100000430 was skipped, or missing in long-term storage"
  },
  "id": 1
}
{
  "jsonrpc": "2.0",
  "result": {
    "blockHeight": 346328196,
    "blockTime": 1738488135,
    "blockhash": "HGrdL4Z5vEvQVWchG2pnJEQ5dMxQhqiXncpL6GJvZimx",
    "parentSlot": 358300847,
    "previousBlockhash": "3QuyTMBRAK5PhLXehDCTMH4zgnfn7xgZnHG3fBnZFsiQ",
    "transactions": [
      ...
          {
            "meta": {
                "computeUnitsConsumed": 2100,
                "err": null,
                "fee": 5000,
                "innerInstructions": [],
                "loadedAddresses": {
                    "readonly": [],
                    "writable": []
                },
                "logMessages": [
                    "Program Vote111111111111111111111111111111111111111 invoke [1]",
                    "Program Vote111111111111111111111111111111111111111 success"
                ],
                "postBalances": [
                    912514572484583,
                    8402660357128602,
                    1
                ],
                "postTokenBalances": [],
                "preBalances": [
                    912514572489583,
                    8402660357128602,
                    1
                ],
                "preTokenBalances": [],
                "rewards": null,
                "status": {
                    "Ok": null
                }
            },
            "transaction": {
                "message": {
                    "accountKeys": [
                        "dv4ACNkpYPcE3aKmYDqZm9G5EB3J4MRoeE7WNDRBVJB",
                        "23AoPQc3EPkfLWb14cKiWNahh1H9rtb3UBk8gWseohjF",
                        "Vote111111111111111111111111111111111111111"
                    ],
                    "header": {
                        "numReadonlySignedAccounts": 0,
                        "numReadonlyUnsignedAccounts": 1,
                        "numRequiredSignatures": 1
                    },
                    "instructions": [
                        {
                            "accounts": [
                                1,
                                0
                            ],
                            "data": "67MGn8CZqesiDCbWWYBQre79LVuia8eBTRNppRhYeXWhLFMWQsHQrrLxehkcbeCZ9ncYWSaBVdzbL6iUumunuVXWuVgnYAZ6oLZVMsEUZohcJMkAa7sAhbmGAT4ZUjfcZd7LH99cRPUwUZu7HUaG5Q8Y7TM98PR6bJJoySwh8QpuxtWzBqpLAH4nv2cDY946EQPmXS5zvB",
                            "programIdIndex": 2,
                            "stackHeight": null
                        }
                    ],
                    "recentBlockhash": "3QuyTMBRAK5PhLXehDCTMH4zgnfn7xgZnHG3fBnZFsiQ"
                },
                "signatures": [
                    "56uXBR4rgLNbDCtCr8gyTktGYCBBsKdJ4mUEGS9rEkcS9tG9ZghksbZCUdjXdoLJqnScG2e4zQCxvUKE6ctwyjpw"
                ]
            },
            "version": "legacy"
        }
    ...
    ]
  },
  "id": 1
}

getBlockCommitment

返回特定区块的承诺

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getBlockCommitment",
    "params": [
      346328196
    ]
  }
'

返回

{
  "jsonrpc": "2.0",
  "result": {
    "commitment": null,
    "totalStake": 157541826450322300
  },
  "id": 1
}

getBlockHeight

返回节点当前的区块高度,和以太坊eth_blockNumber 类似

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getBlockHeight",
    "params": []
  }
'

返回

{
  "jsonrpc": "2.0",
  "result": 358302920,
  "id": 1
}

getBlockProduction

获取当前epoch期间内,所有产生区块的地址和相应的产块结果[应产出:实际产出]
range是当前周期的起始和当前slot区间

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getBlockProduction"
  }
'

注:对于产块结果,可以作为对应验证节点的稳定性判定

getBlocks

返回两个 slot 之间已确认的区块列表

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getBlocks",
    "params": [
      5, // 起始Slot
      10 // 结束Slot (间距<= 500000)
    ]
  }
'

getBlocksWithLimit

返回从给定槽开始的已确认区块列表
和getBlocks类似,区别就是由结束Slot改为了Limit

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getBlocksWithLimit",
    "params": [
      5, // 起始Slot
      3 // Limit (间距<= 500000)
    ]
  }
'

getBlockTime

返回某个区块的预计生产时间。
每个验证者都会定期向账本报告其 UTC 时间,方法是间歇性地为特定区块的投票添加时间戳。请求的区块时间是根据账本上记录的一组最近区块中投票时间戳的权益加权平均值计算得出的。

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getBlockTime",
    "params": [
      358319114
    ]
  }
'

getClusterNodes

返回有关参与集群的所有节点的信息

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getClusterNodes"
  }
'

getEpochInfo

返回有关当前纪元的信息

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getEpochInfo"
  }
'

返回

{
  "jsonrpc": "2.0",
  "result": {
    "absoluteSlot": 370353885, // 当前槽位
    "blockHeight": 358332171, // 当前区块高度
    "epoch": 857, // 当前epoch
    "slotIndex": 129885, // 当前 slot 相对于当前 epoch 的开始
    "slotsInEpoch": 432000, // 此纪元中的时隙数
    "transactionCount": 15406146576 // 自创世以来无错误处理的交易总数
  },
  "id": 1
}

getEpochSchedule

从此集群的创世配置中返回纪元调度信息

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getEpochSchedule"
  }
'

返回

{
  "jsonrpc": "2.0",
  "result": {
    "firstNormalEpoch": 0,
    "firstNormalSlot": 0,
    "leaderScheduleSlotOffset": 432000,
    "slotsPerEpoch": 432000,
    "warmup": false // 是否周期一开始很短,然后逐渐增长
  },
  "id": 1
}

getFeeForMessage

获取网络针对特定消息收取的费用,和以太坊eth_estimateGas类似

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getFeeForMessage",
    "params": [
      "AQABAgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQAA",
      {
        "commitment": "processed"
      }
    ]
  }
'

返回

{
  "jsonrpc": "2.0",
  "result": {
    "context": { "slot": 5068 },
    "value": 5000
  },
  "id": 1
}

getFirstAvailableBlock

返回当前节点上可访问的最早的区块编号(block number)

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getFirstAvailableBlock"
  }
'

注:简单说就是查询当前RPC节点存储的历史区块最早的高度,由于存储成本问题,对于官方devnet testnet都只保留近期一部分数据,对于主网保留所有数据,对于历史数据保存需要使用bigtable外部存储,对于设置启动参数--limit-ledger-size,大概保存近期 600w左右区块数据

getGenesisHash

查询genesis hash

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getGenesisHash"
  }
'

返回

{
  "jsonrpc": "2.0",
  "result": "EtWTRABZaYq6iMfeYKouRu166VU2xqa1wcaWoxPkrZBG",
  "id": 1
}

getHealth

返回节点的当前健康状况。健康节点是指位于 HEALTH_CHECK_SLOT_DISTANCE最新集群确认槽内的节点。

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getHealth"
  }
'

注:如果节点健康将返回ok,可用于节点的监控指标

getHighestSnapshotSlot

返回节点具有快照的最高插槽信息。
这将找到最高的完整快照槽,以及基于完整快照槽的最高增量快照槽(如果有)

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getHighestSnapshotSlot"
  }
'

getIdentity

返回当前节点的身份公钥

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getIdentity"
  }
'

注:也就是返回对应RPC 配置的--identity的pubkey

getInflationGovernor

返回当前通货膨胀调控器

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getInflationGovernor"
  }
'

getInflationRate

返回当前时期的具体通胀值

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getInflationRate"
  }
'

getInflationReward

返回某个时期地址列表的通胀/权益奖励

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getInflationReward",
    "params": [
      [
        "6dmNQ5jwLeLk5REvio1JcMshcbvkYMwy26sJ8pbkvStu",
        "BGsqMegLpV6n6Ve146sSX2dTjUMj3M92HnU8BbNRMhF2"
      ],
      {
        "epoch": 2
      }
    ]
  }
'

getLargestAccounts

按 Lamport 余额返回前 20 个最大的账户(结果可能缓存长达两小时)

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getLargestAccounts",
    "params": []
  }
'

getLatestBlockhash

返回最新的区块哈希

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getLatestBlockhash",
    "params": [
      {
        "commitment": "processed"
      }
    ]
  }
'

注:当前值用于常规交易发送时的recentBlockhash

getLeaderSchedule

返回一个时期的领导者时间表

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getLeaderSchedule",
    "params": [
      null,
      {
        "identity": "4Qkev8aNZcqFNSRhQzwyLMFSsi94jHqE8WNVTJzTP99F"
      }
    ]
  }
'

getMaxRetransmitSlot

获取重传阶段看到的最大时隙。

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getMaxRetransmitSlot"
  }
'

getMaxShredInsertSlot

获取撕碎插入后看到的最大插槽。

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getMaxShredInsertSlot"
  }
'

getMinimumBalanceForRentExemption

返回免除账户租金所需的最低余额。

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getMinimumBalanceForRentExemption",
    "params": [
      50
    ]
  }
'

getMultipleAccounts

返回公钥列表的帐户信息。

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getMultipleAccounts",
    "params": [
      [
        "vines1vzrYbzLMRdu58ou5XTby4qAqVRLmqo36NKPTg",
        "4fYNw3dojWmQ4dXtSGE9epjRGy9pFSx62YypT7avPYvA"
      ],
      {
        "encoding": "base58"
      }
    ]
  }
'

getProgramAccounts

返回所提供程序 Pubkey 拥有的所有账户

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getProgramAccounts",
    "params": [
      "4Nd1mBQtrMJVYVfKf2PJy9NZUZdTAsp7D4xWLs4gDB4T",
      {
        "filters": [
          { "dataSize": 17 },
          {
            "memcmp": {
              "offset": 4,
              "bytes": "3Mc6vR"
            }
          }
        ]
      }
    ]
  }
'

getRecentPerformanceSamples

返回最近性能样本的列表(按槽位倒序排列)。性能样本每 60 秒采集一次,包括给定时间窗口内发生的事务和槽位数量。

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getRecentPerformanceSamples",
    "params": [
      2
    ]
  }
'

getRecentPrioritizationFees

返回最近区块的优先费用列表。

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getRecentPrioritizationFees",
    "params": [
      ["CxELquR1gPP8wHe33gZ4QxqGB3sZ9RSwsJ2KshVewkFY"]
    ]
  }
'

getSignaturesForAddress

返回已确认交易的签名,这些交易的列表中包含指定地址accountKeys。按时间倒序返回提供的签名或最近确认的区块的签名

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getSignaturesForAddress",
    "params": [
      "Vote111111111111111111111111111111111111111",
      {
        "limit": 1 // 1-1000, 默认1000
      }
    ]
  }
'

getSignatureStatuses

返回签名列表的状态。每个签名必须是 txid,即交易的第一个签名。
除非searchTransactionHistory包含配置参数,否则此方法仅搜索签名的近期状态缓存,其中保留所有活动插槽和MAX_RECENT_BLOCKHASHES根插槽的状态。

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getSignatureStatuses",
    "params": [
      [
        "5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW"
      ],
      {
        "searchTransactionHistory": true
      }
    ]
  }
'

返回

{
  "jsonrpc": "2.0",
  "result": {
    "context": {
      "apiVersion": "2.0.18",
      "slot": 24219064
    },
    "value": [
      {
        "confirmationStatus": "finalized", // processed->confirmed->finalized
        "confirmations": null,
        "err": null,
        "slot": 24218468,
        "status": {
          "Ok": null
        }
      }
    ]
  },
  "id": 1
}

getSlot

返回已达到给定或默认承诺级别的槽

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getSlot"
  }
'

注:默认是返回最近finalized的Slot

getSlotLeader

返回当前 slot 领导者

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getSlotLeader"
  }
'

注:如果定制链做Rollup,那返回的为sequencer的账户地址

getSlotLeaders

返回给定槽范围的槽领导者

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getSlotLeaders",
    "params": [
      100, // 起始Slot
      10 //Limit (1-5000)
    ]
  }
'

getStakeMinimumDelegation

以 lampors 为单位,返回股权最低授权。

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getStakeMinimumDelegation"
  }
'

注:如果限制外部节点的加入,可以修改链代码,将最小委托量修改为10000000000000000000

getSupply

返回有关当前供应的信息。

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getSupply"
  }
'

getTokenAccountBalance

返回 SPL 代币账户的代币余额

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getTokenAccountBalance",
    "params": [
      "3tykH6gHjjyqYHBigukDXZaj3K6XupjKxYLMA2Srfbc2" // 对应账户地址->ATA账户
    ]
  }
'

返回

{
  "jsonrpc": "2.0",
  "result": {
    "context": {
      "apiVersion": "2.1.15",
      "slot": 370370345
    },
    "value": {
      "amount": "100000000000",
      "decimals": 9,
      "uiAmount": 100.0, // amount /(10的decimals次方)
      "uiAmountString": "100" // uiAmount 转字符串
    }
  },
  "id": 1
}

注:查询地址为对应账户->对应Token(mint)->ATA账户地址

getTokenAccountsByDelegate

返回已获批准的代表的所有 SPL 代币账户

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getTokenAccountsByDelegate",
    "params": [
      "4Nd1mBQtrMJVYVfKf2PJy9NZUZdTAsp7D4xWLs4gDB4T",
      {
        "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
      },
      {
        "encoding": "jsonParsed"
      }
    ]
  }
'

getTokenAccountsByOwner

按代币所有者返回所有 SPL 代币账户

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getTokenAccountsByOwner",
    "params": [
      "8TKG1ez28ZYzNgGTein6Pc97yvxWwnHecVL9ZhdZTxd5", // 这里是Keypair原地址
      {
        "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
      },
      {
        "encoding": "jsonParsed"
      }
    ]
  }
'

返回

{
  "jsonrpc": "2.0",
  "result": {
    "context": {
      "apiVersion": "2.2.3",
      "slot": 370371798
    },
    "value": [
      {
        "account": {
          "data": {
            "parsed": {
              "info": {
                "isNative": false,
                "mint": "34GbKbRzLMfvpHEFywhe2KyheKMMxab8tQAFj5t2rBYT", // 代币标志位
                "owner": "8TKG1ez28ZYzNgGTein6Pc97yvxWwnHecVL9ZhdZTxd5",
                "state": "initialized",
                "tokenAmount": {
                  "amount": "100000000000",
                  "decimals": 9,
                  "uiAmount": 100.0,
                  "uiAmountString": "100"
                }
              },
              "type": "account"
            },
            "program": "spl-token",
            "space": 165
          },
          "executable": false,
          "lamports": 2039280,
          "owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
          "rentEpoch": 18446744073709551615,
          "space": 165
        },
        "pubkey": "3tykH6gHjjyqYHBigukDXZaj3K6XupjKxYLMA2Srfbc2" // ATA账户地址
      }
    ]
  },
  "id": 1
}

getTokenLargestAccounts

返回特定 SPL 代币类型的 20 个最大余额账户。

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getTokenLargestAccounts",
    "params": [
      "34GbKbRzLMfvpHEFywhe2KyheKMMxab8tQAFj5t2rBYT" // 代币mint地址
    ]
  }
'

返回

{
  "jsonrpc": "2.0",
  "result": {
    "context": {
      "apiVersion": "2.1.15",
      "slot": 370372669
    },
    "value": [
      {
        "address": "ChpLTT95FAZNVSeM79C7iun141Lp54dxjoURegsQAtZN",
        "amount": "100000000000",
        "decimals": 9,
        "uiAmount": 100.0,
        "uiAmountString": "100"
      },
      {
        "address": "3tykH6gHjjyqYHBigukDXZaj3K6XupjKxYLMA2Srfbc2",
        "amount": "100000000000",
        "decimals": 9,
        "uiAmount": 100.0,
        "uiAmountString": "100"
      }
    ]
  },
  "id": 1
}

getTokenSupply

返回 SPL 代币类型的总供应量

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getTokenSupply",
    "params": [
      "34GbKbRzLMfvpHEFywhe2KyheKMMxab8tQAFj5t2rBYT"
    ]
  }
'

返回

{
  "jsonrpc": "2.0",
  "result": {
    "context": {
      "apiVersion": "2.1.15",
      "slot": 370372872
    },
    "value": {
      "amount": "200000000000",
      "decimals": 9,
      "uiAmount": 200.0,
      "uiAmountString": "200"
    }
  },
  "id": 1
}

getTransaction

返回已确认交易的交易详情

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getTransaction",
    "params": [
      "5cvy15goDmKA81kaMKk8nuuWuSN3AgCssjTK4qTvm49FyeZPoHXp5DjatqvSoUTDYArjio5PHE2LCuzBss9P7BDK",
      "json"
    ]
  }
'

返回

{
  "jsonrpc": "2.0",
  "result": {
    "blockTime": 1743069997,
    "meta": {
      "computeUnitsConsumed": 4648,
      "err": null,
      "fee": 5000,
      "innerInstructions": [],
      "loadedAddresses": {
        "readonly": [],
        "writable": []
      },
      "logMessages": [
        "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [1]",
        "Program log: Instruction: MintToChecked",
        "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 4498 of 4648 compute units",
        "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success",
        "Program ComputeBudget111111111111111111111111111111 invoke [1]",
        "Program ComputeBudget111111111111111111111111111111 success"
      ],
      "postBalances": [
        4994414840,
        1461600,
        2039280,
        1,
        934087680
      ],
      "postTokenBalances": [
        {
          "accountIndex": 2,
          "mint": "34GbKbRzLMfvpHEFywhe2KyheKMMxab8tQAFj5t2rBYT",
          "owner": "8TKG1ez28ZYzNgGTein6Pc97yvxWwnHecVL9ZhdZTxd5",
          "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
          "uiTokenAmount": {
            "amount": "100000000000",
            "decimals": 9,
            "uiAmount": 100.0,
            "uiAmountString": "100"
          }
        }
      ],
      "preBalances": [
        4994419840,
        1461600,
        2039280,
        1,
        934087680
      ],
      "preTokenBalances": [
        {
          "accountIndex": 2,
          "mint": "34GbKbRzLMfvpHEFywhe2KyheKMMxab8tQAFj5t2rBYT",
          "owner": "8TKG1ez28ZYzNgGTein6Pc97yvxWwnHecVL9ZhdZTxd5",
          "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
          "uiTokenAmount": {
            "amount": "0",
            "decimals": 9,
            "uiAmount": null,
            "uiAmountString": "0"
          }
        }
      ],
      "rewards": [],
      "status": {
        "Ok": null
      }
    },
    "slot": 370161711,
    "transaction": {
      "message": {
        "accountKeys": [
          "8TKG1ez28ZYzNgGTein6Pc97yvxWwnHecVL9ZhdZTxd5",
          "34GbKbRzLMfvpHEFywhe2KyheKMMxab8tQAFj5t2rBYT",
          "3tykH6gHjjyqYHBigukDXZaj3K6XupjKxYLMA2Srfbc2",
          "ComputeBudget111111111111111111111111111111",
          "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
        ],
        "header": {
          "numReadonlySignedAccounts": 0,
          "numReadonlyUnsignedAccounts": 2,
          "numRequiredSignatures": 1
        },
        "instructions": [
          {
            "accounts": [
              1,
              2,
              0
            ],
            "data": "ndcLHYJ5qd8F2",
            "programIdIndex": 4,
            "stackHeight": null
          },
          {
            "accounts": [],
            "data": "F7UDE7",
            "programIdIndex": 3,
            "stackHeight": null
          }
        ],
        "recentBlockhash": "BUuLwgC1zPLC1VcQ5xRrKSnDz2vheZaxW82GUJX59CLM"
      },
      "signatures": [
        "5cvy15goDmKA81kaMKk8nuuWuSN3AgCssjTK4qTvm49FyeZPoHXp5DjatqvSoUTDYArjio5PHE2LCuzBss9P7BDK"
      ]
    }
  },
  "id": 1
}

getTransactionCount

返回当前网络从创世以来所有的交易总数

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getTransactionCount"
  }
'

getVersion

返回当前查询RPC节点所运行的Solana 版本

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getVersion"
  }
'

getVoteAccounts

返回当前银行所有投票账户的账户信息和相关股份。

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getVoteAccounts",
    "params": [
      {
        "votePubkey": "3ZT31jkAGhUaw8jsy4bTknwBMP8i4Eueh52By4zXcsVw"
      }
    ]
  }
'

isBlockhashValid

查询指定块hash是否仍然有效

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 45,
    "method": "isBlockhashValid",
    "params": [
      "J7rBdM6AecPDEZp8aPq5iPSNKVkU5Q76F3oAV4eW5wsW",
      {
        "commitment": "processed"
      }
    ]
  }
'

参考:https://solana.com/zh/docs/rpc/http

solana 交易 confirmationStatus 有几种状态

在 Solana 中,交易的 confirmationStatus 用于表示交易的确认状态,主要有以下几种状态:

1. finalized

  • 含义 :这是最高级别的确认状态。当交易达到 finalized 状态时,意味着该交易已经被集群中的大多数验证节点确认,并且不会被回滚。在区块链的共识机制下,这是一个非常安全的状态,表明交易已经被永久记录在区块链上。
  • 应用场景 :在需要确保交易不可逆转的场景中,如涉及资金转移、重要数据记录等,通常会等待交易达到 finalized 状态。

2. confirmed

  • 含义 :表示交易已经被集群中的一个验证节点确认。虽然交易处于 confirmed 状态,但仍然存在一定的风险,因为在某些情况下,该交易可能会被回滚。不过,这种情况相对较少发生。
  • 应用场景 :在一些对交易确认速度要求较高,而对交易最终确定性要求不是特别严格的场景中,可以使用 confirmed 状态。例如,在一些实时性要求较高的游戏或交互场景中,当用户完成操作后,可以快速告知用户交易已 confirmed ,让用户继续后续操作。

3. processed

  • 含义 :表示交易已经被集群接收并处理,但尚未被任何验证节点确认。这是交易确认过程中的初始状态,意味着交易已经进入了集群的处理流程,但还没有得到足够的确认。
  • 应用场景 :在开发过程中,当需要快速反馈交易是否已经被集群接收时,可以使用 processed 状态。例如,在用户提交交易后,立即告知用户交易已被接收并正在处理中。

代码示例

在使用 @solana/web3.js 进行交易时,可以指定 confirmationStatus 参数。以下是一个示例代码:

const { Connection, Keypair, LAMPORTS_PER_SOL, PublicKey, SystemProgram, Transaction, sendAndConfirmTransaction } = require('@solana/web3.js');

// 连接到 Solana 网络
const connection = new Connection('https://api-devnet.solana.com', 'confirmed');

// 生成发送方和接收方的密钥对
const fromKeypair = Keypair.generate();
const toPublicKey = new PublicKey('...'); // 替换为实际的接收方公钥

// 创建交易
const transaction = new Transaction().add(
    SystemProgram.transfer({
        fromPubkey: fromKeypair.publicKey,
        toPubkey: toPublicKey,
        lamports: 1 * LAMPORTS_PER_SOL,
    })
);

// 发送并确认交易,指定 confirmationStatus 为 'finalized'
sendAndConfirmTransaction(connection, transaction, [fromKeypair], {
    commitment: 'finalized'
})
.then((signature) => {
    console.log(`交易已成功发送,签名: ${signature}`);
})
.catch((error) => {
    console.error('转账过程中出现错误:', error);
});


在上述代码中, commitment 参数指定了交易的确认状态,这里设置为 finalized ,表示等待交易达到最终确认状态。你可以根据实际需求将其设置为 confirmed 或 processed 。

Solana RPC 方法的常见 JSON 数据结构

各种 Solana RPC 方法将返回更复杂的响应作为结构化 JSON 对象,并填充特定的键值。
这些 JSON 数据结构中最常见的包括:

交易

交易与其他区块链上的交易截然不同。请务必查看交易剖析以了解 Solana 上的交易。

JSON

交易的JSON结构定义如下:

"transaction": {
  "message": {
    "accountKeys": [
      "EF3cbGuhJus5mCdGZkVz7GQce7QHbswBhZu6fmK9zkCR",
      "4LAyP5B5jNyNm7Ar2dG8sNipEiwTMEyCHd1iCHhhXYkY",
      "11111111111111111111111111111111"
    ],
    "header": {
      "numReadonlySignedAccounts": 0,
      "numReadonlyUnsignedAccounts": 1,
      "numRequiredSignatures": 1
    },
    "instructions": [
      {
        "accounts": [
          0,
          1
        ],
        "data": "3Bxs411Dtc7pkFQj",
        "programIdIndex": 2,
        "stackHeight": null
      }
    ],
    "recentBlockhash": "6pw7JBwq9tb5GHiBQgVY6RAp5otbouwYvEc1kbbxKFec"
  },
  "signatures": [
    "2M8mvwhtxyz3vAokXESVeR9FQ4t9QQxF5ek6ENNBBHVkW5XyZvJVK5MQej5ccwTZH6iWBJJoZ2CcizBs89pvpPBh"
  ]
}
  • message: <object> - 定义交易的内容。

  • accountKeys: <array[string]>- 交易使用的 base-58 编码公钥列表,包括指令和签名。第一个 message.header.numRequiredSignatures公钥必须签署交易。

  • header: <object>- 详细说明交易所需的账户类型和签名。

  • numRequiredSignatures: <number>- 使交易有效所需的签名总数。签名必须与第一个numRequiredSignatures签名相匹配message.accountKeys

  • numReadonlySignedAccounts: <number>- 最后 numReadonlySignedAccounts一个签名密钥是只读帐户。程序可以处理在单个 PoH 条目中加载只读帐户的多个交易,但不允许贷记或借记 lamport 或修改帐户数据。针对同一读写帐户的交易将按顺序进行评估。

  • numReadonlyUnsignedAccounts: <number>- 最后一个 numReadonlyUnsignedAccounts未签名的密钥是只读账户。

  • recentBlockhash: <string>- 账本中最近一个区块的 Base58 编码哈希值,用于防止交易重复并提供交易生命周期。

  • instructions: <array[object]> - 将按顺序执行并在一个原子事务中提交的程序指令列表(如果全部成功)。

  • programIdIndex: <number>- 数组索引,message.accountKeys指示执行该指令的程序账户。

  • accounts: <array[number]>- 数组中有序索引的列表, message.accountKeys指示要传递给程序的帐户。

  • data: <string>- 程序输入以 base-58 字符串编码的数据。

  • addressTableLookups: <array[object]|undefined> - 交易使用的地址表查找列表,用于从链上地址查找表中动态加载地址。如果maxSupportedTransactionVersion 未设置,则为未定义。

  • accountKey: <string>- 用于地址查找表帐户的 base-58 编码公钥。

  • writableIndexes: <array[number]>- 用于从查找表加载可写帐户地址的索引列表。

  • readonlyIndexes: <array[number]>- 用于从查找表加载只读帐户地址的索引列表。

  • signatures: <array[string]>- 应用于交易的 base-58 编码签名列表。列表的长度始终为 message.header.numRequiredSignatures且不为空。索引处的签名 对应于中i索引处的公钥。第一个用作 交易 IDi``message.accountKeys

JSON 解析

交易的 JSON 解析结构遵循与常规 JSON 格式类似的结构,并附加了帐户和指令信息的解析:

"transaction": {
  "message": {
    "accountKeys": [
      {
        "pubkey": "EF3cbGuhJus5mCdGZkVz7GQce7QHbswBhZu6fmK9zkCR",
        "signer": true,
        "source": "transaction",
        "writable": true
      },
      {
        "pubkey": "4LAyP5B5jNyNm7Ar2dG8sNipEiwTMEyCHd1iCHhhXYkY",
        "signer": false,
        "source": "transaction",
        "writable": true
      },
      {
        "pubkey": "11111111111111111111111111111111",
        "signer": false,
        "source": "transaction",
        "writable": false
      }
    ],
    "instructions": [
      {
        "parsed": {
          "info": {
            "destination": "4LAyP5B5jNyNm7Ar2dG8sNipEiwTMEyCHd1iCHhhXYkY",
            "lamports": 100000000,
            "source": "EF3cbGuhJus5mCdGZkVz7GQce7QHbswBhZu6fmK9zkCR"
          },
          "type": "transfer"
        },
        "program": "system",
        "programId": "11111111111111111111111111111111",
        "stackHeight": null
      }
    ],
    "recentBlockhash": "6pw7JBwq9tb5GHiBQgVY6RAp5otbouwYvEc1kbbxKFec"
  },
  "signatures": [
    "2M8mvwhtxyz3vAokXESVeR9FQ4t9QQxF5ek6ENNBBHVkW5XyZvJVK5MQej5ccwTZH6iWBJJoZ2CcizBs89pvpPBh"
  ]
}
  • message: <object> - 定义交易的内容。

    • accountKeys: <array[object]> - 交易使用的账户信息列表。

    • pubkey: <string>- 账户的 Base58 编码公钥。

    • signer: <boolean>- 表明该账户是否是必需的交易签名者。

    • writable: <boolean>- 指示该帐户是否可写。

    • source: <string>- 账户来源(交易或查找表)。

    • recentBlockhash: <string>- 账本中最近一个区块的 Base58 编码哈希值,用于防止交易重复并提供交易生命周期。

    • instructions: <array[object]> - 已解析的程序指令列表。

    • program: <string>- 被调用的程序的名称。

    • programId: <string>- 该程序的 Base58 编码公钥。

    • stackHeight: <number|null>- 指令的堆栈高度。

    • parsed: <object> - 特定程序的解析数据。

    • type: <string>- 指令的类型(例如“转移”)。

    • info: <object>- 解析特定于程序和指令类型的指令信息。

  • signatures: <array[string]>- 应用于交易的 Base-58 编码签名列表。

交易状态元数据

{
  "meta": {
    "err": null,
    "fee": 5000,
    "innerInstructions": [],
    "logMessages": [],
    "postBalances": [499998932500, 26858640, 1, 1, 1],
    "postTokenBalances": [],
    "preBalances": [499998937500, 26858640, 1, 1, 1],
    "preTokenBalances": [],
    "rewards": null,
    "status": {
      "Ok": null
    }
  }
}
  • err: <object|null>- 如果交易失败则返回错误,如果交易成功则返回 null。TransactionError 定义

  • fee: <u64>- 本次交易收取的费用,以 u64 整数形式

  • preBalances: <array>- 交易处理前的 u64 账户余额数组

  • postBalances: <array>- 交易处理后的 u64 账户余额数组

  • innerInstructions: <array|null>-内部指令列表 或null 此交易期间是否未启用内部指令记录

  • preTokenBalances: <array|undefined>- 交易处理之前的代币余额列表 ,如果在此交易期间尚未启用代币余额记录,则省略

  • postTokenBalances: <array|undefined>- 交易处理后的代币余额列表 ,如果在此交易期间尚未启用代币余额记录,则省略

  • logMessages: <array|null>- 字符串日志消息数组或null如果在此事务期间未启用日志消息记录

  • rewards: <array|null> - 交易级奖励;包含以下内容的 JSON 对象数组:

    • pubkey: <string>- 收到奖励的账户的公钥,以 base58 编码的字符串形式
    • lamports: <i64>- 账户记入或扣除的奖励灯数量,以 i64 表示
    • postBalance: <u64>- 奖励使用后 lampors 账户余额
    • rewardType: <string|undefined>- 奖励类型:“费用”、“租金”、“投票”、“质押”
    • commission: <u8|undefined>- 奖励到账时投票账户佣金,仅适用于投票和质押奖励
  • 已弃用:

    • status: <object> - 交易状态
      • "Ok": <null>- 交易成功
      • "Err": <ERR>- 交易失败,出现 TransactionError
  • loadedAddresses: <object|undefined> - 从地址查找表加载交易地址。如果 axSupportedTransactionVersion 请求参数中未设置,或者 jsonParsed 请求参数中设置了编码,则未定义。

    • writable: <array[string]>- 可写加载账户的 Base58 编码地址的有序列表
    • readonly: <array[string]>- 只读加载帐户的 Base58 编码地址的有序列表
  • returnData: <object|undefined> - 交易中指令生成的最新返回数据,包含以下字段:

    • programId: <string>- 生成返回数据的程序,以 base-58 编码的公钥形式
    • data: <[string, encoding]>- 返回数据本身,以 base64 编码的二进制数据
  • computeUnitsConsumed: <u64|undefined>-交易消耗的计算单元数量

  • version: <"legacy"|number|undefined>- 交易版本。如果 maxSupportedTransactionVersion请求参数中未设置,则为未定义。

  • signatures: <array>- 如果请求“签名”以获取交易详细信息,则存在;签名字符串数组,对应于块中的交易顺序

内部指示

Solana 运行时记录在交易处理期间调用的跨程序指令,并使这些指令可用,从而提高每个交易指令在链上执行内容的透明度。调用的指令按原始交易指令分组,并按处理顺序列出。

内部指令的 JSON 结构定义为以下结构的对象列表

"innerInstructions": [
  {
    "index": 0,
    "instructions": [
      {
        "accounts": [
          0,
          1,
          2
        ],
        "data": "WPNHsFPyEMr",
        "programIdIndex": 3,
        "stackHeight": 2
      },
      {
        "accounts": [
          0,
          1
        ],
        "data": "11111dBUPbGETd4QtNMQVg8HqgcZtKy6DcJm6R4TZufJkuhkDS47VsauCCGhLf2xrm5BQ",
        "programIdIndex": 2,
        "stackHeight": 3
      }
    ]
  }
]

参考交易:

"transaction": {
  "message": {
    "accountKeys": [
      "4kh6HxYZiAebF8HWLsUWod2EaQQ6iWHpHYCz8UcmFbM1",
      "Bpo7aaM9kqfCjM6JgZCSdev7HsaUFj51mBPPEhQcDpUR",
      "11111111111111111111111111111111",
      "8HupNBr7SBhBLcBsLhbtes3tCarBm6Bvpqp5AfVjHuj8",
      "GENmb1D59wqCKRwujq4PJ8461EccQ5srLHrXyXp4HMTH"
    ],
    "header": {
      "numReadonlySignedAccounts": 0,
      "numReadonlyUnsignedAccounts": 3,
      "numRequiredSignatures": 2
    },
    "instructions": [
      {
        "accounts": [
          0,
          1,
          2,
          3
        ],
        "data": "H2ot5wbZsmL",
        "programIdIndex": 4,
        "stackHeight": null
      }
    ],
    "recentBlockhash": "28CroH2jyCaCFF6ssyUK989zBZY6dBxnUNU9A4oPUbER"
  },
  "signatures": [
    "4i4EuRQ1sNzKWEBYwg28VAMkQbaAeHyRRwu1tQRksowtQhGRJtgaHXpDAhBfpYZnVodGoQYwiUiB5yBRDoWbZ7VH",
    "2dipFcFF4CvwtbCFbRdctQmyzAYcq8RWrLryZErbKPhnriCJ6wDmKfJoSJfDjFNzUEcJDKkfasS2pcjvGEUjdYN6"
  ]
}
  • index: number- 内部指令来源的交易指令索引

  • instructions: <array[object]>- 在单个事务指令期间调用的内部程序指令的有序列表。

    • programIdIndex: <number>- 数组索引,message.accountKeys指示执行该指令的程序账户。
    • accounts: <array[number]>- 数组中有序索引的列表, message.accountKeys指示要传递给程序的帐户。
    • data: <string>- 程序输入以 base-58 字符串编码的数据。

代币余额

"postTokenBalances": [
  {
    "accountIndex": 1,
    "mint": "6HvU8PbqP3nZLkFF59rr2zkTHqetPLgb6NnxKZLHQxNp",
    "owner": "DKypunNAGLPGBj3SocY8fF4ZrnDNVTf6QcUyW4trvkB",
    "programId": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb",
    "uiTokenAmount": {
      "amount": "0",
      "decimals": 2,
      "uiAmount": null,
      "uiAmountString": "0"
    }
  },
  {
    "accountIndex": 2,
    "mint": "6HvU8PbqP3nZLkFF59rr2zkTHqetPLgb6NnxKZLHQxNp",
    "owner": "8xm9beCpBH7SgqRz1mKua7KJF52whAVCiDEV1qREGHNV",
    "programId": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb",
    "uiTokenAmount": {
      "amount": "100",
      "decimals": 2,
      "uiAmount": 1.0,
      "uiAmountString": "1"
    }
  }
],

"preTokenBalances": [
  {
    "accountIndex": 1,
    "mint": "6HvU8PbqP3nZLkFF59rr2zkTHqetPLgb6NnxKZLHQxNp",
    "owner": "DKypunNAGLPGBj3SocY8fF4ZrnDNVTf6QcUyW4trvkB",
    "programId": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb",
    "uiTokenAmount": {
      "amount": "100",
      "decimals": 2,
      "uiAmount": 1.0,
      "uiAmountString": "1"
    }
  },
  {
    "accountIndex": 2,
    "mint": "6HvU8PbqP3nZLkFF59rr2zkTHqetPLgb6NnxKZLHQxNp",
    "owner": "8xm9beCpBH7SgqRz1mKua7KJF52whAVCiDEV1qREGHNV",
    "programId": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb",
    "uiTokenAmount": {
      "amount": "0",
      "decimals": 2,
      "uiAmount": null,
      "uiAmountString": "0"
    }
  }
]

参考交易:

"transaction": {
  "message": {
    "accountKeys": [
      {
        "pubkey": "DKypunNAGLPGBj3SocY8fF4ZrnDNVTf6QcUyW4trvkB",
        "signer": true,
        "source": "transaction",
        "writable": true
      },
      {
        "pubkey": "39nzuQ2WYHf231DJRPt1TLfaXSWXEKYGcqP3NQf6zK7G",
        "signer": false,
        "source": "transaction",
        "writable": true
      },
      {
        "pubkey": "DtCPWGmvCTov7CNmNTx8AFe3SEFSxgy265kZawv8SVL3",
        "signer": false,
        "source": "transaction",
        "writable": true
      },
      {
        "pubkey": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb",
        "signer": false,
        "source": "transaction",
        "writable": false
      }
    ],
    "addressTableLookups": [],
    "instructions": [
      {
        "parsed": {
          "info": {
            "amount": "100",
            "authority": "DKypunNAGLPGBj3SocY8fF4ZrnDNVTf6QcUyW4trvkB",
            "destination": "DtCPWGmvCTov7CNmNTx8AFe3SEFSxgy265kZawv8SVL3",
            "source": "39nzuQ2WYHf231DJRPt1TLfaXSWXEKYGcqP3NQf6zK7G"
          },
          "type": "transfer"
        },
        "program": "spl-token",
        "programId": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb",
        "stackHeight": null
      }
    ],
    "recentBlockhash": "42mjf871LtzDK8NVZUAht1xBoCvNMagQGSM7BnFWZD6M"
  },
  "signatures": [
    "5ZQqsF4tTFJDR5vuNJxejtw2GMc8KEtnPXnQjwhGzAtdbPTKtrLfPkFAbBTyPjZSVB3CbR5BiP5S8zAfZNtuwh88"
  ]
}

代币余额的 JSON 结构定义为以下结构的对象列表:

  • accountIndex: <number>- 提供代币余额的账户索引。

  • mint: <string>- 代币铸币厂的公钥。

  • owner: <string|undefined>- 代币余额所有者的公钥。

  • programId: <string|undefined>- 拥有该账户的Token程序的公钥。

  • uiTokenAmount: <object>

    • amount: <string>- 代币的原始数量为字符串,忽略小数。
    • decimals: <number>- 为代币铸造配置的小数位数。
    • uiAmount: <number|null>- 代币数量为浮点数,包含小数。已弃用
    • uiAmountString: <string>- Token数量为字符串,保留小数位。

原文:https://solana.com/zh/docs/rpc/json-structures

搜索