Модель облікових записів 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 keys
const keypairSigner = await generateKeyPairSigner();
console.log(keypairSigner);
Console
Click to execute the code.

Хоча відкриті ключі зазвичай використовуються як адреси облікових записів, 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}`);
Console
Click to execute the code.

Тип облікового запису

Облікові записи мають максимальний розмір 10MiB, і кожен обліковий запис у Solana має однаковий базовий тип Account.

Тип облікового записуТип облікового запису

Кожен обліковий запис у Solana має такі поля.

Base Account Type
pub struct Account {
/// lamports in the account
pub 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 rent
pub rent_epoch: Epoch,
}

Поле Lamports

Баланс облікового запису в lamport, найменшій одиниці SOL (1 SOL = 1 мільярд lamport). Баланс SOL облікового запису — це сума в полі lamports, перетворена в SOL.

Облікові записи Solana повинні мати мінімальний баланс lamport, який пропорційний кількості даних, що зберігаються в обліковому записі (у байтах). Цей мінімальний баланс називається "rent".

Баланс lamport, що зберігається в обліковому записі, можна повністю відновити при закритті облікового запису.

Поле даних

Масив байтів, який зберігає довільні дані для облікового запису. Поле даних зазвичай називають "даними облікового запису".

  • Для програмних облікових записів (смарт-контрактів) це поле містить або виконуваний програмний код, або адресу іншого облікового запису, який зберігає виконуваний програмний код.
  • Для невиконуваних облікових записів це зазвичай зберігає стан, який призначений для читання.

Читання даних з облікового запису Solana включає два кроки:

  1. Отримати обліковий запис, використовуючи його адресу (відкритий ключ)
  2. Десеріалізувати поле даних облікового запису з необроблених байтів у відповідну структуру даних, яка визначається програмою, що володіє обліковим записом

Поле власника

Ідентифікатор програми (відкритий ключ) програми, яка володіє цим обліковим записом.

Кожен обліковий запис Solana має призначену програму як свого власника. Тільки програма, яка володіє обліковим записом, може змінювати дані облікового запису або зменшувати його баланс lamport.

Інструкції, визначені в програмі, визначають, як можуть бути змінені дані облікового запису та баланс lamport.

Поле виконуваності

Це поле вказує, чи є обліковий запис виконуваною програмою.

  • Якщо true, обліковий запис є виконуваною програмою Solana.
  • Якщо false, обліковий запис є обліковим записом даних, який зберігає стан.

Для виконуваних облікових записів поле owner містить ідентифікатор програми завантажувача. Програми завантажувачі — це вбудовані програми, відповідальні за завантаження та управління виконуваними програмними обліковими записами.

Поле епохи оренди

Поле rent_epoch є застарілим полем, яке більше не використовується.

Спочатку це поле відстежувало, коли обліковий запис повинен був платити rent (в lamport) для підтримки своїх даних у мережі. Однак цей механізм збору rent з того часу застарів.

Поле Lamports

Баланс облікового запису в lamport, найменшій одиниці SOL (1 SOL = 1 мільярд lamport). Баланс SOL облікового запису — це сума в полі lamports, перетворена в SOL.

Облікові записи Solana повинні мати мінімальний баланс lamport, який пропорційний кількості даних, що зберігаються в обліковому записі (у байтах). Цей мінімальний баланс називається "rent".

Баланс lamport, що зберігається в обліковому записі, можна повністю відновити при закритті облікового запису.

Поле даних

Масив байтів, який зберігає довільні дані для облікового запису. Поле даних зазвичай називають "даними облікового запису".

  • Для програмних облікових записів (смарт-контрактів) це поле містить або виконуваний програмний код, або адресу іншого облікового запису, який зберігає виконуваний програмний код.
  • Для невиконуваних облікових записів це зазвичай зберігає стан, який призначений для читання.

Читання даних з облікового запису Solana включає два кроки:

  1. Отримати обліковий запис, використовуючи його адресу (відкритий ключ)
  2. Десеріалізувати поле даних облікового запису з необроблених байтів у відповідну структуру даних, яка визначається програмою, що володіє обліковим записом

Поле власника

Ідентифікатор програми (відкритий ключ) програми, яка володіє цим обліковим записом.

Кожен обліковий запис Solana має призначену програму як свого власника. Тільки програма, яка володіє обліковим записом, може змінювати дані облікового запису або зменшувати його баланс lamport.

Інструкції, визначені в програмі, визначають, як можуть бути змінені дані облікового запису та баланс lamport.

Поле виконуваності

Це поле вказує, чи є обліковий запис виконуваною програмою.

  • Якщо true, обліковий запис є виконуваною програмою Solana.
  • Якщо false, обліковий запис є обліковим записом даних, який зберігає стан.

Для виконуваних облікових записів поле owner містить ідентифікатор програми завантажувача. Програми завантажувачі — це вбудовані програми, відповідальні за завантаження та управління виконуваними програмними обліковими записами.

Поле епохи оренди

Поле rent_epoch є застарілим полем, яке більше не використовується.

Спочатку це поле відстежувало, коли обліковий запис повинен був платити rent (в lamport) для підтримки своїх даних у мережі. Однак цей механізм збору rent з того часу застарів.

Base Account Type
pub struct Account {
/// lamports in the account
pub lamports: u64,
}
// Example Token Mint Account
Account {
lamports: 1461600,
}
// Example Token Program Account
Account {
lamports: 4513200894,
}

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 AccountSystem Account

Коли SOL вперше надсилається на нову адресу, автоматично створюється рахунок за цією адресою, яким володіє System Program.

У наведеному нижче прикладі створюється нова keypair, яка поповнюється SOL. Запустіть код, щоб побачити результат. Зверніть увагу, що поле owner рахунку — це System Program з адресою 11111111111111111111111111111111.

import {
airdropFactory,
createSolanaRpc,
createSolanaRpcSubscriptions,
generateKeyPairSigner,
lamports
} from "@solana/kit";
// Create a connection to Solana cluster
const rpc = createSolanaRpc("http://localhost:8899");
const rpcSubscriptions = createSolanaRpcSubscriptions("ws://localhost:8900");
// Generate a new keypair
const keypair = await generateKeyPairSigner();
console.log(`Public Key: ${keypair.address}`);
// Funding an address with SOL automatically creates an account
const 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);
Console
Click to execute the code.

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 data
const clock = await fetchSysvarClock(rpc);
console.log(clock);
Console
Click to execute the code.

Program Account

Розгортання програми Solana створює виконуваний program account. Program account зберігає виконуваний код програми. Program accounts належать Loader Program.

Program AccountProgram 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);
Console
Click to execute the code.

Коли ви розгортаєте програму 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 AccountProgram Data Account

Не плутайте ці program data accounts з акаунтами даних програм (див. нижче).

Акаунт даних

У Solana виконуваний код програми зберігається в іншому акаунті, ніж стан програми. Це схоже на те, як операційні системи зазвичай мають окремі файли для програм та їхніх даних.

Для підтримки стану програми визначають інструкції для створення окремих акаунтів, якими вони володіють. Кожен з цих акаунтів має свою унікальну адресу і може зберігати будь-які довільні дані, визначені програмою.

Акаунт данихАкаунт даних

Зауважте, що лише System Program може створювати нові акаунти. Після того, як System Program створює акаунт, він може передати право власності на новий акаунт іншій програмі.

Іншими словами, створення облікового запису даних для користувацької програми відбувається у два кроки:

  1. Викликати System Program для створення облікового запису, а потім передати право власності користувацькій програмі
  2. Викликати користувацьку програму, яка тепер володіє обліковим записом, щоб ініціалізувати дані облікового запису, як визначено в інструкції програми

Цей процес створення облікового запису часто абстрагується як один крок, але корисно розуміти базовий процес.

Наступний приклад показує, як створити та отримати обліковий запис 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 example
const rpc = createSolanaRpc("http://localhost:8899");
const rpcSubscriptions = createSolanaRpcSubscriptions("ws://localhost:8900");
// Generate keypairs for fee payer
const feePayer = await generateKeyPairSigner();
// Fund fee payer
await airdropFactory({ rpc, rpcSubscriptions })({
recipientAddress: feePayer.address,
lamports: lamports(1_000_000_000n),
commitment: "confirmed"
});
// Generate keypair to use as address of mint
const mint = await generateKeyPairSigner();
// Get default mint account size (in bytes), no extensions enabled
const space = BigInt(getMintSize());
// Get minimum balance for rent exemption
const rent = await rpc.getMinimumBalanceForRentExemption(space).send();
// Instruction to create new account for mint (token 2022 program)
// Invokes the system program
const createAccountInstruction = getCreateAccountInstruction({
payer: feePayer,
newAccount: mint,
lamports: rent,
space,
programAddress: TOKEN_2022_PROGRAM_ADDRESS
});
// Instruction to initialize mint account data
// Invokes the token 2022 program
const initializeMintInstruction = getInitializeMintInstruction({
mint: mint.address,
decimals: 9,
mintAuthority: feePayer.address
});
const instructions = [createAccountInstruction, initializeMintInstruction];
// Get latest blockhash to include in transaction
const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();
// Create transaction message
const 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 transaction
await sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions })(
signedTransaction,
{ commitment: "confirmed" }
);
// Get transaction signature
const 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);
Console
Click to execute the code.

Is this page helpful?