نموذج حساب سولانا
في سولانا، يتم تخزين جميع البيانات فيما يسمى "الحسابات". يمكنك التفكير في البيانات على سولانا كقاعدة بيانات عامة تحتوي على جدول "حسابات" واحد، حيث كل إدخال في هذا الجدول هو "حساب". كل حساب سولانا يشترك في نفس نوع الحساب الأساسي.
الحسابات
النقاط الرئيسية
- يمكن للحسابات تخزين ما يصل إلى 10MiB من البيانات، والتي تحتوي إما على رمز البرنامج القابل للتنفيذ أو حالة البرنامج.
- تتطلب الحسابات إيداع rent بوحدات lamport (SOL) يتناسب مع كمية البيانات المخزنة، ويمكنك استرداده بالكامل عند إغلاق الحساب.
- كل حساب له مالك برنامج. فقط البرنامج الذي يملك الحساب يمكنه تغيير بياناته أو خصم رصيد الـ lamport الخاص به. لكن يمكن لأي شخص زيادة الرصيد.
- حسابات Sysvar هي حسابات خاصة تخزن حالة مجموعة الشبكة.
- حسابات البرامج تخزن الرمز القابل للتنفيذ للعقود الذكية.
- حسابات البيانات يتم إنشاؤها بواسطة البرامج لتخزين وإدارة حالة البرنامج.
الحساب
كل حساب على سولانا له عنوان فريد مكون من 32 بايت، غالبًا ما يظهر كسلسلة مشفرة
بنظام base58 (مثل vines1vzrYbzLMRdu58ou5XTby4qAqVRLmqo6Xd7M7ag
).
تعمل العلاقة بين الحساب وعنوانه مثل زوج المفتاح والقيمة، حيث يكون العنوان هو المفتاح لتحديد موقع البيانات المقابلة على السلسلة للحساب. يعمل عنوان الحساب كـ "معرف فريد" لكل إدخال في جدول "الحسابات".
عنوان الحساب
تستخدم معظم حسابات Solana مفتاح Ed25519 العام كعنوان لها.
import { generateKeyPairSigner } from "@solana/kit";// Kit does not enable extractable private keysconst keypairSigner = await generateKeyPairSigner();console.log(keypairSigner);
بينما يتم استخدام المفاتيح العامة بشكل شائع كعناوين للحسابات، تدعم Solana أيضًا ميزة تسمى العناوين المشتقة من البرنامج (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 وكل حساب على Solana يشترك في نفس نوع Account الأساسي.
نوع الحساب
كل حساب على Solana يحتوي على الحقول التالية:
data
: مصفوفة بايت تخزن بيانات عشوائية للحساب. بالنسبة للحسابات غير القابلة للتنفيذ، غالبًا ما تخزن الحالة المقصود قراءتها. بالنسبة لحسابات البرامج (العقود الذكية)، تحتوي على رمز البرنامج القابل للتنفيذ. يُطلق على حقل البيانات عادةً "بيانات الحساب".executable
: هذه العلامة توضح ما إذا كان الحساب برنامجًا.lamports
: رصيد الحساب بوحدة lamport، وهي أصغر وحدة من SOL (1 SOL = 1 مليار lamport).owner
: معرّف البرنامج (المفتاح العام) للبرنامج الذي يملك هذا الحساب. فقط البرنامج المالك يمكنه تغيير بيانات الحساب أو خصم رصيد lamports الخاص به.rent_epoch
: حقل قديم من عندما كان لدى Solana آلية تخصم دوريًا lamports من الحسابات. بينما لا يزال هذا الحقل موجودًا في نوع الحساب، إلا أنه لم يعد يُستخدم منذ إلغاء تحصيل rent.
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,}
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);
rent
لتخزين البيانات على السلسلة، يجب أن تحتفظ الحسابات أيضًا برصيد من lamport (SOL) يتناسب مع كمية البيانات المخزنة في الحساب (بالبايت). يُسمى هذا الرصيد "rent"، لكنه يعمل أكثر كوديعة لأنه يمكنك استرداد المبلغ بالكامل عند إغلاق الحساب. يمكنك العثور على طريقة الحساب هنا باستخدام هذه الثوابت.
يأتي مصطلح "rent" من آلية قديمة كانت تخصم بانتظام وحدات lamport من الحسابات التي تقل عن حد الإيجار. هذه الآلية لم تعد نشطة الآن.
مالك البرنامج
في سولانا، تُسمى "العقود الذكية" برامج. ملكية البرنامج هي جزء أساسي من نموذج حساب سولانا. كل حساب له برنامج معين كمالك له. فقط البرنامج المالك يمكنه:
- تغيير حقل
data
للحساب - خصم وحدات lamport من رصيد الحساب
برنامج النظام
بشكل افتراضي، جميع الحسابات الجديدة مملوكة لبرنامج النظام. يقوم برنامج النظام بعدة أمور رئيسية:
- إنشاء حساب جديد: فقط برنامج النظام يمكنه إنشاء حسابات جديدة.
- تخصيص المساحة: يحدد سعة البايت لحقل البيانات لكل حساب.
- نقل / تعيين ملكية البرنامج: بمجرد أن ينشئ برنامج النظام حسابًا، يمكنه إعادة تعيين مالك البرنامج المحدد إلى حساب برنامج مختلف. هكذا تأخذ البرامج المخصصة ملكية الحسابات الجديدة التي أنشأها برنامج النظام.
جميع حسابات "المحفظة" على سولانا هي مجرد حسابات مملوكة لبرنامج النظام. يُظهر رصيد lamport في هذه الحسابات كمية SOL المملوكة للمحفظة. فقط الحسابات المملوكة لبرنامج النظام يمكنها دفع رسوم المعاملات.
حساب النظام
حسابات Sysvar
حسابات Sysvar هي حسابات خاصة بعناوين محددة مسبقًا توفر وصولاً إلى بيانات حالة المجموعة. تتحدث هذه الحسابات ديناميكيًا مع بيانات حول مجموعة الشبكة. يمكنك العثور على القائمة الكاملة لحسابات Sysvar هنا.
import { Address, createSolanaRpc } from "@solana/kit";const rpc = createSolanaRpc("https://api.mainnet-beta.solana.com");const SYSVAR_CLOCK_ADDRESS ="SysvarC1ock11111111111111111111111111111111" as Address;const accountInfo = await rpc.getAccountInfo(SYSVAR_CLOCK_ADDRESS, { encoding: "base64" }).send();console.log(accountInfo);
حساب البرنامج
نشر برنامج Solana ينشئ حساب برنامج قابل للتنفيذ. يخزن حساب البرنامج الشفرة القابلة للتنفيذ للبرنامج.
حسابات البرامج مملوكة من قبل برنامج المحمّل.
حساب البرنامج
للتبسيط، يمكنك التعامل مع حساب البرنامج كالبرنامج نفسه. عندما تستدعي تعليمات البرنامج، تحدد عنوان حساب البرنامج (يُسمى عادةً "معرّف البرنامج").
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);
عند نشر برنامج Solana، يتم تخزينه في حساب برنامج. حسابات البرامج مملوكة من قبل برنامج المحمّل. هناك عدة إصدارات من المحمّل، لكن جميعها باستثناء loader-v3 تخزن الشفرة القابلة للتنفيذ مباشرة في حساب البرنامج. يخزن loader-v3 الشفرة القابلة للتنفيذ في "حساب بيانات برنامج" منفصل ويشير حساب البرنامج فقط إليه. عند نشر برنامج جديد، يستخدم Solana CLI أحدث إصدار من المحمّل افتراضيًا.
حساب المخزن المؤقت
يحتوي loader-v3 على نوع حساب خاص لتجهيز تحميل البرنامج مؤقتًا أثناء النشر أو إعادة النشر/الترقيات. في loader-v4، لا تزال هناك مخازن مؤقتة، لكنها مجرد حسابات برامج عادية.
حساب بيانات البرنامج
يعمل loader-v3 بشكل مختلف عن جميع برامج BPF Loader الأخرى. يحتوي حساب البرنامج فقط على عنوان حساب بيانات البرنامج، الذي يخزن الشفرة القابلة للتنفيذ الفعلية:
حساب بيانات البرنامج
لا تخلط بين حسابات بيانات البرنامج هذه وحسابات البيانات الخاصة بالبرامج (انظر أدناه).
حساب البيانات
في Solana، يتم تخزين الشفرة القابلة للتنفيذ للبرنامج في حساب مختلف عن حالة البرنامج. هذا يشبه كيفية قيام أنظمة التشغيل عادةً بالفصل بين ملفات البرامج وبياناتها.
للحفاظ على الحالة، تحدد البرامج تعليمات لإنشاء حسابات منفصلة تمتلكها. كل من هذه الحسابات له عنوان فريد خاص به ويمكنه تخزين أي بيانات عشوائية يحددها البرنامج.
حساب البيانات
لاحظ أن برنامج النظام فقط هو الذي يمكنه إنشاء حسابات جديدة. بمجرد أن ينشئ برنامج النظام حسابًا، يمكنه بعد ذلك nنقل أو تعيين ملكية الحساب الجديد إلى برنامج آخر.
بعبارة أخرى، إنشاء حساب بيانات لبرنامج مخصص يتطلب خطوتين:
- استدعاء برنامج النظام لإنشاء حساب، ثم نقل الملكية إلى البرنامج المخصص
- استدعاء البرنامج المخصص، الذي يمتلك الآن الحساب، لتهيئة بيانات الحساب كما هو محدد في تعليمات البرنامج
غالبًا ما يتم تبسيط عملية إنشاء الحساب هذه كخطوة واحدة، ولكن من المفيد فهم العملية الأساسية.
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} from "@solana-program/token-2022";// Create Connection, local validator in this exampleconst rpc = createSolanaRpc("http://127.0.0.1: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);
Is this page helpful?