بنية المعاملة

ملخص

تحتوي المعاملة على توقيعات + رسالة. تحتوي الرسالة على رأسية، وعناوين الحسابات، وتجزئة كتلة حديثة، وتعليمات مجمعة. الحد الأقصى للحجم المتسلسل: 1,232 بايت.

تحتوي Transaction على حقلين رئيسيين:

  • signatures: مصفوفة من التوقيعات
  • message: معلومات المعاملة، بما في ذلك قائمة التعليمات المراد معالجتها
Transaction
pub struct Transaction {
pub signatures: Vec<Signature>,
pub message: Message,
}

رسم تخطيطي يوضح الجزأين من المعاملةرسم تخطيطي يوضح الجزأين من المعاملة

يجب ألا يتجاوز الحجم الإجمالي المتسلسل للمعاملة PACKET_DATA_SIZE (1,232 بايت). يساوي هذا الحد 1,280 بايت (الحد الأدنى لـ IPv6 MTU) مطروحًا منه 48 بايت لرؤوس الشبكة (40 بايت IPv6 + 8 بايت رأسية الجزء). تشمل الـ 1,232 بايت كلاً من مصفوفة signatures وبنية message.

رسم تخطيطي يوضح تنسيق المعاملة وحدود الحجمرسم تخطيطي يوضح تنسيق المعاملة وحدود الحجم

التوقيعات

حقل signatures هو مصفوفة مشفرة بشكل مضغوط من قيم Signature. كل Signature هو توقيع Ed25519 بحجم 64 بايت لـ Message المتسلسل، موقع بالمفتاح الخاص لحساب الموقع. يلزم توقيع واحد لكل حساب موقع مشار إليه بواسطة تعليمات المعاملة.

ينتمي التوقيع الأول في المصفوفة إلى دافع الرسوم، وهو الحساب الذي يدفع الرسوم الأساسية ورسوم الأولوية للمعاملة. يعمل هذا التوقيع الأول أيضًا كـ معرف المعاملة، المستخدم للبحث عن المعاملة على الشبكة. يُشار عادةً إلى معرف المعاملة باسم توقيع المعاملة.

متطلبات دافع الرسوم:

  • يجب أن يكون الحساب الأول في الرسالة (الفهرس 0) وموقّعًا.
  • يجب أن يكون حسابًا مملوكًا لبرنامج النظام أو حساب nonce (يتم التحقق منه بواسطة validate_fee_payer).
  • يجب أن يحتوي على lamports كافية لتغطية rent_exempt_minimum + total_fee؛ وإلا فإن المعاملة تفشل مع InsufficientFundsForFee.

الرسالة

حقل message هو بنية Message تحتوي على حمولة المعاملة:

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 هو بنية 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 بايت يخدم غرضين:

  1. الطابع الزمني: يثبت أن المعاملة تم إنشاؤها مؤخراً.
  2. إزالة التكرار: يمنع معالجة نفس المعاملة مرتين.

ينتهي صلاحية blockhash بعد 150 فتحة. إذا لم يعد blockhash صالحاً عند وصول المعاملة، يتم رفضها بـ BlockhashNotFound، ما لم تكن معاملة nonce دائمة صالحة.

تتيح لك طريقة RPC getLatestBlockhash الحصول على blockhash الحالي وآخر ارتفاع كتلة سيكون عنده blockhash صالحاً.

التعليمات

حقل instructions هو مصفوفة مشفرة بشكل مضغوط من بنيات CompiledInstruction. كل CompiledInstruction يشير إلى الحسابات بواسطة الفهرس في مصفوفة account_keys بدلاً من المفتاح العام الكامل. يحتوي على:

  1. program_id_index: فهرس في account_keys يحدد البرنامج المراد استدعاؤه.
  2. accounts: مصفوفة من الفهارس في account_keys تحدد الحسابات التي سيتم تمريرها إلى البرنامج.
  3. data: مصفوفة بايت تحتوي على محدد التعليمات والمعاملات المسلسلة.
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 بايت للقيم 0-127 و 2-3 بايت للقيم الأكبر.

تخطيط المعاملة القديمة (على السلك):

الحقلالحجمالوصف
num_signatures1-3 بايت (compact-u16)عدد التوقيعات
signaturesnum_signatures × 64 بايتتوقيعات Ed25519
num_required_signatures1 بايتحقل MessageHeader 1
num_readonly_signed1 بايتحقل MessageHeader 2
num_readonly_unsigned1 بايتحقل MessageHeader 3
num_account_keys1-3 بايت (compact-u16)عدد مفاتيح الحسابات الثابتة
account_keysnum_account_keys × 32 بايتالمفاتيح العامة
recent_blockhash32 بايتBlockhash
num_instructions1-3 بايت (compact-u16)عدد التعليمات
instructionsمتغيرمصفوفة من التعليمات المجمعة

يتم تسلسل كل تعليمة مترجمة على النحو التالي:

الحقلالحجمالوصف
program_id_index1 بايتفهرس في مفاتيح الحسابات
num_accounts1-3 بايتات (compact-u16)عدد فهارس الحسابات
account_indicesnum_accounts x 1 بايتفهارس مفاتيح الحسابات
data_len1-3 بايتات (compact-u16)طول بيانات التعليمة
datadata_len بايتبيانات تعليمة غير شفافة

حساب الحجم

بمعرفة PACKET_DATA_SIZE = 1,232 بايت، يمكن حساب المساحة المتاحة:

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 من حساب إلى آخر.

تشير البيانات الوصفية لحساب المرسل إلى أنه يجب عليه التوقيع على المعاملة. وهذا يسمح لبرنامج النظام بخصم lamports. يجب أن يكون كل من حساب المرسل والمستلم قابلاً للكتابة، حتى يتمكن رصيد lamport الخاص بهما من التغيير. لتنفيذ هذه التعليمة، ترسل محفظة المرسل المعاملة التي تحتوي على توقيعها والرسالة التي تحتوي على تعليمة تحويل SOL.

رسم تخطيطي لتحويل SOLرسم تخطيطي لتحويل SOL

بعد إرسال المعاملة، يقوم برنامج النظام بمعالجة تعليمة التحويل وتحديث رصيد lamport لكلا الحسابين.

رسم تخطيطي لعملية تحويل SOLرسم تخطيطي لعملية تحويل SOL

يوضح المثال أدناه الكود المتعلق بالرسوم التخطيطية أعلاه. راجع دالة transfer الخاصة ببرنامج النظام.

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
}
}
]
}

جلب تفاصيل المعاملة

بعد الإرسال، استرجع تفاصيل المعاملة باستخدام توقيع المعاملة وطريقة getTransaction في RPC.

يمكنك أيضاً العثور على المعاملة باستخدام 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 مؤسسة سولانا.
جميع الحقوق محفوظة.
تواصل معنا