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