المعاملات والتعليمات

على سولانا، يرسل المستخدمون معاملات للتفاعل مع الشبكة. تحتوي المعاملات على واحدة أو أكثر من التعليمات التي تحدد العمليات المراد معالجتها. يتم تخزين منطق التنفيذ للتعليمات على البرامج المنشورة على شبكة سولانا، حيث يحدد كل برنامج مجموعته الخاصة من التعليمات.

فيما يلي تفاصيل أساسية حول معالجة معاملات سولانا:

  • إذا تضمنت المعاملة تعليمات متعددة، فسيتم تنفيذ التعليمات بالترتيب الذي تمت إضافتها به إلى المعاملة.
  • المعاملات "ذرية" - يجب معالجة جميع التعليمات بنجاح، وإلا فستفشل المعاملة بأكملها ولن تحدث أي تغييرات.

المعاملة هي في الأساس طلب لمعالجة تعليمة واحدة أو أكثر. يمكنك اعتبار المعاملة كمظروف يحتوي على نماذج. كل نموذج هو تعليمة تخبر الشبكة بما يجب فعله. إرسال المعاملة يشبه إرسال المظروف بالبريد لمعالجة النماذج.

معاملة مبسطةمعاملة مبسطة

النقاط الرئيسية

  • تتضمن معاملات سولانا تعليمات تستدعي البرامج على الشبكة.
  • المعاملات ذرية - إذا فشل أي تعليمة، تفشل المعاملة بأكملها ولا تحدث أي تغييرات.
  • تنفذ التعليمات في المعاملة بترتيب متسلسل.
  • حد حجم المعاملة هو 1232 بايت.
  • تتطلب كل تعليمة ثلاثة أنواع من المعلومات:
    1. عنوان البرنامج المراد استدعاؤه
    2. الحسابات التي تقرأ منها التعليمة أو تكتب إليها
    3. أي بيانات إضافية مطلوبة بواسطة التعليمة (مثل وسيطات الدالة)

مثال على تحويل SOL

يمثل الرسم البياني أدناه معاملة بتعليمة واحدة لتحويل SOL من المرسل إلى المستلم.

في سولانا، "المحافظ" هي حسابات مملوكة من قبل System Program. فقط مالك البرنامج يمكنه تغيير بيانات الحساب، لذلك يتطلب تحويل SOL إرسال معاملة لاستدعاء System Program.

تحويل SOLتحويل SOL

يجب على حساب المرسل التوقيع (is_signer) على المعاملة للسماح لـ System Program بخصم رصيد lamport الخاص به. يجب أن يكون حسابا المرسل والمستلم قابلين للكتابة (is_writable) لأن أرصدة lamport الخاصة بهما ستتغير.

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

عملية تحويل SOLعملية تحويل SOL

توضح الأمثلة أدناه كيفية إرسال معاملة تقوم بتحويل SOL من حساب إلى آخر. يمكنك الاطلاع على شفرة المصدر لتعليمة التحويل في 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.

غالبًا ما تقوم مكتبات العميل بتجريد تفاصيل بناء تعليمات البرنامج. إذا لم تكن هناك مكتبة متاحة، يمكنك بناء التعليمات يدويًا. هذا يتطلب منك معرفة تفاصيل تنفيذ التعليمات.

توضح الأمثلة أدناه كيفية بناء تعليمات التحويل يدويًا. علامة التبويب Expanded Instruction تعادل وظيفيًا علامة التبويب Instruction.

const transferAmount = 0.01; // 0.01 SOL
const transferInstruction = getTransferSolInstruction({
source: sender,
destination: recipient.address,
amount: transferAmount * LAMPORTS_PER_SOL
});

في الأقسام أدناه، سنستعرض تفاصيل المعاملات والتعليمات.

التعليمات

يمكن اعتبار التعليمة في برنامج سولانا بمثابة دالة عامة يمكن لأي شخص استدعاؤها باستخدام شبكة سولانا.

يمكنك التفكير في برنامج سولانا كخادم ويب مستضاف على شبكة سولانا، حيث كل تعليمة تشبه نقطة نهاية API عامة يمكن للمستخدمين استدعاؤها لأداء إجراءات محددة. استدعاء تعليمة يشبه إرسال طلب POST إلى نقطة نهاية API، مما يسمح للمستخدمين بتنفيذ المنطق التجاري للبرنامج.

