Интроспекция транзакций

Краткое содержание

@solana/transaction-introspection преобразует ответ RPC getTransaction в список инструкций с реальными адресами. Он разрешает индексы аккаунтов (включая адреса таблиц подстановки), декодирует внутренние инструкции CPI и обходит полное дерево инструкций в порядке, принятом в обозревателях блоков.

Ищете информацию о том, как программа инспектирует смежные инструкции on-chain во время выполнения? Это сисвар Instructions — см. Интроспекция инструкций. Данная страница посвящена декодированию транзакции off-chain из ответа RPC.

Ответ getTransaction не предоставляет готовый список инструкций. Аккаунты хранятся как числовые индексы, версионированные транзакции разделяют эти аккаунты на статические ключи и адреса, загруженные из таблиц подстановки адресов, а внутренние инструкции из Cross Program Invocation (CPI) поступают в виде закодированных блобов в метаданных транзакции.

@solana/transaction-introspection выполняет это разрешение за вас. Он декодирует транзакцию в проводном формате, сопоставляет каждый индекс аккаунта с его Address, нормализует внутренние инструкции и возвращает инструкции в том же порядке «внешние, затем внутренние», который используется в обозревателях блоков. Возвращаемые инструкции напрямую подключаются к хелперам parseXInstruction клиентов, сгенерированных с помощью codama (например, @solana-program/*).

Terminal
npm install @solana/transaction-introspection @solana/kit @solana/instructions @solana-program/token

Декодирование ответа

Получите транзакцию с кодировкой в проводном формате и передайте ответ в decodeTransactionFromRpcResponse.

Decode a transaction
import { createSolanaRpc, signature } from "@solana/kit";
import { decodeTransactionFromRpcResponse } from "@solana/transaction-introspection";
const rpc = createSolanaRpc("https://api.mainnet-beta.solana.com");
const txid =
"3jUKrQp1UGq5ih6FTDUUt2kkqUfoG2o4kY5T1DoVHK2tXXDLdxJSXzuJGY4JPoRivgbi45U2bc7LZfMa6C4R3szX";
const rpcTx = await rpc
.getTransaction(signature(txid), {
commitment: "confirmed",
encoding: "base64",
maxSupportedTransactionVersion: 0
})
.send();
if (!rpcTx) throw new Error(`Transaction ${txid} not found`);
const { compiledMessage, loadedAddresses } =
decodeTransactionFromRpcResponse(rpcTx);

В оставшихся фрагментах повторно используются rpcTx, compiledMessage и loadedAddresses из этого шага.

Выполняйте запрос с кодировкой base64, base58 или json. decodeTransactionFromRpcResponse отклоняет ответы в формате jsonParsed — эта кодировка предписывает RPC-ноде разбирать инструкции на стороне сервера, поэтому на клиенте ничего не остаётся для декодирования.

Обход инструкций

walkInstructions возвращает каждую инструкцию — внешнюю и внутреннюю — в виде массива в том порядке, в котором их отображает блок-эксплорер: каждая внешняя инструкция, за которой сразу следуют внутренние инструкции, порождённые её CPI. Каждая инструкция содержит поле trace, описывающее её положение в иерархии вызовов.

Walk every instruction
import { walkInstructions } from "@solana/transaction-introspection";
for (const ix of walkInstructions({
compiledMessage,
loadedAddresses,
meta: rpcTx.meta
})) {
const location =
ix.trace.kind === "outer"
? `outer[${ix.trace.index}]`
: `inner[${ix.trace.outerIndex}/${ix.trace.innerIndex}]`;
console.log(location, ix.programAddress, ix.accounts?.length ?? 0);
}

trace — размеченное объединение: { kind: "outer", index } для инструкции верхнего уровня или { kind: "inner", outerIndex, innerIndex, stackHeight? } для инструкции CPI, вложенной во внешнюю.

meta является необязательным — передавайте rpcTx.meta напрямую, даже если оно может быть null. Без мета walkInstructions возвращает только внешние инструкции.

Разбор с помощью клиента программы

Каждая инструкция из walkInstructions является ResolvedInstruction, поэтому она работает напрямую с предикатами из @solana/instructions и вспомогательными функциями identifyXInstruction / parseXInstruction, сгенерированными клиентами @solana-program/* — без шага преобразования.

В этом примере выполняется аудит каждой инструкции Token Program SyncNative в транзакции — независимо от того, выполнялась ли она на верхнем уровне или внутри CPI.

Find and parse SyncNative instructions
import {
isInstructionForProgram,
isInstructionWithAccounts,
isInstructionWithData
} from "@solana/instructions";
import { walkInstructions } from "@solana/transaction-introspection";
import {
identifyTokenInstruction,
parseSyncNativeInstruction,
TOKEN_PROGRAM_ADDRESS,
TokenInstruction
} from "@solana-program/token";
for (const ix of walkInstructions({
compiledMessage,
loadedAddresses,
meta: rpcTx.meta
})) {
if (!isInstructionForProgram(ix, TOKEN_PROGRAM_ADDRESS)) continue;
if (!isInstructionWithData(ix) || !isInstructionWithAccounts(ix)) continue;
if (identifyTokenInstruction(ix) !== TokenInstruction.SyncNative) continue;
const parsed = parseSyncNativeInstruction(ix);
console.log(ix.trace, parsed);
}

Вспомогательные функции низкого уровня

walkInstructions покрывает большинство потребностей, однако пакет также предоставляет составные части, на которых он построен:

  • getInstructionsFromCompiledTransactionMessage(compiledMessage, loadedAddresses?) возвращает только внешние инструкции с разрешёнными аккаунтами и декодированными данными.
  • getInnerInstructionsFromMeta(meta, accountMetas) декодирует внутренние CPI-инструкции из метаданных транзакции.
  • getAccountMetasFromCompiledTransactionMessage(compiledMessage, loadedAddresses?) возвращает полный список аккаунтов в порядке транзакции — входные данные, необходимые предыдущей вспомогательной функции для разрешения индексов аккаунтов внутренних инструкций.
Resolve outer and inner instructions separately
import {
getAccountMetasFromCompiledTransactionMessage,
getInnerInstructionsFromMeta,
getInstructionsFromCompiledTransactionMessage
} from "@solana/transaction-introspection";
const outer = getInstructionsFromCompiledTransactionMessage(
compiledMessage,
loadedAddresses
);
const accountMetas = getAccountMetasFromCompiledTransactionMessage(
compiledMessage,
loadedAddresses
);
if (!rpcTx.meta) throw new Error("Transaction metadata missing");
const inner = getInnerInstructionsFromMeta(rpcTx.meta, accountMetas);

Используете Rust?

Эквивалента этого пакета на Rust не существует. Чтобы получить разобранные инструкции из Rust, запросите UiTransactionEncoding::JsonParsed у getTransaction и позвольте RPC-ноде разрешить аккаунты и внутренние инструкции за вас.

Смотрите также

Is this page helpful?

Содержание

Редактировать страницу