Структура транзакції

Підсумок

Транзакція містить підписи + повідомлення. Повідомлення містить заголовок, адреси облікових записів, останній блокхеш та скомпільовані інструкції. Максимальний серіалізований розмір: 1 232 байти.

Transaction має два поля верхнього рівня:

  • signatures: масив підписів
  • message: інформація про транзакцію, включаючи список інструкцій для обробки
Transaction
pub struct Transaction {
pub signatures: Vec<Signature>,
pub message: Message,
}

Діаграма, що показує дві частини транзакціїДіаграма, що показує дві частини транзакції

Загальний серіалізований розмір транзакції не повинен перевищувати PACKET_DATA_SIZE (1 232 байти). Це обмеження дорівнює 1 280 байтам (мінімальний MTU IPv6) мінус 48 байтів для мережевих заголовків (40 байтів IPv6 + 8 байтів заголовок фрагмента). 1 232 байти включають як масив signatures, так і структуру message.

Діаграма, що показує формат транзакції та обмеження розміруДіаграма, що показує формат транзакції та обмеження розміру

Підписи

Поле signatures — це компактно закодований масив значень Signature. Кожен Signature — це 64-байтний підпис Ed25519 серіалізованого Message, підписаний приватним ключем облікового запису підписувача. Один підпис потрібен для кожного облікового запису підписувача, на який посилаються інструкції транзакції.

Перший підпис у масиві належить платнику комісії — обліковому запису, який сплачує базову комісію та пріоритетну комісію транзакції. Цей перший підпис також служить ідентифікатором транзакції, який використовується для пошуку транзакції в мережі. Ідентифікатор транзакції зазвичай називають підписом транзакції.

Вимоги до платника комісії:

  • Має бути першим акаунтом у повідомленні (індекс 0) і підписантом.
  • Має бути акаунтом, що належить системній програмі, або nonce-акаунтом (перевіряється через validate_fee_payer).
  • Має містити достатньо лампортів для покриття rent_exempt_minimum + total_fee; інакше транзакція завершиться помилкою InsufficientFundsForFee.

Повідомлення

Поле message є структурою Message, яка містить корисне навантаження транзакції:

Message
pub struct Message {
/// The message header, identifying signed and read-only `account_keys`.
pub header: MessageHeader,
/// All the account keys used by this transaction.
#[serde(with = "short_vec")]
pub account_keys: Vec<Pubkey>,
/// The id of a recent ledger entry.
pub recent_blockhash: Hash,
/// Programs that will be executed in sequence and committed in
/// one atomic transaction if all succeed.
#[serde(with = "short_vec")]
pub instructions: Vec<CompiledInstruction>,
}

Заголовок

Поле header є структурою MessageHeader з трьома полями u8, які розділяють масив account_keys на групи дозволів:

  • num_required_signatures: Загальна кількість підписів, необхідних для транзакції.
  • num_readonly_signed_accounts: Кількість підписаних акаунтів, доступних лише для читання.
  • num_readonly_unsigned_accounts: Кількість непідписаних акаунтів, доступних лише для читання.
MessageHeader
pub struct MessageHeader {
/// The number of signatures required for this message to be considered
/// valid. The signers of those signatures must match the first
/// `num_required_signatures` of [`Message::account_keys`].
pub num_required_signatures: u8,
/// The last `num_readonly_signed_accounts` of the signed keys are read-only
/// accounts.
pub num_readonly_signed_accounts: u8,
/// The last `num_readonly_unsigned_accounts` of the unsigned keys are
/// read-only accounts.
pub num_readonly_unsigned_accounts: u8,
}

Діаграма, що показує три частини заголовка повідомленняДіаграма, що показує три частини заголовка повідомлення

Адреси акаунтів

Поле account_keys є компактно закодованим масивом публічних ключів. Кожен запис ідентифікує акаунт, який використовується принаймні однією з інструкцій транзакції. Масив має включати кожен акаунт і дотримуватися такого суворого порядку:

  1. Підписант + доступний для запису
  2. Підписант + лише для читання
  3. Не підписант + доступний для запису
  4. Не підписант + лише для читання

