Solana-dokumentaatioOhjelmien kehittäminenRust-ohjelmat

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ä:

  1. initialize: Luo ja alustaa uuden tilin alkuarvolla.
  2. 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.

Terminal
$
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.

Terminal
$
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.

Cargo.toml
[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:

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:

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:

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:

lib.rs
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:

lib.rs
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:

lib.rs
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än counter_account
  • Purkaa sen CounterAccount rakenteeksi
  • Kasvattaa count kenttää yhdellä
  • Sarjallistaa CounterAccount rakenteen takaisin tilin data kenttään

Lisää seuraava koodi tiedostoon lib.rs päivittäen process_increment_counter funktio:

lib.rs
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.

Luo uusi ohjelma

Luodaan ensin uusi Rust-projekti Solana-ohjelmallemme.

Terminal
$
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.

Terminal
$
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.

Cargo.toml
[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:

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:

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:

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:

lib.rs
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:

lib.rs
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:

lib.rs
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än counter_account
  • Purkaa sen CounterAccount rakenteeksi
  • Kasvattaa count kenttää yhdellä
  • Sarjallistaa CounterAccount rakenteen takaisin tilin data kenttään

Lisää seuraava koodi tiedostoon lib.rs päivittäen process_increment_counter funktio:

lib.rs
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.

Cargo.toml
lib.rs
[package]
name = "counter_program"
version = "0.1.0"
edition = "2021"
[dependencies]

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.

Terminal
$
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:

lib.rs
#[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:

lib.rs
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.

Terminal
$
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.

lib.rs
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:

lib.rs
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:

lib.rs
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:

lib.rs
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:

lib.rs
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.

Terminal
$
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: 42
Testing 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

Lisää testaukseen tarvittavat riippuvuudet

Lisätään ensin testaukseen tarvittavat riippuvuudet. Käytämme litesvm testaukseen ja solana-sdk.

Terminal
$
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:

lib.rs
#[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:

lib.rs
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.

Terminal
$
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.

lib.rs
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:

lib.rs
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:

lib.rs
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:

lib.rs
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:

lib.rs
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.

Terminal
$
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: 42
Testing 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
Cargo.toml
[package]
name = "counter_program"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib", "lib"]
[dependencies]
borsh = "1.5.7"
solana-program = "2.2.0"
[dev-dependencies]
litesvm = "0.6.1"
solana-sdk = "2.2.0"

Osa 3: Ohjelman kutsuminen

Nyt lisätään asiakasohjelma ohjelman kutsumista varten.

Luo asiakasesimerkki

Luodaan Rust-asiakas vuorovaikutusta varten käyttöönotetun ohjelmamme kanssa.

Terminal
$
mkdir examples
$
touch examples/client.rs

Lisää seuraava konfiguraatio tiedostoon Cargo.toml:

Cargo.toml
[[example]]
name = "client"
path = "examples/client.rs"

Asenna asiakasriippuvuudet:

Terminal
$
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:

Terminal
$
solana address -k ./target/deploy/counter_program-keypair.json

Lisää asiakaskoodi tiedostoon examples/client.rs ja korvaa program_id edellisen komennon tulosteella:

examples/client.rs
let program_id = Pubkey::from_str("BDLLezrtFEXVGYqG3aS7eAC7GVeojJ4JHhKJM6pAFCDH")
.expect("Invalid program ID");
examples/client.rs
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 deployment
let program_id = Pubkey::from_str("BDLLezrtFEXVGYqG3aS7eAC7GVeojJ4JHhKJM6pAFCDH")
.expect("Invalid program ID");
// Connect to local cluster
let rpc_url = String::from("http://localhost:8899");
let client = RpcClient::new_with_commitment(rpc_url, CommitmentConfig::confirmed());
// Generate a new keypair for paying fees
let payer = Keypair::new();
// Request airdrop of 1 SOL for transaction fees
println!("Requesting airdrop...");
let airdrop_signature = client
.request_airdrop(&payer.pubkey(), 1_000_000_000)
.expect("Failed to request airdrop");
// Wait for airdrop confirmation
loop {
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 data
let 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 data
let 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);
}
}
}

