跳到主要内容

调试与排查

调试 OXN 交易和调试以太坊大体一致——同样的 gas 概念、同样的 revert、同样的事件日志——只有一个大转折:revert reason 是加密的。交易失败时你无法从外部读取错误消息。本页覆盖实战调试手册。

读取 revert reason

以太坊上: require(false, "not authorized") revert 时,区块浏览器在交易详情里显示 "not authorized"。

OXN 上: 同样的调用 revert 时 reason 加密。浏览器看到密文。只有提交交易的调用者能解密错误。

怎么看到真实原因

用持有会话密钥的 wrapped provider:

try {
await token.transfer(recipient, amount, { gasLimit: 500_000n });
} catch (err) {
console.log(err.reason); // "not authorized"——被 wrapped provider 解密
console.log(err.data); // 完整 error 对象
}

如果调用者是你自己的客户端(通常是),你有密钥。如果调用者是用户,而你要从你这边调试他的失败,你没有密钥——只能向用户要错误信息,或者自己复现失败。

调试用户报告的失败

用户报告"交易失败",你的选项:

  1. 让用户查钱包。 MetaMask / OXN Wallet 会显示解密后的 revert reason(他是调用者)。
  2. 用同样的输入复现。 从自己的地址用同样参数签一次调用看会 revert 什么。
  3. 加更好的链上断言。 自定义 error(error NotAuthorized(address caller);)带结构化字段比字符串更好识别。

常见 revert 及原因

invalid code——用比 paris 更新的 evmVersion 编译了。含 PUSH0(或其他不支持的 opcode),运行时无法执行。见 EVM 兼容性。以 evmVersion: "paris" 重新编译。

out of gas——gasLimit 太小。加大。默认值见 Gas 与手续费

insufficient balance to pay fees——账户余额太低。领水龙头或测试网上用 gasPrice: 0n

钱包 UI 显示 "Invalid address"——实际上可能是 insufficient balance——OXN 网页钱包在某些版本里把这个错误误标为地址错误。断定"地址错了"前先通过脚本或浏览器查看底层交易状态。

调用意外返回默认值(0、空字符串、零地址)——你发的是明文 eth_call,不是签名查询。确认合约是用 signer 构造的,不是裸 provider。见签名查询

部署成功但 getCode 返回 0x——部署过程中 gas 用完了。把 gasLimit 提到 5_000_000n 或以上重新部署。

Trace 交易

深度调试用 debug_traceTransaction

const trace = await rawProvider.send("debug_traceTransaction", [
txHash,
{ tracer: "callTracer" }
]);
console.log(JSON.stringify(trace, null, 2));

显示调用栈与每帧的 gas。OXN 上,加密 calldata 在 trace 里显示为不透明的十六进制字节,除非你从调用者的上下文运行它。

从失败交易读事件

失败交易发出事件(Solidity emit 在 revert 时被回滚)。getTransactionReceipt(txHash).logs 里看到的是空。Revert reason 在 receipt 的 status 里:

const receipt = await provider.getTransactionReceipt(txHash);
if (receipt.status === 0) {
console.log("Transaction failed");
}

想要解密后的原因,如上文所示,从原始 send 那里 catch 错误。

Solidity 的 console.log

Hardhat 为 Solidity 提供 console.log,在测试中工作:

import "hardhat/console.sol";

contract MyContract {
function doThing(uint256 x) external {
console.log("Received:", x);
console.log("Sender :", msg.sender);
}
}

这些日志出现在你的 Hardhat 测试输出里。部署编译时会被剔除,真实网络上不产生 gas 成本。但 console.log 在真实 OXN 上不可用——只在本地 Hardhat network 里工作。

Foundry trace 调试

Foundry 的 forge test -vvvv 打印每个测试的完整执行 trace:

forge test -vvvv --match-test testMyFailure

显示每个调用、revert、log、每帧 gas。本地。

浏览器 trace 工具

explorer.bout.network 显示公开数据的交易 trace(调用流、每帧 gas)。加密 calldata 与事件在浏览器 UI 里显示为密文。用它回答"交易到底跑没跑、走的什么路径";用本地调试器回答"为什么失败"。

常见集成调试模式

验证一笔交易是加密的
const raw = new ethers.JsonRpcProvider("https://rpc.bout.network");
const tx = await raw.getTransaction(txHash);
const firstByte = parseInt(tx.data.slice(2, 4), 16);
console.log("Encrypted:", (firstByte & 0xe0) === 0xa0);
以 owner 视角 vs 外部视角对比 receipt
const asOwner = await wrapped.getTransactionReceipt(txHash); // logs 已解密
const asOutsider = await raw.getTransactionReceipt(txHash); // logs 密文

console.log("Decrypted event count:", asOwner.logs.length);
console.log("Raw event topic 0: ", asOutsider.logs[0].topics[0]);
广播前 dry-run
try {
const result = await contract.myFunction.staticCall(args);
console.log("Would return:", result);
} catch (err) {
console.log("Would revert with:", err.reason);
}

下一步