Samenvatting
Een transactie heeft handtekeningen + een bericht. Het bericht bevat een header, accountadressen, recente blockhash en gecompileerde instructies. Maximale geserialiseerde grootte: 1.232 bytes.
Een
Transaction
heeft twee velden op het hoogste niveau:
signatures: Een array van handtekeningenmessage: Transactie-informatie, inclusief de lijst met instructies die verwerkt moeten worden
pub struct Transaction {pub signatures: Vec<Signature>,pub message: Message,}
Diagram dat de twee delen van een transactie toont
De totale geserialiseerde grootte van een transactie mag niet groter zijn dan
PACKET_DATA_SIZE
(1.232 bytes). Deze limiet is gelijk aan 1.280 bytes (de IPv6 minimum MTU) minus
48 bytes voor netwerkheaders (40 bytes IPv6 + 8 bytes fragmentheader). De 1.232
bytes omvatten zowel de signatures array als de
message struct.
Diagram dat het transactieformaat en groottelimieten toont
Handtekeningen
Het signatures veld is een compact-gecodeerde array van
Signature
waarden. Elke Signature is een 64-byte Ed25519 handtekening van het
geserialiseerde Message, ondertekend met de private key van het ondertekenende
account. Eén handtekening is vereist voor elk
ondertekenend account waarnaar wordt verwezen door de
instructies van de transactie.
De eerste handtekening in de array behoort toe aan de fee payer, het account dat de transactie basiskosten en prioriteringskosten betaalt. Deze eerste handtekening dient ook als de transactie-ID, gebruikt om de transactie op het netwerk op te zoeken. De transactie-ID wordt gewoonlijk de transactiehandtekening genoemd.
Vereisten voor de betaler van de vergoeding:
- Moet het eerste account in het bericht zijn (index 0) en een ondertekenaar.
- Moet een account zijn dat eigendom is van het System Program of een
nonce-account (gevalideerd door
validate_fee_payer). - Moet voldoende lamports bevatten om
rent_exempt_minimum + total_feete dekken; anders mislukt de transactie metInsufficientFundsForFee.
Bericht
Het veld message is een
Message
struct die de payload van de transactie bevat:
header: De bericht headeraccount_keys: Een array van accountadressen die vereist zijn voor de instructies van de transactierecent_blockhash: Een blockhash die fungeert als tijdstempel voor de transactieinstructions: Een array van instructies
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
Het veld header is een
MessageHeader
struct met drie u8 velden die de account_keys array verdelen in
toestemmingsgroepen:
num_required_signatures: Totaal aantal handtekeningen vereist voor de transactie.num_readonly_signed_accounts: Aantal ondertekende accounts die alleen-lezen zijn.num_readonly_unsigned_accounts: Aantal niet-ondertekende accounts die alleen-lezen zijn.
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 dat de drie delen van de berichtheader toont
Accountadressen
Het veld
account_keys
is een compact gecodeerde array van publieke sleutels. Elke entry identificeert
een account dat door ten minste één van de instructies van de transactie wordt
gebruikt. De array moet elk account bevatten en moet deze strikte volgorde
volgen:
- Ondertekenaar + schrijfbaar
- Ondertekenaar + alleen-lezen
- Niet-ondertekenaar + schrijfbaar
- Niet-ondertekenaar + alleen-lezen
Deze strikte volgorde maakt het mogelijk om de account_keys array te
combineren met de drie tellingen in de header van het bericht om
de toestemmingen voor elk account te bepalen zonder metadata-vlaggen per
account op te slaan. De header-tellingen verdelen de array in de vier
hierboven vermelde toestemmingsgroepen.
Diagram dat de volgorde van de array met accountadressen toont
Recente blockhash
Het recent_blockhash veld is een 32-byte hash die twee doelen dient:
- Tijdstempel: bewijst dat de transactie recent is aangemaakt.
- Deduplicatie: voorkomt dat dezelfde transactie twee keer wordt verwerkt.
Een blockhash verloopt na 150 slots. Als de blockhash niet meer geldig is
wanneer de transactie aankomt, wordt deze afgewezen met BlockhashNotFound,
tenzij het een geldige
durable nonce transactie is.
De getLatestBlockhash RPC-methode stelt
je in staat om de huidige blockhash en laatste blokhoogte te verkrijgen waarop
de blockhash geldig zal zijn.
Instructies
Het
instructions
veld is een compact-gecodeerde array van
CompiledInstruction
structuren. Elke CompiledInstruction verwijst naar accounts via index in
de account_keys array in plaats van via volledige publieke sleutel. Het bevat:
program_id_index: Index inaccount_keysdie het aan te roepen programma identificeert.accounts: Array van indices inaccount_keysdie de accounts specificeren die aan het programma worden doorgegeven.data: Byte-array met de instructie discriminator en geserialiseerde argumenten.
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>,}
Compacte array van instructies
Binair formaat van transacties
Transacties worden geserialiseerd met behulp van een compact coderingsschema. Alle arrays met variabele lengte (handtekeningen, accountsleutels, instructies) worden voorafgegaan door een compact-u16 lengtecodering. Dit formaat gebruikt 1 byte voor waarden 0-127 en 2-3 bytes voor grotere waarden.
Legacy transactie-indeling (over de lijn):
| Veld | Grootte | Beschrijving |
|---|---|---|
num_signatures | 1-3 bytes (compact-u16) | Aantal handtekeningen |
signatures | num_signatures x 64 bytes | Ed25519 handtekeningen |
num_required_signatures | 1 byte | MessageHeader veld 1 |
num_readonly_signed | 1 byte | MessageHeader veld 2 |
num_readonly_unsigned | 1 byte | MessageHeader veld 3 |
num_account_keys | 1-3 bytes (compact-u16) | Aantal statische accountsleutels |
account_keys | num_account_keys x 32 bytes | Publieke sleutels |
recent_blockhash | 32 bytes | Blockhash |
num_instructions | 1-3 bytes (compact-u16) | Aantal instructies |
instructions | variabel | Array van gecompileerde instructies |
Elke gecompileerde instructie wordt geserialiseerd als:
| Veld | Grootte | Beschrijving |
|---|---|---|
program_id_index | 1 byte | Index in accountsleutels |
num_accounts | 1-3 bytes (compact-u16) | Aantal accountindexen |
account_indices | num_accounts x 1 byte | Accountsleutelindexen |
data_len | 1-3 bytes (compact-u16) | Lengte van instruction data |
data | data_len bytes | Ondoorzichtige instruction data |
Grootteberekening
Gegeven PACKET_DATA_SIZE = 1.232 bytes, kan de beschikbare ruimte worden
berekend:
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
Voorbeeld: SOL-overboekingstransactie
Het onderstaande diagram toont hoe transacties en instructies samenwerken om gebruikers in staat te stellen met het netwerk te communiceren. In dit voorbeeld wordt SOL van het ene account naar het andere overgemaakt.
De metadata van het afzenderaccount geeft aan dat het de transactie moet ondertekenen. Hierdoor kan het System Program lamports aftrekken. Zowel het afzender- als het ontvangeraccount moeten schrijfbaar zijn, om hun lamportsaldo te kunnen wijzigen. Om deze instructie uit te voeren, stuurt de portemonnee van de afzender de transactie met zijn handtekening en het bericht dat de SOL-overboekingsinstructie bevat.
SOL-overboekingsdiagram
Nadat de transactie is verzonden, verwerkt het System Program de overboekingsinstructie en werkt het lamportsaldo van beide accounts bij.
SOL-overboekingsprocesdiagram
Het onderstaande voorbeeld toont de code die relevant is voor de bovenstaande
diagrammen. Zie de
transfer-functie
van het 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 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);
Het volgende voorbeeld toont de structuur van een transactie die een enkele SOL-overboekingsinstructie bevat.
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));
De onderstaande code toont de output van de vorige codefragmenten. Het formaat verschilt per SDK, maar merk op dat elke instructie dezelfde vereiste informatie bevat.
{"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}}]}
Transactiedetails ophalen
Haal na het indienen de transactiedetails op met behulp van de transactiehandtekening en de getTransaction RPC-methode.
Je kunt de transactie ook vinden via 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?