Struktura transakcji

Podsumowanie

Transakcja składa się z podpisów oraz wiadomości. Wiadomość zawiera nagłówek, adresy kont, ostatni blockhash i skompilowane instrukcje. Maksymalny rozmiar po serializacji: 1 232 bajty.

A Transaction posiada dwa główne pola:

  • signatures: Tablica podpisów
  • message: Informacje o transakcji, w tym lista instrukcji do przetworzenia
Transaction
pub struct Transaction {
pub signatures: Vec<Signature>,
pub message: Message,
}

Diagram przedstawiający dwie części transakcjiDiagram przedstawiający dwie części transakcji

Całkowity rozmiar transakcji po serializacji nie może przekroczyć PACKET_DATA_SIZE (1 232 bajtów). Ten limit to 1 280 bajtów (minimalny MTU IPv6) minus 48 bajtów na nagłówki sieciowe (40 bajtów IPv6 + 8 bajtów nagłówka fragmentacji). Te 1 232 bajty obejmują zarówno tablicę signatures, jak i strukturę message.

Diagram przedstawiający format transakcji i limity rozmiaruDiagram przedstawiający format transakcji i limity rozmiaru

Podpisy

Pole signatures to tablica zakodowana w formacie compact, zawierająca wartości Signature. Każdy Signature to 64-bajtowy podpis Ed25519 serializowanego Message, podpisany prywatnym kluczem konta podpisującego. Jeden podpis jest wymagany dla każdego konta podpisującego wskazanego w instrukcjach transakcji.

Pierwszy podpis w tablicy należy do fee payera, czyli konta, które opłaca bazową opłatę i opłatę za priorytet za transakcję. Ten pierwszy podpis służy również jako ID transakcji, wykorzystywane do wyszukiwania transakcji w sieci. ID transakcji jest często określane jako podpis transakcji.

Wymagania dotyczące płatnika opłat:

  • Musi być pierwszym kontem w wiadomości (indeks 0) i podpisującym.
  • Musi być kontem należącym do System Program lub kontem nonce (weryfikowane przez validate_fee_payer).
  • Musi posiadać wystarczającą liczbę lamportów, aby pokryć rent_exempt_minimum + total_fee; w przeciwnym razie transakcja zakończy się błędem InsufficientFundsForFee.

Wiadomość

Pole message to Message struktura zawierająca ładunek transakcji:

  • header: Nagłówek wiadomości (header)
  • account_keys: Tablica adresów kont wymaganych przez instrukcje transakcji
  • recent_blockhash: Blockhash, który pełni rolę znacznika czasu dla transakcji
  • instructions: Tablica instrukcji
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>,
}

Nagłówek

Pole header to MessageHeader struktura z trzema polami u8, które dzielą tablicę account_keys na grupy uprawnień:

  • num_required_signatures: Całkowita liczba podpisów wymaganych przez transakcję.
  • num_readonly_signed_accounts: Liczba podpisanych kont, które są tylko do odczytu.
  • num_readonly_unsigned_accounts: Liczba niepodpisanych kont, które są tylko do odczytu.
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,
}

Diagram przedstawiający trzy części nagłówka wiadomościDiagram przedstawiający trzy części nagłówka wiadomości

Adresy kont

Pole account_keys to tablica kluczy publicznych zakodowana w formacie compact. Każdy wpis identyfikuje konto używane przez co najmniej jedną z instrukcji transakcji. Tablica musi zawierać każde konto i zachować następującą, ścisłą kolejność:

  1. Podpisujący + z prawem zapisu
  2. Podpisujący + tylko do odczytu
  3. Niepodpisujący + z prawem zapisu
  4. Niepodpisujący + tylko do odczytu

Ta ścisła kolejność pozwala połączyć tablicę account_keys z trzema licznikami w nagłówku wiadomości (header), aby określić uprawnienia dla każdego konta bez przechowywania metadanych dla każdego konta osobno. Liczniki nagłówka dzielą tablicę na cztery grupy uprawnień wymienione powyżej.

Diagram przedstawiający kolejność tablicy adresów kontDiagram przedstawiający kolejność tablicy adresów kont

Ostatni blockhash

Pole recent_blockhash to 32-bajtowy hash, który pełni dwie funkcje:

  1. Znacznik czasu: potwierdza, że transakcja została utworzona niedawno.
  2. Deduplikacja: zapobiega przetwarzaniu tej samej transakcji więcej niż raz.

