Ako Solani pristupite kao „bržem Ethereumu“, prve prototipe ćete verovatno napraviti brzo, ali ćete ubrzo početi da se borite protiv same platforme.
Solana nije EVM sa kraćim vremenom bloka. Njen programski model više podseća na operativni sistem u kojem se izvršni kod, podaci, dozvole i resursi eksplicitno navode pre izvršavanja. Program ne poseduje skriveni globalni storage. Klijent unapred opisuje koje naloge transakcija čita, koje menja i ko mora da je potpiše.
To je razlog zbog kog Solana može paralelno da obrađuje transakcije koje ne menjaju isto stanje. Istovremeno, to je izvor većine grešaka developera koji dolaze iz EVM sveta.
Ovaj članak objašnjava šta je Solana iz perspektive aplikacionog developera, kako rade nalozi i programi, gde se pojavljuje paralelizacija, kako izgleda realna integracija i koliko operacije zapravo koštaju. Fokus nije na maksimalnom teorijskom TPS-u, već na odlukama koje utiču na dizajn aplikacije.
Šta je Solana u praktičnom smislu
Za aplikacionog developera, Solana se može posmatrati kao distribuirano izvršno okruženje sa nekoliko osnovnih primitiva:
- nalozi koji čuvaju SOL, podatke ili izvršni kod
- programi koji obrađuju instrukcije
- transakcije koje grupišu jednu ili više instrukcija
- digitalni potpisi koji autorizuju promene
- Program Derived Addresses, odnosno determinističke adrese bez privatnog ključa
- Cross-Program Invocations za pozivanje drugih programa
- JSON-RPC i WebSocket interfejsi za komunikaciju sa mrežom
Programi se kompajliraju u Solana Bytecode Format, odnosno sBPF, i izvršavaju se u sandbox okruženju. Programski kod je stateless u smislu da se promenljivo stanje ne čuva unutar izvršnog programa. Stanje se nalazi u odvojenim nalozima koji se prosleđuju instrukciji.[1]
Ovo razdvajanje je osnova cele arhitekture.
Na EVM-u je prirodno razmišljati ovako:
contract.state.balance[user]
Na Solani je bliži mentalni model:
program + instruction data + explicit account list
Program dobija naloge nad kojima treba da radi. Runtime proverava da li su označeni kao writable, da li su potrebni potpisnici prisutni i da li program ima pravo da menja njihove podatke.
Proof of History nije zamena za konsenzus
Proof of History često se opisuje kao Solanin konsenzusni mehanizam, ali je to pojednostavljenje koje nije naročito korisno developerima.
Proof of History je sekvencijalni kriptografski zapis koji mreži daje proverljiv redosled događaja i meru proteklog vremena. Konsenzus se i dalje oslanja na proof-of-stake glasanje validatora i Tower BFT pravila. Proof of History pomaže mreži da smanji deo komunikacije potreban za usaglašavanje oko vremena i redosleda događaja.[2]
Za većinu aplikacija nije potrebno implementaciono razumevanje Tower BFT-a. Važnije posledice su:
- mreža je organizovana u slotove
- validator koji je lider proizvodi blok za svoj slot
- transakcija uključuje recent blockhash
- blockhash ograničava životni vek transakcije
- različiti nivoi potvrde predstavljaju različit nivo sigurnosti da stanje neće biti odbačeno
Drugim rečima, recent blockhash nije samo polje koje SDK zahteva. On je deo mehanizma za ograničavanje validnosti transakcije i zaštitu od ponovnog izvršavanja.
Nalog je osnovna jedinica stanja
Na Solani se praktično sve predstavlja nalogom. Svaki nalog ima istu osnovnu strukturu:
- adresu od 32 bajta
- balans izražen u lamportima
- niz bajtova sa podacima
- adresu programa koji je vlasnik naloga
-
executablezastavicu - istorijsko polje
rent_epoch
Jedan SOL sadrži milijardu lamporta. Lamport je najmanja obračunska jedinica u kojoj RPC i programi predstavljaju SOL iznose.
Bitna razlika između vlasnika naloga i autoriteta korisnika često izaziva zabunu. Polje owner nije nužno wallet korisnika. Ono označava program koji sme da menja podatke naloga ili da umanjuje njegov SOL balans.
Na primer:
- običan wallet nalog najčešće poseduje System Program
- token nalog poseduje odgovarajući Token Program
- nalog sa stanjem vaše aplikacije poseduje vaš program
- executable nalog sadrži program koji runtime može da izvrši
Bilo koji program može dodati lamporte writable nalogu, ali samo program koji poseduje nalog može oduzeti lamporte ili menjati njegove podatke, uz dodatne runtime provere.[3]
Ovaj model ima praktičnu posledicu: aplikacija ne bi trebalo da „traži podatke unutar ugovora“. Ona treba da zna adresu naloga u kojem se podaci nalaze.
Nalozi nisu objekti u klasičnom smislu
Pretpostavimo da pravite marketplace i želite stanje porudžbine:
pub struct Order {
pub buyer: Pubkey,
pub seller: Pubkey,
pub amount: u64,
pub status: u8,
}
Na Solani ovaj objekat neće živeti u mapi unutar programa. Svaka porudžbina može biti poseban data account.
To donosi nekoliko prednosti:
- porudžbine mogu da se čitaju direktno preko RPC-a
- različite porudžbine mogu da se menjaju paralelno
- nalog može imati determinističku adresu
- trošak prostora može se pripisati korisniku koji kreira stanje
- nalog se može zatvoriti, a njegov skladišni depozit vratiti
Ali postoje i troškovi:
- morate projektovati adresiranje stanja
- morate znati koliko bajtova alocirate
- migracija strukture podataka zahteva planiranje
- svaki nalog povećava listu naloga u transakciji
- indeksiranje većeg broja naloga postaje zaseban infrastrukturni problem
Solana zato nagrađuje pažljivo modelovanje podataka. „Jedan veliki nalog za sve“ često stvara contention, dok „poseban nalog za svaki sitan događaj“ može nepotrebno povećati trošak skladišta i složenost indeksiranja.
Zašto eksplicitni nalozi omogućavaju paralelizaciju
Svaka instrukcija navodi:
- program koji se poziva
- listu naloga
- da li je svaki nalog writable
- da li nalog mora biti signer
- niz bajtova sa argumentima instrukcije
Runtime iz liste naloga može da vidi koje transakcije imaju konflikt.
Ako transakcija A piše u nalog order_1, a transakcija B piše u order_2, nema nužnog konflikta. One mogu biti kandidati za paralelno izvršavanje.
Ako obe pišu u globalni nalog marketplace_state, moraju se serijalizovati oko tog writable naloga.
Važna nijansa je da se instrukcije unutar jedne transakcije izvršavaju redom. Paralelizacija se odnosi na raspoređivanje transakcija koje nemaju konfliktne write lockove. Sve instrukcije unutar jedne transakcije čine atomsku celinu: ili sve uspeju, ili se sve promene vraćaju.[4]
Zbog toga je izbor naloga istovremeno:
- model podataka
- sigurnosni model
- concurrency model
- deo troškovnog modela
Globalni brojač kao skriveni bottleneck
Čest prvi program izgleda ovako:
global_state.total_orders += 1
Ako svaka kupovina mora da menja isti global_state, sve kupovine konkurišu za isti writable nalog. Mreža može imati slobodne resurse, ali vaša aplikacija je sama uvela serijalizaciju.
Bolji dizajn može koristiti:
- jedan PDA po porudžbini
- particionisane agregate
- periodično off-chain sabiranje
- više shard naloga izabranih deterministički
- događaje i indeksiranje umesto globalnog brojača
Ne postoji univerzalno rešenje. Bitno je da se writable account set tretira kao deo performansnog dizajna, a ne kao detalj SDK-a.
Program Derived Addresses kao zamena za internu mapu
Program Derived Address, odnosno PDA, deterministički se izvodi iz:
- adrese programa
- jednog ili više seed vrednosti
- bump vrednosti
PDA se namerno nalazi van Ed25519 krive, pa za njega ne postoji privatni ključ. Samo program iz čijeg je ID-a PDA izveden može da „potpiše“ operaciju u njegovo ime, koristeći invoke_signed tokom Cross-Program Invocation poziva.[5]
Za marketplace porudžbinu adresa može konceptualno da se izvede iz:
["order", merchant_address, order_id]
Prednosti su praktične:
- backend ne mora da čuva novu adresu u posebnoj tabeli
- frontend može samostalno da izračuna adresu
- program može proveriti da je prosleđen očekivani nalog
- jedan skup seed vrednosti mapira se na jednu kanonsku adresu
- PDA može biti autoritet token vault naloga
PDA nije nalog sam po sebi. Derivacija daje adresu, ali nalog na toj adresi i dalje mora biti kreiran i finansiran.
Seed vrednosti su deo javnog API-ja
Kada odaberete seed šemu, ona postaje deo protokola vaše aplikacije. Promena sa:
["order", order_id]
na:
["order", merchant, order_id]
menja sve izvedene adrese.
Zato seed šemu treba tretirati približno kao baznu šemu ili javni endpoint:
- koristite stabilne prefikse
- izbegavajte neograničene korisničke stringove
- uključite tenant ili authority kada je potrebno
- dokumentujte kodiranje numeričkih vrednosti
- razmotrite verzionisanje seed šeme
- koristite kanonski bump
Pojedinačni seed ima ograničenje od 32 bajta, a ukupan broj seed elemenata takođe je ograničen.[5]
Transakcija je atomski paket instrukcija
Solana transakcija sadrži:
- jednu ili više instrukcija
- listu adresa naloga
- recent blockhash ili durable nonce
- fee payer
- potpise potrebnih signera
Legacy i v0 transakcije imaju maksimalnu veličinu od 1.232 bajta. Svaki Ed25519 potpis zauzima 64 bajta, a svaka puna adresa 32 bajta, pa limit može postati relevantan mnogo pre compute limita.[6]
V0 format podržava Address Lookup Tables. Umesto da svaku adresu stavite direktno u poruku, neke adrese se mogu referencirati indeksom iz on-chain tabele. To pomaže transakcijama koje komuniciraju sa većim brojem naloga i programa.
Aktuelna dokumentacija opisuje i v1 format sa većom porukom i drugačijim načinom definisanja resursnih limita. Ipak, format transakcije treba tretirati kao pregovaranu mogućnost između klijenta, walleta, RPC provajdera i validatora, a ne samo kao lokalni izbor biblioteke. RPC pozivi kao što su getTransaction i getBlock treba da navedu maxSupportedTransactionVersion koji aplikacija zaista ume da parsira.[7]
Atomskost je jedan od najkorisnijih primitiva
U jednu transakciju možete staviti više instrukcija, na primer:
- kreiranje token naloga primaoca
- transfer tokena
- beleženje receipt naloga
- promenu statusa porudžbine
- emitovanje događaja iz programa
Ako treća instrukcija ne uspe, promene prethodnih instrukcija se vraćaju. Naknada za transakciju se ipak naplaćuje.
Ova atomskost omogućava settlement workflowe koji bi u klasičnom backendu zahtevali distribuiranu transakciju, outbox tabelu ili kompenzacione operacije. Ne uklanja potrebu za off-chain bazom, ali može ukloniti određene klase parcijalno završenih finansijskih operacija.
Realan use-case: kripto plaćanja i reconciliation
Dobar prvi Solana projekat nije DEX, lending protokol ili novi token. Realističniji use-case je servis koji prima kripto plaćanja i uparuje ih sa internim porudžbinama.
Takav sistem može da prihvata:
- SOL
- postojeći SPL token
- stablecoin izdat preko originalnog Token Programa
- token koji koristi Token Extensions Program
Za jednostavno plaćanje custom on-chain program često nije potreban. Standardni System Program i Token Program već implementiraju transfer.
Tipična arhitektura izgleda ovako:
flowchart LR
A[Checkout API] --> B[Orders baza]
A --> C[Wallet ili payment UI]
C --> D[Potpisana Solana transakcija]
D --> E[RPC node]
E --> F[Solana cluster]
F --> G[Indexer ili reconciliation worker]
G --> B
B --> H[Fulfillment servis]
Korak 1: Backend kreira nameru plaćanja
U SQL bazi kreira se porudžbina sa najmanje sledećim poljima:
order_id
expected_mint
expected_amount_base_units
recipient_address
reference
status
expires_at
transaction_signature
Iznos treba čuvati u osnovnim celobrojnim jedinicama tokena. Nemojte koristiti float ili double za finansijske vrednosti.
Korak 2: Klijent konstruiše transfer
Klijent kreira instrukciju za System Program ili odgovarajući Token Program. Transakcija dobija fee payer, recent blockhash i potrebne potpise.
Solana dokumentacija trenutno preporučuje @solana/kit kao TypeScript SDK. Kit koristi kompozicioni model sa pluginovima za signer, RPC i slanje transakcija.[8]
Minimalan lokalni SOL transfer može izgledati ovako:
import {
createClient,
generateKeyPairSigner,
lamports,
} from "@solana/kit";
import {
rpcAirdrop,
solanaRpc,
} from "@solana/kit-plugin-rpc";
import {
airdropPayer,
payer,
} from "@solana/kit-plugin-signer";
import { getTransferSolInstruction } from "@solana-program/system";
const sender = await generateKeyPairSigner();
const recipient = await generateKeyPairSigner();
const LAMPORTS_PER_SOL = 1_000_000_000n;
const client = createClient()
.use(payer(sender))
.use(
solanaRpc({
rpcUrl: "http://localhost:8899",
rpcSubscriptionsUrl: "ws://localhost:8900",
}),
)
.use(rpcAirdrop())
.use(airdropPayer(lamports(LAMPORTS_PER_SOL)));
const transfer = getTransferSolInstruction({
source: sender,
destination: recipient.address,
amount: lamports(10_000_000n),
});
const { context } = await client.sendTransaction([transfer]);
console.log(context.signature);
Primer koristi lokalni validator i testni airdrop. Generisani keypair u memoriji prikladan je za test, ali nije model za produkciono čuvanje ključeva.
Backend koji kontroliše sredstva ne bi trebalo da drži nekriptovane keypair fajlove unutar repozitorijuma, container image-a ili običnih environment promenljivih. Produkcioni signer treba izolovati pomoću KMS-a, HSM-a, custody servisa ili drugog kontrolisanog signing sloja.
RPC endpointu se šalje već potpisana transakcija. RPC provajderu nikada nije potreban privatni ključ korisnika.[9]
Korak 3: Ne verujte samo uspehu wallet interfejsa
Dobijena signature vrednost znači da imate identifikator transakcije. Ne znači automatski da je transakcija:
- uključena u blok
- uspešno izvršena
- potvrđena na zahtevanom nivou
- poslala očekivani token
- poslala očekivani iznos
- poslata očekivanom primaocu
sendTransaction prosleđuje transakciju mreži, ali uspešan RPC odgovor nije dokaz konačnog izvršenja. Reconciliation servis mora proveriti status i sadržaj transakcije.[10]
Za plaćanje treba proveriti najmanje:
- da je
errjednaknull - da je dostignut potreban commitment
- da instrukcija koristi očekivani program
- da je mint adresa tačna
- da su source i destination token nalozi očekivani
- da je transferisan tačan broj osnovnih jedinica
- da signature nije već korišćen za drugu porudžbinu
- da transakcija nije nastala pre kreiranja ili posle isteka porudžbine
Najvažnije pravilo je da se ne veruje objektu koji frontend pošalje backendu. Backend treba da pročita transakciju sa mreže i sam verifikuje njen sadržaj.
Korak 4: Napravite idempotentan reconciliation
RPC timeout ne znači da transakcija nije stigla do mreže. Ako backend odmah napravi novu transakciju sa novim blockhashom, može se dogoditi da obe transakcije budu izvršene.
Dok je originalni blockhash validan, treba ponovo slati iste potpisane bajtove i pratiti istu signature vrednost. Kada blockhash istekne, proverite istoriju i poslovno stanje pre pravljenja nove transakcije.[9]
U bazi postavite jedinstvena ograničenja nad:
transaction_signature
order_id
reference
Worker može bezbedno da se pokreće više puta ako stanje menja upitom poput:
UPDATE orders
SET status = 'paid',
transaction_signature = $1
WHERE order_id = $2
AND status = 'pending';
On-chain finalnost ne daje automatski idempotentnost vašem HTTP API-ju. To je i dalje odgovornost aplikacije.
Kada je potreban sopstveni program
Custom program ima smisla kada postoje pravila koja moraju biti izvršena atomski i nezavisno od vašeg backenda.
Primeri uključuju:
- escrow između kupca i prodavca
- podelu jedne uplate na više primalaca
- uslovno oslobađanje sredstava
- on-chain autorizaciju refundiranja
- vault kontrolisan PDA autoritetom
- isporuku digitalnog asseta zajedno sa uplatom
- settlement između više tokena
- transparentna pravila koja ne bi trebalo da zavise od jednog servera
Ako backend samo proverava da li je stablecoin stigao na adresu trgovca, custom program može biti nepotreban rizik. Standardni token transfer je jednostavniji, jeftiniji za održavanje i ima manje prostora za sigurnosne greške.
Ako plaćanje i promena stanja moraju biti jedna atomska operacija, program postaje opravdan.
Kratak Anchor primer: uplata i on-chain receipt
Anchor je framework za pisanje Solana programa u Rustu. On generiše deo boilerplate koda za deserijalizaciju instrukcija, validaciju naloga, diskriminatore i IDL.[11]
Sledeći primer prenosi SOL preko System Programa i kreira jedan receipt PDA po porudžbini:
use anchor_lang::prelude::*;
use anchor_lang::system_program;
declare_id!("11111111111111111111111111111111");
#[program]
pub mod checkout {
use super::*;
pub fn pay(
ctx: Context<Pay>,
order_id: [u8; 32],
amount: u64,
) -> Result<()> {
require!(amount > 0, CheckoutError::InvalidAmount);
let transfer_accounts = system_program::Transfer {
from: ctx.accounts.payer.to_account_info(),
to: ctx.accounts.merchant.to_account_info(),
};
let transfer_context = CpiContext::new(
ctx.accounts.system_program.to_account_info(),
transfer_accounts,
);
system_program::transfer(transfer_context, amount)?;
let receipt = &mut ctx.accounts.receipt;
receipt.order_id = order_id;
receipt.payer = ctx.accounts.payer.key();
receipt.merchant = ctx.accounts.merchant.key();
receipt.amount = amount;
receipt.created_at = Clock::get()?.unix_timestamp;
receipt.bump = ctx.bumps.receipt;
Ok(())
}
}
#[derive(Accounts)]
#[instruction(order_id: [u8; 32])]
pub struct Pay<'info> {
#[account(mut)]
pub payer: Signer<'info>,
#[account(mut)]
pub merchant: SystemAccount<'info>,
#[account(
init,
payer = payer,
space = 8 + PaymentReceipt::INIT_SPACE,
seeds = [
b"receipt",
merchant.key().as_ref(),
order_id.as_ref(),
],
bump,
)]
pub receipt: Account<'info, PaymentReceipt>,
pub system_program: Program<'info, System>,
}
#[account]
#[derive(InitSpace)]
pub struct PaymentReceipt {
pub order_id: [u8; 32],
pub payer: Pubkey,
pub merchant: Pubkey,
pub amount: u64,
pub created_at: i64,
pub bump: u8,
}
#[error_code]
pub enum CheckoutError {
#[msg("Iznos mora biti veći od nule")]
InvalidAmount,
}
Transfer i kreiranje receipt naloga nalaze se u istoj transakciji. Ako transfer ne uspe, receipt se ne kreira. Ako kreiranje ili serijalizacija receipt naloga ne uspe, transfer se vraća.
PaymentReceipt::INIT_SPACE izračunava potreban prostor za polja, dok dodatnih osam bajtova pokriva Anchor diskriminator naloga.[12]
Primer ipak nije kompletan produkcioni checkout. Klijent trenutno može proslediti bilo koji merchant nalog. U produkciji bi merchant trebalo povezati sa verifikovanim configuration nalogom, na primer pomoću:
-
addressograničenja -
has_oneograničenja - PDA konfiguracije trgovca
- proverene marketplace authority vrednosti
Program mora proveravati poslovna pravila, a ne samo tipove podataka.
Anchor ograničenja nisu dekoracija
Kod Anchor programa, najveći deo sigurnosnog modela često se nalazi u #[derive(Accounts)] strukturi, a ne u handler funkciji.
Relevantna ograničenja uključuju:
-
Signer<'info>za obavezan potpis -
#[account(mut)]za naloge koji se menjaju -
seedsibumpza PDA validaciju -
has_oneza relacije između naloga -
ownerza proveru vlasničkog programa -
addressza očekivanu fiksnu adresu -
closeza zatvaranje naloga -
reallocza promenu alociranog prostora - token i associated-token ograničenja
Nemojte koristiti UncheckedAccount samo da biste „naterali kod da se kompajlira“. Time odgovornost za sve validacije prelazi na handler, a jedna propuštena owner ili address provera može otvoriti ozbiljnu ranjivost.
Aktuelni Anchor takođe podrazumevano sprečava određene duplikate mutable naloga. Ako dva parametra smeju da budu ista adresa, to treba dozvoliti eksplicitno i obrazložiti.[13]
Cross-Program Invocation i kompozabilnost
Cross-Program Invocation, odnosno CPI, omogućava jednom programu da pozove instrukciju drugog programa.
Marketplace program može, na primer, pozvati:
- System Program radi SOL transfera
- Token Program radi token transfera
- Associated Token Account Program radi kreiranja token naloga
- drugi poslovni program radi isporuke asseta
Postoje dva osnovna mehanizma:
-
invokekada su potrebni potpisi već prisutni u originalnoj transakciji -
invoke_signedkada vaš program potpisuje u ime PDA adrese
Callee program ne može povećati privilegije koje nije dobio od caller programa. Nalog koji je read-only u parent instrukciji ne može postati writable u CPI pozivu. Isto važi za signer privilegije, osim validnog PDA potpisivanja kroz runtime.[14]
CPI troši compute budget iste transakcije. Duboki lanci poziva povećavaju compute potrošnju, otežavaju debugging i približavaju program limitima instruction stacka.
Kompozabilnost zato nije besplatna apstrakcija. Svaki dodatni program uvodi:
- dodatne naloge u poruci
- dodatnu compute potrošnju
- potencijalni write contention
- još jednu verzionisanu zavisnost
- još jednu površinu za greške
SPL Token i Token Extensions nisu isto
Originalni Token Program pokriva standardne operacije:
- kreiranje mint naloga
- kreiranje token naloga
- mintovanje
- transfer
- burn
- delegate autorizaciju
- freeze i thaw
- promenu authority adresa
- zatvaranje token naloga
Token Extensions Program, često nazivan Token-2022, podržava dodatne funkcije kroz ekstenzije, kao što su transfer fees, transfer hooks, metadata pointer, permanent delegate, non-transferable tokeni, confidential transfer mogućnosti i druge kontrole.[15]
Ključna projektantska odluka je da se većina mint ekstenzija bira prilikom kreiranja tokena. Ne mogu se sve naknadno dodati. Neke ekstenzije su međusobno nekompatibilne.
To znači da izdavanje tokena nije samo poziv createMint. Pre izdavanja treba analizirati:
- koje wallet aplikacije podržavaju izabrane ekstenzije
- koje berze i custody sistemi umeju da ih obrade
- da li transfer hook može blokirati integracije
- ko kontroliše privileged authorities
- može li se authority opozvati
- šta se događa ako custom hook program ne radi
- koliko dodatnog prostora zahtevaju mint i token nalozi
Za aplikaciju koja samo prihvata postojeći stablecoin, sigurnije je integrisati njegov postojeći mint i program nego izdavati novi token bez jasne potrebe.
Koliko Solana integracija košta
Trošak nije jedna brojka. Sastoji se od najmanje četiri kategorije:
- transakcione naknade
- optional priority fees
- skladišni depoziti za naloge
- RPC i indekserska infrastruktura
Osnovna naknada
Aktuelna osnovna naknada iznosi 5.000 lamporta po potpisu. Transakcija sa više signera plaća osnovnu naknadu za svaki potpis.
Naknada se plaća i kada izvršavanje transakcije ne uspe.[16]
Zato retry logika koja stalno šalje nove, unapred osuđene transakcije nije samo tehnički problem. Ona nepotrebno troši sredstva fee payer naloga.
Priority fee
Za legacy i v0 transakcije, priority fee se zasniva na:
compute unit price × requested compute unit limit
Cena se izražava u micro-lamportima po compute unit jedinici, a konačan rezultat konvertuje u lamporte.
Važna posledica je da se naknada računa prema traženom compute limitu, ne prema stvarnoj potrošnji. Ako zatražite mnogo više compute units nego što transakcija koristi, plaćate prioritet i za neiskorišćeni deo limita.[16]
Praktičan workflow je:
- konstruišite transakciju
- simulirajte je
- očitajte
unitsConsumed - dodajte razumnu sigurnosnu marginu
- postavite compute unit limit
- izaberite priority fee prema trenutnim uslovima
Dokumentacija kao polaznu tačku navodi marginu od oko 10 procenata iznad simulirane potrošnje, ali produkcioni sistem treba da meri sopstvene transakcije i greške.[17]
Compute limiti
Za legacy i v0 format, podrazumevani limit za non-builtin instrukciju je 200.000 compute units, dok transakcija može zahtevati najviše 1.400.000 compute units.
Compute Units nisu isto što i vreme procesora u milisekundama. Oni su deterministička runtime mera operacija kao što su:
- instrukcije virtuelne mašine
- memorijske operacije
- kriptografske funkcije
- logging
- CPI pozivi
- serijalizacija podataka
Optimizacija programa zato uključuje smanjenje compute potrošnje, ali i izbor manjeg broja naloga, izbegavanje nepotrebnih CPI poziva i ograničavanje količine podataka koja se učitava.
Skladišni depozit
Nalog koji čuva podatke mora imati minimalan broj lamporta proporcionalan veličini naloga. Ovaj iznos nije klasična periodična hosting pretplata. Predstavlja minimalni balance koji ostaje zaključan dok nalog postoji.
Kada se nalog pravilno zatvori, lamporti se mogu vratiti na odabrani destination nalog.
Nemojte hardkodovati približan rent-exempt iznos iz bloga ili starog primera. Pre kreiranja naloga pozovite RPC metodu:
getMinimumBalanceForRentExemption
Ulaz je veličina podataka u bajtovima, a rezultat je potreban broj lamporta.[18]
Ako aplikacija pravi nalog po svakoj porudžbini, treba definisati:
- ko finansira kreiranje
- kada se nalog zatvara
- kome se depozit vraća
- da li je podatak zaista potreban zauvek
- da li bi događaj i off-chain indexer bili bolji izbor
RPC trošak
Javni Solana RPC endpointi korisni su za razvoj, ali nisu odgovarajuća produkciona garancija.
Komercijalni RPC provajderi naplaćuju prema različitim modelima:
- broj zahteva
- ponderisana cena RPC metode
- response bytes
- bandwidth ili egress
- broj WebSocket konekcija
- arhivska istorija
- dedicated kapacitet
- dodatni transaction delivery servis
Zbog promenljivih ponuda nema smisla navoditi jednu „cenu Solane po mesecu“. Potrebno je meriti konkretan workload, posebno getProgramAccounts, istorijske transakcije, block upite i WebSocket subscriptions.[9]
RPC je deo sistema, ne samo URL u konfiguraciji
U prototipu RPC često izgleda kao jedna konstanta:
const RPC_URL = process.env.SOLANA_RPC_URL;
U produkciji RPC sloj utiče na:
- svežinu podataka
- uspešnost slanja transakcija
- potvrđivanje
- dostupnost istorije
- latenciju korisničkog interfejsa
- reconnect ponašanje WebSocket klijenta
- tačnost reconciliation workera
Pouzdan setup treba da ima:
- request timeout
- ograničene retry pokušaje
- exponential backoff sa jitterom
- proveru slot freshness vrednosti
- najmanje jedan nezavisni fallback za kritične putanje
- odvojene metrike za read i write zahteve
- HTTP reconciliation nakon WebSocket reconnecta
WebSocket obaveštenje nije durable message queue. Ako veza pukne, događaji mogu biti propušteni. Nakon reconnecta treba ponovo pročitati trenutno stanje preko HTTP RPC-a.[9]
Nemojte nasumično mešati RPC endpointove
Jedan endpoint može vratiti blockhash iz novijeg slota, dok drugi kasni i ne prepoznaje taj blockhash tokom simulacije ili slanja.
Operacije koje pripadaju istom transaction lifecycle-u poželjno je držati na jednom zdravom endpointu:
getLatestBlockhashsimulateTransactionsendTransactiongetSignatureStatuses
Fallback treba uključiti kada postoje jasni signali da je endpoint spor, nedostupan ili zastareo, a ne round-robin rotacijom svakog poziva.
Commitment nivoi i korisničko iskustvo
Solana RPC koristi tri uobičajena commitment nivoa:
processedconfirmedfinalized
processed znači da je određeni čvor obradio blok, ali blok još može pripadati fork-u koji neće opstati.
confirmed znači da je blok dobio glasove supervećine stake-a.
finalized predstavlja dublje potvrđeno stanje koje je ukorenjeno u ledgeru.
Za većinu interaktivnih aplikacija confirmed daje praktičan balans između latencije i sigurnosti. Finansijski workflow može prikazati korisniku međustanje, ali fulfillment ili povlačenje sredstava odložiti do strožeg nivoa koji odgovara poslovnom riziku.[19]
Bitno je da frontend ne koristi termine kao što su „završeno“ i „potvrđeno“ bez jasno definisane semantike.
Dobar statusni model može biti:
created
awaiting_signature
submitted
processed
confirmed
finalized
failed
expired
Simulacija je deo normalnog workflowa
simulateTransaction izvršava transakciju nad stanjem dostupnim RPC čvoru, ali je ne šalje mreži.
Rezultat simulacije može sadržati:
- runtime grešku
- program logs
- inner instructions
- potrošene compute units
- povratne podatke
- pre i post balanse
- učitane adrese
Simulacija je korisna za:
- hvatanje grešaka pre wallet potpisa
- procenu compute limita
- prikaz razumljivije poruke korisniku
- proveru da li su nalozi pravilno prosleđeni
- debugging CPI lanca
Ali simulacija nije garancija izvršenja. Stanje se može promeniti između simulacije i slanja, blockhash može isteći, writable nalog može postati zauzet, a transakcija može izgubiti prioritet.[20]
Testiranje bez čekanja na Devnet
Devnet je koristan za integraciono testiranje, ali nije dobar inner development loop.
Anchor dokumentacija podržava više testnih pristupa:
- LiteSVM za in-process Solana VM
- Mollusk za fokusirano testiranje instrukcija
- lokalni validator za RPC i validator ponašanje
- Devnet za testiranje spoljne integracije
- fuzzing za sistematsko traženje rubnih slučajeva
LiteSVM je prikladan za brzo testiranje programa i client koda. Lokalni validator je i dalje koristan kada su potrebne RPC metode ili ponašanje bliže pravom validatoru.[21]
Praktična test piramida može izgledati ovako:
- Rust unit testovi za čistu poslovnu logiku
- LiteSVM ili Mollusk testovi za instrukcije
- lokalni validator za RPC workflow
- mali broj Devnet end-to-end testova
- Mainnet smoke test sa strogo ograničenim iznosima
Testirajte negativne putanje
On-chain test suite ne bi trebalo da potvrđuje samo happy path.
Obavezni slučajevi uključuju:
- pogrešan signer
- pogrešan owner naloga
- pogrešan PDA
- pogrešan bump
- isti mutable nalog prosleđen na dva mesta
- neautorizovan merchant
- duplirana porudžbina
- pogrešan mint
- nedovoljan balance
- prekoračen compute limit
- overflow i underflow
- zatvoren ili pogrešno inicijalizovan nalog
- zamena legitimnog programa lažnim executable nalogom
Na Solani napadač kontroliše transakciju i izbor naloga koje šalje vašem programu. Program mora pretpostaviti da je svaki nalog neprijateljski dok sve relevantne osobine nisu proverene.
Trade-off: brzina mreže ne uklanja složenost aplikacije
Solana može biti veoma dobar izbor za aplikacije koje imaju:
- veliki broj nezavisnih promena stanja
- interaktivna plaćanja
- on-chain tržišta
- igre sa čestim korisničkim akcijama
- programe koji komponuju više protokola
- tokene sa specifičnim pravilima
- potrebu za atomskim settlementom
Međutim, nije besplatna zamena za bazu podataka.
Prednosti
- eksplicitno stanje omogućava paralelno izvršavanje
- više instrukcija može se izvršiti atomski
- PDA uklanja upravljanje privatnim ključevima za programske autoritete
- standardni programi pružaju zajedničke primitive
- naknade su deterministički izražene u lamportima
- nalog je direktno adresabilna jedinica podataka
- Rust i Anchor daju relativno strogo razvojno okruženje
Nedostaci
- account model zahteva drugačiji način razmišljanja
- transaction size može biti ograničenje
- writable account contention može poništiti paralelizaciju
- RPC infrastruktura postaje kritična komponenta
- on-chain skladištenje traži depozit
- programi imaju stroge compute i memorijske limite
- parsing transakcija i token operacija nije trivijalan
- wallet, RPC i transaction-version kompatibilnost moraju se pratiti
- upgrade authority uvodi operativni i governance rizik
Upgrade authority je deo threat modela
Program postavljen preko upgradeable loadera može imati upgrade authority. Ko kontroliše taj ključ, može postaviti novu verziju programa.
Zato „program je na blockchainu“ ne znači automatski da je nepromenljiv.
Produkcioni tim mora odlučiti:
- da li program treba da ostane upgradeable
- da li authority pripada jednom ključu ili multisig sistemu
- postoji li timelock ili javni proces promene
- kako se verifikuje novi build
- kako se korisnici obaveštavaju o upgrade-u
- kada authority treba trajno opozvati
Opozivanje upgrade authority vrednosti program čini immutable, ali tada se greške više ne mogu ispraviti redeployem na istoj adresi. To je sigurnosni trade-off, ne checkbox za decentralizaciju.[1]
Razuman razvojni workflow
Za prvi ozbiljan Solana projekat workflow može izgledati ovako.
1. Prvo modelujte naloge
Pre pisanja programa odgovorite:
- koji podatak mora biti on-chain
- koji program poseduje svaki nalog
- ko finansira njegovo kreiranje
- ko sme da ga menja
- kako se izvodi njegova adresa
- može li se zatvoriti
- koji workflowi pišu u isti nalog
2. Prototipujte bez custom programa
Ako use-case može da koristi System Program ili Token Program, prvo napravite tu verziju.
To pomaže da odvojeno rešite:
- wallet povezivanje
- transakcije
- RPC
- potvrđivanje
- reconciliation
- finansijsku preciznost
- monitoring
3. Uvedite program samo za atomsku logiku
Program treba da rešava pravilo koje nije bezbedno ostaviti backendu, kao što su escrow, split ili programski vault.
4. Simulirajte svaku klasu transakcije
Sakupljajte:
- compute units
- veličinu poruke
- broj naloga
- broj signera
- učestalost neuspeha
- tip runtime greške
5. Testirajte konfliktno opterećenje
Ne merite samo koliko jedna transakcija traje. Pošaljite konkurentne transakcije koje:
- menjaju različite PDA naloge
- menjaju isti globalni nalog
- koriste isti vault
- pozivaju isti program sa različitim writable setovima
Tako ćete otkriti da li vaš model stanja omogućava paralelizaciju koju očekujete.
6. Projektujte retry i idempotency pre Mainneta
Za svaku write operaciju definišite:
- stabilan application-level identifikator
- mapiranje na signature
- ponašanje pre blockhash isteka
- ponašanje posle isteka
- proveru da li je operacija već izvršena
- maksimalan broj retry pokušaja
7. Odvojite chain state od projection baze
Blockchain je izvor istine za on-chain stanje, ali nije nužno najbolji query engine za proizvod.
Produkcione aplikacije često održavaju SQL ili analitičku projekciju za:
- pretragu
- filtriranje
- agregate
- istoriju korisnika
- notifikacije
- dashboarde
- poslovne izveštaje
Indexer mora biti sposoban za replay i reconciliation. Ako WebSocket worker propusti događaj, stanje mora moći da se obnovi iz RPC istorije ili drugog pouzdanog izvora.
Kada Solana verovatno nije pravi izbor
Solana možda nije opravdana ako:
- svi korisnici veruju jednom backendu
- nema potrebe za digitalnim vlasništvom ili settlementom
- podaci moraju ostati privatni
- promene stanja su retke i jednostavne
- aplikacija nema način da bezbedno upravlja ključevima
- tim ne može da održava RPC, indeksiranje i monitoring
- zahtev se može rešiti običnim payment processorom i SQL bazom
Blockchain uvodi distribuiranu verifikaciju i mogućnost kompozicije, ali takođe uvodi ključeve, naknade, javno stanje, nepromenljivu istoriju i drugačiji operativni model.
Dobra Solana aplikacija ne stavlja sve on-chain. Ona bira najmanju količinu stanja koja mora biti javno proverljiva ili atomski menjana.
Zaključak
Solanina najvažnija osobina za developera nije reklamirani TPS. To je eksplicitni account model.
Kada transakcija unapred navodi naloge koje čita i menja, runtime dobija dovoljno informacija da raspoređuje nezavisne transakcije paralelno. Cena tog dizajna je da developer mora da projektuje stanje, adrese, write lockove i authority relacije mnogo pažljivije nego u sistemu sa implicitnim globalnim storageom.
Najpraktičniji početak nije pisanje velikog DeFi protokola. Bolje je napraviti mali payment ili escrow workflow i proći ceo životni ciklus:
- konstruisanje instrukcije
- određivanje naloga
- simulacija
- potpisivanje
- slanje
- potvrđivanje
- on-chain verifikacija
- idempotentno knjiženje
- oporavak od RPC greške
Tek tada account model prestaje da deluje neobično i počinje da pokazuje zašto je koristan.
Sponzorstvo
Ovaj članak je sponzorisan od strane Volet.com. Na Volet.com možete kupiti, prodati, čuvati i razmenjivati podržane kriptovalute i fiat valute.
Više informacija dostupno je na stranici Volet Srbija.
Za otvaranje naloga možete koristiti Volet.com pozivni link.
Izvori
- Solana Core Concepts
- Solana Whitepaper
- Solana Accounts
- Solana Instructions
- Program Derived Addresses
- Solana Transactions
- Versioned Transactions
- Solana Kit Client
- Choosing an RPC Provider
- sendTransaction RPC Method
- Anchor Framework Documentation
- Anchor Account Space
- Anchor Account Constraints
- Solana Cross-Program Invocation
- Solana Token Extensions
- Solana Fee Structure
- Solana Compute Budget
- getMinimumBalanceForRentExemption
- Transaction Confirmation and Expiration
- simulateTransaction RPC Method
- Anchor Testing and LiteSVM
- Volet Srbija
Top comments (0)