Rust-ohjelman rakenne
Rustilla kirjoitetuilla Solana-ohjelmilla on minimaaliset rakennevaatimukset, mikä mahdollistaa joustavuuden koodin organisoinnissa. Ainoa vaatimus on, että ohjelmalla täytyy olla entrypoint, joka määrittää mistä ohjelman suoritus alkaa.
Ohjelman rakenne
Vaikka tiedostorakenteelle ei ole tiukkoja sääntöjä, Solana-ohjelmat noudattavat tyypillisesti yleistä kaavaa:
- lib.rs: Määrittää sisääntulokodan, joka ohjaa saapuvat käskyt.
- state.rs: Määrittää ohjelman tilan (tilin data).
- instruction.rs: Määrittää käskyt, joita ohjelma voi suorittaa.
- processor.rs: Määrittää käskyjen käsittelijät (funktiot), jotka toteuttavat liiketoimintalogiikan kullekin käskylle.
- error.rs: Määrittää mukautetut virheet, joita ohjelma voi palauttaa.
Esimerkkinä katso Token Program.
Esimerkkiohjelma
Havainnollistaaksemme kuinka rakennetaan natiivi Rust-ohjelma useilla käskyillä, käymme läpi yksinkertaisen laskuriohjelma, joka toteuttaa kaksi käskyä:
- initialize: Luo ja alustaa uuden tilin alkuarvolla.
- increment: 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 aloitusarvolla ja kasvattaa sitä.
Luo uusi ohjelma
Luodaan ensin uusi Rust-projekti Solana-ohjelmallemme.
$cargo new counter_program --lib$cd counter_program
Sinun pitäisi nähdä oletusarvoiset src/lib.rs
ja Cargo.toml
tiedostot.
Päivitä edition
kenttä tiedostossa Cargo.toml
arvoon 2021. Muuten saatat
kohdata virheen ohjelmaa rakentaessa.
Lisää riippuvuudet
Lisätään nyt tarvittavat riippuvuudet Solana-ohjelman rakentamiseen. Tarvitsemme
solana-program
ydin SDK:ta varten ja borsh
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 täytyy kääntää dynaamisina kirjastoina. Lisää [lib]
osio
määrittääksesi 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 käynnistetään. Aloitetaan lisäämällä tarvittavat tuonnit ohjelmaa varten ja määrittämä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
funktiolla on seuraava funktiosignature. Kehittäjät
voivat vapaasti luoda oman toteutuksensa entrypoint
funktiolle.
#[no_mangle]pub unsafe extern "C" fn entrypoint(input: *mut u8) -> u64;
Määritä ohjelman tila
Määritellään nyt tietorakenne, 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 ohje-enum
Määritellään ohjeet, joita ohjelmamme voi suorittaa. Käytämme enumia, jossa jokainen variantti edustaa eri ohjetta.
Lisää seuraava koodi tiedostoon lib.rs
:
#[derive(BorshSerialize, BorshDeserialize, Debug)]pub enum CounterInstruction {InitializeCounter { initial_value: u64 },IncrementCounter,}
Toteuta ohjeiden deserialisointi
Nyt meidän täytyy deserialisoida instruction_data
(raakatavut) yhdeksi
CounterInstruction
enum-varianteista. Borsh try_from_slice
metodi hoitaa
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 ohjeet käsittelijöille
Päivitetään nyt pääfunktio process_instruction
ohjaamaan ohjeet niiden
asianmukaisille käsittelijäfunktioille.
Tämä reititystapa on yleinen Solana-ohjelmissa. instruction_data
deserialisoidaan ohjetta edustavaksi enum-variantiksi, jonka jälkeen kutsutaan
asianmukaista käsittelijäfunktiota. Jokainen käsittelijäfunktio sisältää
toteutuksen kyseiselle ohjeelle.
Lisää seuraava koodi tiedostoon lib.rs
päivittäen process_instruction
funktiota ja lisäten käsittelijät InitializeCounter
ja IncrementCounter
ohjeille:
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 siten, että ohjelmamme on sen omistaja, mikä antaa ohjelmallemme
mahdollisuuden kirjoittaa tilille ja alustaa data.
Lisää seuraava koodi tiedostoon lib.rs
päivittäen process_initialize_counter
funktio:
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 havainnollistamistarkoituksiin. Se ei sisällä tuotanto-ohjelmissa vaadittavia turvallisuus- ja validointitarkistuksia.
Toteuta kasvattamiskäsittelijä
Toteutetaan nyt käsittelijä, joka kasvattaa olemassa olevaa laskuria. Tämä ohje:
- Lukee tilin
data
kentäncounter_account
- Purkaa sen
CounterAccount
rakenteeksi - Kasvattaa
count
kenttää yhdellä - Sarjallistaa
CounterAccount
rakenteen takaisin tilindata
kenttään
Lisää seuraava koodi tiedostoon lib.rs
päivittäen process_increment_counter
funktio:
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 havainnollistamistarkoituksiin. Se ei sisällä tuotanto-ohjelmissa vaadittavia turvallisuus- ja validointitarkistuksia.
Valmis ohjelma
Onnittelut! Olet rakentanut kokonaisen Solana-ohjelman, joka demonstroi kaikkien Solana-ohjelmien jakamaa perusrakennetta:
- Sisäänkäyntipiste: Määrittää mistä ohjelman suoritus alkaa ja ohjaa kaikki saapuvat pyynnöt asianmukaisille ohjekäsittelijöille
- Ohjeiden käsittely: Määrittää ohjeet ja niiden käsittelijäfunktiot
- Tilan hallinta: Määrittää tilitietorakenteet ja hallinnoi niiden tilaa ohjelman omistamissa tileissä
- Cross Program Invocation (CPI): Kutsuu System Programia luomaan uusia ohjelman omistamia tilejä
Seuraava vaihe on testata ohjelma varmistaaksemme, että kaikki toimii oikein.
Osa 2: Ohjelman testaaminen
Testataan nyt laskuriohjelma. Käytämme LiteSVM-testikehystä, joka mahdollistaa ohjelmien testaamisen ilman klusteriin käyttöönottoa.
Lisää testaukseen tarvittavat riippuvuudet
Lisätään ensin testaukseen tarvittavat riippuvuudet. Käytämme litesvm
testaukseen ja solana-sdk
.
$cargo add litesvm@0.6.1 --dev$cargo add solana-sdk@2.2.0 --dev
Luo testimoduuli
Lisätään nyt testimoduuli ohjelmallemme. Aloitamme perusrakenteella ja tuonneilla.
Lisää seuraava koodi tiedostoon lib.rs
, suoraan ohjelmakoodin alapuolelle:
#[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}}
Attribuutti #[cfg(test)]
varmistaa, että tämä koodi käännetään vain testejä
suoritettaessa.
Alusta testiympäristö
Määritetään testiympäristö LiteSVM:llä ja rahoitetaan maksajatili.
LiteSVM simuloi Solanan ajoympäristöä, mikä mahdollistaa ohjelmamme testaamisen ilman, että sitä tarvitsee ottaa käyttöön oikeassa klusterissa.
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
komento cargo build-sbf
rakentaaksesi ohjelman. Tämä luo tiedoston
counter_program.so
hakemistoon target/deploy
.
$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 alustusohje
Testataan alustusohjetta luomalla uusi laskuritili aloitusarvolla.
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 tarkistetaan, 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 kasvatus-ohjetta
Nyt testataan kasvatus-ohjetta varmistaaksemme, että se päivittää laskurin arvon 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 lopulliset tulokset
Lopuksi varmistetaan, että kasvatus toimi oikein tarkistamalla päivitetty laskurin arvo.
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. Lippu --nocapture
tulostaa testin
tulosteen.
$cargo test -- --nocapture
Odotettu tuloste:
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
Nyt lisätään asiakasohjelma ohjelman kutsumista varten.
Luo asiakasesimerkki
Luodaan Rust-asiakas vuorovaikutusta varten käyttöönotetun ohjelmamme kanssa.
$mkdir examples$touch examples/client.rs
Lisää seuraava konfiguraatio tiedostoon Cargo.toml
:
[[example]]name = "client"path = "examples/client.rs"
Asenna asiakasriippuvuudet:
$cargo add solana-client@2.2.0 --dev$cargo add tokio --dev
Toteuta asiakaskoodi
Nyt toteutetaan asiakas, joka kutsuu käyttöönotettua ohjelmaamme.
Suorita seuraava komento saadaksesi ohjelmasi ID:n 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 meillä on ohjelma ja asiakas valmiina, 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 kansioon
target/deploy/
:
counter_program.so # The compiled programcounter_program-keypair.json # Keypair for the program ID
Voit tarkastella ohjelmasi tunnusta suorittamalla seuraavan komennon:
$solana address -k ./target/deploy/counter_program-keypair.json
Esimerkki tulostuksesta:
HQ5Q2XXqbTKKQsWPtLzMn7rDhM8v9UPYPe7DfSoFQqJF
Käynnistä paikallinen validator
Kehitystä varten käytämme paikallista testivalidatoria.
Ensin määritä 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 testivalidator erillisessä terminaalissa:
$solana-test-validator
Ota ohjelma käyttöön
Kun validator on käynnissä, ota ohjelmasi käyttöön paikallisessa klusterissa:
$solana program deploy ./target/deploy/counter_program.so
Esimerkki tulostuksesta:
Program Id: HQ5Q2XXqbTKKQsWPtLzMn7rDhM8v9UPYPe7DfSoFQqJFSignature: 5xKdnh3dDFnZXB5UevYYkFBpCVcuqo5SaUPLnryFWY7eQD2CJxaeVDKjQ4ezQVJfkGNqZGYqMZBNqymPKwCQQx5h
Voit vahvistaa käyttöönoton käyttämällä komentoa solana program show
ja
ohjelmasi tunnusta:
$solana program show HQ5Q2XXqbTKKQsWPtLzMn7rDhM8v9UPYPe7DfSoFQqJF
Esimerkkitulos:
Program Id: HQ5Q2XXqbTKKQsWPtLzMn7rDhM8v9UPYPe7DfSoFQqJFOwner: BPFLoaderUpgradeab1e11111111111111111111111ProgramData Address: 47MVf5tRZ4zWXQMX7ydrkgcFQr8XTk1QBjohwsUzaiuMAuthority: 4kh6HxYZiAebF8HWLsUWod2EaQQ6iWHpHYCz8UcmFbM1Last Deployed In Slot: 16Data Length: 82696 (0x14308) bytesBalance: 0.57676824 SOL
Suorita asiakas
Kun paikallinen validator on edelleen käynnissä, suorita asiakas:
$cargo run --example client
Odotettu tulos:
Requesting airdrop...Airdrop confirmedInitializing counter...Counter initialized!Transaction: 2uenChtqNeLC1fitqoVE2LBeygSBTDchMZ4gGqs7AiDvZZVJguLDE5PfxsfkgY7xs6zFWnYsbEtb82dWv9tDT14kCounter address: EppPAmwqD42u4SCPWpPT7wmWKdFad5VnM9J4R9ZfofcyIncrementing counter...Counter incremented!Transaction: 4qv1Rx6FHu1M3woVgDQ6KtYUaJgBzGcHnhej76ZpaKGCgsTorbcHnPKxoH916UENw7X5ppnQ8PkPnhXxEwrYuUxS
Kun paikallinen validator on käynnissä, voit tarkastella tapahtumia
Solana Explorerissa käyttämällä
tulostettuja tapahtumien allekirjoituksia. Huomaa, että Solana Explorerissa
klusterin asetukseksi on valittava "Custom RPC URL", jonka oletusarvo on
http://localhost:8899
, jossa solana-test-validator
on käynnissä.
Is this page helpful?