لاستدعاء تعليمة برنامج على سولانا، تحتاج إلى إنشاء Instruction بثلاثة أجزاء من المعلومات:

  • معرّف البرنامج: عنوان البرنامج الذي يحتوي على المنطق التجاري للتعليمة التي يتم استدعاؤها.
  • الحسابات: قائمة بجميع الحسابات التي تقرأ منها التعليمة أو تكتب إليها.
  • بيانات التعليمة: مصفوفة بايت تحدد أي تعليمة يجب استدعاؤها في البرنامج وأي وسائط مطلوبة بواسطة التعليمة.
Instruction
pub struct Instruction {
/// Pubkey of the program that executes this instruction.
pub program_id: Pubkey,
/// Metadata describing accounts that should be passed to the program.
pub accounts: Vec<AccountMeta>,
/// Opaque data passed to the program for its own interpretation.
pub data: Vec<u8>,
}

تعليمة المعاملةتعليمة المعاملة

AccountMeta

عند إنشاء Instruction، يجب عليك توفير كل حساب مطلوب كـ AccountMeta. يحدد AccountMeta ما يلي:

  • pubkey: عنوان الحساب
  • is_signer: ما إذا كان يجب على الحساب التوقيع على المعاملة
  • is_writable: ما إذا كانت التعليمة تعدل بيانات الحساب
AccountMeta
pub struct AccountMeta {
/// An account's public key.
pub pubkey: Pubkey,
/// True if an `Instruction` requires a `Transaction` signature matching `pubkey`.
pub is_signer: bool,
/// True if the account data or metadata may be mutated during program execution.
pub is_writable: bool,
}

من خلال تحديد الحسابات التي تقرأ منها التعليمة أو تكتب إليها مسبقًا، يمكن تنفيذ المعاملات التي لا تعدل نفس الحسابات بشكل متوازٍ.

لمعرفة الحسابات التي تتطلبها التعليمة، بما في ذلك تلك التي يجب أن تكون قابلة للكتابة، أو للقراءة فقط، أو توقع المعاملة، يجب عليك الرجوع إلى تنفيذ التعليمة كما هو محدد بواسطة البرنامج.

في الواقع العملي، عادةً لا تحتاج إلى إنشاء Instruction يدويًا. معظم مطوري البرامج يوفرون مكتبات عميلة مع دوال مساعدة تقوم بإنشاء التعليمات نيابةً عنك.

بيانات الحساببيانات الحساب

مثال على بنية التعليمات

قم بتشغيل الأمثلة أدناه لمشاهدة بنية تعليمة تحويل SOL.

import { generateKeyPairSigner, lamports } from "@solana/kit";
import { getTransferSolInstruction } from "@solana-program/system";
// 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
});
console.log(JSON.stringify(transferInstruction, null, 2));
Console
Click to execute the code.

توضح الأمثلة التالية المخرجات من مقتطفات الشيفرة السابقة. قد يختلف التنسيق الدقيق اعتمادًا على مجموعة أدوات التطوير (SDK)، ولكن كل تعليمة في سولانا تتطلب المعلومات التالية:

  • معرّف البرنامج: عنوان البرنامج الذي سينفذ التعليمة.
  • الحسابات: قائمة بالحسابات المطلوبة بواسطة التعليمة. لكل حساب، يجب أن تحدد التعليمة عنوانه، وما إذا كان يجب عليه توقيع المعاملة، وما إذا كان سيتم الكتابة فيه.
  • البيانات: مخزن بايت يخبر البرنامج بالتعليمة المراد تنفيذها ويتضمن أي وسائط مطلوبة بواسطة التعليمة.
{
"accounts": [
{
"address": "Hu28vRMGWpQXN56eaE7jRiDDRRz3vCXEs7EKHRfL6bC",
"role": 3,
"signer": {
"address": "Hu28vRMGWpQXN56eaE7jRiDDRRz3vCXEs7EKHRfL6bC",
"keyPair": {
"privateKey": {},
"publicKey": {}
}
}
},
{
"address": "2mBY6CTgeyJNJDzo6d2Umipw2aGUquUA7hLdFttNEj7p",
"role": 1
}
],
"programAddress": "11111111111111111111111111111111",
"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
}
}

المعاملات

بعد إنشاء التعليمات التي تريد استدعاءها، الخطوة التالية هي إنشاء Transaction وإضافة التعليمات إلى المعاملة. تتكون معاملة سولانا من:

  1. التوقيعات: مصفوفة من التوقيعات من جميع الحسابات المطلوبة كموقعين للتعليمات في المعاملة. يتم إنشاء التوقيع عن طريق توقيع Message المعاملة باستخدام المفتاح الخاص للحساب.
  2. الرسالة: تتضمن رسالة المعاملة قائمة بالتعليمات التي سيتم معالجتها بشكل ذري.
