Özet
Bir işlem, imzalar + bir mesajdan oluşur. Mesaj bir başlık, hesap adresleri, güncel blok hash'i ve derlenmiş talimatlar içerir. Maksimum serileştirilmiş boyut: 1.232 bayt.
Bir
Transaction
iki üst düzey alana sahiptir:
signatures: İmza dizisimessage: İşlenecek talimatların listesi dahil olmak üzere işlem bilgileri
pub struct Transaction {pub signatures: Vec<Signature>,pub message: Message,}
Bir işlemin iki bölümünü gösteren diyagram
Bir işlemin toplam serileştirilmiş boyutu
PACKET_DATA_SIZE
değerini (1.232 bayt) aşmamalıdır. Bu sınır, 1.280 bayta (IPv6 minimum MTU)
eşittir ve ağ başlıkları için 48 bayt (40 bayt IPv6 + 8 bayt fragment başlığı)
çıkarılmıştır. 1.232 bayt hem signatures dizisini hem de
message yapısını içerir.
İşlem formatını ve boyut sınırlarını gösteren diyagram
İmzalar
signatures alanı, kompakt kodlanmış bir
Signature
değerleri dizisidir. Her Signature, imzalayan hesabın özel anahtarıyla
imzalanmış, serileştirilmiş Message'ın 64 baytlık bir Ed25519 imzasıdır.
İşlemin talimatları tarafından referans verilen her
imzalayan hesap için bir imza gereklidir.
Her imza bir özel anahtar tarafından üretilir. Bu anahtarın nerede bulunduğu — yerel bir keypair, bulut tabanlı bir HSM veya KMS ya da yönetilen bir cüzdan hizmeti — bir üretim tasarım kararıdır. Bkz. Üretimde İmzalama.
Dizideki ilk imza, işlem temel ücretini ve önceliklendirme ücretini ödeyen hesap olan ücret ödeyiciye aittir. Bu ilk imza aynı zamanda ağ üzerinde işlemi aramak için kullanılan işlem kimliği olarak da hizmet eder. İşlem kimliği yaygın olarak işlem imzası olarak adlandırılır.
Ücret ödeyici gereksinimleri:
- Mesajdaki ilk hesap (indeks 0) olmalı ve imzalayan olmalıdır.
- System Program'a ait bir hesap veya nonce hesabı olmalıdır
(
validate_fee_payertarafından doğrulanır). rent_exempt_minimum + total_fee'yi karşılayacak kadar lamport bulundurmalıdır; aksi takdirde işlemInsufficientFundsForFeehatasıyla başarısız olur.
Mesaj
message alanı, işlemin yükünü içeren bir
Message
yapısıdır:
header: Mesaj başlığıaccount_keys: İşlemin talimatları tarafından gereken hesap adresleri dizisirecent_blockhash: İşlem için zaman damgası işlevi gören bir blok karmasıinstructions: Bir talimat dizisi
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>,}
Başlık
header alanı, account_keys dizisini izin gruplarına ayıran üç u8 alanına
sahip bir
MessageHeader
yapısıdır:
num_required_signatures: İşlem tarafından gereken toplam imza sayısı.num_readonly_signed_accounts: Salt okunur olan imzalı hesap sayısı.num_readonly_unsigned_accounts: Salt okunur olan imzasız hesap sayısı.
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,}
Eski ve sürümlü mesaj önekleri
Eski bir işlem mesajında, ilk mesaj baytı num_required_signatures olup
ardından diğer iki MessageHeader baytı gelir. Sürümlü bir işlem mesajında
ise ilk bayt bir sürüm önekidir; üç baytlık MessageHeader bu önekten hemen
sonra başlar. Tam v0 mesaj düzeni için bkz. sürümlü
işlemler.
Mesaj başlığının üç bölümünü gösteren diyagram
Hesap adresleri
account_keys
alanı, sıkıştırılmış kodlanmış bir genel anahtar dizisidir. Her giriş, işlemin
talimatlarından en az biri tarafından kullanılan bir hesabı tanımlar. Dizi, tüm
hesapları içermeli ve şu katı sıralamayı izlemelidir:
- İmzalayan + Yazılabilir
- İmzalayan + Salt okunur
- İmzalamayan + Yazılabilir
- İmzalamayan + Salt okunur
Bu katı sıralama, account_keys dizisinin, mesajın header
bölümündeki üç sayımla birleştirilerek her hesap için izinlerin, hesap başına
meta veri bayrakları saklanmadan belirlenmesine olanak tanır. Başlık
sayımları, diziyi yukarıda listelenen dört izin grubuna böler.
Hesap adresleri dizisinin sırasını gösteren diyagram
Son blok karması
recent_blockhash alanı, iki amaca hizmet eden 32 baytlık bir karmadır:
- Zaman damgası: işlemin yakın zamanda oluşturulduğunu kanıtlar.
- Tekilleştirme: aynı işlemin iki kez işlenmesini önler.
Bir blok karması 150 slot sonra geçerliliğini yitirir. İşlem ulaştığında blok
karması artık geçerli değilse, geçerli bir
dayanıklı nonce işlemi olmadığı sürece
BlockhashNotFound hatasıyla reddedilir.
getLatestBlockhash RPC yöntemi, geçerli
blok karmasını ve blok karmasının geçerli olacağı son blok yüksekliğini
almanıza olanak tanır.
Talimatlar
instructions
alanı, kompakt kodlanmış bir
CompiledInstruction
yapı dizisidir. Her CompiledInstruction, hesaplara tam public key yerine
account_keys dizisindeki indeks üzerinden başvurur. Şunları içerir:
program_id_index: Çağrılacak programı tanımlayanaccount_keysiçindeki indeks.accounts: Programa iletilecek hesapları belirtenaccount_keysiçindeki indeks dizisi.data: Talimat ayrımcısını ve serileştirilmiş argümanları içeren bayt dizisi.
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>,}
Kompakt Talimat Dizisi
İşlem ikili biçimi
İşlemler, kompakt bir kodlama şeması kullanılarak serileştirilir. Tüm değişken uzunluklu diziler (imzalar, hesap anahtarları, talimatlar) compact-u16 uzunluk kodlamasıyla öneklenir. Bu biçim, 0-127 arasındaki değerler için 1 bayt, daha büyük değerler için 2-3 bayt kullanır.
Eski işlem düzeni (aktarım üzerinde):
| Alan | Boyut | Açıklama |
|---|---|---|
num_signatures | 1-3 bayt (compact-u16) | İmza sayısı |
signatures | num_signatures x 64 bayt | Ed25519 imzaları |
num_required_signatures | 1 bayt | MessageHeader alan 1 |
num_readonly_signed | 1 bayt | MessageHeader alan 2 |
num_readonly_unsigned | 1 bayt | MessageHeader alan 3 |
num_account_keys | 1-3 bayt (compact-u16) | Statik hesap anahtarı sayısı |
account_keys | num_account_keys x 32 bayt | Public key'ler |
recent_blockhash | 32 bayt | Blockhash |
num_instructions | 1-3 bayt (compact-u16) | Talimat sayısı |
instructions | değişken | Derlenmiş talimat dizisi |
Her derlenmiş talimat şu şekilde serileştirilir:
| Alan | Boyut | Açıklama |
|---|---|---|
program_id_index | 1 bayt | Hesap anahtarları içindeki indeks |
num_accounts | 1-3 bayt (compact-u16) | Hesap indeksi sayısı |
account_indices | num_accounts x 1 bayt | Hesap anahtarı indeksleri |
data_len | 1-3 bayt (compact-u16) | instruction data uzunluğu |
data | data_len bayt | Opak instruction data |
Boyut hesaplama
PACKET_DATA_SIZE = 1.232 bayt olduğunda, kullanılabilir alan
hesaplanabilir:
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
Örnek: SOL transfer işlemi
Aşağıdaki diyagram, işlemler ve talimatların kullanıcıların ağla etkileşime girmesine nasıl olanak tanıdığını göstermektedir. Bu örnekte SOL, bir hesaptan diğerine aktarılmaktadır.
Gönderici hesabının meta verisi, işlemi imzalaması gerektiğini belirtir. Bu, System Program'ın lamport düşmesine olanak tanır. lamport bakiyelerinin değişebilmesi için hem gönderici hem de alıcı hesapların yazılabilir olması gerekir. Bu talimatı yürütmek için göndericinin cüzdanı, kendi imzasını ve SOL transfer talimatını içeren mesajı barındıran işlemi gönderir.
SOL transfer diyagramı
İşlem gönderildikten sonra System Program, transfer talimatını işler ve her iki hesabın lamport bakiyesini günceller.
SOL transfer süreci diyagramı
SOL göndermeden önce alıcıyı doğrulayın
Bir System Program transferi, herhangi bir hesaba lamport ekler. Alıcının SOL'u geri çıkarabildiğine dair protokol düzeyinde bir kontrol yoktur. lamport yalnızca hesabın sahibi olan program tarafından taşınabilir; dolayısıyla SOL'u bir token mint'e, bir programa veya kontrolünüzde olmayan bir PDA'ya göndermek kalıcı fon kaybı riskini beraberinde getirir — bunları yalnızca sahibi olan program tarafından belirlenen bir yetki geri alabilir. Bir token account'a gönderilen SOL yalnızca o hesabın sahibi tarafından geri alınabilir; gönderici tarafından asla alınamaz.
SPL token transferleri kısmen kendini koruyacak biçimde tasarlanmıştır: Token Program, hesapları beklenen mint ile eşleşmeyen bir transferi reddeder. Yerel SOL transferlerinde böyle bir güvence yoktur; bu nedenle gönderici imzalamadan önce alıcıyı doğrulamalıdır. Tam sınıflandırma mantığı için Adresi Doğrula sayfasına bakın.
Aşağıdaki örnek, yukarıdaki diyagramlarla ilgili kodu göstermektedir. System
Program'ın
transfer fonksiyonuna
bakın.
import { createClient, generateKeyPairSigner, lamports } from "@solana/kit";import { solanaRpc, rpcAirdrop } from "@solana/kit-plugin-rpc";import { generatedPayer, airdropPayer } from "@solana/kit-plugin-signer";import { systemProgram } from "@solana-program/system";const client = await createClient().use(generatedPayer()).use(solanaRpc({rpcUrl: "http://localhost:8899",rpcSubscriptionsUrl: "ws://localhost:8900"})).use(rpcAirdrop()).use(airdropPayer(lamports(1_000_000_000n))).use(systemProgram());const sender = client.payer;const recipient = await generateKeyPairSigner();const LAMPORTS_PER_SOL = 1_000_000_000n;const transferAmount = lamports(LAMPORTS_PER_SOL / 100n); // 0.01 SOL// Check balance before transferconst { value: preBalance1 } = await client.rpc.getBalance(sender.address).send();const { value: preBalance2 } = await client.rpc.getBalance(recipient.address).send();// Create a transfer instruction for transferring SOL from sender to recipientconst transferInstruction = client.system.instructions.transferSol({source: sender,destination: recipient.address,amount: transferAmount // 0.01 SOL in lamports});const transactionSignature = await client.sendTransaction([transferInstruction]);// Check balance after transferconst { value: postBalance1 } = await client.rpc.getBalance(sender.address).send();const { value: postBalance2 } = await client.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.context.signature);
Aşağıdaki örnek, tek bir SOL transfer talimatı içeren bir işlemin yapısını göstermektedir.
import {createClient,generateKeyPairSigner,lamports,createTransactionMessage,setTransactionMessageFeePayerSigner,setTransactionMessageLifetimeUsingBlockhash,appendTransactionMessageInstructions,pipe,signTransactionMessageWithSigners,getCompiledTransactionMessageDecoder} from "@solana/kit";import { solanaRpc, rpcAirdrop } from "@solana/kit-plugin-rpc";import { generatedPayer, airdropPayer } from "@solana/kit-plugin-signer";import { systemProgram } from "@solana-program/system";const client = await createClient().use(generatedPayer()).use(solanaRpc({rpcUrl: "http://localhost:8899",rpcSubscriptionsUrl: "ws://localhost:8900"})).use(rpcAirdrop()).use(airdropPayer(lamports(1_000_000_000n))).use(systemProgram());const { value: latestBlockhash } = await client.rpc.getLatestBlockhash().send();const sender = client.payer;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 = client.system.instructions.transferSol({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));
Aşağıdaki kod, önceki kod parçacıklarının çıktısını göstermektedir. Biçim SDK'lar arasında farklılık gösterse de her talimatın aynı zorunlu bilgileri içerdiğine dikkat edin.
{"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}}]}
Transferden önce alıcıyı doğrulayın
SOL transferi herhangi bir hesaba başarıyla gerçekleşebildiğinden, imzalamadan önce alıcıyı kontrol edin. Hesabı çekip yalnızca bir System Program cüzdanına (veya fonlanmamış bir eğri üzerindeki adrese) gönderin; kontrolünüzde olmayan mint'leri, token account'ları, programları ve PDA'ları reddedin.
import {type Address,createSolanaRpc,fetchJsonParsedAccount,isOffCurveAddress} from "@solana/kit";const rpc = createSolanaRpc("https://api.mainnet-beta.solana.com");const SYSTEM_PROGRAM = "11111111111111111111111111111111" as Address;/*** Throws if `recipient` cannot safely receive native SOL.** Only System Program wallets (or unfunded on-curve addresses) are safe. Any* other account locks the lamports because no authority can debit them.*/async function assertSafeSolRecipient(recipient: Address): Promise<void> {const account = await fetchJsonParsedAccount(rpc, recipient);if (!account.exists) {// Off-curve = a PDA with no account; reject conservatively.if (isOffCurveAddress(recipient)) {throw new Error("Recipient is a PDA with no account; SOL would be locked");}// On-curve = an unfunded wallet, safe to fund.return;}if (account.programAddress !== SYSTEM_PROGRAM) {throw new Error(`Recipient is owned by ${account.programAddress}, not a wallet; SOL would be locked`);}}// A wallet: safe.await assertSafeSolRecipient("H8sMJSCQxfKiFTCfDR3DUMLPwcRbM61LGFJ8N4dK3WjS" as Address);// The USDC mint: rejected before any SOL leaves the sender.await assertSafeSolRecipient("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" as Address);
Bu kod parçacığı yalnızca native SOL alıcılarını kontrol eder. SPL token gönderimleriyle de ilgilenen tam sınıflandırma için (token account'lar, ATA'lar, Token-2022), bkz. Adres Doğrulama.
İşlem ayrıntılarını getirme
Gönderimin ardından, işlem imzasını ve getTransaction RPC yöntemini kullanarak işlem ayrıntılarını alın.
İşlemi Solana Explorer kullanarak da bulabilirsiniz.
{"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"}
Ham yanıt, hesapları dizine göre tanımlar ve iç (CPI) talimatlarını kodlanmış blob'lar olarak depolar. Bunları adreslere çözümlemek ve tam talimat ağacında gezinmek için bkz. İşlem İncelemesi.
Is this page helpful?