نموذج حساب سولانا
في سولانا، يتم تخزين جميع البيانات فيما يسمى "الحسابات". يمكنك التفكير في البيانات على سولانا كقاعدة بيانات عامة تحتوي على جدول واحد "الحسابات"، حيث كل إدخال في هذا الجدول هو "حساب". كل حساب في سولانا يشترك في نفس نوع الحساب الأساسي.
الحسابات
النقاط الرئيسية
- يمكن للحسابات تخزين ما يصل إلى 10MiB من البيانات، والتي تحتوي إما على رمز البرنامج القابل للتنفيذ أو حالة البرنامج.
- تتطلب الحسابات إيداع إيجار (rent) بوحدات lamport (SOL) يتناسب مع كمية البيانات المخزنة، ويمكنك استرداده بالكامل عند إغلاق الحساب.
- كل حساب له مالك برنامج. فقط البرنامج الذي يملك الحساب يمكنه تغيير بياناته أو خصم رصيد الـ lamport الخاص به. ولكن يمكن لأي شخص زيادة الرصيد.
- حسابات Sysvar هي حسابات خاصة تخزن حالة مجموعة الشبكة.
- حسابات البرامج (program account) تخزن الرمز القابل للتنفيذ للعقود الذكية.
- حسابات البيانات يتم إنشاؤها بواسطة البرامج لتخزين وإدارة حالة البرنامج.
الحساب
كل حساب على سولانا له عنوان فريد مكون من 32 بايت، غالبًا ما يظهر كسلسلة مشفرة
بنظام base58 (مثل vines1vzrYbzLMRdu58ou5XTby4qAqVRLmqo36NKPTg
).
تعمل العلاقة بين الحساب وعنوانه مثل زوج المفتاح والقيمة، حيث يكون العنوان هو المفتاح لتحديد موقع البيانات المقابلة على السلسلة للحساب. يعمل عنوان الحساب كـ "معرف فريد" لكل إدخال في جدول "الحسابات".
عنوان الحساب
تستخدم معظم حسابات سولانا مفتاح 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 وكل حساب على سولانا يشترك في نفس نوع الحساب الأساسي.
نوع الحساب
كل حساب على سولانا يحتوي على الحقول التالية:
data
: مصفوفة بايت تخزن بيانات عشوائية للحساب. بالنسبة للحسابات غير القابلة للتنفيذ، غالبًا ما تخزن الحالة المقصود قراءتها. بالنسبة لحسابات البرامج (العقود الذكية)، تحتوي على رمز البرنامج القابل للتنفيذ. يُطلق على حقل البيانات عادةً "بيانات الحساب".executable
: هذه العلامة توضح ما إذا كان الحساب برنامجًا.lamports
: رصيد الحساب بوحدة lamport، وهي أصغر وحدة من SOL (1 SOL = 1 مليار lamport).owner
: معرف البرنامج (المفتاح العام) للبرنامج الذي يملك هذا الحساب. فقط البرنامج المالك يمكنه تغيير بيانات الحساب أو خصم رصيد lamports الخاص به.rent_epoch
: حقل قديم من عندما كان لدى سولانا آلية تقوم بخصم 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" من آلية قديمة كانت تخصم lamports بانتظام من الحسابات التي تقل عن حد الإيجار. هذه الآلية لم تعد نشطة الآن.
مالك البرنامج
في سولانا، تُسمى "العقود الذكية" برامج. ملكية البرنامج هي جزء أساسي من نموذج حساب سولانا. كل حساب له برنامج معين كمالك له. فقط البرنامج المالك يمكنه:
- تغيير حقل
data
للحساب - خصم lamports من رصيد الحساب
System Program
بشكل افتراضي، جميع الحسابات الجديدة مملوكة لـ System Program. يقوم System Program بعدة أمور رئيسية:
- إنشاء حساب جديد: فقط System Program يمكنه إنشاء حسابات جديدة.
- تخصيص المساحة: يحدد سعة البايت لحقل البيانات لكل حساب.
- نقل / تعيين ملكية البرنامج: بمجرد أن ينشئ System Program حسابًا، يمكنه إعادة تعيين مالك البرنامج المحدد إلى حساب برنامج مختلف. هكذا تأخذ البرامج المخصصة ملكية الحسابات الجديدة التي أنشأها System Program.
جميع حسابات "المحفظة" على سولانا هي مجرد حسابات مملوكة لـ System Program. يُظهر رصيد lamport في هذه الحسابات كمية SOL المملوكة للمحفظة. فقط الحسابات المملوكة لـ System Program يمكنها دفع رسوم المعاملات.
حساب النظام
حسابات 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);
حساب البرنامج
نشر برنامج سولانا ينشئ حساب برنامج قابل للتنفيذ. يخزن حساب البرنامج الكود القابل للتنفيذ للبرنامج.
حسابات البرنامج مملوكة من قبل برنامج المحمّل.
حساب البرنامج
للتبسيط، يمكنك التعامل مع حساب البرنامج كالبرنامج نفسه. عندما تستدعي تعليمات البرنامج، تحدد عنوان حساب البرنامج (يُسمى عادةً "معرف البرنامج").
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);
عندما تنشر برنامج سولانا، يتم تخزينه في حساب برنامج. حسابات البرنامج مملوكة من قبل برنامج المحمّل. هناك عدة إصدارات من المحمّل، لكن جميعها باستثناء loader-v3 تخزن الكود القابل للتنفيذ مباشرة في حساب البرنامج. يخزن loader-v3 الكود القابل للتنفيذ في "حساب بيانات برنامج" منفصل ويشير حساب البرنامج فقط إليه. عند نشر برنامج جديد، يستخدم واجهة سطر أوامر سولانا أحدث إصدار من المحمّل بشكل افتراضي.
حساب المخزن المؤقت
يحتوي loader-v3 على نوع حساب خاص للتخزين المؤقت لتحميل البرنامج أثناء النشر أو إعادة النشر/الترقيات. في loader-v4، لا تزال هناك مخازن مؤقتة، لكنها مجرد حسابات برنامج عادية.
حساب بيانات البرنامج
يعمل loader-v3 بشكل مختلف عن جميع برامج محمّل BPF الأخرى. يحتوي حساب البرنامج فقط على عنوان حساب بيانات البرنامج، الذي يخزن الكود القابل للتنفيذ الفعلي:
حساب بيانات البرنامج
لا تخلط بين حسابات بيانات البرنامج هذه وحسابات البيانات الخاصة بالبرامج (انظر أدناه).
حساب البيانات
في سولانا، يتم تخزين الشفرة القابلة للتنفيذ للبرنامج في حساب مختلف عن حالة البرنامج. هذا يشبه كيفية قيام أنظمة التشغيل عادةً بالفصل بين ملفات البرامج وبياناتها.
للحفاظ على الحالة، تحدد البرامج تعليمات لإنشاء حسابات منفصلة تمتلكها. كل من هذه الحسابات له عنوان فريد خاص به ويمكنه تخزين أي بيانات عشوائية يحددها البرنامج.
حساب البيانات
لاحظ أن System Program فقط هو الذي يمكنه إنشاء حسابات جديدة. بمجرد أن ينشئ System Program حسابًا، يمكنه بعد ذلك nنقل أو تعيين ملكية الحساب الجديد إلى برنامج آخر.
بعبارة أخرى، إنشاء حساب بيانات لبرنامج مخصص يتطلب خطوتين:
- استدعاء System Program لإنشاء حساب، ثم نقل الملكية إلى البرنامج المخصص
- استدعاء البرنامج المخصص، الذي يمتلك الآن الحساب، لتهيئة بيانات الحساب كما هو محدد في تعليمات البرنامج
غالبًا ما يتم تبسيط عملية إنشاء الحساب هذه كخطوة واحدة، ولكن من المفيد فهم العملية الأساسية.
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?