Transaction
pub struct Transaction {
#[wasm_bindgen(skip)]
#[serde(with = "short_vec")]
pub signatures: Vec<Signature>,
#[wasm_bindgen(skip)]
pub 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>,
}

حجم المعاملة

معاملات سولانا لها حد حجم يبلغ 1232 بايت. يأتي هذا الحد من حجم وحدة الإرسال القصوى (MTU) لـ IPv6 البالغة 1280 بايت، ناقص 48 بايت لرؤوس الشبكة (40 بايت IPv6 + 8 بايت للرأس).

يجب أن يبقى الحجم الإجمالي للمعاملة (التوقيعات والرسالة) تحت هذا الحد ويشمل:

  • التوقيعات: 64 بايت لكل منها
  • الرسالة: الرأس (3 بايت)، مفاتيح الحساب (32 بايت لكل منها)، هاش الكتلة الأخيرة (32 بايت)، والتعليمات

تنسيق المعاملةتنسيق المعاملة

رأس الرسالة

يحدد رأس الرسالة أذونات الحساب في المعاملة. يعمل بالتزامن مع عناوين الحسابات المرتبة بشكل صارم لتحديد الحسابات التي تكون موقعة وتلك التي يمكن الكتابة عليها.

  1. عدد التوقيعات المطلوبة لجميع التعليمات في المعاملة.
  2. عدد الحسابات الموقعة التي هي للقراءة فقط.
  3. عدد الحسابات غير الموقعة التي هي للقراءة فقط.
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,
}

رأس الرسالةرأس الرسالة

تنسيق المصفوفة المضغوطة

المصفوفة المضغوطة في رسالة المعاملة هي مصفوفة مسلسلة بالتنسيق التالي:

  1. طول المصفوفة (مشفر كـ compact-u16)
  2. عناصر المصفوفة مدرجة واحدة تلو الأخرى

تنسيق المصفوفة المضغوطةتنسيق المصفوفة المضغوطة

يستخدم هذا التنسيق لترميز أطوال مصفوفات عناوين الحسابات والتعليمات في رسائل المعاملات.

مصفوفة عناوين الحسابات

تحتوي رسالة المعاملة على قائمة واحدة لجميع عناوين الحسابات المطلوبة بواسطة تعليماتها. تبدأ المصفوفة برقم compact-u16 يشير إلى عدد العناوين التي تحتويها.

لتوفير المساحة، لا تخزن المعاملة أذونات كل حساب بشكل فردي. بدلاً من ذلك، تعتمد على مزيج من MessageHeader و ترتيب صارم لعناوين الحسابات لتحديد الأذونات.

يتم دائمًا ترتيب العناوين بالطريقة التالية:

  1. الحسابات التي هي قابلة للكتابة وموقعة
  2. الحسابات التي هي للقراءة فقط وموقعة
  3. الحسابات التي هي قابلة للكتابة وغير موقعة
  4. الحسابات التي هي للقراءة فقط وغير موقعة

يوفر MessageHeader القيم المستخدمة لتحديد عدد الحسابات لكل مجموعة أذونات.

مصفوفة مضغوطة لعناوين الحساباتمصفوفة مضغوطة لعناوين الحسابات

البلوك هاش الأخير

تتطلب كل معاملة بلوك هاش حديث الذي يخدم غرضين:

  1. يعمل كطابع زمني لوقت إنشاء المعاملة
  2. يمنع المعاملات المكررة

ينتهي صلاحية البلوك هاش بعد 150 كتلة (حوالي دقيقة واحدة بافتراض أوقات كتل 400 مللي ثانية)، وبعد ذلك تعتبر المعاملة منتهية الصلاحية ولا يمكن معالجتها.

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

مصفوفة التعليمات

تحتوي رسالة المعاملة على مصفوفة من التعليمات في نوع CompiledInstruction. يتم تحويل التعليمات إلى هذا النوع عند إضافتها إلى معاملة.

مثل مصفوفة عناوين الحسابات في الرسالة، تبدأ بطول compact-u16 متبوعًا ببيانات التعليمة. تحتوي كل تعليمة على:

  1. مؤشر معرف البرنامج: مؤشر يشير إلى عنوان البرنامج في مصفوفة عناوين الحسابات. هذا يحدد البرنامج الذي يعالج التعليمة.
  2. مؤشرات الحسابات: مصفوفة من المؤشرات التي تشير إلى عناوين الحسابات المطلوبة لهذه التعليمة.
  3. instruction 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>,
}

مصفوفة مضغوطة من التعليماتمصفوفة مضغوطة من التعليمات

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

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

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

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?