Luo asiakasesimerkki

Luodaan Rust-asiakas vuorovaikutusta varten käyttöönotetun ohjelmamme kanssa.

Terminal
$
mkdir examples
$
touch examples/client.rs

Lisää seuraava konfiguraatio tiedostoon Cargo.toml:

Cargo.toml
[[example]]
name = "client"
path = "examples/client.rs"

Asenna asiakasriippuvuudet:

Terminal
$
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:

Terminal
$
solana address -k ./target/deploy/counter_program-keypair.json

Lisää asiakaskoodi tiedostoon examples/client.rs ja korvaa program_id edellisen komennon tulosteella:

examples/client.rs
let program_id = Pubkey::from_str("BDLLezrtFEXVGYqG3aS7eAC7GVeojJ4JHhKJM6pAFCDH")
.expect("Invalid program ID");
Cargo.toml
[package]
name = "counter_program"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib", "lib"]
[dependencies]
borsh = "1.5.7"
solana-program = "2.2.0"
[dev-dependencies]
litesvm = "0.6.1"
solana-sdk = "2.2.0"
solana-client = "2.2.0"
tokio = "1.47.1"
[[example]]
name = "client"
path = "examples/client.rs"

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.

Terminal
$
cargo build-sbf

Tämä komento kääntää ohjelmasi ja luo kaksi tärkeää tiedostoa kansioon target/deploy/:

counter_program.so # The compiled program
counter_program-keypair.json # Keypair for the program ID

Voit tarkastella ohjelmasi tunnusta suorittamalla seuraavan komennon:

Terminal
$
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:

Terminal
$
solana config set -ul

Esimerkki tulostuksesta:

Config File: ~/.config/solana/cli/config.yml
RPC URL: http://localhost:8899
WebSocket URL: ws://localhost:8900/ (computed)
Keypair Path: ~/.config/solana/id.json
Commitment: confirmed

Käynnistä nyt testivalidator erillisessä terminaalissa:

Terminal
$
solana-test-validator

Ota ohjelma käyttöön

Kun validator on käynnissä, ota ohjelmasi käyttöön paikallisessa klusterissa:

Terminal
$
solana program deploy ./target/deploy/counter_program.so

Esimerkki tulostuksesta:

Program Id: HQ5Q2XXqbTKKQsWPtLzMn7rDhM8v9UPYPe7DfSoFQqJF
Signature: 5xKdnh3dDFnZXB5UevYYkFBpCVcuqo5SaUPLnryFWY7eQD2CJxaeVDKjQ4ezQVJfkGNqZGYqMZBNqymPKwCQQx5h

Voit vahvistaa käyttöönoton käyttämällä komentoa solana program show ja ohjelmasi tunnusta:

Terminal
$
solana program show HQ5Q2XXqbTKKQsWPtLzMn7rDhM8v9UPYPe7DfSoFQqJF

Esimerkkitulos:

Program Id: HQ5Q2XXqbTKKQsWPtLzMn7rDhM8v9UPYPe7DfSoFQqJF
Owner: BPFLoaderUpgradeab1e11111111111111111111111
ProgramData Address: 47MVf5tRZ4zWXQMX7ydrkgcFQr8XTk1QBjohwsUzaiuM
Authority: 4kh6HxYZiAebF8HWLsUWod2EaQQ6iWHpHYCz8UcmFbM1
Last Deployed In Slot: 16
Data Length: 82696 (0x14308) bytes
Balance: 0.57676824 SOL

Suorita asiakas

Kun paikallinen validator on edelleen käynnissä, suorita asiakas:

Terminal
$
cargo run --example client

Odotettu tulos:

Requesting airdrop...
Airdrop confirmed
Initializing counter...
Counter initialized!
Transaction: 2uenChtqNeLC1fitqoVE2LBeygSBTDchMZ4gGqs7AiDvZZVJguLDE5PfxsfkgY7xs6zFWnYsbEtb82dWv9tDT14k
Counter address: EppPAmwqD42u4SCPWpPT7wmWKdFad5VnM9J4R9Zfofcy
Incrementing 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?

Sisällysluettelo

Muokkaa sivua