Кратко
Инструкция содержит 3 поля: program_id (какую программу вызвать), accounts
(список AccountMeta с флагами is_signer/is_writable) и data (массив байтов с
данными, которые программа интерпретирует).
Структура инструкции
Instruction
состоит из трёх полей:
program_id: ID программы, которую вызывают.accounts: Массив метаданных аккаунтаdata: Массив байтов с дополнительными данными, которые используются инструкцией.
pub struct Instruction {/// Pubkey of the program that executes this instruction.pub program_id: Pubkey,/// Metadata describing accounts that should be passed to the program.pub accounts: Vec<AccountMeta>,/// Opaque data passed to the program for its own interpretation.pub data: Vec<u8>,}
Program ID
program_id инструкции — это
публичный ключ программы, содержащей логику выполнения инструкции. Во время
выполнения это поле используется для маршрутизации инструкции в нужную программу
для обработки.
Метаданные аккаунта
Массив accounts инструкции — это упорядоченный список
AccountMeta
структур. Метаданные должны быть указаны для каждого аккаунта, с которым
взаимодействует инструкция. Validator использует эти метаданные, чтобы
определить, какие транзакции могут выполняться параллельно. Транзакции, которые
записывают данные в разные аккаунты, могут выполняться одновременно.
На схеме ниже показана транзакция, содержащая одну инструкцию. Массив accounts
инструкции содержит метаданные для двух аккаунтов.
Транзакция с одной инструкцией. Инструкция содержит две структуры AccountMeta в своём массиве accounts.
Каждая AccountMeta содержит три поля:
- pubkey: Публичный ключ аккаунта
- is_signer: Устанавливается в
true, если аккаунт должен подписывать транзакцию - is_writable: Устанавливается в
true, если инструкция изменяет данные аккаунта
Чтобы узнать, какие аккаунты требуются для инструкции, в том числе какие из них должны быть доступными для записи, только для чтения или подписывать транзакцию, необходимо обратиться к реализации инструкции, определённой программой.
pub struct AccountMeta {/// An account's public key.pub pubkey: Pubkey,/// True if an `Instruction` requires a `Transaction` signature matching `pubkey`.pub is_signer: bool,/// True if the account data or metadata may be mutated during program execution.pub is_writable: bool,}
Данные
Поле data инструкции — это массив байтов, который сообщает программе, какую
функцию вызвать и какие аргументы ей передать. Обычно данные начинаются с
дискриминатора или байта(ов) индекса, который определяет целевую функцию, а
затем следуют сериализованные аргументы. Формат кодирования определяется каждой
программой отдельно (например, сериализация Borsh или собственная схема).
Общие соглашения по кодированию:
- Core-программы (System, Stake, Vote): используют индекс варианта enum, сериализованный через Bincode, за которым следуют сериализованные аргументы.
- Anchor-программы: используют 8-байтовый дискриминатор (первые 8 байтов
SHA-256 хэша
"global:<function_name>"), за которым следуют аргументы, сериализованные через Borsh.
Среда выполнения не интерпретирует поле data. Оно передаётся как есть в
entrypoint программы process_instruction.
Скомпилированная инструкция
Когда инструкции сериализуются в сообщение транзакции, они становятся
CompiledInstruction
структурами, в которых все публичные ключи заменяются компактными целочисленными
индексами в массиве account_keys сообщения.
Пример: инструкция перевода SOL
Пример ниже показывает структуру инструкции перевода SOL.
import { generateKeyPairSigner, lamports } from "@solana/kit";import { getTransferSolInstruction } from "@solana-program/system";// Generate sender and recipient keypairsconst sender = await generateKeyPairSigner();const recipient = await generateKeyPairSigner();// Define the amount to transferconst LAMPORTS_PER_SOL = 1_000_000_000n;const transferAmount = lamports(LAMPORTS_PER_SOL / 100n); // 0.01 SOL// Create a transfer instruction for transferring SOL from sender to recipientconst transferInstruction = getTransferSolInstruction({source: sender,destination: recipient.address,amount: transferAmount});console.log(JSON.stringify(transferInstruction, null, 2));
Код ниже показывает вывод из предыдущих примеров кода. Формат может отличаться в
разных SDK, но обратите внимание, что каждая инструкция содержит одни и те же
три обязательных элемента: program_id,
accounts, data.
{"accounts": [{"address": "Hu28vRMGWpQXN56eaE7jRiDDRRz3vCXEs7EKHRfL6bC","role": 3,"signer": {"address": "Hu28vRMGWpQXN56eaE7jRiDDRRz3vCXEs7EKHRfL6bC","keyPair": {"privateKey": {},"publicKey": {}}}},{"address": "2mBY6CTgeyJNJDzo6d2Umipw2aGUquUA7hLdFttNEj7p","role": 1}],"programAddress": "11111111111111111111111111111111","data": {"0": 2,"1": 0,"2": 0,"3": 0,"4": 128,"5": 150,"6": 152,"7": 0,"8": 0,"9": 0,"10": 0,"11": 0}}
Примеры ниже показывают, как вручную создать инструкцию для перевода. (Вкладка
Expanded Instruction функционально эквивалентна вкладке Instruction.)
На практике обычно нет необходимости вручную создавать Instruction.
Большинство программ предоставляют клиентские библиотеки с вспомогательными
функциями, которые формируют инструкции за вас. Если библиотека недоступна,
инструкцию можно собрать вручную.
const transferAmount = 0.01; // 0.01 SOLconst transferInstruction = getTransferSolInstruction({source: sender,destination: recipient.address,amount: transferAmount * LAMPORTS_PER_SOL});
Is this page helpful?