Аккаунты

Все данные в сети Solana хранятся в аккаунтах. Вы можете представить сеть Solana как публичную базу данных с одной таблицей аккаунтов. Отношение между аккаунтом и его адресом похоже на пару ключ-значение, где ключ — это адрес, а значение — аккаунт.

Каждый аккаунт имеет одинаковую базовую структуру и может быть найден с помощью его адреса.

Диаграмма 3 аккаунтов и их адресов. Включает определение структуры аккаунта.Диаграмма 3 аккаунтов и их адресов. Включает определение структуры аккаунта.

Адрес аккаунта

Адрес аккаунта — это уникальный 32-байтовый идентификатор, используемый для поиска аккаунта в блокчейне Solana. Адреса аккаунтов часто отображаются в виде строк, закодированных в base58. Большинство аккаунтов используют Ed25519 публичный ключ в качестве адреса, но это не обязательно, так как Solana также поддерживает программные производные адреса.

Аккаунт с адресом публичного ключа, закодированным в base58Аккаунт с адресом публичного ключа, закодированным в base58

Публичный ключ

Пример ниже демонстрирует, как использовать Solana SDK для создания keypair.

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.

Программный производный адрес

Программный производный адрес (PDA) — это адрес, который детерминированно создается с использованием ID программы и одного или нескольких опциональных входных данных (seed). Пример ниже демонстрирует, как использовать Solana SDK для создания программного производного адреса.

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.

Структура аккаунта

Каждый Account имеет максимальный размер 10MiB и содержит следующую информацию:

Account
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

Баланс аккаунта в lamports.

Каждый аккаунт должен иметь минимальный баланс в lamports, называемый rent, который позволяет хранить его данные в блокчейне. Rent пропорционален размеру аккаунта.

Хотя этот баланс называется арендой, он работает скорее как депозит, так как весь баланс может быть возвращен, когда аккаунт закрывается. (Название "аренда" происходит от устаревшего поля rent epoch.)

(См. формулу минимального баланса и применимые константы.)

Данные

Это поле обычно называется "данные аккаунта". data в этом поле считается произвольным, так как оно может содержать любую последовательность байтов. Каждая программа определяет структуру данных, хранящихся в этом поле.

  • Программные аккаунты: Это поле содержит либо исполняемый код программы, либо адрес аккаунта данных программы, который хранит исполняемый код программы.
  • Аккаунты данных: Это поле обычно хранит данные состояния, предназначенные для чтения.

Чтение данных из аккаунта Solana включает два шага:

  1. Получение аккаунта с использованием его адреса
  2. Десериализация поля data аккаунта из сырых байтов в соответствующую структуру данных, определенную программой, которой принадлежит аккаунт.

Владелец

Это поле содержит ID программы, которой принадлежит аккаунт.

Каждый аккаунт Solana имеет программу, назначенную его владельцем. Только программа-владелец может изменять data аккаунта или вычитать lamports, как указано в инструкциях программы.

(В случае программного аккаунта владельцем является его загрузочная программа.)

Исполняемый

Это поле указывает, является ли аккаунт программным аккаунтом или аккаунтом данных

  • Если true: Аккаунт является программным аккаунтом
  • Если false: Аккаунт является аккаунтом данных

Эпоха аренды

Поле rent_epoch устарело.

Ранее это поле отслеживало, когда аккаунту нужно было платить аренду. Однако этот механизм сбора аренды был впоследствии упразднён.

Lamports

Баланс аккаунта в lamports.

Каждый аккаунт должен иметь минимальный баланс в lamports, называемый rent, который позволяет хранить его данные в блокчейне. Rent пропорционален размеру аккаунта.

Хотя этот баланс называется арендой, он работает скорее как депозит, так как весь баланс может быть возвращен, когда аккаунт закрывается. (Название "аренда" происходит от устаревшего поля rent epoch.)

(См. формулу минимального баланса и применимые константы.)

Данные

Это поле обычно называется "данные аккаунта". data в этом поле считается произвольным, так как оно может содержать любую последовательность байтов. Каждая программа определяет структуру данных, хранящихся в этом поле.

  • Программные аккаунты: Это поле содержит либо исполняемый код программы, либо адрес аккаунта данных программы, который хранит исполняемый код программы.
  • Аккаунты данных: Это поле обычно хранит данные состояния, предназначенные для чтения.

