المعاملات والتعليمات
على سولانا، يرسل المستخدمون معاملات للتفاعل مع الشبكة. تحتوي المعاملات على واحدة أو أكثر من التعليمات التي تحدد العمليات المراد معالجتها. يتم تخزين منطق التنفيذ للتعليمات على البرامج المنشورة على شبكة سولانا، حيث يحدد كل برنامج مجموعته الخاصة من التعليمات.
فيما يلي تفاصيل أساسية حول معالجة معاملات سولانا:
- إذا تضمنت المعاملة تعليمات متعددة، فسيتم تنفيذ التعليمات بالترتيب الذي تمت إضافتها به إلى المعاملة.
- المعاملات "ذرية" - يجب معالجة جميع التعليمات بنجاح، وإلا فستفشل المعاملة بأكملها ولن تحدث أي تغييرات.
المعاملة هي في الأساس طلب لمعالجة تعليمة واحدة أو أكثر.
معاملة مبسطة
المعاملة تشبه مظروفًا يحتوي على نماذج. كل نموذج هو تعليمة تخبر الشبكة بما يجب فعله. إرسال المعاملة يشبه إرسال المظروف بالبريد لمعالجة النماذج.
النقاط الرئيسية
- تتضمن معاملات سولانا تعليمات تستدعي برامج على الشبكة.
- المعاملات ذرية - إذا فشلت أي تعليمة، تفشل المعاملة بأكملها ولا تحدث أي تغييرات.
- يتم تنفيذ التعليمات في المعاملة بترتيب متسلسل.
- الحد الأقصى لحجم المعاملة هو 1232 بايت.
- تتطلب كل تعليمة ثلاث قطع من المعلومات:
- عنوان البرنامج المراد استدعاؤه
- الحسابات التي تقرأ منها التعليمة أو تكتب إليها
- أي بيانات إضافية مطلوبة بواسطة التعليمة (مثل وسيطات الدالة)
مثال على تحويل SOL
يمثل الرسم البياني أدناه معاملة تحتوي على تعليمة واحدة لتحويل SOL من المرسل إلى المستلم.
في سولانا، "المحافظ" هي حسابات مملوكة من قبل System Program. فقط مالك البرنامج يمكنه تغيير بيانات الحساب، لذلك يتطلب تحويل SOL إرسال معاملة لاستدعاء System Program.
تحويل SOL
يجب على حساب المرسل التوقيع (is_signer
) على المعاملة للسماح لـ System Program
بخصم رصيد اللامبورت الخاص به. يجب أن يكون حسابا المرسل والمستلم قابلين للكتابة
(is_writable
) لأن أرصدة اللامبورت الخاصة بهما ستتغير.
بعد إرسال المعاملة، يقوم System Program بمعالجة تعليمة التحويل. ثم يقوم System Program بتحديث أرصدة اللامبورت لكل من حساب المرسل والمستلم.
عملية تحويل SOL
توضح الأمثلة أدناه كيفية إرسال معاملة تقوم بتحويل SOL من حساب إلى آخر.
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);
غالبًا ما تقوم مكتبات العميل بتجريد التفاصيل الخاصة ببناء تعليمات البرنامج. إذا لم تكن هناك مكتبة متاحة، يمكنك بناء التعليمة يدويًا. هذا يتطلب منك معرفة تفاصيل تنفيذ التعليمة.
توضح الأمثلة أدناه كيفية بناء تعليمة التحويل يدويًا. علامة التبويب
Expanded Instruction
مكافئة وظيفيًا لعلامة التبويب Instruction
.
- كيت
const transferAmount = 0.01; // 0.01 SOLconst transferInstruction = getTransferSolInstruction({source: sender,destination: recipient.address,amount: transferAmount * LAMPORTS_PER_SOL});
- ليجاسي
const transferAmount = 0.01; // 0.01 SOLconst transferInstruction = SystemProgram.transfer({fromPubkey: sender.publicKey,toPubkey: receiver.publicKey,lamports: transferAmount * LAMPORTS_PER_SOL});
- راست
let transfer_amount = LAMPORTS_PER_SOL / 100; // 0.01 SOLlet transfer_instruction =system_instruction::transfer(&sender.pubkey(), &recipient.pubkey(), transfer_amount);
في الأقسام أدناه، سنتناول تفاصيل المعاملات والتعليمات.
التعليمات
يمكن اعتبار التعليمة على برنامج سولانا بمثابة دالة عامة يمكن لأي شخص استدعاؤها باستخدام شبكة سولانا.
يتطلب استدعاء تعليمة البرنامج ثلاث قطع أساسية من المعلومات:
- معرّف البرنامج: البرنامج الذي يحتوي على منطق التنفيذ للتعليمة
- الحسابات: قائمة الحسابات التي تحتاجها التعليمة
- instruction data: مصفوفة بايت تحدد التعليمة المراد استدعاؤها على البرنامج وأي وسائط مطلوبة بواسطة التعليمة
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
يجب توفير كل حساب مطلوب بواسطة التعليمة كـ AccountMeta الذي يحتوي على:
pubkey
: عنوان الحسابis_signer
: ما إذا كان يجب على الحساب التوقيع على المعاملةis_writable
: ما إذا كانت التعليمة تعدل بيانات الحساب
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,}
AccountMeta
من خلال تحديد الحسابات التي تقرأها التعليمة أو تكتب فيها مسبقًا، يمكن تنفيذ المعاملات التي لا تعدل نفس الحسابات بشكل متوازٍ.
مثال على بنية التعليمة
قم بتشغيل الأمثلة أدناه لمشاهدة بنية تعليمة تحويل SOL.
import { generateKeyPairSigner, lamports } from "@solana/kit";import { getTransferSolInstruction } from "@solana-program/system";// 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});console.log(JSON.stringify(transferInstruction, null, 2));
توضح الأمثلة التالية المخرجات من مقتطفات الشفرة السابقة. يختلف التنسيق الدقيق اعتمادًا على 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}}
المعاملات
تتكون معاملة سولانا من:
- التوقيعات: مصفوفة من التوقيعات المضمنة في المعاملة.
- الرسالة: قائمة بالتعليمات التي سيتم معالجتها بشكل ذري.
pub struct Transaction {#[wasm_bindgen(skip)]#[serde(with = "short_vec")]pub signatures: Vec<Signature>,#[wasm_bindgen(skip)]pub 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 بايت)، والتعليمات
تنسيق المعاملة
رأس الرسالة
يستخدم رأس الرسالة ثلاثة بايت لتحديد امتيازات الحساب.
- التوقيعات المطلوبة
- عدد حسابات القراءة فقط الموقعة
- عدد حسابات القراءة فقط غير الموقعة
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,}
ترويسة الرسالة
تنسيق المصفوفة المضغوطة
المصفوفة المضغوطة في رسالة المعاملة هي مصفوفة تم تسلسلها بالتنسيق التالي:
- طول المصفوفة (مشفر كـ compact-u16)
- عناصر المصفوفة مدرجة واحدة تلو الأخرى
تنسيق المصفوفة المضغوطة
يُستخدم هذا التنسيق لترميز أطوال مصفوفات عناوين الحسابات و التعليمات في رسائل المعاملات.
مصفوفة عناوين الحسابات
تحتوي رسالة المعاملة على مصفوفة من عناوين الحسابات المطلوبة بواسطة تعليماتها. تبدأ المصفوفة برقم compact-u16 يشير إلى عدد العناوين التي تحتويها. ثم يتم ترتيب العناوين حسب امتيازاتها، كما تحددها ترويسة الرسالة.
- الحسابات القابلة للكتابة والموقعة
- الحسابات للقراءة فقط والموقعة
- الحسابات القابلة للكتابة وغير الموقعة
- الحسابات للقراءة فقط وغير الموقعة
مصفوفة مضغوطة لعناوين الحسابات
هاش الكتلة الأخيرة
تتطلب كل معاملة هاش كتلة حديث الذي يخدم غرضين:
- يعمل كطابع زمني
- يمنع المعاملات المكررة
تنتهي صلاحية هاش الكتلة بعد 150 كتلة (حوالي دقيقة واحدة بافتراض أوقات كتلة 400 مللي ثانية)، وبعد ذلك لا يمكن معالجة المعاملة.
يمكنك استخدام طريقة RPC
getLatestBlockhash
للحصول على هاش الكتلة
الحالي وارتفاع الكتلة الأخيرة الذي سيكون فيه هاش الكتلة صالحًا. إليك مثال على
سولانا بلايجراوند.
مصفوفة التعليمات
تحتوي رسالة المعاملة على مصفوفة من التعليمات في نوع CompiledInstruction. يتم تحويل التعليمات إلى هذا النوع عند إضافتها إلى معاملة.
مثل مصفوفة عناوين الحسابات في الرسالة، تبدأ بطول compact-u16 متبوعًا ببيانات التعليمات. تحتوي كل تعليمة على:
- مؤشر معرّف البرنامج: مؤشر u8 يشير إلى عنوان البرنامج في مصفوفة عناوين الحسابات. هذا يحدد البرنامج الذي سيعالج التعليمات.
- مؤشرات الحسابات: مصفوفة من مؤشرات u8 تشير إلى عناوين الحسابات المطلوبة لهذه التعليمات.
- instruction data: مصفوفة بايت تحدد أي تعليمات يجب استدعاؤها على البرنامج وأي بيانات إضافية مطلوبة بواسطة التعليمات (مثل وسيطات الدالة).
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 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}}]}
عندما تقوم باسترجاع معاملة باستخدام توقيعها بعد إرسالها إلى الشبكة، ستتلقى استجابة بالبنية التالية.
يحتوي الحقل message
على الحقول التالية:
-
header
: يحدد امتيازات القراءة/الكتابة والتوقيع للعناوين في مصفوفةaccountKeys
-
accountKeys
: مصفوفة تحتوي على جميع عناوين الحسابات المستخدمة في تعليمات المعاملة -
recentBlockhash
: البلوكهاش المستخدم لوضع طابع زمني للمعاملة -
instructions
: مصفوفة من التعليمات المراد تنفيذها. كل منaccount
وprogramIdIndex
في التعليمات يشير إلى مصفوفةaccountKeys
بواسطة الفهرس. -
signatures
: مصفوفة تتضمن التوقيعات لجميع الحسابات المطلوبة كموقعين بواسطة التعليمات على المعاملة. يتم إنشاء التوقيع عن طريق توقيع رسالة المعاملة باستخدام المفتاح الخاص المقابل للحساب.
{"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?