Цей суворий порядок дозволяє поєднати масив account_keys з трьома лічильниками в header повідомлення для визначення дозволів для кожного акаунта без зберігання прапорців метаданих для окремих акаунтів. Лічильники заголовка розділяють масив на чотири групи дозволів, перелічені вище.

Діаграма, що показує порядок масиву адрес облікових записівДіаграма, що показує порядок масиву адрес облікових записів

Останній блокхеш

Поле recent_blockhash — це 32-байтний хеш, який виконує дві функції:

  1. Мітка часу: підтверджує, що транзакція була створена нещодавно.
  2. Дедуплікація: запобігає повторній обробці тієї самої транзакції.

Блокхеш втрачає чинність через 150 слотів. Якщо блокхеш більше не є дійсним на момент надходження транзакції, її буде відхилено з помилкою BlockhashNotFound, якщо це не є дійсною транзакцією з довготривалим nonce.

RPC-метод getLatestBlockhash дозволяє отримати поточний блокхеш та останню висоту блоку, на якій блокхеш буде дійсним.

Інструкції

Поле instructions є компактно закодованим масивом структур CompiledInstruction. Кожна CompiledInstruction посилається на облікові записи за індексом у масиві account_keys, а не за повним публічним ключем. Вона містить:

  1. program_id_index: індекс у account_keys, що ідентифікує програму для виклику.
  2. accounts: масив індексів у account_keys, що вказує облікові записи для передачі програмі.
  3. data: байтовий масив, що містить дискримінатор інструкції та серіалізовані аргументи.
CompiledInstruction
pub struct CompiledInstruction {
/// Index into the transaction keys array indicating the program account that executes this instruction.
pub program_id_index: u8,
/// Ordered indices into the transaction keys array indicating which accounts to pass to the program.
#[serde(with = "short_vec")]
pub accounts: Vec<u8>,
/// The program input data.
#[serde(with = "short_vec")]
pub data: Vec<u8>,
}

Компактний масив інструкційКомпактний масив інструкцій

Бінарний формат транзакції

Транзакції серіалізуються з використанням схеми компактного кодування. Усі масиви змінної довжини (підписи, ключі облікових записів, інструкції) мають префікс у вигляді кодування довжини compact-u16. Цей формат використовує 1 байт для значень 0-127 та 2-3 байти для більших значень.

Структура legacy-транзакції (у мережі):

ПолеРозмірОпис
num_signatures1-3 байти (compact-u16)Кількість підписів
signaturesnum_signatures x 64 байтиПідписи Ed25519
num_required_signatures1 байтПоле 1 MessageHeader
num_readonly_signed1 байтПоле 2 MessageHeader
num_readonly_unsigned1 байтПоле 3 MessageHeader
num_account_keys1-3 байти (compact-u16)Кількість статичних ключів облікових записів
account_keysnum_account_keys x 32 байтиПублічні ключі
recent_blockhash32 байтиБлокхеш
num_instructions1-3 байти (compact-u16)Кількість інструкцій
instructionsзміннийМасив скомпільованих інструкцій

Кожна скомпільована інструкція серіалізується як:

ПолеРозмірОпис
program_id_index1 байтІндекс у ключах облікових записів
num_accounts1-3 байти (compact-u16)Кількість індексів облікових записів
account_indicesnum_accounts x 1 байтІндекси ключів облікових записів
data_len1-3 байти (compact-u16)Довжина instruction data
datadata_len байтівНепрозорі instruction data

Розрахунок розміру

Враховуючи PACKET_DATA_SIZE = 1 232 байти, доступний простір можна обчислити:

Total = 1232 bytes
- compact-u16(num_sigs) # 1 byte
- num_sigs * 64 # signature bytes
- 3 # message header
- compact-u16(num_keys) # 1 byte
- num_keys * 32 # account key bytes
- 32 # recent blockhash
- compact-u16(num_ixs) # 1 byte
- sum(instruction_sizes) # per-instruction overhead + data

Приклад: транзакція переказу SOL