Чтение данных из аккаунта Solana включает два шага:

  1. Получение аккаунта с использованием его адреса
  2. Десериализация поля data аккаунта из сырых байтов в соответствующую структуру данных, определенную программой, которой принадлежит аккаунт.

Владелец

Это поле содержит ID программы, которой принадлежит аккаунт.

Каждый аккаунт Solana имеет программу, назначенную его владельцем. Только программа-владелец может изменять data аккаунта или вычитать lamports, как указано в инструкциях программы.

(В случае программного аккаунта владельцем является его загрузочная программа.)

Исполняемый

Это поле указывает, является ли аккаунт программным аккаунтом или аккаунтом данных

  • Если true: Аккаунт является программным аккаунтом
  • Если false: Аккаунт является аккаунтом данных

Эпоха аренды

Поле rent_epoch устарело.

Ранее это поле отслеживало, когда аккаунту нужно было платить аренду. Однако этот механизм сбора аренды был впоследствии упразднён.

Account Examples
// Example Token Mint Account
Account {
lamports: 1461600,
data.len: 82,
owner: TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb,
executable: false,
rent_epoch: 0,
data: 010000001e213c90625a7e643d9555bb01b6c3fe6416d7afd523ce8c7ddd9b923ceafb9d00000000000000000901010000001e213c90625a7e643d9555bb01b6,
}
// Example Token Program Account
Account {
lamports: 4513200894,
data.len: 134080,
owner: BPFLoader2111111111111111111111111111111111,
executable: true,
rent_epoch: 18446744073709551615,
data: 7f454c460201010000000000000000000300f70001000000d8f90000000000004000000000000000800902000000000000000000400038000400400009000800,
}

Типы аккаунтов

Существует две основные категории аккаунтов:

Это разделение означает, что исполняемый код программы и её состояние хранятся в отдельных аккаунтах. (Аналогично операционным системам, которые обычно имеют отдельные файлы для программ и их данных.)

Программные аккаунты

Каждая программа принадлежит загрузочной программе, которая используется для развёртывания и управления аккаунтом. Когда новая программа развёртывается, создаётся аккаунт для хранения её исполняемого кода. Этот аккаунт называется программным аккаунтом. (Для упрощения можно считать программный аккаунт самой программой.)

На диаграмме ниже показано, как загрузочная программа используется для развёртывания программного аккаунта. Поле data программного аккаунта содержит исполняемый код программы.

Диаграмма программного аккаунта, его 4 компонентов и загрузочной программы.Диаграмма программного аккаунта, его 4 компонентов и загрузочной программы.

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

Программы, развёрнутые с использованием loader-v3, не содержат программный код в своём поле data. Вместо этого их поле data указывает на отдельный аккаунт данных программы, который содержит программный код. (См. диаграмму ниже.)

Программный аккаунт с данными. Данные указывают на отдельный аккаунт данных программыПрограммный аккаунт с данными. Данные указывают на отдельный аккаунт данных программы

Во время развертывания или обновления программы буферные аккаунты используются для временного размещения загрузки.

Пример ниже извлекает аккаунт Token Program. Обратите внимание, что поле executable установлено в true, что указывает на то, что аккаунт является программой.

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.

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

Аккаунты данных не содержат исполняемого кода. Вместо этого они хранят информацию.

Аккаунт состояния программы

Программы используют аккаунты данных для поддержания своего состояния. Для этого они должны сначала создать новый аккаунт данных. Процесс создания аккаунта состояния программы часто абстрагирован, но полезно понимать основной процесс.

Чтобы управлять своим состоянием, новая программа должна:

  1. Вызвать System Program для создания аккаунта. (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.

Системные аккаунты

Не все аккаунты получают нового владельца после создания System Program. Аккаунты, принадлежащие System Program, называются системными аккаунтами. Все аккаунты кошельков являются системными аккаунтами, что позволяет им оплачивать комиссии за транзакции.

Кошелек, принадлежащий System Program, содержащий 1,000,000 лампортовКошелек, принадлежащий System Program, содержащий 1,000,000 лампортов

Когда SOL отправляется на новый адрес впервые, на этом адресе создается аккаунт, принадлежащий System Program.

В приведённом ниже примере создаётся новая пара ключей (keypair) и пополняется SOL. После выполнения кода вы можете увидеть адрес аккаунта owner, который является 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.

Is this page helpful?