Blockhash wygasa po 150 slotach. Jeśli blockhash nie jest już ważny w momencie dotarcia transakcji, zostaje ona odrzucona z BlockhashNotFound, chyba że jest to poprawna trwała transakcja z nonce.

Metoda RPC getLatestBlockhash pozwala uzyskać aktualny blockhash oraz ostatnią wysokość bloku, przy której blockhash będzie ważny.

Instrukcje

Pole instructions to tablica zakodowana w sposób kompaktowy struktur CompiledInstruction. Każda CompiledInstruction odwołuje się do kont przez indeks w tablicy account_keys zamiast pełnego klucza publicznego. Zawiera:

  1. program_id_index: Indeks w tablicy account_keys identyfikujący program do wywołania.
  2. accounts: Tablica indeksów w account_keys określająca konta przekazywane do programu.
  3. data: Tablica bajtów zawierająca dyskryminator instrukcji oraz zserializowane argumenty.
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>,
}

Kompaktowa tablica instrukcjiKompaktowa tablica instrukcji

Binarny format transakcji

Transakcje są serializowane przy użyciu kompaktowego schematu kodowania. Wszystkie tablice o zmiennej długości (podpisy, klucze kont, instrukcje) są poprzedzone długością zakodowaną jako compact-u16. Ten format używa 1 bajtu dla wartości 0-127 oraz 2-3 bajtów dla większych wartości.

Układ transakcji legacy (w transmisji):

PoleRozmiarOpis
num_signatures1-3 bajty (compact-u16)Liczba podpisów
signaturesnum_signatures x 64 bajtyPodpisy Ed25519
num_required_signatures1 bajtPole MessageHeader 1
num_readonly_signed1 bajtPole MessageHeader 2
num_readonly_unsigned1 bajtPole MessageHeader 3
num_account_keys1-3 bajty (compact-u16)Liczba statycznych kluczy kont
account_keysnum_account_keys x 32 bajtyKlucze publiczne
recent_blockhash32 bajtyBlockhash
num_instructions1-3 bajty (compact-u16)Liczba instrukcji
instructionszmiennaTablica skomplikowanych instrukcji

Każda skompilowana instrukcja jest serializowana w następujący sposób:

PoleRozmiarOpis
program_id_index1 bajtIndeks w kluczach kont
num_accounts1–3 bajty (compact-u16)Liczba indeksów kont
account_indicesnum_accounts × 1 bajtIndeksy kluczy kont
data_len1–3 bajty (compact-u16)Długość instruction data
datadata_len bajtówOpaque instruction data

Obliczanie rozmiaru

Zakładając, że PACKET_DATA_SIZE = 1 232 bajty, dostępna przestrzeń może być obliczona:

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

Przykład: transakcja transferu SOL

Poniższy diagram pokazuje, jak transakcje i instrukcje współpracują, aby umożliwić użytkownikom interakcję z siecią. W tym przykładzie SOL jest przesyłany z jednego konta na drugie.

Metadane konta nadawcy metadata wskazują, że musi ono podpisać transakcję. Pozwala to System Program na pobranie lamportów. Zarówno konto nadawcy, jak i odbiorcy muszą być zapisywalne, aby ich saldo lamportów mogło się zmienić. Aby wykonać tę instrukcję, portfel nadawcy wysyła transakcję zawierającą jego podpis oraz wiadomość z instrukcją transferu SOL.

Diagram transferu SOLDiagram transferu SOL

Po wysłaniu transakcji System Program przetwarza instrukcję transferu i aktualizuje saldo lamportów na obu kontach.

Diagram procesu transferu SOLDiagram procesu transferu SOL

Poniższy przykład pokazuje kod związany z powyższymi diagramami. Zobacz funkcję System Program transfer.

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.

Poniższy przykład pokazuje strukturę transakcji zawierającej pojedynczą instrukcję transferu 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.

Poniższy kod pokazuje wynik wcześniejszych fragmentów kodu. Format różni się w zależności od SDK, ale zwróć uwagę, że każda instrukcja zawiera te same wymagane informacje.

{
"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
}
}
]
}

Pobieranie szczegółów transakcji

Po przesłaniu pobierz szczegóły transakcji, używając sygnatury transakcji i metody RPC getTransaction.

Możesz także znaleźć transakcję za pomocą 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?

Spis treści

Edytuj stronę

Zarządzane przez

© 2026 Solana Foundation.
Wszelkie prawa zastrzeżone.
Bądź na bieżąco