Δομή συναλλαγής

Περίληψη

Μια συναλλαγή περιέχει υπογραφές + ένα μήνυμα. Το μήνυμα περιέχει μια κεφαλίδα, διευθύνσεις λογαριασμών, πρόσφατο blockhash και μεταγλωττισμένες εντολές. Μέγιστο σειριοποιημένο μέγεθος: 1.232 bytes.

Μια Transaction έχει δύο πεδία ανώτατου επιπέδου:

  • signatures: Ένας πίνακας υπογραφών
  • message: Πληροφορίες συναλλαγής, συμπεριλαμβανομένης της λίστας εντολών που θα επεξεργαστούν
Transaction
pub struct Transaction {
pub signatures: Vec<Signature>,
pub message: Message,
}

Διάγραμμα που δείχνει τα δύο μέρη μιας συναλλαγήςΔιάγραμμα που δείχνει τα δύο μέρη μιας συναλλαγής

Το συνολικό σειριοποιημένο μέγεθος μιας συναλλαγής δεν πρέπει να υπερβαίνει τα PACKET_DATA_SIZE (1.232 bytes). Αυτό το όριο ισούται με 1.280 bytes (το ελάχιστο MTU του IPv6) μείον 48 bytes για κεφαλίδες δικτύου (40 bytes IPv6 + 8 bytes κεφαλίδα τμήματος). Τα 1.232 bytes περιλαμβάνουν τόσο τον πίνακα signatures όσο και τη δομή message.

Διάγραμμα που δείχνει τη μορφή συναλλαγής και τα όρια μεγέθουςΔιάγραμμα που δείχνει τη μορφή συναλλαγής και τα όρια μεγέθους

Υπογραφές

Το πεδίο signatures είναι ένας πίνακας με συμπαγή κωδικοποίηση από Signature τιμές. Κάθε Signature είναι μια υπογραφή Ed25519 64 bytes του σειριοποιημένου Message, υπογεγραμμένη με το ιδιωτικό κλειδί του λογαριασμού υπογράφοντος. Απαιτείται μία υπογραφή για κάθε λογαριασμό υπογράφοντος στον οποίο αναφέρονται οι εντολές της συναλλαγής.

Η πρώτη υπογραφή στον πίνακα ανήκει στον πληρωτή τέλους, τον λογαριασμό που πληρώνει το βασικό τέλος και το τέλος προτεραιότητας της συναλλαγής. Αυτή η πρώτη υπογραφή χρησιμεύει επίσης ως αναγνωριστικό συναλλαγής, που χρησιμοποιείται για την αναζήτηση της συναλλαγής στο δίκτυο. Το αναγνωριστικό συναλλαγής αναφέρεται συνήθως ως υπογραφή συναλλαγής.

Απαιτήσεις πληρωτή τέλους:

  • Πρέπει να είναι ο πρώτος λογαριασμός στο μήνυμα (δείκτης 0) και υπογράφων.
  • Πρέπει να είναι λογαριασμός που ανήκει στο System Program ή λογαριασμός nonce (επικυρώνεται από το validate_fee_payer).
  • Πρέπει να διαθέτει αρκετά lamports για να καλύψει το rent_exempt_minimum + total_fee· διαφορετικά η συναλλαγή αποτυγχάνει με InsufficientFundsForFee.

Μήνυμα

Το πεδίο message είναι ένα struct Message που περιέχει το payload της συναλλαγής:

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 είναι ένα struct 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 του μηνύματος για να προσδιοριστούν τα δικαιώματα για κάθε λογαριασμό χωρίς την αποθήκευση σημαιών μεταδεδομένων ανά λογαριασμό. Οι μετρητές της κεφαλίδας χωρίζουν τον πίνακα στις τέσσερις ομάδες δικαιωμάτων που αναφέρονται παραπάνω.

Διάγραμμα που δείχνει τη σειρά του πίνακα διευθύνσεων λογαριασμώνΔιάγραμμα που δείχνει τη σειρά του πίνακα διευθύνσεων λογαριασμών

Πρόσφατο blockhash

Το πεδίο recent_blockhash είναι ένα hash 32 bytes που εξυπηρετεί δύο σκοπούς:

  1. Χρονοσήμανση: αποδεικνύει ότι η συναλλαγή δημιουργήθηκε πρόσφατα.
  2. Αποκαταδίπλωση: αποτρέπει την επεξεργασία της ίδιας συναλλαγής δύο φορές.

Ένα blockhash λήγει μετά από 150 slots. Εάν το blockhash δεν είναι πλέον έγκυρο όταν φτάσει η συναλλαγή, απορρίπτεται με BlockhashNotFound, εκτός εάν είναι έγκυρη durable nonce συναλλαγή.

Η μέθοδος RPC getLatestBlockhash σας επιτρέπει να λάβετε το τρέχον blockhash και το τελευταίο ύψος block στο οποίο το blockhash θα είναι έγκυρο.

Οδηγίες

Το πεδίο instructions είναι ένας πίνακας με συμπαγή κωδικοποίηση από CompiledInstruction δομές. Κάθε CompiledInstruction αναφέρεται σε λογαριασμούς μέσω ευρετηρίου στον πίνακα account_keys αντί για πλήρες δημόσιο κλειδί. Περιέχει:

  1. program_id_index: Ευρετήριο στο account_keys που προσδιορίζει το πρόγραμμα προς κλήση.
  2. accounts: Πίνακας ευρετηρίων στο account_keys που καθορίζει τους λογαριασμούς προς μεταβίβαση στο πρόγραμμα.
  3. data: Πίνακας bytes που περιέχει τον διακριτή οδηγίας και τα σειριοποιημένα ορίσματα.
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 byte για τιμές 0-127 και 2-3 bytes για μεγαλύτερες τιμές.

Διάταξη legacy συναλλαγής (στο δίκτυο):

ΠεδίοΜέγεθοςΠεριγραφή
num_signatures1-3 bytes (compact-u16)Αριθμός υπογραφών
signaturesnum_signatures x 64 bytesΥπογραφές Ed25519
num_required_signatures1 byteΠεδίο 1 του MessageHeader
num_readonly_signed1 byteΠεδίο 2 του MessageHeader
num_readonly_unsigned1 byteΠεδίο 3 του MessageHeader
num_account_keys1-3 bytes (compact-u16)Αριθμός στατικών κλειδιών λογαριασμών
account_keysnum_account_keys x 32 bytesΔημόσια κλειδιά
recent_blockhash32 bytesBlockhash
num_instructions1-3 bytes (compact-u16)Αριθμός οδηγιών
instructionsμεταβλητόΠίνακας μεταγλωττισμένων οδηγιών

Κάθε μεταγλωττισμένη εντολή σειριοποιείται ως:

ΠεδίοΜέγεθοςΠεριγραφή
program_id_index1 byteΕυρετήριο στα κλειδιά λογαριασμών
num_accounts1-3 bytes (compact-u16)Αριθμός ευρετηρίων λογαριασμών
account_indicesnum_accounts x 1 byteΕυρετήρια κλειδιών λογαριασμών
data_len1-3 bytes (compact-u16)Μήκος δεδομένων εντολής
datadata_len bytesΑδιαφανή δεδομένα εντολής

Υπολογισμός μεγέθους

Δεδομένου ότι PACKET_DATA_SIZE = 1.232 bytes, ο διαθέσιμος χώρος μπορεί να υπολογιστεί:

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.
Με επιφύλαξη παντός δικαιώματος.
Συνδεθείτε