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ówmessage: Informacje o transakcji, w tym lista instrukcji do przetworzenia
pub struct Transaction {pub signatures: Vec<Signature>,pub message: Message,}
Diagram 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 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łędemInsufficientFundsForFee.
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 transakcjirecent_blockhash: Blockhash, który pełni rolę znacznika czasu dla transakcjiinstructions: Tablica instrukcji
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.
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ś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ść:
- Podpisujący + z prawem zapisu
- Podpisujący + tylko do odczytu
- Niepodpisujący + z prawem zapisu
- Niepodpisujący + tylko do odczytu
Diagram przedstawiający kolejność tablicy adresów kont
Ostatni blockhash
Pole recent_blockhash to 32-bajtowy hash, który pełni dwie funkcje:
- Znacznik czasu: potwierdza, że transakcja została utworzona niedawno.
- 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:
program_id_index: Indeks w tablicyaccount_keysidentyfikujący program do wywołania.accounts: Tablica indeksów waccount_keysokreślająca konta przekazywane do programu.data: Tablica bajtów zawierająca dyskryminator instrukcji oraz zserializowane argumenty.
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 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):
| Pole | Rozmiar | Opis |
|---|---|---|
num_signatures | 1-3 bajty (compact-u16) | Liczba podpisów |
signatures | num_signatures x 64 bajty | Podpisy Ed25519 |
num_required_signatures | 1 bajt | Pole MessageHeader 1 |
num_readonly_signed | 1 bajt | Pole MessageHeader 2 |
num_readonly_unsigned | 1 bajt | Pole MessageHeader 3 |
num_account_keys | 1-3 bajty (compact-u16) | Liczba statycznych kluczy kont |
account_keys | num_account_keys x 32 bajty | Klucze publiczne |
recent_blockhash | 32 bajty | Blockhash |
num_instructions | 1-3 bajty (compact-u16) | Liczba instrukcji |
instructions | zmienna | Tablica skomplikowanych instrukcji |
Każda skompilowana instrukcja jest serializowana w następujący sposób:
| Pole | Rozmiar | Opis |
|---|---|---|
program_id_index | 1 bajt | Indeks w kluczach kont |
num_accounts | 1–3 bajty (compact-u16) | Liczba indeksów kont |
account_indices | num_accounts × 1 bajt | Indeksy kluczy kont |
data_len | 1–3 bajty (compact-u16) | Długość instruction data |
data | data_len bajtów | Opaque 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 SOL
Po wysłaniu transakcji System Program przetwarza instrukcję transferu i aktualizuje saldo lamportów na obu kontach.
Diagram 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 clusterconst rpc = createSolanaRpc("http://localhost:8899");const rpcSubscriptions = createSolanaRpcSubscriptions("ws://localhost:8900");// Generate sender and recipient keypairsconst 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 airdropawait airdropFactory({ rpc, rpcSubscriptions })({recipientAddress: sender.address,lamports: lamports(LAMPORTS_PER_SOL), // 1 SOLcommitment: "confirmed"});// Check balance before transferconst { 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 recipientconst transferInstruction = getTransferSolInstruction({source: sender,destination: recipient.address,amount: transferAmount // 0.01 SOL in lamports});// Add the transfer instruction to a new transactionconst { 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 networkconst signedTransaction =await signTransactionMessageWithSigners(transactionMessage);await sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions })(signedTransaction,{ commitment: "confirmed" });const transactionSignature = getSignatureFromTransaction(signedTransaction);// Check balance after transferconst { 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);
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 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});// Create transaction messageconst transactionMessage = pipe(createTransactionMessage({ version: 0 }),(tx) => setTransactionMessageFeePayerSigner(sender, tx),(tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),(tx) => appendTransactionMessageInstructions([transferInstruction], tx));const signedTransaction =await signTransactionMessageWithSigners(transactionMessage);// Decode the messageBytesconst compiledTransactionMessage =getCompiledTransactionMessageDecoder().decode(signedTransaction.messageBytes);console.log(JSON.stringify(compiledTransactionMessage, null, 2));
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.
{"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?