Περίληψη
Μια συναλλαγή περιέχει υπογραφές + ένα μήνυμα. Το μήνυμα περιέχει μια κεφαλίδα, διευθύνσεις λογαριασμών, πρόσφατο blockhash και μεταγλωττισμένες εντολές. Μέγιστο σειριοποιημένο μέγεθος: 1.232 bytes.
Μια
Transaction
έχει δύο πεδία ανώτατου επιπέδου:
signatures: Ένας πίνακας υπογραφώνmessage: Πληροφορίες συναλλαγής, συμπεριλαμβανομένης της λίστας εντολών που θα επεξεργαστούν
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 της συναλλαγής:
header: Η κεφαλίδα του μηνύματοςaccount_keys: Ένας πίνακας διευθύνσεων λογαριασμών που απαιτούνται από τις οδηγίες της συναλλαγήςrecent_blockhash: Ένα blockhash που λειτουργεί ως χρονική σήμανση για τη συναλλαγήinstructions: Ένας πίνακας οδηγιών
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: Αριθμός μη υπογεγραμμένων λογαριασμών που είναι μόνο για ανάγνωση.
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
είναι ένας πίνακας δημόσιων κλειδιών με συμπαγή κωδικοποίηση. Κάθε καταχώρηση
προσδιορίζει έναν λογαριασμό που χρησιμοποιείται από τουλάχιστον μία από τις
οδηγίες της συναλλαγής. Ο πίνακας πρέπει να περιλαμβάνει κάθε λογαριασμό και
πρέπει να ακολουθεί αυτή την αυστηρή σειρά:
- Υπογράφων + εγγράψιμος
- Υπογράφων + μόνο για ανάγνωση
- Μη υπογράφων + εγγράψιμος
- Μη υπογράφων + μόνο για ανάγνωση
Αυτή η αυστηρή σειρά επιτρέπει στον πίνακα account_keys να συνδυαστεί με
τους τρεις μετρητές στην header του μηνύματος για να
προσδιοριστούν τα δικαιώματα για κάθε λογαριασμό χωρίς την αποθήκευση σημαιών
μεταδεδομένων ανά λογαριασμό. Οι μετρητές της κεφαλίδας χωρίζουν τον πίνακα
στις τέσσερις ομάδες δικαιωμάτων που αναφέρονται παραπάνω.
Διάγραμμα που δείχνει τη σειρά του πίνακα διευθύνσεων λογαριασμών
Πρόσφατο blockhash
Το πεδίο recent_blockhash είναι ένα hash 32 bytes που εξυπηρετεί δύο σκοπούς:
- Χρονοσήμανση: αποδεικνύει ότι η συναλλαγή δημιουργήθηκε πρόσφατα.
- Αποκαταδίπλωση: αποτρέπει την επεξεργασία της ίδιας συναλλαγής δύο φορές.
Ένα blockhash λήγει μετά από 150 slots. Εάν το blockhash δεν είναι πλέον έγκυρο
όταν φτάσει η συναλλαγή, απορρίπτεται με BlockhashNotFound, εκτός εάν
είναι έγκυρη durable nonce συναλλαγή.
Η μέθοδος RPC getLatestBlockhash σας
επιτρέπει να λάβετε το τρέχον blockhash και το τελευταίο ύψος block στο οποίο
το blockhash θα είναι έγκυρο.
Οδηγίες
Το πεδίο
instructions
είναι ένας πίνακας με συμπαγή κωδικοποίηση από
CompiledInstruction
δομές. Κάθε CompiledInstruction αναφέρεται σε λογαριασμούς μέσω ευρετηρίου
στον πίνακα account_keys αντί για πλήρες δημόσιο κλειδί. Περιέχει:
program_id_index: Ευρετήριο στοaccount_keysπου προσδιορίζει το πρόγραμμα προς κλήση.accounts: Πίνακας ευρετηρίων στοaccount_keysπου καθορίζει τους λογαριασμούς προς μεταβίβαση στο πρόγραμμα.data: Πίνακας bytes που περιέχει τον διακριτή οδηγίας και τα σειριοποιημένα ορίσματα.
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_signatures | 1-3 bytes (compact-u16) | Αριθμός υπογραφών |
signatures | num_signatures x 64 bytes | Υπογραφές Ed25519 |
num_required_signatures | 1 byte | Πεδίο 1 του MessageHeader |
num_readonly_signed | 1 byte | Πεδίο 2 του MessageHeader |
num_readonly_unsigned | 1 byte | Πεδίο 3 του MessageHeader |
num_account_keys | 1-3 bytes (compact-u16) | Αριθμός στατικών κλειδιών λογαριασμών |
account_keys | num_account_keys x 32 bytes | Δημόσια κλειδιά |
recent_blockhash | 32 bytes | Blockhash |
num_instructions | 1-3 bytes (compact-u16) | Αριθμός οδηγιών |
instructions | μεταβλητό | Πίνακας μεταγλωττισμένων οδηγιών |
Κάθε μεταγλωττισμένη εντολή σειριοποιείται ως:
| Πεδίο | Μέγεθος | Περιγραφή |
|---|---|---|
program_id_index | 1 byte | Ευρετήριο στα κλειδιά λογαριασμών |
num_accounts | 1-3 bytes (compact-u16) | Αριθμός ευρετηρίων λογαριασμών |
account_indices | num_accounts x 1 byte | Ευρετήρια κλειδιών λογαριασμών |
data_len | 1-3 bytes (compact-u16) | Μήκος δεδομένων εντολής |
data | data_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
Αφού σταλεί η συναλλαγή, το System Program επεξεργάζεται την εντολή μεταφοράς και ενημερώνει το υπόλοιπο lamports και των δύο λογαριασμών.
Διάγραμμα διαδικασίας μεταφοράς 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 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);
Το ακόλουθο παράδειγμα δείχνει τη δομή μιας συναλλαγής που περιέχει μία εντολή μεταφοράς 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));
Ο παρακάτω κώδικας δείχνει το αποτέλεσμα από τα προηγούμενα αποσπάσματα κώδικα. Η μορφή διαφέρει μεταξύ των 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.
{"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?