Pagsubok sa Pagkakapareho ng Tampok
Kapag sinusubukan ang iyong programa, ang mga katiyakan na pareho itong tatakbo sa iba't ibang mga kumpol ay mahalaga sa parehong kalidad at paggawa ng inaasahang resulta.
Mga Katotohanan
Fact Sheet
- Ang mga tampok ay mga kakayahan na ipinakilala sa mga validator ng Solana at nangangailangan ng pag-activate upang magamit.
- Maaaring i-activate ang mga feature sa isang cluster (hal. testnet) ngunit hindi sa isa pa (hal. mainnet-beta).
- Gayunpaman; kapag lokal na pinapatakbo ang default na
solana-test-validator
, lahat ng available na feature sa iyong Ang bersyon ng Solana ay awtomatikong naisaaktibo. Ang resulta ay kapag lokal na pagsubok, ang mga kakayahan at resulta ng maaaring hindi pareho ang iyong pagsubok kapag nagde-deploy at tumatakbo sa ibang cluster!
Sitwasyon
Ipagpalagay na mayroon kang Transaksyon na naglalaman ng tatlong (3) mga instruction at ang bawat instruction ay kumukonsumo ng humigit-kumulang 100_000 Compute Units (CU). Kapag tumatakbo sa bersyon ng Solana 1.8.x, mapapansin mo ang iyong pagtuturo sa pagkonsumo ng CU katulad ng:
Instruction | Simula sa CU | Pagpapatupad | Natitirang CU |
---|---|---|---|
1 | 200_000 | -100_000 | 100_000 |
2 | 200_000 | -100_000 | 100_000 |
3 | 200_000 | -100_000 | 100_000 |
Sa Solana 1.9.2 isang tampok na tinatawag na 'transaction wide compute cap' ay ipinakilala kung saan ang isang Transaksyon, bilang default, ay may 200_000 CU na badyet at ang mga naka-encapsulate na instruction na ay kumukuha mula sa badyet ng Transaksyon na iyon. Parehong tumatakbo ang transaksyon tulad ng nabanggit sa itaas ay magkakaroon ng ibang pag-uugali:
Instruction | Simula sa CU | Pagpapatupad | Natitirang CU |
---|---|---|---|
1 | 200_000 | -100_000 | 100_000 |
2 | 100_000 | -100_000 | 0 |
3 | 0 | FAIL!!! | FAIL!!! |
Hays! Kung hindi mo alam ito, malamang na mabigo ka dahil walang pagbabago sa iyong pag-uugali sa pagtuturo magiging sanhi nito. Sa devnet ito ay gumana nang maayos, ngunit sa lokal na ito ay nabigo?!?
May kakayahang taasan ang kabuuang badyet sa Transaksyon, para sabihing 300_000 CU, at iligtas ang iyong katinuan ngunit ito ay nagpapakita kung bakit ang pagsubok sa Feature Parity ay nagbibigay ng isang maagap na paraan upang maiwasan ang anumang pagkalito.
Katayuan ng Tampok
Medyo madaling suriin kung anong mga feature ang pinagana para sa isang partikular na cluster gamit ang command na solana feature status
.
solana feature status -ud // Ipinapakita ayon sa feature status para sa devnet
solana feature status -ut // Ipinapakita para sa testnet
solana feature status -um // Ipinapakita para sa mainnet-beta
status ng tampok na solana -ul // Ipinapakita para sa lokal, nangangailangan ng pagpapatakbo ng solana-test-validator
Bilang kahalili, maaari kang gumamit ng tool tulad ng scfsd upang obserbahan ang lahat ng katayuan ng tampok sa mga cluster na magpapakita, bahagyang screen na ipinapakita dito, at hindi nangangailangan ng solana-test-validator
na tumakbo:
Pagsubok sa Pagkakapantay-pantay
Gaya ng nabanggit sa itaas, awtomatikong ina-activate ng solana-test-validator
ang lahat ng mga feature. Kaya't upang masagot ang tanong na "Paano ko masusubok nang lokal sa isang environment na may pagkakapareho sa devnet, testnet o kahit mainnet-beta?".
Solusyon: Ang mga PR ay idinagdag sa Solana 1.9.6 upang payagan ang pag-deactivate ng mga feature:
solana-test-validator --deactivate-feature <FEATURE_PUBKEY> ...
Simpleng Pagpapakita
Ipagpalagay na mayroon kang isang simpleng programa na nag-log sa data na natatanggap nito sa entry-point nito. At ikaw ay pagsubok ng isang transaksyon na nagdaragdag ng dalawang (2) mga instruction para sa iyong programa.
Na-activate ang lahat ng feature
- Sisimulan mo ang test validator sa isang terminal:
solana config set -ul
solana-test-validator -l ./ledger --bpf-program ADDRESS target/deploy/PROGNAME.so --reset`
- Sa ibang terminal sisimulan mo ang log streamer:
solana logs
- Pagkatapos ay patakbuhin mo ang iyong transaksyon. Makakakita ka ng katulad sa log terminal (na-edit para sa kalinawan):
Program PWDnx8LkjJUn9bAVzG6Fp6BuvB41x7DkBZdo9YLMGcc invoke [1]
Program log: process_instruction: PWDnx8LkjJUn9bAVzG6Fp6BuvB41x7DkBZdo9YLMGcc: 0 accounts, data=[0]
Program PWDnx8LkjJUn9bAVzG6Fp6BuvB41x7DkBZdo9YLMGcc consumed 12843 of 200000 compute units
Program PWDnx8LkjJUn9bAVzG6Fp6BuvB41x7DkBZdo9YLMGcc success
Program PWDnx8LkjJUn9bAVzG6Fp6BuvB41x7DkBZdo9YLMGcc invoke [1]
Program log: process_instruction: PWDnx8LkjJUn9bAVzG6Fp6BuvB41x7DkBZdo9YLMGcc: 0 accounts, data=[1]
Program PWDnx8LkjJUn9bAVzG6Fp6BuvB41x7DkBZdo9YLMGcc consumed 12843 of 187157 compute units
Program PWDnx8LkjJUn9bAVzG6Fp6BuvB41x7DkBZdo9YLMGcc success[
Dahil ang aming feature na 'transaction wide compute cap' ay awtomatikong naisaaktibo bilang default, sinusunod namin ang bawat isa pagtuturo na kumukuha ng CU mula sa panimulang Transaksyon na badyet na 200_000 CU.
Na-deactivate ang mga piling feature
- Para sa run na ito, gusto nating tumakbo upang ang pag-uugali ng badyet ng CU ay naaayon sa kung ano ang tumatakbo sa devnet. Gamit ang (mga) tool na inilalarawan sa Status ng Tampok ibinubukod namin ang pampublikong key na
transaction wide compute cap
at gamitin ang--deactivate-feature
sa test validator startup
solana-test-validator -l ./ledger --deactivate-feature 5ekBxc8itEnPv4NzGJtr8BVVQLNMQuLMNQQj7pHoLNZ9 --bpf-program target/deploy/PROGNAME.so --reset`
- Nakita namin ngayon sa aming mga log na ang aming mga instruction ay mayroon na ngayong sariling 200_000 CU na badyet (na-edit para sa kalinawan) na kasalukuyang estado sa lahat ng upstream cluster:
Program PWDnx8LkjJUn9bAVzG6Fp6BuvB41x7DkBZdo9YLMGcc invoke [1]
Program log: process_instruction: PWDnx8LkjJUn9bAVzG6Fp6BuvB41x7DkBZdo9YLMGcc: 0 accounts, data=[0]
Program PWDnx8LkjJUn9bAVzG6Fp6BuvB41x7DkBZdo9YLMGcc consumed 12843 of 200000 compute units
Program PWDnx8LkjJUn9bAVzG6Fp6BuvB41x7DkBZdo9YLMGcc success
Program PWDnx8LkjJUn9bAVzG6Fp6BuvB41x7DkBZdo9YLMGcc invoke [1]
Program log: process_instruction: PWDnx8LkjJUn9bAVzG6Fp6BuvB41x7DkBZdo9YLMGcc: 0 accounts, data=[0]
Program PWDnx8LkjJUn9bAVzG6Fp6BuvB41x7DkBZdo9YLMGcc consumed 12843 of 200000 compute units
Program PWDnx8LkjJUn9bAVzG6Fp6BuvB41x7DkBZdo9YLMGcc success
Buong Parity Testing
Maaari kang maging ganap na pare-pareho sa isang partikular na cluster sa pamamagitan ng pagtukoy sa bawat feature na hindi na-activate pa at magdagdag ng --deactivate-feature <FEATURE_PUBKEY>
para sa bawat isa kapag gumagamit ng solana-test-validator
:
solana-test-validator --deactivate-feature PUBKEY_1 --deactivate-feature PUBKEY_2 ...
Bilang kahalili, ang scfsd ay nagbibigay ng command switch upang i-output ang kumpletong na-deactivate na feature itinakda para sa isang cluster na direktang i-feed sa startup ng solana-test-validator
:
solana-test-validator -l ./.ledger $(scfsd -c devnet -k -t)
Kung magbubukas ka ng isa pang terminal, habang tumatakbo ang validator, at solana feature status
ang makikita mo mga feature na na-deactivate na nakitang naka-deactivate sa devnet
Buong Parity Testing sa pamamagitan ng pag-program
Para sa mga kumokontrol sa pagpapatakbo ng test validator sa loob ng kanilang test code, nagbabago ang test validator activated/deactivated features ay posible gamit ang TestValidatorGenesis. Sa Solana 1.9.6 isang function ang idinagdag sa tagabuo ng validator upang suportahan ito.
Sa ugat ng folder ng iyong program, lumikha ng bagong folder na tinatawag na tests
at magdagdag ng parity_test.rs
file. Narito ang mga function ng boiler place (boiler-plate kung gugustuhin mo) na ginagamit ng bawat pagsubok
#[cfg(test)]
mod tests {
use std::{error, path::PathBuf, str::FromStr};
// Use gadget-scfs to get interegate feature lists from clusters
// must have `gadgets-scfs = "0.2.0" in Cargo.toml [dev-dependencies] to use
use gadgets_scfs::{ScfsCriteria, ScfsMatrix, SCFS_DEVNET};
use solana_client::rpc_client::RpcClient;
use solana_program::{instruction::Instruction, message::Message, pubkey::Pubkey};
use solana_sdk::{
// Added in Solana 1.9.2
compute_budget::ComputeBudgetInstruction,
pubkey,
signature::{Keypair, Signature},
signer::Signer,
transaction::Transaction,
};
// Extended in Solana 1.9.6
use solana_test_validator::{TestValidator, TestValidatorGenesis};
/// Location/Name of ProgramTestGenesis ledger
const LEDGER_PATH: &str = "./.ledger";
/// Path to BPF program (*.so) change if needed
const PROG_PATH: &str = "target/deploy/";
/// Program name from program Cargo.toml
/// FILL IN WITH YOUR PROGRAM_NAME
const PROG_NAME: &str = "PROGRAM_NAME";
/// Program public key
/// FILL IN WITH YOUR PROGRAM'S PUBLIC KEY str
const PROG_KEY: Pubkey = pubkey!("PROGRAMS_PUBLIC_KEY_BASE58_STRING");
/// 'transaction wide compute cap' public key
const TXWIDE_LIMITS: Pubkey = pubkey!("5ekBxc8itEnPv4NzGJtr8BVVQLNMQuLMNQQj7pHoLNZ9");
/// Setup the test validator passing features
/// you want to deactivate before running transactions
pub fn setup_validator(
invalidate_features: Vec<Pubkey>,
) -> Result<(TestValidator, Keypair), Box<dyn error::Error>> {
// Extend environment variable to include our program location
std::env::set_var("BPF_OUT_DIR", PROG_PATH);
// Instantiate the test validator
let mut test_validator = TestValidatorGenesis::default();
// Once instantiated, TestValidatorGenesis configuration functions follow
// a builder pattern enabling chaining of settings function calls
let (test_validator, kp) = test_validator
// Set the ledger path and name
// maps to `solana-test-validator --ledger <DIR>`
.ledger_path(LEDGER_PATH)
// Load our program. Ignored if reusing ledger
// maps to `solana-test-validator --bpf-program <ADDRESS_OR_PATH BPF_PROGRAM.SO>`
.add_program(PROG_NAME, PROG_KEY)
// Identify features to deactivate. Ignored if reusing ledger
// maps to `solana-test-validator --deactivate-feature <FEATURE_PUBKEY>`
.deactivate_features(&invalidate_features)
// Start the test validator
.start();
Ok((test_validator, kp))
}
/// Convenience function to remove existing ledger before TestValidatorGenesis setup
/// maps to `solana-test-validator ... --reset`
pub fn clean_ledger_setup_validator(
invalidate_features: Vec<Pubkey>,
) -> Result<(TestValidator, Keypair), Box<dyn error::Error>> {
if PathBuf::from_str(LEDGER_PATH).unwrap().exists() {
std::fs::remove_dir_all(LEDGER_PATH).unwrap();
}
setup_validator(invalidate_features)
}
/// Submits a transaction with programs instruction
/// Boiler plate
fn submit_transaction(
rpc_client: &RpcClient,
wallet_signer: &dyn Signer,
instructions: Vec<Instruction>,
) -> Result<Signature, Box<dyn std::error::Error>> {
let mut transaction =
Transaction::new_unsigned(Message::new(&instructions, Some(&wallet_signer.pubkey())));
let recent_blockhash = rpc_client
.get_latest_blockhash()
.map_err(|err| format!("error: unable to get recent blockhash: {}", err))?;
transaction
.try_sign(&vec![wallet_signer], recent_blockhash)
.map_err(|err| format!("error: failed to sign transaction: {}", err))?;
let signature = rpc_client
.send_and_confirm_transaction(&transaction)
.map_err(|err| format!("error: send transaction: {}", err))?;
Ok(signature)
}
// UNIT TEST FOLLOWS
}
/// Setup the test validator passing features
/// you want to deactivate before running transactions
pub fn setup_validator(
invalidate_features: Vec<Pubkey>,
) -> Result<(TestValidator, Keypair), Box<dyn error::Error>> {
// Extend environment variable to include our program location
std::env::set_var("BPF_OUT_DIR", PROG_PATH);
// Instantiate the test validator
let mut test_validator = TestValidatorGenesis::default();
// Once instantiated, TestValidatorGenesis configuration functions follow
// a builder pattern enabling chaining of settings function calls
let (test_validator, kp) = test_validator
// Set the ledger path and name
// maps to `solana-test-validator --ledger <DIR>`
.ledger_path(LEDGER_PATH)
// Load our program. Ignored if reusing ledger
// maps to `solana-test-validator --bpf-program <ADDRESS_OR_PATH BPF_PROGRAM.SO>`
.add_program(PROG_NAME, PROG_KEY)
// Identify features to deactivate. Ignored if reusing ledger
// maps to `solana-test-validator --deactivate-feature <FEATURE_PUBKEY>`
.deactivate_features(&invalidate_features)
// Start the test validator
.start();
Ok((test_validator, kp))
}
/// Convenience function to remove existing ledger before TestValidatorGenesis setup
/// maps to `solana-test-validator ... --reset`
pub fn clean_ledger_setup_validator(
invalidate_features: Vec<Pubkey>,
) -> Result<(TestValidator, Keypair), Box<dyn error::Error>> {
if PathBuf::from_str(LEDGER_PATH).unwrap().exists() {
std::fs::remove_dir_all(LEDGER_PATH).unwrap();
}
setup_validator(invalidate_features)
}
/// Submits a transaction with programs instruction
/// Boiler plate
fn submit_transaction(
rpc_client: &RpcClient,
wallet_signer: &dyn Signer,
instructions: Vec<Instruction>,
) -> Result<Signature, Box<dyn std::error::Error>> {
let mut transaction =
Transaction::new_unsigned(Message::new(&instructions, Some(&wallet_signer.pubkey())));
let recent_blockhash = rpc_client
.get_latest_blockhash()
.map_err(|err| format!("error: unable to get recent blockhash: {}", err))?;
transaction
.try_sign(&vec![wallet_signer], recent_blockhash)
.map_err(|err| format!("error: failed to sign transaction: {}", err))?;
let signature = rpc_client
.send_and_confirm_transaction(&transaction)
.map_err(|err| format!("error: send transaction: {}", err))?;
Ok(signature)
}
Maaari na kaming magdagdag ng mga function ng pagsubok sa katawan ng mod test {...}
upang ipakita ang default setup ng validator (naka-enable ang lahat ng feature) at pagkatapos ay i-disable ang transaction wide compute cap
bilang bawat nakaraang mga halimbawa na nagpapatakbo ng solana-test-validator
mula sa command line.
#[test]
fn test_base_pass() {
// Run with all features activated (default for TestValidatorGenesis)
let inv_feat = vec![];
// Start validator with clean (new) ledger
let (test_validator, main_payer) = clean_ledger_setup_validator(inv_feat).unwrap();
// Get the RpcClient
let connection = test_validator.get_rpc_client();
// Capture our programs log statements
solana_logger::setup_with_default("solana_runtime::message=debug");
// This example doesn't require sending any accounts to program
let accounts = &[];
// Build instruction array and submit transaction
let txn = submit_transaction(
&connection,
&main_payer,
// Add two (2) instructions to transaction to demonstrate
// that each instruction CU draws down from default Transaction CU (200_000)
// Replace with instructions that make sense for your program
[
Instruction::new_with_borsh(PROG_KEY, &0u8, accounts.to_vec()),
Instruction::new_with_borsh(PROG_KEY, &1u8, accounts.to_vec()),
]
.to_vec(),
);
assert!(txn.is_ok());
}
#[test]
fn test_deactivate_tx_cu_pass() {
// Run with all features activated except 'transaction wide compute cap'
let inv_feat = vec![TXWIDE_LIMITS];
// Start validator with clean (new) ledger
let (test_validator, main_payer) = clean_ledger_setup_validator(inv_feat).unwrap();
// Get the RpcClient
let connection = test_validator.get_rpc_client();
// Capture our programs log statements
solana_logger::setup_with_default("solana_runtime::message=debug");
// This example doesn't require sending any accounts to program
let accounts = &[];
// Build instruction array and submit transaction
let txn = submit_transaction(
&connection,
&main_payer,
[
// This instruction adds CU to transaction budget (1.9.2) but does nothing
// when we deactivate the 'transaction wide compute cap' feature
ComputeBudgetInstruction::request_units(400_000u32),
// Add two (2) instructions to transaction
// Replace with instructions that make sense for your program
// You will see that each instruction has the 1.8.x 200_000 CU per budget
Instruction::new_with_borsh(PROG_KEY, &0u8, accounts.to_vec()),
Instruction::new_with_borsh(PROG_KEY, &1u8, accounts.to_vec()),
]
.to_vec(),
);
assert!(txn.is_ok());
}
Bilang kahalili, ang scfs engine gadget ay maaaring makagawa ng isang buong vector ng na-deactivate mga tampok para sa isang kumpol. Ipinapakita ng sumusunod ang paggamit ng makinang iyon upang makakuha ng listahan ng lahat ng na-deactivate na feature para sa devnet.
#[test]
fn test_devnet_parity_pass() {
// Use gadget-scfs to get all deactivated features from devnet
// must have `gadgets-scfs = "0.2.0" in Cargo.toml to use
// Here we setup for a run that samples features only
// from devnet
let mut my_matrix = ScfsMatrix::new(Some(ScfsCriteria {
clusters: Some(vec![SCFS_DEVNET.to_string()]),
..Default::default()
}))
.unwrap();
// Run the sampler matrix
assert!(my_matrix.run().is_ok());
// Get all deactivated features
let deactivated = my_matrix
.get_features(Some(&ScfsMatrix::any_inactive))
.unwrap();
// Confirm we have them
assert_ne!(deactivated.len(), 0);
// Setup test validator and logging while deactivating all
// features that are deactivated in devnet
let (test_validator, main_payer) = clean_ledger_setup_validator(deactivated).unwrap();
let connection = test_validator.get_rpc_client();
solana_logger::setup_with_default("solana_runtime::message=debug");
let accounts = &[];
let txn = submit_transaction(
&connection,
&main_payer,
[
// Add two (2) instructions to transaction
// Replace with instructions that make sense for your program
Instruction::new_with_borsh(PROG_KEY, &0u8, accounts.to_vec()),
Instruction::new_with_borsh(PROG_KEY, &1u8, accounts.to_vec()),
]
.to_vec(),
);
assert!(txn.is_ok());
}
Happy Testing!