> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbitflare.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 交易调试

> 查明 Solana 交易为何失败、为何在发送前被拒绝或为何始终没有上链：解读模拟错误和程序日志、blockhash 过期、编码错误以及优先费。

一笔交易最终会落入三种结果之一，每种结果的处理方法都不同。在修改任何东西之前，先弄清是哪一种。

## 第 1 步：弄清发生了什么

用签名向集群查询。`searchTransactionHistory` 让查询对任意时间的交易都有效。

```bash theme={"theme":{"light":"github-light","dark":"github-dark-default"}}
curl -s https://fra.rpc.orbitflare.com \
  -H "x-api-key: $ORBITFLARE_LICENSE_KEY" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"getSignatureStatuses","params":[["SIGNATURE"],{"searchTransactionHistory":true}]}'
```

| 结果 | 含义 | 下一步 |
| - | - | - |
| `"err": null` 且 `"confirmationStatus": "confirmed"` 或 `"finalized"` | 交易已上链并执行成功 | 无需调试。如果结果仍不符合预期，说明指令执行的操作与您预想的不同 |
| `"err": { ... }` | 交易已上链但**失败**。手续费已扣除，其他状态均未改变 | [已上链但失败](#已上链但失败) |
| `null` | 集群从未见过这笔交易 | [从未上链](#从未上链) |

如果是 `sendTransaction` 本身返回了错误，就没有可查询的签名。交易在发送前就被拒绝了。请转到[发送前被拒绝](#发送前被拒绝)。

如需在代码中检查签名，请参考[等待确认的示例](/cn/recipes#等待交易确认)。

## 发送前被拒绝

默认情况下，`sendTransaction` 会先模拟交易，如果模拟失败就拒绝发送。您会收到错误 `-32002`，其中包含原因和程序日志：

```json theme={"theme":{"light":"github-light","dark":"github-dark-default"}}
{
  "code": -32002,
  "message": "Transaction simulation failed: Error processing Instruction 0: custom program error: 0x1",
  "data": {
    "err": { "InstructionError": [0, { "Custom": 1 }] },
    "logs": [
      "Program 11111111111111111111111111111111 invoke [1]",
      "Transfer: insufficient lamports 60773561, need 1000000000000",
      "Program 11111111111111111111111111111111 failed: custom program error: 0x1"
    ],
    "unitsConsumed": 150
  }
}
```

按以下顺序阅读：

1. **`data.logs`** 通常会用直白的语言说明出了什么问题。这里，付款方只有 0.06 SOL，却试图转出 1,000 SOL。
2. **`data.err`** 指出哪条指令失败。`InstructionError: [0, ...]` 是第一条指令，从零开始计数。
3. **自定义错误码**属于失败的那个程序，而不是 Solana。System Program 的 `Custom: 1` 表示“余额不足”，但其他程序的 `Custom: 1` 含义不同。请在该程序的错误列表（其 IDL 或源码）中查找这个数字。消息中以十六进制显示，因此 `0x1770` 就是 `6000`，即 Anchor 程序中的第一个自定义错误。

被拒绝不会产生任何费用。交易根本没有离开 RPC 节点。

### 常见拒绝原因

| 错误 | 原因 | 解决方法 |
| - | - | - |
| `Transaction simulation failed: Blockhash not found` | blockhash 已过期、来自其他集群，或是随意编造的 | 在签名前一刻用 `getLatestBlockhash` 获取新的 blockhash |
| `Transaction simulation failed: Attempt to debit an account but found no record of a prior credit` | 手续费付款方在此集群上从未持有过 SOL | 为手续费付款方充值。在 devnet 上，请确认您没有使用在 devnet 上没有余额的主网钱包 |
| System Program 返回的 `custom program error: 0x1` | lamports 不足以支付转账金额和手续费 | 检查余额，包括交易所创建账户需要的租金 |
| `exceeded CUs meter at BPF instruction` | 计算单元上限设置过低 | 先模拟，读取 `unitsConsumed`，再将上限设置为比它高约 10% |
| `invalid base58 encoding: InvalidCharacter` | 您发送了 base64 字节但没有声明。`sendTransaction` 默认按 base58 解析 | 将 `{"encoding": "base64"}` 作为第二个参数传入 |
| `failed to deserialize ... VersionedTransaction: io error: failed to fill whole buffer` | 字节不是一笔完整的交易：被截断、被重复编码或未签名 | 将已签名的交易序列化一次、编码一次 |

## 使用 simulateTransaction 调试

如果只想查看日志而不发送任何内容，请进行模拟。调试时，以下两个选项会让模拟有用得多：

```bash theme={"theme":{"light":"github-light","dark":"github-dark-default"}}
curl -s https://fra.rpc.orbitflare.com \
  -H "x-api-key: $ORBITFLARE_LICENSE_KEY" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"simulateTransaction","params":["BASE64_TX",{"encoding":"base64","replaceRecentBlockhash":true,"sigVerify":false}]}'
```

* **`replaceRecentBlockhash: true`** 会替换为当前的 blockhash，因此在交易的 blockhash 过期后，您仍可以继续模拟同一笔交易。它不能与 `sigVerify: true` 同时使用。
* **`sigVerify: false`** 会跳过签名检查，因此您可以模拟未签名或部分签名的交易。

即使模拟成功，结果中也包含 `err`、`logs` 和 `unitsConsumed`。用 `unitsConsumed` 设置准确的计算单元上限：上限越紧，您按单元支付的优先费就越划算。

> [!WARNING]
>
> 调试时避免使用 `skipPreflight: true`。它会跳过模拟直接发送交易，因此有问题的交易会上链、失败并扣除手续费，而错误要到之后在 `getSignatureStatuses` 中才会出现。等确认交易可以正常工作、并且需要速度时再开启它。

## 已上链但失败

交易已经上链，手续费已扣除，但它的所有更改都没有生效。获取完整记录：

```bash theme={"theme":{"light":"github-light","dark":"github-dark-default"}}
curl -s https://fra.rpc.orbitflare.com \
  -H "x-api-key: $ORBITFLARE_LICENSE_KEY" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"getTransaction","params":["SIGNATURE",{"encoding":"json","maxSupportedTransactionVersion":1}]}'
```

`meta.err` 与模拟错误的结构相同，`meta.logMessages` 包含程序日志。请按[发送前被拒绝](#发送前被拒绝)中描述的方法阅读。

模拟成功的交易仍可能在链上失败，因为在此期间状态发生了变化。常见原因包括兑换时的滑点、某个账户被他人关闭或修改，或者 nonce 已被使用。请基于当前状态重新模拟以确认原因，然后留出容错空间：放宽滑点、获取新报价，或在重试时重新构建交易。

## 从未上链

`getSignatureStatuses` 返回 `null`：RPC 节点接受了这笔交易，但没有任何领导者将其打包。不会产生任何费用。

### blockhash 已过期

交易仅在其 blockhash 之后的 **150 个区块**内有效。每个槽约 215 毫秒，相当于大约 30 到 35 秒。一旦超过，交易就永远无法上链，因此请停止等待并重新构建交易。

检查 blockhash 是否仍然可用：

```bash theme={"theme":{"light":"github-light","dark":"github-dark-default"}}
curl -s https://fra.rpc.orbitflare.com \
  -H "x-api-key: $ORBITFLARE_LICENSE_KEY" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"isBlockhashValid","params":["BLOCKHASH",{"commitment":"processed"}]}'
```

将 `getLatestBlockhash` 返回的 `lastValidBlockHeight` 与 blockhash 一起保存。将它与 `getBlockHeight` 比较：只要区块高度更低，就持续重发同一笔已签名的交易。一旦区块高度更高，这笔交易就已失效。

### 优先费过低

区块繁忙时，领导者会优先打包每个计算单元支付最多的交易。查看最近涉及您所操作账户的交易支付了多少：

```bash theme={"theme":{"light":"github-light","dark":"github-dark-default"}}
curl -s https://fra.rpc.orbitflare.com \
  -H "x-api-key: $ORBITFLARE_LICENSE_KEY" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"getRecentPrioritizationFees","params":[["ACCOUNT_YOUR_TX_WRITES"]]}'
```

从最近的结果中选择一个值，用 `SetComputeUnitPrice` 设置，并配合通过模拟得到的准确 `SetComputeUnitLimit`。

### 在传输途中被丢弃

已发送的交易可能在 RPC 节点和领导者之间丢失，在网络拥堵时尤其如此。每隔几秒重发同样的已签名字节，直到交易确认或 blockhash 过期。重发是安全的：同一个签名只能上链一次。

对于需要快速上链的交易，请通过 [Apex](/cn/apex/overview) 发送。Apex 会同时通过多条路径将每笔交易发送给即将出块的领导者，并持续重试，直到交易上链或过期。Apex 自身的错误列在 [Apex 错误与速率限制](/cn/apex/errors-and-rate-limits)中。

## 仍未解决

请收集签名（如果交易从未获得签名，则收集 base64 交易）、包含 `data.logs` 的完整错误、您发送时使用的区域，以及是否开启了预检。然后在 [Discord](https://discord.gg/orbitflare) 中提问。


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.