Rustilla kirjoitetuilla Solana-ohjelmilla on minimaaliset rakenteelliset
vaatimukset, mikä mahdollistaa joustavuuden koodin organisoinnissa. Ainoa
vaatimus on, että ohjelmalla on oltava entrypoint, joka määrittää, mistä
ohjelman suoritus alkaa.
Ohjelman rakenne
Vaikka tiedostorakenteelle ei ole tiukkoja sääntöjä, Solana-ohjelmat noudattavat tyypillisesti yhteistä mallia:
entrypoint.rs: Määrittää sisääntulopisteen, joka ohjaa saapuvat käskyt.state.rs: Määrittää ohjelman tilan (tilin tiedot).instructions.rs: Määrittää käskyt, joita ohjelma voi suorittaa.processor.rs: Määrittää käskykäsittelijät (funktiot), jotka toteuttavat liiketoimintalogiikan kullekin käskylle.error.rs: Määrittää mukautetut virheet, joita ohjelma voi palauttaa.
Katso esimerkiksi Token Program.
Esimerkkiohjelma
Havainnollistaaksemme, miten rakennetaan natiivi Rust-ohjelma useilla käskyillä, käymme läpi yksinkertaisen laskuriohjelma, joka toteuttaa kaksi käskyä:
InitializeCounter: Luo ja alustaa uuden tilin alkuarvolla.IncrementCounter: Kasvattaa olemassa olevaan tiliin tallennettua arvoa.
Yksinkertaisuuden vuoksi ohjelma toteutetaan yhdessä lib.rs-tiedostossa,
vaikka käytännössä saatat haluta jakaa suuremmat ohjelmat useisiin tiedostoihin.
Osa 1: Ohjelman kirjoittaminen
Aloitetaan rakentamalla laskuriohjelma. Luomme ohjelman, joka voi alustaa laskurin alkuarvolla ja kasvattaa sitä.
Luo uusi ohjelma
Luodaan ensin uusi Rust-projekti Solana-ohjelmaamme varten.
$cargo new counter_program --lib$cd counter_program
Sinun pitäisi nähdä oletusmääräiset src/lib.rs- ja Cargo.toml-tiedostot.
Päivitä edition-kenttä tiedostossa Cargo.toml arvoon 2021. Muuten saatat
kohdata virheen ohjelmaa käännettäessä.
Lisää riippuvuudet
Lisätään nyt tarvittavat riippuvuudet Solana-ohjelman rakentamiseen. Tarvitsemme
solana-program:n ydin-SDK:ta varten ja borsh:n serialisointia varten.
$cargo add solana-program@2.2.0$cargo add borsh
Borshin käyttö ei ole pakollista. Se on kuitenkin yleisesti käytetty serialisointikirjasto Solana-ohjelmissa.
Määritä crate-type
Solana-ohjelmat on käännettävä dynaamisiksi kirjastoiksi. Lisää [lib]-osio
määrittelemään, miten Cargo rakentaa ohjelman.
[lib]crate-type = ["cdylib", "lib"]
Jos et sisällytä tätä konfiguraatiota, target/deploy-hakemistoa ei luoda, kun rakennat ohjelman.
Määritä ohjelman sisääntulopiste
Jokaisella Solana-ohjelmalla on sisääntulopiste, joka on funktio, jota kutsutaan, kun ohjelma suoritetaan. Aloitetaan lisäämällä tarvittavat importit ohjelmaan ja määrittelemällä sisääntulopiste.
Lisää seuraava koodi tiedostoon lib.rs:
use borsh::{BorshDeserialize, BorshSerialize};use solana_program::{account_info::{next_account_info, AccountInfo},entrypoint,entrypoint::ProgramResult,msg,program::invoke,program_error::ProgramError,pubkey::Pubkey,system_instruction,sysvar::{rent::Rent, Sysvar},};entrypoint!(process_instruction);pub fn process_instruction(program_id: &Pubkey,accounts: &[AccountInfo],instruction_data: &[u8],) -> ProgramResult {Ok(())}
Entrypoint-makro
käsittelee input-datan deserialisoinnin process_instruction-funktion
parametreiksi.
Solana-ohjelman entrypoint:lla on seuraava funktiosignatuuri. Kehittäjät
voivat vapaasti luoda oman toteutuksensa entrypoint-funktiosta.
#[no_mangle]pub unsafe extern "C" fn entrypoint(input: *mut u8) -> u64;
Määrittele ohjelman tila
Määritellään nyt datarakenne, joka tallennetaan laskuritileihimme. Tämä on data,
joka tallennetaan tilin data -kenttään.
Lisää seuraava koodi tiedostoon lib.rs:
#[derive(BorshSerialize, BorshDeserialize, Debug)]pub struct CounterAccount {pub count: u64,}
Määrittele käskyenum
Määritellään käskyt, joita ohjelmamme voi suorittaa. Käytämme enumia, jossa jokainen variantti edustaa eri käskyä.
Lisää seuraava koodi tiedostoon lib.rs:
#[derive(BorshSerialize, BorshDeserialize, Debug)]pub enum CounterInstruction {InitializeCounter { initial_value: u64 },IncrementCounter,}
Toteuta käskyn deserialisointi
Nyt meidän täytyy deserialisoida instruction_data (raakatavut) yhdeksi
CounterInstruction -enumin variantiksi. Borshin try_from_slice -metodi
käsittelee tämän muunnoksen automaattisesti.
Päivitä process_instruction -funktio käyttämään Borsh-deserialisointia:
pub fn process_instruction(program_id: &Pubkey,accounts: &[AccountInfo],instruction_data: &[u8],) -> ProgramResult {let instruction = CounterInstruction::try_from_slice(instruction_data).map_err(|_| ProgramError::InvalidInstructionData)?;Ok(())}
Ohjaa käskyt käsittelijöille
Päivitetään nyt pää-process_instruction -funktio ohjaamaan käskyt niiden
asianmukaisille käsittelijäfunktioille.
Tämä ohjausmalli on yleinen Solana-ohjelmissa. instruction_data
deserialisoidaan käskyä edustavaksi enumin variantiksi, jonka jälkeen
asianmukainen käsittelijäfunktio kutsutaan. Jokainen käsittelijäfunktio sisältää
toteutuksen kyseiselle käskylle.
Lisää seuraava koodi tiedostoon lib.rs päivittäen process_instruction
-funktiota ja lisäten käsittelijät InitializeCounter- ja IncrementCounter
-käskyille:
pub fn process_instruction(program_id: &Pubkey,accounts: &[AccountInfo],instruction_data: &[u8],) -> ProgramResult {let instruction = CounterInstruction::try_from_slice(instruction_data).map_err(|_| ProgramError::InvalidInstructionData)?;match instruction {CounterInstruction::InitializeCounter { initial_value } => {process_initialize_counter(program_id, accounts, initial_value)?}CounterInstruction::IncrementCounter => {process_increment_counter(program_id, accounts)?}};Ok(())}fn process_initialize_counter(program_id: &Pubkey,accounts: &[AccountInfo],initial_value: u64,) -> ProgramResult {Ok(())}fn process_increment_counter(program_id: &Pubkey,accounts: &[AccountInfo],) -> ProgramResult {Ok(())}
Toteuta alustuskäsittelijä
Toteutetaan käsittelijä uuden laskuritilin luomiseen ja alustamiseen. Koska vain System Program voi luoda tilejä Solanassa, käytämme Cross Program Invocationia (CPI), eli kutsumme toista ohjelmaa ohjelmastamme.
Ohjelmamme tekee CPI-kutsun System Programin create_account-ohjeelle. Uusi
tili luodaan ohjelmamme omistukseen, mikä antaa ohjelmallemme mahdollisuuden
kirjoittaa tilille ja alustaa data.
Lisää seuraava koodi tiedostoon lib.rs päivittäen
process_initialize_counter-funktiota:
fn process_initialize_counter(program_id: &Pubkey,accounts: &[AccountInfo],initial_value: u64,) -> ProgramResult {let accounts_iter = &mut accounts.iter();let counter_account = next_account_info(accounts_iter)?;let payer_account = next_account_info(accounts_iter)?;let system_program = next_account_info(accounts_iter)?;let account_space = 8;let rent = Rent::get()?;let required_lamports = rent.minimum_balance(account_space);invoke(&system_instruction::create_account(payer_account.key,counter_account.key,required_lamports,account_space as u64,program_id,),&[payer_account.clone(),counter_account.clone(),system_program.clone(),],)?;let counter_data = CounterAccount {count: initial_value,};let mut account_data = &mut counter_account.data.borrow_mut()[..];counter_data.serialize(&mut account_data)?;msg!("Counter initialized with value: {}", initial_value);Ok(())}
Tämä ohje on vain esittelytarkoituksiin. Se ei sisällä turvallisuus- ja validointitarkistuksia, joita tuotanto-ohjelmissa vaaditaan.
Toteuta kasvatuskäsittelijä
Toteutetaan nyt käsittelijä, joka kasvattaa olemassa olevaa laskuria. Tämä ohje:
- Lukee tilin
data-kentäncounter_account-tilille - Deserialisoi sen
CounterAccount-structiksi - Kasvattaa
count-kenttää yhdellä - Serialisoi
CounterAccount-structin takaisin tilindata-kenttään
Lisää seuraava koodi tiedostoon lib.rs päivittäen
process_increment_counter-funktiota:
fn process_increment_counter(program_id: &Pubkey,accounts: &[AccountInfo],) -> ProgramResult {let accounts_iter = &mut accounts.iter();let counter_account = next_account_info(accounts_iter)?;if counter_account.owner != program_id {return Err(ProgramError::IncorrectProgramId);}let mut data = counter_account.data.borrow_mut();let mut counter_data: CounterAccount = CounterAccount::try_from_slice(&data)?;counter_data.count = counter_data.count.checked_add(1).ok_or(ProgramError::InvalidAccountData)?;counter_data.serialize(&mut &mut data[..])?;msg!("Counter incremented to: {}", counter_data.count);Ok(())}
Tämä ohje on vain esittelytarkoituksiin. Se ei sisällä turvallisuus- ja validointitarkistuksia, joita tuotanto-ohjelmissa vaaditaan.
Valmis ohjelma
Onnittelut! Olet rakentanut täydellisen Solana-ohjelman, joka demonstroi perusrakenteen, joka on yhteinen kaikille Solana-ohjelmille:
- Entrypoint: Määrittää, mistä ohjelman suoritus alkaa ja reitittää kaikki saapuvat pyynnöt asianmukaisille ohjekäsittelijöille
- Ohjeiden käsittely: Määrittää ohjeet ja niiden käsittelijäfunktiot
- Tilan hallinta: Määrittää tilidatarakenteet ja hallitsee niiden tilaa ohjelman omistamissa tileissä
- Cross Program Invocation (CPI): Kutsuu System Programia luomaan uusia ohjelman omistamia tilejä
Seuraava vaihe on testata ohjelma varmistaaksesi, että kaikki toimii oikein.
Osa 2: Ohjelman testaaminen
Testataan nyt laskuriohjelmaamme. Käytämme LiteSVM-testikehystä, jonka avulla voimme testata ohjelmia ilman niiden julkaisemista klusteriin.
Lisää testiriippuvuudet
Lisätään ensin testaamiseen tarvittavat riippuvuudet. Käytämme
litesvm-kirjastoa testaamiseen ja solana-sdk-kirjastoa.
$cargo add litesvm@0.6.1 --dev$cargo add solana-sdk@2.2.0 --dev
Luo testimoduuli
Lisätään nyt testimoduuli ohjelmaamme. Aloitetaan perusrakenteella ja importeilla.
Lisää seuraava koodi tiedostoon lib.rs, suoraan ohjelmakoodin alle:
#[cfg(test)]mod test {use super::*;use litesvm::LiteSVM;use solana_sdk::{account::ReadableAccount,instruction::{AccountMeta, Instruction},message::Message,signature::{Keypair, Signer},system_program,transaction::Transaction,};#[test]fn test_counter_program() {// Test implementation will go here}}
#[cfg(test)]-attribuutti varmistaa, että tämä koodi käännetään vain testejä
ajettaessa.
Alusta testiympäristö
Konfiguroidaan testiympäristö LiteSVM:llä ja rahoitetaan maksajatili.
LiteSVM simuloi Solana-ajoympäristöä, jolloin voimme testata ohjelmaamme ilman julkaisemista oikeaan klusteriin.
Lisää seuraava koodi tiedostoon lib.rs päivittäen
test_counter_program-funktiota:
let mut svm = LiteSVM::new();let payer = Keypair::new();svm.airdrop(&payer.pubkey(), 1_000_000_000).expect("Failed to airdrop");
Lataa ohjelma
Nyt meidän täytyy rakentaa ja ladata ohjelmamme testiympäristöön. Suorita
cargo build-sbf-komento rakentaaksesi ohjelman. Tämä luo
counter_program.so-tiedoston target/deploy-hakemistoon.
$cargo build-sbf
Varmista, että edition tiedostossa Cargo.toml on asetettu arvoon 2021.
Rakentamisen jälkeen voimme ladata ohjelman.
Päivitä test_counter_program-funktio lataamaan ohjelma testiympäristöön.
let program_keypair = Keypair::new();let program_id = program_keypair.pubkey();svm.add_program_from_file(program_id,"target/deploy/counter_program.so").expect("Failed to load program");
Sinun täytyy suorittaa cargo build-sbf ennen testien ajamista luodaksesi
.so-tiedoston. Testi lataa käännetyn ohjelman.
Testaa alustusinstruktio
Testataan alustusinstruktio luomalla uusi laskuritili alkuarvolla.
Lisää seuraava koodi tiedostoon lib.rs päivittäen
test_counter_program-funktiota:
let counter_keypair = Keypair::new();let initial_value: u64 = 42;println!("Testing counter initialization...");let init_instruction_data =borsh::to_vec(&CounterInstruction::InitializeCounter { initial_value }).expect("Failed to serialize instruction");let initialize_instruction = Instruction::new_with_bytes(program_id,&init_instruction_data,vec![AccountMeta::new(counter_keypair.pubkey(), true),AccountMeta::new(payer.pubkey(), true),AccountMeta::new_readonly(system_program::id(), false),],);let message = Message::new(&[initialize_instruction], Some(&payer.pubkey()));let transaction = Transaction::new(&[&payer, &counter_keypair],message,svm.latest_blockhash());let result = svm.send_transaction(transaction);assert!(result.is_ok(), "Initialize transaction should succeed");let logs = result.unwrap().logs;println!("Transaction logs:\n{:#?}", logs);
Varmista alustus
Alustuksen jälkeen varmistetaan, että laskuritili luotiin oikein odotetulla arvolla.
Lisää seuraava koodi tiedostoon lib.rs päivittäen
test_counter_program-funktiota:
let account = svm.get_account(&counter_keypair.pubkey()).expect("Failed to get counter account");let counter: CounterAccount = CounterAccount::try_from_slice(account.data()).expect("Failed to deserialize counter data");assert_eq!(counter.count, 42);println!("Counter initialized successfully with value: {}", counter.count);
Testaa kasvattamisinstruktio
Testataan nyt kasvattamisinstruktio varmistaaksemme, että se päivittää laskuriarvon oikein.
Lisää seuraava koodi tiedostoon lib.rs päivittäen
test_counter_program-funktiota:
println!("Testing counter increment...");let increment_instruction_data =borsh::to_vec(&CounterInstruction::IncrementCounter).expect("Failed to serialize instruction");let increment_instruction = Instruction::new_with_bytes(program_id,&increment_instruction_data,vec![AccountMeta::new(counter_keypair.pubkey(), true)],);let message = Message::new(&[increment_instruction], Some(&payer.pubkey()));let transaction = Transaction::new(&[&payer, &counter_keypair],message,svm.latest_blockhash());let result = svm.send_transaction(transaction);assert!(result.is_ok(), "Increment transaction should succeed");let logs = result.unwrap().logs;println!("Transaction logs:\n{:#?}", logs);
Varmista lopputulokset
Lopuksi varmistetaan, että kasvattaminen toimi oikein tarkistamalla päivitetty laskuriarvo.
Lisää seuraava koodi tiedostoon lib.rs päivittäen
test_counter_program-funktiota:
let account = svm.get_account(&counter_keypair.pubkey()).expect("Failed to get counter account");let counter: CounterAccount = CounterAccount::try_from_slice(account.data()).expect("Failed to deserialize counter data");assert_eq!(counter.count, 43);println!("Counter incremented successfully to: {}", counter.count);
Suorita testit seuraavalla komennolla. --nocapture -lippu tulostaa testin
tulosteen.
$cargo test -- --nocapture
Odotettu tulos:
Testing counter initialization...Transaction logs:["Program 3QpyHXhFtYY32iY7foF3EjkVdCDrUppADk9aDwSWn6Sq invoke [1]","Program 11111111111111111111111111111111 invoke [2]","Program 11111111111111111111111111111111 success","Program log: Counter initialized with value: 42","Program 3QpyHXhFtYY32iY7foF3EjkVdCDrUppADk9aDwSWn6Sq consumed 3803 of 200000 compute units","Program 3QpyHXhFtYY32iY7foF3EjkVdCDrUppADk9aDwSWn6Sq success",]Counter initialized successfully with value: 42Testing counter increment...Transaction logs:["Program 3QpyHXhFtYY32iY7foF3EjkVdCDrUppADk9aDwSWn6Sq invoke [1]","Program log: Counter incremented to: 43","Program 3QpyHXhFtYY32iY7foF3EjkVdCDrUppADk9aDwSWn6Sq consumed 762 of 200000 compute units","Program 3QpyHXhFtYY32iY7foF3EjkVdCDrUppADk9aDwSWn6Sq success",]Counter incremented successfully to: 43
Osa 3: Ohjelman kutsuminen
Lisätään nyt asiakasohjelma ohjelman kutsumista varten.
Luo asiakasesimerkki
Luodaan Rust-asiakas vuorovaikutukseen käyttöönotetun ohjelman kanssa.
$mkdir examples$touch examples/client.rs
Lisää seuraava konfiguraatio tiedostoon Cargo.toml:
[[example]]name = "client"path = "examples/client.rs"
Asenna asiakkaan riippuvuudet:
$cargo add solana-client@2.2.0 --dev$cargo add tokio --dev
Toteuta asiakaskoodi
Toteutetaan nyt asiakas, joka kutsuu käyttöönotettua ohjelmaamme.
Suorita seuraava komento saadaksesi ohjelmatunnuksesi keypair-tiedostosta:
$solana address -k ./target/deploy/counter_program-keypair.json
Lisää asiakaskoodi tiedostoon examples/client.rs ja korvaa program_id
edellisen komennon tulosteella:
let program_id = Pubkey::from_str("BDLLezrtFEXVGYqG3aS7eAC7GVeojJ4JHhKJM6pAFCDH").expect("Invalid program ID");
use solana_client::rpc_client::RpcClient;use solana_sdk::{commitment_config::CommitmentConfig,instruction::{AccountMeta, Instruction},pubkey::Pubkey,signature::{Keypair, Signer},system_program,transaction::Transaction,};use std::str::FromStr;use counter_program::CounterInstruction;#[tokio::main]async fn main() {// Replace with your actual program ID from deploymentlet program_id = Pubkey::from_str("BDLLezrtFEXVGYqG3aS7eAC7GVeojJ4JHhKJM6pAFCDH").expect("Invalid program ID");// Connect to local clusterlet rpc_url = String::from("http://localhost:8899");let client = RpcClient::new_with_commitment(rpc_url, CommitmentConfig::confirmed());// Generate a new keypair for paying feeslet payer = Keypair::new();// Request airdrop of 1 SOL for transaction feesprintln!("Requesting airdrop...");let airdrop_signature = client.request_airdrop(&payer.pubkey(), 1_000_000_000).expect("Failed to request airdrop");// Wait for airdrop confirmationloop {if client.confirm_transaction(&airdrop_signature).unwrap_or(false){break;}std::thread::sleep(std::time::Duration::from_millis(500));}println!("Airdrop confirmed");println!("\nInitializing counter...");let counter_keypair = Keypair::new();let initial_value = 100u64;// Serialize the initialize instruction datalet instruction_data = borsh::to_vec(&CounterInstruction::InitializeCounter { initial_value }).expect("Failed to serialize instruction");let initialize_instruction = Instruction::new_with_bytes(program_id,&instruction_data,vec![AccountMeta::new(counter_keypair.pubkey(), true),AccountMeta::new(payer.pubkey(), true),AccountMeta::new_readonly(system_program::id(), false),],);let mut transaction =Transaction::new_with_payer(&[initialize_instruction], Some(&payer.pubkey()));let blockhash = client.get_latest_blockhash().expect("Failed to get blockhash");transaction.sign(&[&payer, &counter_keypair], blockhash);match client.send_and_confirm_transaction(&transaction) {Ok(signature) => {println!("Counter initialized!");println!("Transaction: {}", signature);println!("Counter address: {}", counter_keypair.pubkey());}Err(err) => {eprintln!("Failed to initialize counter: {}", err);return;}}println!("\nIncrementing counter...");// Serialize the increment instruction datalet increment_data = borsh::to_vec(&CounterInstruction::IncrementCounter).expect("Failed to serialize instruction");let increment_instruction = Instruction::new_with_bytes(program_id,&increment_data,vec![AccountMeta::new(counter_keypair.pubkey(), true)],);let mut transaction =Transaction::new_with_payer(&[increment_instruction], Some(&payer.pubkey()));transaction.sign(&[&payer, &counter_keypair], blockhash);match client.send_and_confirm_transaction(&transaction) {Ok(signature) => {println!("Counter incremented!");println!("Transaction: {}", signature);}Err(err) => {eprintln!("Failed to increment counter: {}", err);}}}
Osa 4: Ohjelman käyttöönotto
Nyt kun ohjelmamme ja asiakkasmme ovat valmiit, rakennetaan, otetaan käyttöön ja kutsutaan ohjelmaa.
Rakenna ohjelma
Ensin rakennetaan ohjelmamme.
$cargo build-sbf
Tämä komento kääntää ohjelmasi ja luo kaksi tärkeää tiedostoa hakemistoon
target/deploy/:
counter_program.so # The compiled programcounter_program-keypair.json # Keypair for the program ID
Voit tarkastella ohjelmasi ID:tä suorittamalla seuraavan komennon:
$solana address -k ./target/deploy/counter_program-keypair.json
Esimerkki tulostuksesta:
HQ5Q2XXqbTKKQsWPtLzMn7rDhM8v9UPYPe7DfSoFQqJF
Käynnistä paikallinen validaattori
Kehitystyössä käytämme paikallista testivalidaattoria.
Ensin määritetään Solana CLI käyttämään localhostia:
$solana config set -ul
Esimerkki tulostuksesta:
Config File: ~/.config/solana/cli/config.ymlRPC URL: http://localhost:8899WebSocket URL: ws://localhost:8900/ (computed)Keypair Path: ~/.config/solana/id.jsonCommitment: confirmed
Käynnistä nyt testivalidaattori erillisessä terminaalissa:
$solana-test-validator
Ohjelman käyttöönotto
Kun validaattori on käynnissä, ota ohjelma käyttöön paikallisessa klusterissa:
$solana program deploy ./target/deploy/counter_program.so
Esimerkki tulostuksesta:
Program Id: HQ5Q2XXqbTKKQsWPtLzMn7rDhM8v9UPYPe7DfSoFQqJFSignature: 5xKdnh3dDFnZXB5UevYYkFBpCVcuqo5SaUPLnryFWY7eQD2CJxaeVDKjQ4ezQVJfkGNqZGYqMZBNqymPKwCQQx5h
Voit varmistaa käyttöönoton käyttämällä solana program show -komentoa ohjelman
tunnuksella:
$solana program show HQ5Q2XXqbTKKQsWPtLzMn7rDhM8v9UPYPe7DfSoFQqJF
Esimerkki tulostuksesta:
Program Id: HQ5Q2XXqbTKKQsWPtLzMn7rDhM8v9UPYPe7DfSoFQqJFOwner: BPFLoaderUpgradeab1e11111111111111111111111ProgramData Address: 47MVf5tRZ4zWXQMX7ydrkgcFQr8XTk1QBjohwsUzaiuMAuthority: 4kh6HxYZiAebF8HWLsUWod2EaQQ6iWHpHYCz8UcmFbM1Last Deployed In Slot: 16Data Length: 82696 (0x14308) bytesBalance: 0.57676824 SOL
Asiakkaan suorittaminen
Kun paikallinen validaattori on edelleen käynnissä, suorita asiakas:
$cargo run --example client
Odotettu tuloste:
Requesting airdrop...Airdrop confirmedInitializing counter...Counter initialized!Transaction: 2uenChtqNeLC1fitqoVE2LBeygSBTDchMZ4gGqs7AiDvZZVJguLDE5PfxsfkgY7xs6zFWnYsbEtb82dWv9tDT14kCounter address: EppPAmwqD42u4SCPWpPT7wmWKdFad5VnM9J4R9ZfofcyIncrementing counter...Counter incremented!Transaction: 4qv1Rx6FHu1M3woVgDQ6KtYUaJgBzGcHnhej76ZpaKGCgsTorbcHnPKxoH916UENw7X5ppnQ8PkPnhXxEwrYuUxS
Kun paikallinen validaattori on käynnissä, voit tarkastella transaktioita
Solana Explorerissa käyttämällä
tulosteen transaktioallekirjoituksia. Huomaa, että Solana Explorerin klusterin
on oltava asetettu "Custom RPC URL" -tilaan, joka oletuksena on
http://localhost:8899, jossa solana-test-validator on käynnissä.
Is this page helpful?