Діаграма нижче показує, як транзакції та інструкції працюють разом, щоб дозволити користувачам взаємодіяти з мережею. У цьому прикладі SOL переказується з одного облікового запису на інший.

Метадані облікового запису відправника вказують, що він повинен підписати транзакцію. Це дозволяє System Program списати lamports. Обидва облікові записи — відправника та одержувача — мають бути доступними для запису, щоб їхній баланс lamports міг змінитися. Для виконання цієї інструкції гаманець відправника надсилає транзакцію, що містить його підпис та повідомлення з інструкцією переказу SOL.

Діаграма переказу SOLДіаграма переказу SOL

Після надсилання транзакції System Program обробляє інструкцію переказу та оновлює баланс lamports обох облікових записів.

Діаграма процесу переказу SOLДіаграма процесу переказу SOL

Приклад нижче показує код, що стосується наведених вище діаграм. Див. функцію transfer System Program.

import {
airdropFactory,
appendTransactionMessageInstructions,
createSolanaRpc,
createSolanaRpcSubscriptions,
createTransactionMessage,
generateKeyPairSigner,
getSignatureFromTransaction,
lamports,
pipe,
sendAndConfirmTransactionFactory,
setTransactionMessageFeePayerSigner,
setTransactionMessageLifetimeUsingBlockhash,
signTransactionMessageWithSigners
} from "@solana/kit";
import { getTransferSolInstruction } from "@solana-program/system";
// Create a connection to cluster
const rpc = createSolanaRpc("http://localhost:8899");
const rpcSubscriptions = createSolanaRpcSubscriptions("ws://localhost:8900");
// Generate sender and recipient keypairs
const sender = await generateKeyPairSigner();
const recipient = await generateKeyPairSigner();
const LAMPORTS_PER_SOL = 1_000_000_000n;
const transferAmount = lamports(LAMPORTS_PER_SOL / 100n); // 0.01 SOL
// Fund sender with airdrop
await airdropFactory({ rpc, rpcSubscriptions })({
recipientAddress: sender.address,
lamports: lamports(LAMPORTS_PER_SOL), // 1 SOL
commitment: "confirmed"
});
// Check balance before transfer
const { value: preBalance1 } = await rpc.getBalance(sender.address).send();
const { value: preBalance2 } = await rpc.getBalance(recipient.address).send();
// Create a transfer instruction for transferring SOL from sender to recipient
const transferInstruction = getTransferSolInstruction({
source: sender,
destination: recipient.address,
amount: transferAmount // 0.01 SOL in lamports
});
// Add the transfer instruction to a new transaction
const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();
const transactionMessage = pipe(
createTransactionMessage({ version: 0 }),
(tx) => setTransactionMessageFeePayerSigner(sender, tx),
(tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
(tx) => appendTransactionMessageInstructions([transferInstruction], tx)
);
// Send the transaction to the network
const signedTransaction =
await signTransactionMessageWithSigners(transactionMessage);
await sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions })(
signedTransaction,
{ commitment: "confirmed" }
);
const transactionSignature = getSignatureFromTransaction(signedTransaction);
// Check balance after transfer
const { value: postBalance1 } = await rpc.getBalance(sender.address).send();
const { value: postBalance2 } = await rpc.getBalance(recipient.address).send();
console.log(
"Sender prebalance:",
Number(preBalance1) / Number(LAMPORTS_PER_SOL)
);
console.log(
"Recipient prebalance:",
Number(preBalance2) / Number(LAMPORTS_PER_SOL)
);
console.log(
"Sender postbalance:",
Number(postBalance1) / Number(LAMPORTS_PER_SOL)
);
console.log(
"Recipient postbalance:",
Number(postBalance2) / Number(LAMPORTS_PER_SOL)
);
console.log("Transaction Signature:", transactionSignature);
Console
Click to execute the code.

Наступний приклад показує структуру транзакції, що містить одну інструкцію переказу SOL.

