Transactiestructuur

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 handtekeningen
  • message: Transactie-informatie, inclusief de lijst met instructies die verwerkt moeten worden
Transaction
pub struct Transaction {
pub signatures: Vec<Signature>,
pub message: Message,
}

Diagram dat de twee delen van een transactie toontDiagram 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 toontDiagram 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_fee te dekken; anders mislukt de transactie met InsufficientFundsForFee.

Bericht

Het veld message is een Message struct die de payload van de transactie bevat:

  • header: De bericht header
  • account_keys: Een array van accountadressen die vereist zijn voor de instructies van de transactie
  • recent_blockhash: Een blockhash die fungeert als tijdstempel voor de transactie
  • instructions: Een array van instructies
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>,
}

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.
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 dat de drie delen van de berichtheader toontDiagram 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:

  1. Ondertekenaar + schrijfbaar
  2. Ondertekenaar + alleen-lezen
  3. Niet-ondertekenaar + schrijfbaar
  4. 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 toontDiagram dat de volgorde van de array met accountadressen toont

Recente blockhash

Het recent_blockhash veld is een 32-byte hash die twee doelen dient:

  1. Tijdstempel: bewijst dat de transactie recent is aangemaakt.
  2. 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:

  1. program_id_index: Index in account_keys die het aan te roepen programma identificeert.
  2. accounts: Array van indices in account_keys die de accounts specificeren die aan het programma worden doorgegeven.
  3. data: Byte-array met de instructie discriminator en geserialiseerde argumenten.
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>,
}

Compacte array van instructiesCompacte 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):

VeldGrootteBeschrijving
num_signatures1-3 bytes (compact-u16)Aantal handtekeningen
signaturesnum_signatures x 64 bytesEd25519 handtekeningen
num_required_signatures1 byteMessageHeader veld 1
num_readonly_signed1 byteMessageHeader veld 2
num_readonly_unsigned1 byteMessageHeader veld 3
num_account_keys1-3 bytes (compact-u16)Aantal statische accountsleutels
account_keysnum_account_keys x 32 bytesPublieke sleutels
recent_blockhash32 bytesBlockhash
num_instructions1-3 bytes (compact-u16)Aantal instructies
instructionsvariabelArray van gecompileerde instructies

Elke gecompileerde instructie wordt geserialiseerd als:

VeldGrootteBeschrijving
program_id_index1 byteIndex in accountsleutels
num_accounts1-3 bytes (compact-u16)Aantal accountindexen
account_indicesnum_accounts x 1 byteAccountsleutelindexen
data_len1-3 bytes (compact-u16)Lengte van instruction data
datadata_len bytesOndoorzichtige 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-overboekingsdiagramSOL-overboekingsdiagram

Nadat de transactie is verzonden, verwerkt het System Program de overboekingsinstructie en werkt het lamportsaldo van beide accounts bij.

SOL-overboekingsprocesdiagramSOL-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 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.

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 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.

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.

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?

Inhoudsopgave

Pagina Bewerken

Beheerd door

© 2026 Solana Foundation.
Alle rechten voorbehouden.
Blijf Verbonden