Вступ до Solana
Перш ніж розробляти на Solana, вам потрібно зрозуміти кілька фундаментальних концепцій, які роблять Solana унікальною. Цей посібник охоплює облікові записи, транзакції, програми та їхню взаємодію.
Облікові записи на Solana
Архітектура Solana зосереджена навколо облікових записів: контейнерів даних, які зберігають інформацію в блокчейні. Уявіть облікові записи як окремі файли у файловій системі, де кожен файл має певні властивості та власника, який ним керує.
Кожен обліковий запис Solana має однакову базову структуру:
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,
}
Кожен обліковий запис має унікальну 32-байтову адресу, яка відображається як рядок у кодуванні base58 (наприклад, 14grJpemFaf88c8tiVb77W7TYg2W3ir6pfkKz3YjhhZ5
). Ця адреса служить ідентифікатором облікового запису в блокчейні і є способом знаходження конкретних даних.
Облікові записи можуть зберігати до 10 МіБ даних, які можуть містити або виконуваний програмний код, або дані, специфічні для програми.
Усі облікові записи вимагають депозиту в лампортах, пропорційного розміру їхніх даних, щоб стати "вільними від оренди". Термін "оренда" є історичним, оскільки раніше лампорти вираховувалися з облікових записів кожну епоху, але ця функція тепер вимкнена. Сьогодні депозит працює більше як поворотна застава. Доки ваш обліковий запис підтримує мінімальний баланс для свого розміру даних, він залишається вільним від оренди і зберігається необмежено довго. Коли обліковий запис більше не потрібен, ви можете закрити його і повністю повернути цей депозит.
Кожен обліковий запис належить програмі, і тільки ця програма-власник може змінювати дані облікового запису або знімати його лампорти. Однак будь-хто може збільшити баланс лампортів облікового запису, що корисно для фінансування операцій або оплати оренди без необхідності викликати саму програму.
Повноваження підпису працюють по-різному залежно від власності. Облікові записи, якими володіє Системна Програма, можуть підписувати транзакції для зміни власних даних, передачі права власності або повернення збережених лампортів. Після передачі права власності іншій програмі, ця програма отримує повний контроль над обліковим записом, незалежно від того, чи все ще маєте ви приватний ключ. Ця передача контролю є постійною та незворотною.
Типи облікових записів
Найпоширенішим типом облікового запису є Системний Обліковий Запис, який зберігає лампорти (найменшу одиницю SOL) і належить Системній Програмі. Вони функціонують як базові гаманці, з якими користувачі взаємодіють безпосередньо для надсилання та отримання SOL.
Токенові Облікові Записи мають спеціалізоване призначення, зберігаючи інформацію про SPL токени, включаючи дані про власність та метадані токенів. Токенова Програма володіє цими обліковими записами та керує всіма операціями, пов'язаними з токенами, в екосистемі Solana. Токенові Облікові Записи є Обліковими Записами Даних.
Облікові Записи Даних зберігають інформацію, специфічну для додатків, і належать користувацьким програмам. Ці облікові записи містять стан вашого додатка і можуть бути структуровані так, як вимагає ваша програма, від простих профілів користувачів до складних фінансових даних.
Нарешті, Програмні Облікові Записи містять виконуваний код, який працює на Solana, де власне й живе смарт-контракт. Ці облікові записи позначені як executable: true
і зберігають логіку програми, яка обробляє інструкції та керує станом.
Робота з даними облікового запису
Ось як програми взаємодіють з даними облікового запису:
#[derive(BorshSerialize, BorshDeserialize)]
pub struct UserAccount {
pub name: String,
pub balance: u64,
pub posts: Vec<u32>,
}
pub fn update_user_data(accounts: &[AccountInfo], new_name: String) -> ProgramResult {
let user_account = &accounts[0];
// Deserialize existing data
let mut user_data = UserAccount::try_from_slice(&user_account.data.borrow())?;
// Modify the data
user_data.name = new_name;
// Serialize back to account
user_data.serialize(&mut &mut user_account.data.borrow_mut()[..])?;
Ok(())
}
На відміну від баз даних, де ви просто вставляєте записи, облікові записи Solana повинні бути явно створені та профінансовані перед використанням.
Транзакції на Solana
Транзакції Solana — це атомарні операції, які можуть містити кілька інструкцій. Усі інструкції в транзакції або успішно виконуються разом, або разом зазнають невдачі: часткового виконання не існує.
Транзакція складається з:
- Інструкцій: окремих операцій для виконання
- Рахунків: конкретних рахунків, з яких кожна інструкція буде читати або в які буде записувати
- Підписантів: рахунків, які авторизують транзакцію
Transaction {
instructions: [
// Instruction 1: Transfer SOL
system_program::transfer(from_wallet, to_wallet, amount),
// Instruction 2: Update user profile
my_program::update_profile(user_account, new_name),
// Instruction 3: Log activity
my_program::log_activity(activity_account, "transfer", amount),
],
accounts: [from_wallet, to_wallet, user_account, activity_account]
signers: [user_keypair],
}
Вимоги до транзакцій та комісії
Транзакції обмежені загальним розміром 1 232 байти, що обмежує кількість інструкцій та рахунків, які ви можете включити.
Кожна інструкція в транзакції потребує трьох основних компонентів: адреси програми для виклику, всіх рахунків, з яких інструкція буде читати або в які буде записувати, та будь-яких додаткових даних, таких як аргументи функції.
Інструкції виконуються послідовно в порядку, який ви вказуєте в транзакції.
Кожна транзакція має базову комісію 5 000 лампортів за підпис для компенсації валідаторам за обробку вашої транзакції.
Ви також можете сплатити додаткову комісію за пріоритезацію, щоб збільшити ймовірність того, що поточний лідер швидко обробить вашу транзакцію. Ця комісія за пріоритезацію розраховується як ваш ліміт обчислювальних одиниць, помножений на вашу ціну обчислювальної одиниці (вимірюється в мікро-лампортах).
prioritization_fee = compute_unit_limit × compute_unit_price
Programs on Solana
Програми на Solana є принципово безстановими, тобто вони не зберігають внутрішнього стану між викликами функцій. Натомість вони отримують рахунки як вхідні дані, обробляють дані в цих рахунках і повертають змінені результати.
Такий безстановий дизайн забезпечує передбачувану поведінку та уможливлює потужні шаблони компонування.
Самі програми зберігаються в спеціальних рахунках, позначених як executable: true
, що містять скомпільований бінарний код, який виконується при виклику.
Користувачі взаємодіють з цими програмами, надсилаючи транзакції, що містять конкретні інструкції, кожна з яких націлена на певні функції програми з необхідними даними рахунків та параметрами.
use solana_program::prelude::*;
#[derive(BorshSerialize, BorshDeserialize)]
pub struct User {
pub name: String,
pub created_at: i64,
}
pub fn create_user(
accounts: &[AccountInfo],
name: String,
) -> ProgramResult {
let user_account = &accounts[0];
let user = User {
name,
created_at: Clock::get()?.unix_timestamp,
};
user.serialize(&mut &mut user_account.data.borrow_mut()[..])?;
Ok(())
}
Програми можуть оновлюватися їхнім призначеним органом оновлення, що дозволяє розробникам виправляти помилки та додавати функції після розгортання. Однак видалення цього органу оновлення робить програму назавжди незмінною, надаючи користувачам гарантії, що код ніколи не зміниться.
Для прозорості та безпеки користувачі можуть перевірити, що програми в мережі відповідають їхньому публічному вихідному коду через верифіковані збірки, забезпечуючи точну відповідність розгорнутого байт-коду опублікованому вихідному коду.
Адреси, похідні від програм (PDAs)
PDAs — це детерміновано згенеровані адреси, які забезпечують потужні шаблони програмування. Вони створюються за допомогою сідів та ідентифікатора програми, генеруючи адреси без відповідних приватних ключів.
PDAs використовують SHA-256
хешування з певними вхідними даними, включаючи ваші власні сіди, значення bump для забезпечення того, щоб результат був поза кривою, ідентифікатор програми, яка володітиме PDA, та постійний маркер.
Коли хеш створює адресу на кривій (що трапляється приблизно у 50% випадків), система ітерується від bump 255 до 254, 253 і так далі, доки не знайде результат поза кривою.
use solana_nostd_sha256::hashv;
const PDA_MARKER: &[u8; 21] = b"ProgramDerivedAddress";
let pda = hashv(&[
seed_data.as_ref(), // Your custom seeds
&[bump], // Bump to ensure off-curve
program_id.as_ref(), // Program that owns this PDA
PDA_MARKER,
]);
Переваги
Детермінована природа PDAs усуває необхідність зберігати адреси: ви можете регенерувати їх з тих самих сідів, коли це потрібно.
Це створює передбачувані схеми адресації, які функціонують як структури hashmap у мережі. Що важливіше, програми можуть підписуватися за свої власні PDAs, забезпечуючи автономне управління активами без розкриття приватних ключів:
let seeds = &[b"vault", user.as_ref(), &[bump]];
invoke_signed(
&transfer_instruction,
&[from_pda, to_account, token_program],
&[&seeds[..]], // Program proves PDA control
)?;
Міжпрограмний виклик (CPI)
CPI дозволяють програмам викликати інші програми в межах однієї транзакції, створюючи справжню компонованість, де кілька програм можуть взаємодіяти атомарно без зовнішньої координації.
Це дозволяє розробникам створювати складні додатки, комбінуючи існуючі програми, а не перебудовуючи функціональність з нуля.
CPI слідують тому ж шаблону, що й звичайні інструкції, вимагаючи вказати цільову програму, необхідні облікові записи та дані інструкції, з основною відмінністю в тому, що вони можуть виконуватися всередині інших програм.
Програма, що викликає, зберігає контроль над потоком, делегуючи конкретні операції спеціалізованим програмам:
let cpi_accounts = TransferAccounts {
from: source_account.clone(),
to: destination_account.clone(),
authority: authority_account.clone(),
};
let cpi_ctx = CpiContext::new(token_program.clone(), cpi_accounts);
token_program::cpi::transfer(cpi_ctx, amount)?;
Обмеження та можливості
Оригінальні підписанти транзакції зберігають свої повноваження протягом усього ланцюжка CPI, дозволяючи програмам безперешкодно діяти від імені користувачів.
Однак програми можуть робити CPI лише до 4 рівнів глибини (A → B → C → D
), щоб запобігти нескінченній рекурсії. Програми також можуть підписуватися за свої PDA в CPI, використовуючи CpiContext::new_with_signer
, що дозволяє здійснювати складні автономні операції.
Ця компонованість дозволяє виконувати складні операції через кілька програм в межах однієї атомарної транзакції, роблячи додатки Solana високомодульними та інтероперабельними.