import {
createSolanaRpc,
generateKeyPairSigner,
lamports,
createTransactionMessage,
setTransactionMessageFeePayerSigner,
setTransactionMessageLifetimeUsingBlockhash,
appendTransactionMessageInstructions,
pipe,
signTransactionMessageWithSigners,
getCompiledTransactionMessageDecoder
} from "@solana/kit";
import { getTransferSolInstruction } from "@solana-program/system";
const rpc = createSolanaRpc("http://localhost:8899");
const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();
// Generate sender and recipient keypairs
const sender = await generateKeyPairSigner();
const recipient = await generateKeyPairSigner();
// Define the amount to transfer
const 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 recipient
const transferInstruction = getTransferSolInstruction({
source: sender,
destination: recipient.address,
amount: transferAmount
});
// Create transaction message
const transactionMessage = pipe(
createTransactionMessage({ version: 0 }),
(tx) => setTransactionMessageFeePayerSigner(sender, tx),
(tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
(tx) => appendTransactionMessageInstructions([transferInstruction], tx)
);
const signedTransaction =
await signTransactionMessageWithSigners(transactionMessage);
// Decode the messageBytes
const compiledTransactionMessage =
getCompiledTransactionMessageDecoder().decode(signedTransaction.messageBytes);
console.log(JSON.stringify(compiledTransactionMessage, null, 2));
Console
Click to execute the code.

Код нижче показує результат виконання попередніх фрагментів коду. Формат відрізняється між SDK, але зверніть увагу, що кожна інструкція містить однакову необхідну інформацію.

{
"version": 0,
"header": {
"numSignerAccounts": 1,
"numReadonlySignerAccounts": 0,
"numReadonlyNonSignerAccounts": 1
},
"staticAccounts": [
"HoCy8p5xxDDYTYWEbQZasEjVNM5rxvidx8AfyqA4ywBa",
"5T388jBjovy7d8mQ3emHxMDTbUF8b7nWvAnSiP3EAdFL",
"11111111111111111111111111111111"
],
"lifetimeToken": "EGCWPUEXhqHJWYBfDirq3mHZb4qDpATmYqBZMBy9TBC1",
"instructions": [
{
"programAddressIndex": 2,
"accountIndices": [0, 1],
"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
}
}
]
}

Отримання деталей транзакції

Після відправки отримайте деталі транзакції, використовуючи підпис транзакції та RPC-метод getTransaction.

Ви також можете знайти транзакцію за допомогою Solana Explorer.

Transaction Data
{
"blockTime": 1745196488,
"meta": {
"computeUnitsConsumed": 150,
"err": null,
"fee": 5000,
"innerInstructions": [],
"loadedAddresses": {
"readonly": [],
"writable": []
},
"logMessages": [
"Program 11111111111111111111111111111111 invoke [1]",
"Program 11111111111111111111111111111111 success"
],
"postBalances": [989995000, 10000000, 1],
"postTokenBalances": [],
"preBalances": [1000000000, 0, 1],
"preTokenBalances": [],
"rewards": [],
"status": {
"Ok": null
}
},
"slot": 13049,
"transaction": {
"message": {
"header": {
"numReadonlySignedAccounts": 0,
"numReadonlyUnsignedAccounts": 1,
"numRequiredSignatures": 1
},
"accountKeys": [
"8PLdpLxkuv9Nt8w3XcGXvNa663LXDjSrSNon4EK7QSjQ",
"7GLg7bqgLBv1HVWXKgWAm6YoPf1LoWnyWGABbgk487Ma",
"11111111111111111111111111111111"
],
"recentBlockhash": "7ZCxc2SDhzV2bYgEQqdxTpweYJkpwshVSDtXuY7uPtjf",
"instructions": [
{
"accounts": [0, 1],
"data": "3Bxs4NN8M2Yn4TLb",
"programIdIndex": 2,
"stackHeight": null
}
],
"indexToProgramIds": {}
},
"signatures": [
"3jUKrQp1UGq5ih6FTDUUt2kkqUfoG2o4kY5T1DoVHK2tXXDLdxJSXzuJGY4JPoRivgbi45U2bc7LZfMa6C4R3szX"
]
},
"version": "legacy"
}

Is this page helpful?

Керується

© 2026 Фонд Solana.
Всі права захищені.
Залишайтеся на зв'язку