Модель облікових записів Solana
У Solana всі дані зберігаються в тому, що називається "облікові записи" (accounts). Можна уявити дані в Solana як публічну базу даних з єдиною таблицею "Облікові записи", де кожен запис у цій таблиці є "обліковим записом". Кожен обліковий запис Solana має однаковий базовий тип Account.
Облікові записи
Ключові моменти
- Облікові записи можуть зберігати до 10MiB даних, які містять або виконуваний програмний код, або стан програми.
- Облікові записи потребують депозиту rent у lamport (SOL), який пропорційний обсягу збережених даних, і ви можете повністю повернути його, коли закриваєте обліковий запис.
- Кожен обліковий запис має програмного власника. Тільки програма, яка володіє обліковим записом, може змінювати його дані або зменшувати його баланс у lamport. Але будь-хто може збільшити баланс.
- Облікові записи Sysvar — це спеціальні облікові записи, які зберігають стан мережевого кластера.
- Програмні облікові записи (program account) зберігають виконуваний код смарт-контрактів.
- Облікові записи даних створюються програмами для зберігання та керування станом програми.
Обліковий запис
Кожен обліковий запис у Solana має унікальну 32-байтову адресу, яка часто
відображається як рядок у кодуванні base58 (наприклад,
vines1vzrYbzLMRdu58ou5XTby4qAqVRLmqo6X8TsVXREG
).
Зв'язок між обліковим записом та його адресою працює як пара ключ-значення, де адреса є ключем для пошуку відповідних онлайн-даних облікового запису. Адреса облікового запису діє як "унікальний ідентифікатор" для кожного запису в таблиці "Облікові записи".
Адреса облікового запису
Більшість облікових записів Solana використовують відкритий ключ Ed25519 як свою адресу.
import { generateKeyPairSigner } from "@solana/kit";// Kit does not enable extractable private keysconst keypairSigner = await generateKeyPairSigner();console.log(keypairSigner);
Хоча відкриті ключі зазвичай використовуються як адреси облікових записів, Solana також підтримує функцію під назвою Програмно похідні адреси (PDA). PDA — це спеціальні адреси, які можна детерміновано отримати з ідентифікатора програми та опціональних вхідних даних (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
: Масив байтів, який зберігає довільні дані для облікового запису. Для невиконуваних облікових записів це часто зберігає стан, який призначений для читання. Для program account (смарт-контрактів) це містить виконуваний програмний код. Поле даних зазвичай називають "даними облікового запису".executable
: Цей прапорець показує, чи є обліковий запис програмою.lamports
: Баланс облікового запису в lamport, найменшій одиниці SOL (1 SOL = 1 мільярд lamport).owner
: Ідентифікатор програми (відкритий ключ) програми, яка володіє цим обліковим записом. Тільки програма-власник може змінювати дані облікового запису або зменшувати його баланс у lamport.rent_epoch
: Застаріле поле з часів, коли Solana мала механізм, який періодично знімав lamport з облікових записів. Хоча це поле все ще існує в типі Account, воно більше не використовується, оскільки збір 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 з рахунків, що падали нижче порогу оренди. Цей механізм більше не активний.
Власник програми
У Solana "розумні контракти" називаються програмами. Власність програми є ключовою частиною моделі рахунків Solana. Кожен рахунок має призначену програму як свого власника. Тільки програма-власник може:
- Змінювати поле
data
рахунку - Знімати lamport з балансу рахунку
System Program
За замовчуванням усі нові рахунки належать System Program. System Program виконує кілька ключових функцій:
- Створення нових рахунків: Тільки System Program може створювати нові рахунки.
- Розподіл простору: Встановлює байтову ємність для поля даних кожного рахунку.
- Передача / призначення власності програми: Після створення рахунку System Program може перепризначити визначеного власника програми на інший program account. Саме так користувацькі програми беруть власність над новими рахунками, створеними System Program.
Усі "гаманцеві" рахунки на Solana — це просто рахунки, якими володіє 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);
Акаунт програми
Розгортання програми Solana створює виконуваний акаунт програми. Акаунт програми зберігає виконуваний код програми.
Акаунти програм належать програмі-завантажувачу.
Акаунт програми
Для простоти можна вважати акаунт програми самою програмою. Коли ви викликаєте інструкції програми, ви вказуєте адресу акаунта програми (зазвичай називається "Program ID").
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 виконуваний код програми зберігається в іншому рахунку, ніж стан програми. Це схоже на те, як операційні системи зазвичай мають окремі файли для програм та їхніх даних.
Для підтримки стану програми визначають інструкції для створення окремих рахунків, якими вони володіють. Кожен з цих рахунків має свою унікальну адресу і може зберігати будь-якідовільні дані, визначені програмою.
Рахунок даних
Зауважте, що лише System Program може створювати нові рахунки. Після того, як System Program створює рахунок, він може передати або призначити право власності на новий рахунок іншій програмі.
Іншими словами, створення рахунку даних для користувацької програми відбувається у два етапи:
- Викликати 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?