نموذج حساب سولانا
في سولانا، يتم تخزين جميع البيانات فيما يسمى "الحسابات". يمكنك التفكير في البيانات على سولانا كقاعدة بيانات عامة تحتوي على جدول واحد "الحسابات"، حيث كل إدخال في هذا الجدول هو "حساب". كل حساب في سولانا يشترك في نفس نوع الحساب الأساسي.
الحسابات
النقاط الرئيسية
- يمكن للحسابات تخزين ما يصل إلى 10MiB من البيانات، والتي تحتوي إما على رمز البرنامج القابل للتنفيذ أو حالة البرنامج.
- تتطلب الحسابات إيداع إيجار (rent) بوحدات lamport (SOL) يتناسب مع كمية البيانات المخزنة، ويمكنك استرداده بالكامل عند إغلاق الحساب.
- كل حساب له مالك برنامج. فقط البرنامج الذي يملك الحساب يمكنه تغيير بياناته أو خصم رصيد الـ lamport الخاص به. ولكن يمكن لأي شخص زيادة الرصيد.
- حسابات Sysvar هي حسابات خاصة تخزن حالة مجموعة الشبكة.
- حسابات البرامج (program account) تخزن الرمز القابل للتنفيذ للعقود الذكية.
- حسابات البيانات يتم إنشاؤها بواسطة البرامج لتخزين وإدارة حالة البرنامج.
الحساب
كل حساب على سولانا له عنوان فريد مكون من 32 بايت، غالبًا ما يظهر كسلسلة مشفرة
بنظام base58 (مثل 14grJpemFaf88c8tiVb77W7TYg2W3ir6pfkKz3YjhhZ5
).
تعمل العلاقة بين الحساب وعنوانه مثل زوج المفتاح والقيمة، حيث يكون العنوان هو المفتاح لتحديد موقع البيانات المقابلة على السلسلة للحساب. يعمل عنوان الحساب كـ "معرف فريد" لكل إدخال في جدول "الحسابات".
عنوان الحساب
تستخدم معظم حسابات سولانا مفتاح Ed25519 العام كعنوان لها.
import { generateKeyPairSigner } from "@solana/kit";// Kit does not enable extractable private keysconst keypairSigner = await generateKeyPairSigner();console.log(keypairSigner);
بينما يتم استخدام المفاتيح العامة بشكل شائع كعناوين للحسابات، تدعم سولانا أيضًا ميزة تسمى العناوين المشتقة من البرنامج (PDAs). العناوين المشتقة من البرنامج هي عناوين خاصة يمكنك اشتقاقها بشكل حتمي من معرف البرنامج ومدخلات اختيارية (seeds).
import { Address, getProgramDerivedAddress } from "@solana/kit";const programAddress = "11111111111111111111111111111111" as Address;const seeds = ["helloWorld"];const [pda, bump] = await getProgramDerivedAddress({programAddress,seeds});console.log(`PDA: ${pda}`);console.log(`Bump: ${bump}`);
نوع الحساب
الحسابات لها حجم أقصى يبلغ 10MiB وكل حساب على سولانا يشترك في نفس نوع الحساب الأساسي.
نوع الحساب
كل حساب على سولانا يحتوي على الحقول التالية.
pub struct Account {/// lamports in the accountpub lamports: u64,/// data held in this account#[cfg_attr(feature = "serde", serde(with = "serde_bytes"))]pub data: Vec<u8>,/// the program that owns this account. If executable, the program that loads this account.pub owner: Pubkey,/// this account's data contains a loaded program (and is now read-only)pub executable: bool,/// the epoch at which this account will next owe rentpub rent_epoch: Epoch,}
حقل لامبورت
رصيد الحساب بوحدة لامبورت، وهي أصغر وحدة من SOL (1 SOL = 1 مليار لامبورت). رصيد
SOL للحساب هو المبلغ الموجود في حقل lamports
محولًا إلى SOL.
يجب أن تحتوي حسابات سولانا على رصيد لامبورت أدنى يتناسب مع كمية البيانات المخزنة في الحساب (بالبايت). يسمى هذا الرصيد الأدنى "rent".
يمكن استرداد رصيد اللامبورت المخزن في الحساب بالكامل عند إغلاق الحساب.
حقل البيانات
مصفوفة بايت تخزن بيانات عشوائية للحساب. يُطلق على حقل البيانات عادةً "بيانات الحساب".
- بالنسبة لحسابات البرامج (العقود الذكية)، يحتوي هذا الحقل إما على رمز البرنامج القابل للتنفيذ نفسه أو عنوان حساب آخر يخزن رمز البرنامج القابل للتنفيذ.
- بالنسبة للحسابات غير القابلة للتنفيذ، فإنها تخزن عمومًا الحالة المقصود قراءتها.
تتضمن قراءة البيانات من حساب سولانا خطوتين:
- جلب الحساب باستخدام عنوانه (المفتاح العام)
- فك تشفير حقل بيانات الحساب من البايتات الخام إلى بنية البيانات المناسبة، والتي يحددها البرنامج الذي يملك الحساب
حقل المالك
معرف البرنامج (المفتاح العام) للبرنامج الذي يملك هذا الحساب.
كل حساب في سولانا له برنامج معين كمالك له. فقط البرنامج الذي يملك الحساب يمكنه تغيير بيانات الحساب أو خصم رصيد اللامبورت الخاص به.
تحدد التعليمات المعرفة في البرنامج كيفية تغيير بيانات الحساب ورصيد lamport.
حقل قابلية التنفيذ
يشير هذا الحقل إلى ما إذا كان الحساب برنامجًا قابلاً للتنفيذ.
- إذا كان
true
، فإن الحساب هو برنامج سولانا قابل للتنفيذ. - إذا كان
false
، فإن الحساب هو حساب بيانات يخزن الحالة.
بالنسبة للحسابات القابلة للتنفيذ، يحتوي حقل owner
على معرف برنامج المحمّل.
برامج المحمّل هي برامج مدمجة مسؤولة عن تحميل وإدارة حسابات البرامج القابلة
للتنفيذ.
حقل فترة الإيجار
حقل rent_epoch
هو حقل قديم لم يعد مستخدمًا.
في الأصل، كان هذا الحقل يتتبع متى سيحتاج الحساب إلى دفع الإيجار (بوحدات lamport) للحفاظ على بياناته على الشبكة. ومع ذلك، تم إلغاء آلية تحصيل الإيجار هذه منذ ذلك الحين.
الإيجار
لتخزين البيانات على السلسلة، يجب أن تحتفظ الحسابات أيضًا برصيد lamport (SOL) يتناسب مع كمية البيانات المخزنة في الحساب (بالبايت). يُسمى هذا الرصيد "الإيجار"، لكنه يعمل أكثر كوديعة لأنه يمكنك استرداد المبلغ بالكامل عند إغلاق الحساب. يمكنك العثور على طريقة الحساب هنا باستخدام هذه الثوابت.
يأتي مصطلح "الإيجار" من آلية قديمة كانت تخصم بانتظام وحدات lamport من الحسابات التي تقل عن حد الإيجار. هذه الآلية لم تعد نشطة.
مالك البرنامج
في سولانا، تُسمى "العقود الذكية" برامج. ملكية البرنامج هي جزء أساسي من نموذج حساب سولانا. كل حساب له برنامج معين كمالك له. فقط البرنامج المالك يمكنه:
- تغيير حقل
data
للحساب - خصم lamports من رصيد الحساب
يحدد كل برنامج بنية البيانات المخزنة في حقل data
للحساب. تحدد تعليمات البرنامج
كيفية تغيير هذه البيانات ورصيد lamports
للحساب.
System Program
بشكل افتراضي، تكون جميع الحسابات الجديدة مملوكة لـ System Program. يقوم System Program بأداء الوظائف الرئيسية التالية:
الوظيفة | الوصف |
---|---|
إنشاء حساب جديد | فقط System Program يمكنه إنشاء حسابات جديدة. |
تخصيص المساحة | يحدد سعة البايت لحقل البيانات لكل حساب. |
تعيين ملكية البرنامج | بمجرد أن ينشئ System Program حسابًا، يمكنه إعادة تعيين مالك البرنامج المحدد إلى حساب برنامج مختلف. هكذا تأخذ البرامج المخصصة ملكية الحسابات الجديدة التي تم إنشاؤها بواسطة System Program. |
تحويل SOL | تحويل lamports (SOL) من حسابات النظام إلى حسابات أخرى. |
لاحظ أن جميع حسابات "المحفظة" على سولانا هي "حسابات نظام" مملوكة لـ System Program. يظهر رصيد lamport في هذه الحسابات مقدار SOL المملوك للمحفظة. فقط حسابات النظام يمكنها دفع رسوم المعاملات.
حساب النظام
عندما يتم إرسال SOL إلى عنوان جديد لأول مرة، يتم إنشاء حساب تلقائيًا في ذلك العنوان مملوك لـ System Program.
في المثال أدناه، يتم إنشاء زوج مفاتيح جديد وتمويله بـ SOL. قم بتشغيل الكود لرؤية
النتيجة. لاحظ أن حقل owner
للحساب هو System Program بالعنوان
11111111111111111111111111111111
.
import {airdropFactory,createSolanaRpc,createSolanaRpcSubscriptions,generateKeyPairSigner,lamports} from "@solana/kit";// Create a connection to Solana clusterconst rpc = createSolanaRpc("http://localhost:8899");const rpcSubscriptions = createSolanaRpcSubscriptions("ws://localhost:8900");// Generate a new keypairconst keypair = await generateKeyPairSigner();console.log(`Public Key: ${keypair.address}`);// Funding an address with SOL automatically creates an accountconst signature = await airdropFactory({ rpc, rpcSubscriptions })({recipientAddress: keypair.address,lamports: lamports(1_000_000_000n),commitment: "confirmed"});const accountInfo = await rpc.getAccountInfo(keypair.address).send();console.log(accountInfo);
حسابات Sysvar
حسابات Sysvar هي حسابات خاصة بعناوين محددة مسبقًا توفر وصولاً إلى بيانات حالة المجموعة. تتحدث هذه الحسابات ديناميكيًا مع بيانات حول مجموعة الشبكة. يمكنك العثور على القائمة الكاملة لحسابات Sysvar هنا.
يوضح المثال التالي كيفية جلب وفك تشفير البيانات من حساب Sysvar Clock.
import { createSolanaRpc } from "@solana/kit";import { fetchSysvarClock, SYSVAR_CLOCK_ADDRESS } from "@solana/sysvars";const rpc = createSolanaRpc("https://api.mainnet-beta.solana.com");const accountInfo = await rpc.getAccountInfo(SYSVAR_CLOCK_ADDRESS, { encoding: "base64" }).send();console.log(accountInfo);// Automatically fetch and deserialize the account dataconst clock = await fetchSysvarClock(rpc);console.log(clock);
program account
نشر برنامج سولانا ينشئ program account قابل للتنفيذ. يخزن program account الكود القابل للتنفيذ للبرنامج. تكون program accounts مملوكة من قبل برنامج التحميل.
Program Account
للتبسيط، يمكنك التعامل مع program account كما لو كان البرنامج نفسه. عندما تستدعي تعليمات البرنامج، فإنك تحدد عنوان program account (يُطلق عليه عادةً "Program ID").
يقوم المثال التالي بجلب Token Program لإظهار أن program accounts لها نفس النوع
الأساسي Account
، باستثناء أن الحقل executable
يتم تعيينه إلى true
. نظرًا
لأن program accounts تحتوي على كود قابل للتنفيذ في حقل البيانات الخاص بها، فإننا
لا نقوم بفك تشفير البيانات.
import { Address, createSolanaRpc } from "@solana/kit";const rpc = createSolanaRpc("https://api.mainnet-beta.solana.com");const programId = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" as Address;const accountInfo = await rpc.getAccountInfo(programId, { encoding: "base64" }).send();console.log(accountInfo);
عند نشر برنامج سولانا، يتم تخزينه في program account. تكون program accounts مملوكة من قبل برنامج التحميل. هناك عدة إصدارات من برنامج التحميل، لكن جميعها باستثناء loader-v3 تخزن الكود القابل للتنفيذ مباشرة في program account. يقوم loader-v3 بتخزين الكود القابل للتنفيذ في "حساب بيانات البرنامج" منفصل ويشير program account فقط إليه. عند نشر برنامج جديد، تستخدم واجهة سطر أوامر سولانا أحدث إصدار من برنامج التحميل افتراضيًا.
حساب المخزن المؤقت
يحتوي Loader-v3 على نوع حساب خاص لتجهيز تحميل البرنامج مؤقتًا أثناء النشر أو الترقيات. في loader-v4، لا تزال هناك مخازن مؤقتة، لكنها مجرد حسابات برنامج عادية.
حساب بيانات البرنامج
يعمل Loader-v3 بشكل مختلف عن جميع برامج BPF Loader الأخرى. يحتوي حساب البرنامج فقط على عنوان حساب بيانات البرنامج، الذي يخزن الكود القابل للتنفيذ الفعلي:
حساب بيانات البرنامج
لا تخلط بين حسابات بيانات البرنامج هذه وحسابات البيانات الخاصة بالبرامج (انظر أدناه).
حساب البيانات
في سولانا، يتم تخزين الكود القابل للتنفيذ للبرنامج في حساب مختلف عن حالة البرنامج. هذا يشبه كيفية احتفاظ أنظمة التشغيل عادةً بملفات منفصلة للبرامج وبياناتها.
للحفاظ على الحالة، تحدد البرامج تعليمات لإنشاء حسابات منفصلة تمتلكها. يحتوي كل من هذه الحسابات على عنوانه الفريد الخاص ويمكنه تخزين أي بيانات عشوائية يحددها البرنامج.
حساب البيانات
لاحظ أن System Program فقط يمكنه إنشاء حسابات جديدة. بمجرد إنشاء System Program لحساب، يمكنه بعد ذلك تعيين ملكية الحساب الجديد لبرنامج آخر.
بعبارة أخرى، يتطلب إنشاء حساب بيانات لبرنامج مخصص خطوتين:
- استدعاء System Program لإنشاء حساب، ثم نقل الملكية إلى البرنامج المخصص
- استدعاء البرنامج المخصص، الذي يمتلك الحساب الآن، لتهيئة بيانات الحساب كما هو محدد في تعليمات البرنامج
غالبًا ما يتم تبسيط عملية إنشاء الحساب هذه كخطوة واحدة، ولكن من المفيد فهم العملية الأساسية.
يوضح المثال التالي كيفية إنشاء واسترجاع حساب mint account مملوك من قبل برنامج Token 2022.
import {airdropFactory,appendTransactionMessageInstructions,createSolanaRpc,createSolanaRpcSubscriptions,createTransactionMessage,generateKeyPairSigner,getSignatureFromTransaction,lamports,pipe,sendAndConfirmTransactionFactory,setTransactionMessageFeePayerSigner,setTransactionMessageLifetimeUsingBlockhash,signTransactionMessageWithSigners} from "@solana/kit";import { getCreateAccountInstruction } from "@solana-program/system";import {getInitializeMintInstruction,getMintSize,TOKEN_2022_PROGRAM_ADDRESS,fetchMint} from "@solana-program/token-2022";// Create Connection, local validator in this exampleconst rpc = createSolanaRpc("http://localhost:8899");const rpcSubscriptions = createSolanaRpcSubscriptions("ws://localhost:8900");// Generate keypairs for fee payerconst feePayer = await generateKeyPairSigner();// Fund fee payerawait airdropFactory({ rpc, rpcSubscriptions })({recipientAddress: feePayer.address,lamports: lamports(1_000_000_000n),commitment: "confirmed"});// Generate keypair to use as address of mintconst mint = await generateKeyPairSigner();// Get default mint account size (in bytes), no extensions enabledconst space = BigInt(getMintSize());// Get minimum balance for rent exemptionconst rent = await rpc.getMinimumBalanceForRentExemption(space).send();// Instruction to create new account for mint (token 2022 program)// Invokes the system programconst createAccountInstruction = getCreateAccountInstruction({payer: feePayer,newAccount: mint,lamports: rent,space,programAddress: TOKEN_2022_PROGRAM_ADDRESS});// Instruction to initialize mint account data// Invokes the token 2022 programconst initializeMintInstruction = getInitializeMintInstruction({mint: mint.address,decimals: 9,mintAuthority: feePayer.address});const instructions = [createAccountInstruction, initializeMintInstruction];// Get latest blockhash to include in transactionconst { value: latestBlockhash } = await rpc.getLatestBlockhash().send();// Create transaction messageconst transactionMessage = pipe(createTransactionMessage({ version: 0 }), // Create transaction message(tx) => setTransactionMessageFeePayerSigner(feePayer, tx), // Set fee payer(tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx), // Set transaction blockhash(tx) => appendTransactionMessageInstructions(instructions, tx) // Append instructions);// Sign transaction message with required signers (fee payer and mint keypair)const signedTransaction =await signTransactionMessageWithSigners(transactionMessage);// Send and confirm transactionawait sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions })(signedTransaction,{ commitment: "confirmed" });// Get transaction signatureconst transactionSignature = getSignatureFromTransaction(signedTransaction);console.log("Mint Address:", mint.address);console.log("Transaction Signature:", transactionSignature);const accountInfo = await rpc.getAccountInfo(mint.address).send();console.log(accountInfo);const mintAccount = await fetchMint(rpc, mint.address);console.log(mintAccount);
Is this page helpful?