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

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

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

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

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

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

  • data: Масив байтів, який зберігає довільні дані для облікового запису. Для невиконуваних облікових записів це часто зберігає стан, який призначений для читання. Для program account (смарт-контрактів) це містить виконуваний програмний код. Поле даних зазвичай називають "даними облікового запису".
  • 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);
Console
Click to execute the code.

Rent

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

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

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

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

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

System Program

За замовчуванням усі нові рахунки належать System Program. 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);
Console
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);
Console
Click to execute the code.

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

Буферний акаунт

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

Is this page helpful?