Рассмотрим две самые перспективные платформы смарт-контрактов в мире – Near Protocol и Solana. Не смотря на отсутствие масштабируемости и высокие комиссии, Ethereum по-прежнему доминирует на рынке платформ смарт-контрактов, однако многие разработчики начали искать жизнеспособные альтернативы, среди которых Near и Solana выбились в лидеры.
Что такое Solana?
Компания Solana была основана в 2017 году Анатолием Яковенко, который ранее работал в DropBox (компании, предоставляющий файловый хостинг – прим. переводчика). Яковенко вместе с Эриком Уильямсом и техническим директором Грегом Фрицджеральдом создали Solana для решения существующих проблем в Bitcoin и Ethereum. Проект привлек инвестиции от венчурных компаний Multicoin Capital, Foundation Capital, SLOW Capital, CMCC Global, Abstract Ventures и др.
Особенности блокчейна Solana
-
50 000 транзакций в секунду и время блока 0,4 секунды
-
28,4 миллиона транзакций в секунду в 40-гигабитной сети.
-
алгоритм консенсуса Proof-of-History.
Как работает Proof-of-History (PoH)?
Для децентрализованной сети метод достижения консенсуса крайне важен. Для обеспечения консенсуса сеть Биткойн использует доказательства выполнения работы (proof-of-work, PoW). Хотя метод очень безопасен, трудно не заметить его самую существенную проблему – отсутствие масштабируемости. Сеть Биткойн может проводить только 7 транзакций в секунду.
Solana использует доказательство истории (Proof-of-History, PoH), в котором создаются исторические записи, доказывающие, что событие произошло в определенный момент времени. Вот некоторые моменты, которые необходимо иметь в виду:
-
Алгоритм использует высокочастотную функцию проверяемой задержки (Verifiable Delay Function, VDF), которая требует определенного количества последовательных шагов для завершения.
-
Транзакции или события, с тактовым временем сети, будут обозначены уникальным порядковым номером хеширования (count), который может быть публично проверен.
-
Порядковый номер хеширования позволяет сети точно знать, когда произошла транзакция или событие.
-
Каждая нода имеет криптографические часы, которые помогают отслеживать время в сети и порядок событий.
Благодаря PoH сеть Solana поддерживает 50 000 транзакций в секунду при работе на графических процессорах.
Что такое кластер Solana?
Кластер – это набор независимых компьютеров, работающих вместе, которые можно рассматривать как единую систему. Основные характеристики кластера:
-
Помогает проверять вывод программ, переданных пользователем.
-
Ведет запись любой транзакции или события, которое совершает пользователь.
-
Отслеживает, какие компьютеры выполняли значимую работу для поддержания работоспособности сети.
-
Отслеживает владение активами реального мира.
Что из перечисленного использует шардинг?
Программирование в Solana
Смарт-контракты в Solana написаны на Rust или C и скомпилированы в байткод фильтра пакетов Berkeley (Berkeley Packet Filter, BPF). Рекомендуется писать на Rust, поскольку для него доступно больше инструментов. Новичкам следует писать свои программы с использованием фреймворка Anchor, который упрощает их выполнение.
Solana имеет уникальную модель аккаунтов, которая похожа на файлы в операционной системе Linux. Они могут содержать произвольные данные, а также метаданные о том, как к ним можно получить доступ. Следует помнить, что аккаунты имеют фиксированный размер и не могут быть изменены.
Текущая модель программирования Solana может вынудить вас перенести логику приложения за пределы блокчейна или изменить функциональность так, чтобы она соответствовала ограничениям размера аккаунта. Однако это может снизить эффективность программы
Что из перечисленного ниже является важной частью Solana?
Example Contract
#![feature(proc_macro_hygiene)] use anchor_lang::prelude::*; use anchor_spl::token::{self, TokenAccount, Transfer}; #[program] pub mod plutocratic_hosting { use super::*; /// Initialize a new contract with initialized content. #[access_control(Initialize::validate(&ctx, nonce))] pub fn initialize( ctx: Context<Initialize>, price: u64, content: String, nonce: u8, ) -> ProgramResult { // Transfer funds to the contract vault. let cpi_accounts = Transfer { from: ctx.accounts.from.to_account_info().clone(), to: ctx.accounts.vault.to_account_info().clone(), authority: ctx.accounts.owner.clone(), }; let cpi_program = ctx.accounts.token_program.clone(); let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts); token::transfer(cpi_ctx, price)?; // Initialize the content data. let content_record = &mut ctx.accounts.content; content_record.price = price; content_record.content = content; content_record.nonce = nonce; content_record.owner = *ctx.accounts.owner.to_account_info().key; content_record.vault = *ctx.accounts.vault.to_account_info().key; Ok(()) } /// Purchase content address for new price, if transferring more tokens. #[access_control(check_funds(&ctx.accounts.content, price))] pub fn purchase(ctx: Context<Purchase>, price: u64, content: String) -> ProgramResult { // Transfer funds from contract back to owner. let seeds = &[ ctx.accounts.content.to_account_info().key.as_ref(), &[ctx.accounts.content.nonce], ]; let signer = &[&seeds[..]]; let cpi_accounts = Transfer { from: ctx.accounts.vault.to_account_info().clone(), to: ctx.accounts.owner_token.to_account_info().clone(), authority: ctx.accounts.contract_signer.clone(), }; let cpi_program = ctx.accounts.token_program.clone(); let cpi_ctx = CpiContext::new_with_signer(cpi_program, cpi_accounts, signer); token::transfer(cpi_ctx, ctx.accounts.content.price)?; // Transfer funds from new owner to contract. let cpi_accounts = Transfer { from: ctx.accounts.new_owner_token.to_account_info().clone(), to: ctx.accounts.vault.to_account_info().clone(), authority: ctx.accounts.new_owner.clone(), }; let cpi_program = ctx.accounts.token_program.clone(); let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts); token::transfer(cpi_ctx, price)?; // Overwrite content let content_record = &mut ctx.accounts.content; content_record.price = price; content_record.content = content; content_record.owner = *ctx.accounts.new_owner.to_account_info().key; Ok(()) } } #[account] pub struct ContentRecord { /// Price at which the current content is owned. pub price: u64, /// Content Data. pub content: String, /// Public key of current owner of the content. pub owner: Pubkey, /// Address for token program of funds locked in contract. pub vault: Pubkey, /// Nonce for the content, to create valid program derived addresses. pub nonce: u8, } #[derive(Accounts)] pub struct Initialize<'info> { #[account(init)] content: ProgramAccount<'info, ContentRecord>, #[account(mut, "&vault.owner == contract_signer.key")] vault: CpiAccount<'info, TokenAccount>, /// Program derived address for the contract. contract_signer: AccountInfo<'info>, /// Token account the contract is made from. #[account(mut, has_one = owner)] from: CpiAccount<'info, TokenAccount>, /// Owner of the `from` token account. owner: AccountInfo<'info>, token_program: AccountInfo<'info>, rent: Sysvar<'info, Rent>, } impl<'info> Initialize<'info> { pub fn validate(ctx: &Context<Self>, nonce: u8) -> Result<()> { let signer = Pubkey::create_program_address( &[ ctx.accounts.content.to_account_info().key.as_ref(), &[nonce], ], ctx.program_id, ) .map_err(|_| ErrorCode::InvalidNonce)?; if &signer != ctx.accounts.contract_signer.to_account_info().key { return Err(ErrorCode::InvalidSigner.into()); } Ok(()) } } #[derive(Accounts)] pub struct Purchase<'info> { #[account(mut, has_one = vault)] content: ProgramAccount<'info, ContentRecord>, #[account(mut)] vault: CpiAccount<'info, TokenAccount>, #[account(seeds = [ content.to_account_info().key.as_ref(), &[content.nonce], ])] contract_signer: AccountInfo<'info>, #[account(mut, has_one = owner)] owner_token: CpiAccount<'info, TokenAccount>, #[account(mut)] new_owner_token: CpiAccount<'info, TokenAccount>, #[account(signer)] new_owner: AccountInfo<'info>, owner: AccountInfo<'info>, token_program: AccountInfo<'info>, } fn check_funds(check: &ContentRecord, new_price: u64) -> Result<()> { if check.price >= new_price { return Err(ErrorCode::InsufficientFunds.into()); } Ok(()) } #[error] pub enum ErrorCode { #[msg("The given nonce does not create a valid program derived address.")] InvalidNonce, #[msg("The derived signer does not match that which was given.")] InvalidSigner, #[msg("Insufficient funds provided to purchase route.")] InsufficientFunds, }
Что происходит в контракте?
-
Все аккаунты, к которым необходимо получить доступ, описываются в структуре для каждого вызова с помощью #[derive(Accounts)].
-
Дополнительные функции помогают инициализировать данные аккаунта первоначального владельца и покупок. Это позволяет любому желающему приобрести их за большее количество токенов.
-
Временные данные передаются в параметрах функции. Эти параметры находятся внутри функций initialize и purchase. Сюда также входит контекст (Context), в котором хранятся аккаунты, необходимые для транзакции.
-
Состояние контракта находится в структуре ContentRecord. Он дополнительно помечен #[account], показывая, что он представляет собой слой данных для аккаунта.
Что такое NEAR Protocol?
Протокол появился летом 2018 года и был разработан для создания идеальной среды для децентрализованных приложений. Он обеспечивает более высокую скорость транзакций и лучшую совместимость с другими цепочками. В NEAR есть уникальная технология шардинга и введен механизм генерации блоков под названием “Doomslug”, предложенный в 2019 году. Doomslug обеспечивает рациональную или “Doomslug” финальность, гарантируя, что блоки финализируются за считанные секунды.
Протокол NEAR разработан NEAR Collective, сообществом разработчиков и исследователей, сотрудничающих в создании проекта. Вот некоторые критические особенности NEAR:
-
NEAR – это шардированная система, обеспечивающая бесконечную масштабируемость.
-
NEAR – это простой в использовании протокол, который позволяет разработчикам легко и быстро создавать приложения.
-
NEAR – это полноценный блокчейн первого уровня.
-
Децентрализованные приложения (dApps), созданные с помощью NEAR, работают поверх блокчейна NEAR.
Что такое NEAR Collective?
NEAR Collective включает в себя отдельные организации и любых других участников, которые постоянно работают над улучшением протокола NEAR. Collective работает над такими проектами, как написание изначального кода и реализация в сети NEAR. NEAR полностью децентрализован, работает независимо и не может быть остановлен или подвержен манипуляциям даже теми, кто его создал.
Это некоммерческая организация, деятельность которой сосредоточена на создании живой экосистемы вокруг блокчейна NEAR. Она помогает в координации деятельности по управлению и разработке. Блокчейн NEAR – лишь один из нескольких проектов под эгидой Collective.
Механизм консенсуса NEAR
Механизм консенсуса, реализованный в NEAR, называется Nightshade. Nightshade моделирует систему как единый блокчейн. Список всех транзакций в каждом блоке разбивается на физические кусочки – чанки (chunks), по одному на каждый шард. Все чанки накапливаются в один блок. Обратите внимание, что чанки могут быть подтверждены (валидированы) только нодами, которые поддерживают состояние этого шарда.
Ключевым компонентом NEAR являются валидаторы. Валидаторы отвечают за поддержание консенсуса в рамках протокола. Валидаторы – это специализированные ноды, которые должны поддерживать свои серверы онлайн 100% времени, при этом постоянно обновляя свои системы.
Вот несколько моментов, которые нужно помнить о валидаторах сети.
-
NEAR определяет своих сетевых валидаторов каждую новую эпоху, избирая их на основе их заблокированной доли – стейка.
-
Уже избранные валидаторы повторно регистрируются автоматически заново отправляя в стейк свои токены и начисленные вознаграждения.
-
Потенциальные валидаторы должны иметь стейк выше динамически определяемого уровня.
-
Есть два способа, которые валидатор может использовать для увеличения своего стейка – он может купить токены самостоятельно или одолжить через делегирование доли.
-
Вознаграждение, которое вы получаете, прямо пропорционально вашему стейку – больше стейк, больше вознаграждение.
Консенсус основан на выборе самой тяжелой цепочки. Это означает что, как только производитель блока публикует блок, он собирает подписи нод-валидаторов. Вес блока – это совокупный стейк всех подписавших нод, чьи подписи включены в блок. Вес цепочки – это сумма весов блоков. Кроме того, в консенсусе используется гаджет финальности, который вводит дополнительные условия наказаний (слешинга) для повышения безопасности цепочки.
Doomslug" - это механизм генерации блоков какого протокола?
Aurora и NEAR Protocol
Aurora также запущенная на NEAR Protocol, обеспечивает работу с Ethereum являясь Layer-2 решением. Ключевые особенности Aurora:
-
Помогает увеличить пропускную способность до тысяч транзакций в секунду.
-
Время финализации блока составляет 2 секунды.
-
Устойчивость к росту экосистемы в будущем
-
Низкие комиссии за транзакции, которые в 1000 раз ниже, чем у Ethereum.
-
Абсолютная совместимость с Ethereum.
Пример контракта
use near_sdk::borsh::{self, BorshDeserialize, BorshSerialize}; use near_sdk::collections::LookupMap; use near_sdk::{env, near_bindgen, AccountId, Balance, Promise}; #[global_allocator] static ALLOC: near_sdk::wee_alloc::WeeAlloc = near_sdk::wee_alloc::WeeAlloc::INIT; #[derive(BorshDeserialize, BorshSerialize)] pub struct ContentRecord { pub price: Balance, pub content: String, pub owner: AccountId, } #[near_bindgen] #[derive(BorshDeserialize, BorshSerialize)] pub struct ContentTracker { values: LookupMap<String, ContentRecord>, contract_owner: AccountId, } impl Default for ContentTracker { fn default() -> Self { let contract_owner = env::predecessor_account_id(); Self { values: LookupMap::new(b"v".to_vec()), contract_owner, } } } #[near_bindgen] impl ContentTracker { /// Gets content at a given route. pub fn get_route(&self, route: String) -> Option<String> { self.values.get(&route).map(|v| v.content) } /// Purchases a route based on funds sent to the contract. #[payable] pub fn purchase(&mut self, route: String, content: String) { let deposit = env::attached_deposit(); assert!(deposit > 0); if let Some(entry) = self.values.get(&route) { assert!( deposit > entry.price, "Not enough deposit to purchase route, price: {} deposit: {}", entry.price, deposit ); // Refund purchase to existing owner Promise::new(entry.owner).transfer(entry.price); } // Update record for the contract state. self.values.insert( &route, &ContentRecord { price: deposit, content, owner: env::predecessor_account_id(), }, ); } /// Allows owner of the contract withdraw funds. pub fn withdraw(&mut self) { assert_eq!(env::predecessor_account_id(), self.contract_owner); // Send the contract funds to the contract owner Promise::new(self.contract_owner.clone()).transfer(env::account_balance()); } }
Что происходит в контракте?
Давайте рассмотрим подробнее, что же происходит в контракте.
-
Контракт определяется #[near_bindgen] в ContentTracker, он похож на конструктор в Solidity (язык программирования смарт-контрактов в Ethereum) и вызывается при развертывании контракта.
-
Описание функции покупки #[payable].
-
Контракт включает асинхронные вызовы в виде строк Promise::new(…).transfer(…);.
-
Структура данных LookupMap<String, ContentRecord> обрабатывает поиск ключ-значение, которое обращается к хранилищу. Это равносильно “маппингу” (mapping) в Solidity.
Заключение
И Solana, и NEAR Protocol – это блестящие платформы смарт-контрактов, созданные очень активными сообществами. Обе они обладают специальными функциями, которые помогают бороться с самой большой проблемой децентрализованного мира – скоростью. Обе платформы продолжают развиваться и имеют большие перспективы.