Модель облікових записів Solana

На Solana всі дані зберігаються в тому, що називається "accounts" (облікові записи). Можна уявити дані на Solana як публічну базу даних з єдиною таблицею "Accounts", де кожен запис у цій таблиці є "account" (обліковим записом). Кожен обліковий запис Solana має однакову базову структуру Account.

Облікові записиОблікові записи

Ключові моменти

  • Облікові записи можуть зберігати до 10MiB даних, які містять або виконуваний програмний код, або стан програми.
  • Облікові записи вимагають депозиту rent у lamport (SOL), який пропорційний обсягу збережених даних, і ви можете повністю повернути його, коли закриваєте обліковий запис.
  • Кожен обліковий запис має програму власника. Тільки програма, яка володіє обліковим записом, може змінювати його дані або зменшувати його баланс у lamport. Але будь-хто може збільшити баланс.
  • Облікові записи Sysvar — це спеціальні облікові записи, які зберігають стан мережевого кластера.
  • Програмні облікові записи зберігають виконуваний код смарт-контрактів.
  • Облікові записи даних створюються програмами для зберігання та керування станом програми.

Обліковий запис

Кожен обліковий запис на Solana має унікальну 32-байтову адресу, яка часто відображається як рядок у кодуванні base58 (наприклад, vines1vzrYbzLMRdu58ou5XTby4qAqVRLmqo6NmXEyxm).

Зв'язок між обліковим записом та його адресою працює як пара ключ-значення, де адреса є ключем для пошуку відповідних онлайн-даних облікового запису. Адреса облікового запису діє як "унікальний ідентифікатор" для кожного запису в таблиці "Accounts".

Адреса облікового записуАдреса облікового запису

Більшість облікових записів Solana використовують відкритий ключ Ed25519 як свою адресу.

import { generateKeyPairSigner } from "@solana/kit";
// Kit does not enable extractable private keys
const keypairSigner = await generateKeyPairSigner();
console.log(keypairSigner);
Click to execute the code.

Хоча відкриті ключі зазвичай використовуються як адреси облікових записів, Solana також підтримує функцію під назвою Program Derived Addresses (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}`);
Click to execute the code.

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

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

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

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

  • data: Масив байтів, який зберігає довільні дані для облікового запису. Для невиконуваних облікових записів це часто зберігає стан, який призначений для читання. Для програмних облікових записів (смарт-контрактів) це містить виконуваний програмний код. Поле даних зазвичай називають "даними облікового запису".
  • executable: Цей прапорець показує, чи є обліковий запис програмою.
  • lamports: Баланс облікового запису в lamport, найменшій одиниці SOL (1 SOL = 1 мільярд lamport).
  • owner: Ідентифікатор програми (відкритий ключ) програми, яка володіє цим обліковим записом. Тільки програма-власник може змінювати дані облікового запису або зменшувати його баланс в lamport.
  • rent_epoch: Застаріле поле з часів, коли Solana мала механізм, який періодично знімав lamport з облікових записів. Хоча це поле все ще існує в типі Account, воно більше не використовується, оскільки збір rent був застарілим.
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,
}
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);
Click to execute the code.

Rent

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

Термін "rent" походить від застарілого механізму, який регулярно вираховував lamport з акаунтів, що падали нижче порогу оренди. Цей механізм більше не активний.

Власник програми

У Solana "розумні контракти" називаються програмами. Власність програми є ключовою частиною моделі акаунтів Solana. Кожен акаунт має призначену програму як свого власника. Тільки програма-власник може:

  • Змінювати поле data акаунта
  • Знімати lamport з балансу акаунта

Системна програма

За замовчуванням усі нові акаунти належать Системній програмі. Системна програма виконує кілька ключових функцій:

  • Створення нових акаунтів: Тільки Системна програма може створювати нові акаунти.
  • Розподіл простору: Встановлює байтову ємність для поля даних кожного акаунта.
  • Передача / Призначення власності програми: Після створення акаунта Системною програмою, вона може перепризначити визначеного власника програми на інший програмний акаунт. Так користувацькі програми отримують власність над новими акаунтами, створеними Системною програмою.

Усі акаунти "гаманців" на Solana — це просто акаунти, якими володіє Системна програма. Баланс 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);
Click to execute the code.

Програмний рахунок

Розгортання програми 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);
Click to execute the code.

Коли ви розгортаєте програму Solana, вона зберігається в програмному рахунку. Програмні рахунки належать Програмі-завантажувачу. Існує кілька версій завантажувача, але всі, крім loader-v3, зберігають виконуваний код безпосередньо в програмному рахунку. Loader-v3 зберігає виконуваний код в окремому "рахунку даних програми", а програмний рахунок просто вказує на нього. Коли ви розгортаєте нову програму, CLI Solana за замовчуванням використовує найновішу версію завантажувача.

Буферний рахунок

Loader-v3 має спеціальний тип рахунку для тимчасового розміщення завантаження програми під час розгортання або повторного розгортання/оновлення. У loader-v4 все ще є буфери, але вони є просто звичайними програмними рахунками.

Рахунок даних програми

Loader-v3 працює інакше, ніж усі інші програми BPF Loader. Програмний рахунок містить лише адресу рахунку даних програми, який зберігає фактичний виконуваний код:

Акаунт даних програмиАкаунт даних програми

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

Акаунт даних

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

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

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

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

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

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

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

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 example
const rpc = createSolanaRpc("http://127.0.0.1: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);
Click to execute the code.

Is this page helpful?