Created
January 17, 2021 02:21
-
-
Save bennofs/84d81d43a8ed872dd5600044ef82de06 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#![allow(unused)] | |
use std::{collections::HashSet, path::Path}; | |
pub use std::{process::ExitStatus, fs::File, io::Read, net::SocketAddr, net::SocketAddrV4, net::UdpSocket, path::PathBuf, time::Duration, time::Instant}; | |
pub use std::collections::HashMap; | |
pub use bincode::serialize; | |
use itertools::izip; | |
pub use log::*; | |
use solana_bpf_loader_program::{ThisInstructionMeter, solana_bpf_loader_deprecated_program, solana_bpf_loader_program, solana_bpf_loader_upgradeable_program}; | |
pub use solana_bpf_loader_program::{BPFError, bpf_verifier}; | |
pub use solana_cli_output::display::new_spinner_progress_bar; | |
use solana_cli_output::display::println_transaction; | |
use solana_client::rpc_request::RpcError; | |
pub use solana_client::{client_error::ClientErrorKind, rpc_client::{RpcClient}, rpc_config::RpcSendTransactionConfig, rpc_request::MAX_GET_SIGNATURE_STATUSES_QUERY_ITEMS}; | |
pub use solana_client::rpc_response::RpcLeaderSchedule; | |
pub use solana_core::poh_recorder::Slot; | |
pub use solana_faucet::faucet::request_airdrop_transaction; | |
pub use solana_perf::packet::{Packet, Packets}; | |
use solana_program::bpf_loader_upgradeable; | |
use solana_rbpf::vm::EbpfVm; | |
pub use solana_rbpf::vm::{Config, Executable}; | |
use solana_runtime::bank::{Bank, Builtin, Builtins, ExecuteTimings, NonceRollbackInfo, TransactionBalancesSet, TransactionResults}; | |
pub use solana_sdk::{account::Account, commitment_config::CommitmentConfig, genesis_config::GenesisConfig, signature::Keypair, signature::Signer, signature::keypair_from_seed, signature::write_keypair, signature::{Signature, write_keypair_file}, signers::Signers, transaction::Transaction}; | |
pub use solana_cli::send_tpu::{get_leader_tpu, send_transaction_tpu}; | |
pub use solana_sdk::system_program; | |
pub use solana_sdk::system_instruction; | |
pub use solana_program::{bpf_loader, clock::MAX_HASH_AGE_IN_SECONDS, hash::{Hash, Hasher}, instruction::CompiledInstruction, instruction::{AccountMeta, Instruction}, native_token::lamports_to_sol, loader_instruction, message::Message, message::MessageHeader, native_token::Sol, native_token::sol_to_lamports, pubkey::Pubkey}; | |
use solana_transaction_status::{ConfirmedTransaction, InnerInstructions, TransactionStatusMeta, TransactionWithStatusMeta, token_balances::{TransactionTokenBalancesSet, collect_token_balance_from_account, collect_token_balances}}; | |
pub use solana_transaction_status::UiTransactionEncoding; | |
pub use solana_vote_program::vote_state::MAX_LOCKOUT_HISTORY; | |
pub const DATA_CHUNK_SIZE: usize = 229; | |
pub fn send_and_confirm_transactions_with_spinner<T: Signers>( | |
rpc_client: &RpcClient, | |
mut transactions: Vec<Transaction>, | |
signer_keys: &T | |
) -> Result<(), Box<dyn error::Error>> { | |
let progress_bar = new_spinner_progress_bar(); | |
let mut send_retries = 5; | |
let mut leader_schedule: Option<RpcLeaderSchedule> = None; | |
let mut leader_schedule_epoch = 0; | |
let send_socket = UdpSocket::bind("0.0.0.0:0").unwrap(); | |
let cluster_nodes = rpc_client.get_cluster_nodes().ok(); | |
loop { | |
let mut status_retries = 15; | |
progress_bar.set_message("Finding leader node..."); | |
let epoch_info = rpc_client.get_epoch_info().unwrap(); | |
if epoch_info.epoch > leader_schedule_epoch || leader_schedule.is_none() { | |
leader_schedule = rpc_client.get_leader_schedule(Some(epoch_info.absolute_slot))?; | |
leader_schedule_epoch = epoch_info.epoch; | |
} | |
let tpu_address = get_leader_tpu( | |
std::cmp::min(epoch_info.slot_index + 1, epoch_info.slots_in_epoch), | |
leader_schedule.as_ref(), | |
cluster_nodes.as_ref(), | |
); | |
// Send all transactions | |
let mut pending_transactions = HashMap::new(); | |
let num_transactions = transactions.len(); | |
for transaction in transactions { | |
if let Some(tpu_address) = tpu_address { | |
let wire_transaction = | |
serialize(&transaction).expect("serialization should succeed"); | |
send_transaction_tpu(&send_socket, &tpu_address, &wire_transaction); | |
} else { | |
let _result = rpc_client | |
.send_transaction( | |
&transaction | |
) | |
.ok(); | |
} | |
pending_transactions.insert(transaction.signatures[0], transaction); | |
progress_bar.set_message(&format!( | |
"[{}/{}] Total Transactions sent", | |
pending_transactions.len(), | |
num_transactions | |
)); | |
} | |
// Collect statuses for all the transactions, drop those that are confirmed | |
while status_retries > 0 { | |
status_retries -= 1; | |
progress_bar.set_message(&format!( | |
"[{}/{}] Transactions confirmed", | |
num_transactions - pending_transactions.len(), | |
num_transactions | |
)); | |
let mut statuses = vec![]; | |
let pending_signatures = pending_transactions.keys().cloned().collect::<Vec<_>>(); | |
for pending_signatures_chunk in | |
pending_signatures.chunks(MAX_GET_SIGNATURE_STATUSES_QUERY_ITEMS - 1) | |
{ | |
statuses.extend( | |
rpc_client | |
.get_signature_statuses_with_history(pending_signatures_chunk)? | |
.value | |
.into_iter(), | |
); | |
} | |
assert_eq!(statuses.len(), pending_signatures.len()); | |
for (signature, status) in pending_signatures.into_iter().zip(statuses.into_iter()) { | |
if let Some(status) = status { | |
if status.confirmations.is_none() || status.confirmations.unwrap() > 1 { | |
let _ = pending_transactions.remove(&signature); | |
} | |
} | |
progress_bar.set_message(&format!( | |
"[{}/{}] Transactions confirmed", | |
num_transactions - pending_transactions.len(), | |
num_transactions | |
)); | |
} | |
if pending_transactions.is_empty() { | |
return Ok(()); | |
} | |
std::thread::sleep(Duration::from_millis(500)); | |
} | |
if send_retries == 0 { | |
panic!("transaction failed"); | |
} | |
send_retries -= 1; | |
// Re-sign any failed transactions with a new blockhash and retry | |
let (blockhash, _fee_calculator) = rpc_client | |
.get_recent_blockhash() | |
.unwrap(); | |
transactions = vec![]; | |
for (_, mut transaction) in pending_transactions.into_iter() { | |
transaction.try_sign(signer_keys, blockhash)?; | |
transactions.push(transaction); | |
} | |
} | |
} | |
pub fn build_contract(name: &str) { | |
let mut proc = std::process::Command::new("./cargo-build-bpf") | |
.args(&["--manifest-path", &format!("hacks/{}/Cargo.toml", name)]) | |
.spawn() | |
.unwrap(); | |
let status = proc.wait().unwrap(); | |
if !status.success() { | |
panic!("compile failed: {:?}", status); | |
} | |
} | |
pub fn write_contract_data(rpc_client: &RpcClient, signer: &Keypair, program_id: &Keypair, program_data: Vec<u8>) -> bool { | |
let loader_id = bpf_loader::id(); | |
let minimum_balance = rpc_client.get_minimum_balance_for_rent_exemption(program_data.len()).unwrap(); | |
let signers = [signer, &program_id]; | |
// Check program account to see if partial initialization has occurred | |
let (initial_instructions, _balance_needed) = if let Ok(account) = rpc_client | |
.get_account(&program_id.pubkey()) | |
{ | |
let mut instructions: Vec<Instruction> = vec![]; | |
let mut balance_needed = 0; | |
if account.executable { | |
return true; | |
} | |
if account.owner != loader_id && !system_program::check_id(&account.owner) { | |
panic!("program account is already owned by another account"); | |
} | |
if account.data.is_empty() && system_program::check_id(&account.owner) { | |
instructions.push(system_instruction::allocate( | |
&program_id.pubkey(), | |
program_data.len() as u64, | |
)); | |
if account.owner != loader_id { | |
instructions.push(system_instruction::assign(&program_id.pubkey(), &loader_id)); | |
} | |
} | |
if account.lamports < minimum_balance { | |
let balance = minimum_balance - account.lamports; | |
instructions.push(system_instruction::transfer( | |
&signer.pubkey(), | |
&program_id.pubkey(), | |
balance, | |
)); | |
balance_needed = balance; | |
} | |
(instructions, balance_needed) | |
} else { | |
( | |
vec![system_instruction::create_account( | |
&signer.pubkey(), | |
&program_id.pubkey(), | |
minimum_balance, | |
program_data.len() as u64, | |
&loader_id, | |
)], | |
minimum_balance, | |
) | |
}; | |
let initial_message = if !initial_instructions.is_empty() { | |
Some(Message::new( | |
&initial_instructions, | |
Some(&signer.pubkey()), | |
)) | |
} else { | |
None | |
}; | |
let mut write_messages = vec![]; | |
for (chunk, i) in program_data.chunks(DATA_CHUNK_SIZE).zip(0..) { | |
let instruction = loader_instruction::write( | |
&program_id.pubkey(), | |
&loader_id, | |
(i * DATA_CHUNK_SIZE) as u32, | |
chunk.to_vec(), | |
); | |
let message = Message::new(&[instruction], Some(&signers[0].pubkey())); | |
write_messages.push(message); | |
} | |
let (blockhash, _fee_calculator) = rpc_client | |
.get_recent_blockhash() | |
.unwrap(); | |
if let Some(message) = initial_message { | |
trace!("Creating or modifying program account"); | |
let num_required_signatures = message.header.num_required_signatures; | |
let mut initial_transaction = Transaction::new_unsigned(message); | |
// Most of the initial_transaction combinations require both the fee-payer and new program | |
// account to sign the transaction. One (transfer) only requires the fee-payer signature. | |
// This check is to ensure signing does not fail on a KeypairPubkeyMismatch error from an | |
// extraneous signature. | |
if num_required_signatures == 2 { | |
initial_transaction.sign(&signers, blockhash); | |
} else { | |
initial_transaction.sign(&[signers[0]], blockhash); | |
} | |
let result = rpc_client.send_and_confirm_transaction_with_spinner_and_commitment( | |
&initial_transaction, | |
CommitmentConfig::single(), | |
); | |
if let Err(e) = result { | |
panic!("program account allocation failed: {:?}", e); | |
} | |
} | |
let (blockhash, _) = rpc_client | |
.get_recent_blockhash().unwrap(); | |
let mut write_transactions = vec![]; | |
for message in write_messages.into_iter() { | |
let mut tx = Transaction::new_unsigned(message); | |
tx.sign(&signers, blockhash); | |
write_transactions.push(tx); | |
} | |
trace!("Writing program data"); | |
send_and_confirm_transactions_with_spinner( | |
&rpc_client, | |
write_transactions, | |
&signers, | |
).expect("data writes to program account"); | |
false | |
} | |
pub fn write_buffer_data(rpc_client: &RpcClient, signer: &Keypair, buffer: &Keypair, program_data: Vec<u8>, buf_size: usize) -> bool { | |
let loader_id = bpf_loader_upgradeable::id(); | |
let minimum_balance = rpc_client.get_minimum_balance_for_rent_exemption(buf_size).unwrap(); | |
let signers = [signer, &buffer]; | |
; | |
// check if we need to initialize the buffer | |
let initial_instructions = if rpc_client.get_account(&buffer.pubkey()).is_err() { | |
bpf_loader_upgradeable::create_buffer( | |
&signer.pubkey(), | |
&buffer.pubkey(), | |
Some(&signer.pubkey()), | |
minimum_balance, | |
buf_size, | |
).expect("create buffer instr") | |
} else { | |
Vec::new() | |
}; | |
let initial_message = if !initial_instructions.is_empty() { | |
Some(Message::new( | |
&initial_instructions, | |
Some(&signer.pubkey()), | |
)) | |
} else { | |
None | |
}; | |
let mut write_messages = vec![]; | |
for (chunk, i) in program_data.chunks(DATA_CHUNK_SIZE).zip(0..) { | |
let instruction = bpf_loader_upgradeable::write( | |
&buffer.pubkey(), | |
Some(&signer.pubkey()), | |
(i * DATA_CHUNK_SIZE) as u32, | |
chunk.to_vec(), | |
); | |
let message = Message::new(&[instruction], Some(&signers[0].pubkey())); | |
write_messages.push(message); | |
} | |
let (blockhash, _fee_calculator) = rpc_client | |
.get_recent_blockhash() | |
.unwrap(); | |
if let Some(message) = initial_message { | |
trace!("Creating or modifying program account"); | |
let num_required_signatures = message.header.num_required_signatures; | |
let mut initial_transaction = Transaction::new_unsigned(message); | |
// Most of the initial_transaction combinations require both the fee-payer and new program | |
// account to sign the transaction. One (transfer) only requires the fee-payer signature. | |
// This check is to ensure signing does not fail on a KeypairPubkeyMismatch error from an | |
// extraneous signature. | |
if num_required_signatures == 2 { | |
initial_transaction.sign(&signers, blockhash); | |
} else { | |
initial_transaction.sign(&[signers[0]], blockhash); | |
} | |
let result = rpc_client.send_and_confirm_transaction_with_spinner_and_commitment( | |
&initial_transaction, | |
CommitmentConfig::single(), | |
); | |
if let Err(e) = result { | |
panic!("program account allocation failed: {:?}", e); | |
} | |
} | |
let (blockhash, _) = rpc_client | |
.get_recent_blockhash().unwrap(); | |
let mut write_transactions = vec![]; | |
for message in write_messages.into_iter() { | |
let mut tx = Transaction::new_unsigned(message); | |
tx.sign(&signers, blockhash); | |
write_transactions.push(tx); | |
} | |
trace!("Writing buffer data"); | |
send_and_confirm_transactions_with_spinner( | |
&rpc_client, | |
write_transactions, | |
&signers, | |
).expect("data writes to buffer account"); | |
false | |
} | |
pub fn ensure_deployed<F: FnOnce(&mut [u8])>( | |
rpc_client: &RpcClient, | |
signer: &Keypair, | |
program_location: &str, | |
patch: F | |
) -> Keypair { | |
let mut file = File::open(program_location).unwrap(); | |
let mut program_data = Vec::new(); | |
file.read_to_end(&mut program_data).unwrap(); | |
patch(&mut program_data); | |
let mut hasher = Hasher::default(); | |
hasher.hash(&program_data); | |
let program_id = solana_sdk::signature::keypair_from_seed(&hasher.result().to_bytes()).unwrap(); | |
Executable::<BPFError, ThisInstructionMeter>::from_elf(&program_data, Some(|x| bpf_verifier::check(x, false)), Config::default()) | |
.expect("load elf"); | |
let executable = write_contract_data(&rpc_client, &signer, &program_id, program_data); | |
if executable { | |
return program_id; | |
} | |
let loader_id = bpf_loader::id(); | |
let signers = [signer, &program_id]; | |
let instruction = loader_instruction::finalize(&program_id.pubkey(), &loader_id); | |
let finalize_message = Message::new(&[instruction], Some(&signers[0].pubkey())); | |
let (blockhash, _) = rpc_client | |
.get_recent_blockhash() | |
.unwrap(); | |
let mut finalize_tx = Transaction::new_unsigned(finalize_message); | |
finalize_tx.sign(&signers, blockhash); | |
trace!("Finalizing program account"); | |
println!("{:?}", &finalize_tx); | |
rpc_client | |
.send_and_confirm_transaction_with_spinner_and_config( | |
&finalize_tx, | |
rpc_client.commitment(), | |
RpcSendTransactionConfig { | |
skip_preflight: true, | |
..RpcSendTransactionConfig::default() | |
}, | |
) | |
.expect("finalize program account"); | |
program_id | |
} | |
pub fn ensure_nonce_account(client: &RpcClient, signer: &Keypair) -> (Keypair, Hash) { | |
let id = keypair_from_seed(&[14; 32]).expect("create keypair from seed"); | |
println!("nonce pubkey {:?}", id.pubkey()); | |
if let Ok(account) = client.get_account(&id.pubkey()) { | |
let state = solana_client::nonce_utils::data_from_account(&account).expect("is nonce account"); | |
println!("found existing nonce account: {:?} lamports, owner {:?}, state {:?}", account.lamports, account.owner, state); | |
return (id, state.blockhash); | |
} | |
let instructions = system_instruction::create_nonce_account( | |
&signer.pubkey(), | |
&id.pubkey(), | |
&signer.pubkey(), | |
client.get_minimum_balance_for_rent_exemption(solana_program::nonce::State::size()).expect("get minimum balance") | |
); | |
let message = Message::new(&instructions, Some(&signer.pubkey())); | |
let recent_blockhash = client.get_recent_blockhash().unwrap().0; | |
let transaction = Transaction::new(&[signer, &id], message, recent_blockhash); | |
client.send_and_confirm_transaction_with_spinner(&transaction).expect("create nonce account"); | |
let account = client.get_account(&id.pubkey()).expect("none account created ok"); | |
(id, solana_client::nonce_utils::data_from_account(&account).expect("is nonce account").blockhash) | |
} | |
pub fn wait_until_confirmed(client: &RpcClient, signature: &Signature) { | |
let now = Instant::now(); | |
let progress_bar = new_spinner_progress_bar(); | |
let mut confirmations = 0; | |
let desired_confirmations = MAX_LOCKOUT_HISTORY + 1; | |
loop { | |
progress_bar.set_message(&format!( | |
"[{}/{}] Waiting for transaction {}", | |
confirmations + 1, | |
desired_confirmations, | |
signature, | |
)); | |
if client.get_signature_status(signature).expect("get signature status").is_some() { | |
progress_bar.set_message("Transaction confirmed"); | |
progress_bar.finish_and_clear(); | |
break; | |
} | |
std::thread::sleep(Duration::from_millis(500)); | |
confirmations = client | |
.get_num_blocks_since_signature_confirmation(signature) | |
.unwrap_or(confirmations); | |
if now.elapsed().as_secs() >= MAX_HASH_AGE_IN_SECONDS as u64 { | |
panic!("transaction not finalized. \ | |
This can happen when a transaction lands in an abandoned fork. \ | |
Please retry.".to_string()); | |
} | |
} | |
} | |
pub fn wait_and_print_transaction(client: &RpcClient, signature: &Signature) { | |
wait_until_confirmed(client, signature); | |
let confirmed_transaction = client.get_confirmed_transaction( | |
signature, | |
UiTransactionEncoding::Base64 | |
).expect("get confirmed transaction"); | |
println!( | |
"\nTransaction executed in slot {}:", | |
confirmed_transaction.slot | |
); | |
solana_cli_output::display::println_transaction( | |
&confirmed_transaction | |
.transaction | |
.transaction | |
.decode() | |
.expect("Successful decode"), | |
&confirmed_transaction.transaction.meta, | |
" ", | |
); | |
} | |
pub fn verify_sigs(transaction: &Transaction) -> u8 { | |
let mut packet = Packet::default(); | |
let payload = serialize(transaction).unwrap(); | |
packet.meta.size = payload.len(); | |
packet.data[..payload.len()].copy_from_slice(&payload); | |
solana_perf::sigverify::ed25519_verify_cpu(&[Packets::new(vec![ | |
packet | |
])])[0][0] | |
} | |
pub fn ensure_balance(client: &RpcClient, target: &Keypair, signer: &Keypair, wanted: u64) { | |
let current = client.get_balance(&target.pubkey()).expect("get balance for ensure balance"); | |
let (recent_blockhash, _) = client.get_recent_blockhash().expect("get recent blockhash"); | |
if current > wanted { | |
client.send_and_confirm_transaction_with_spinner(&Transaction::new( | |
&[signer, target], | |
Message::new(&[ | |
system_instruction::transfer(&target.pubkey(), &signer.pubkey(), current - wanted) | |
], Some(&signer.pubkey())), | |
recent_blockhash, | |
)).expect("transfer to ensure balance"); | |
} else if current < wanted { | |
client.send_and_confirm_transaction_with_spinner(&Transaction::new( | |
&[signer, target], | |
Message::new(&[ | |
system_instruction::transfer(&signer.pubkey(), &target.pubkey(), wanted - current) | |
], Some(&signer.pubkey())), | |
recent_blockhash, | |
)).expect("transfer to ensure balance"); | |
} | |
} | |
pub fn setup_client() -> (RpcClient, Keypair, Pubkey) { | |
let url = std::env::args().nth(1).unwrap_or("http://127.0.0.1:8899".to_string()); | |
let client = RpcClient::new(url.to_string()); | |
let signer = keypair_from_seed(&[13; 32]).unwrap(); | |
let (blockhash, _) = client.get_recent_blockhash().unwrap(); | |
let host = solana_net_utils::parse_host(&url::Url::parse(&url).unwrap().host().unwrap().to_string()).unwrap(); | |
let addr = SocketAddr::new(host, 9900); | |
let transaction = request_airdrop_transaction(&addr, &signer.pubkey(), sol_to_lamports(10.0), blockhash).unwrap(); | |
if client.get_balance(&signer.pubkey()).expect("get balance") < 10_000_000 { | |
client.send_and_confirm_transaction_with_spinner(&transaction).unwrap(); | |
} | |
let faucet_pubkey = transaction.signer_key(0, 0).expect("faucet pubkey"); | |
println!("network version: {:?}", client.get_version().expect("get version")); | |
println!("signer: {} balance {}", &signer.pubkey(), client.get_balance(&signer.pubkey()).expect("get balance")); | |
println!("faucet: {} balance {}", &faucet_pubkey, client.get_balance(&faucet_pubkey).expect("get balance")); | |
(client, signer, faucet_pubkey.clone()) | |
} | |
pub fn tx_with_instructions(signers: Vec<&Keypair>, blockhash: Hash, instructions: &[Instruction]) -> Transaction { | |
let message = Message::new(instructions, Some(&signers[0].pubkey())); | |
let num_sigs: usize = message.header.num_required_signatures.into(); | |
let required_sigs = message.account_keys[..num_sigs].into_iter().copied().collect::<HashSet<_>>(); | |
let provided_sigs = signers.iter().map(|x| x.pubkey()).collect::<HashSet<_>>(); | |
for key in required_sigs.difference(&provided_sigs) { | |
println!("missing signature from {}", key.to_string()); | |
} | |
for key in provided_sigs.difference(&required_sigs) { | |
println!("unnecessary signature from {}", key.to_string()); | |
} | |
Transaction::new(&signers, message, blockhash) | |
} | |
pub fn execute_transactions(bank: &Bank, txs: &[Transaction]) -> Vec<ConfirmedTransaction> { | |
let batch = bank.prepare_batch(txs, None); | |
let mut mint_decimals = HashMap::new(); | |
let tx_pre_token_balances = collect_token_balances(&bank, &batch, &mut mint_decimals); | |
let slot = bank.slot(); | |
let mut timings = ExecuteTimings::default(); | |
let (TransactionResults { execution_results, .. }, | |
TransactionBalancesSet { pre_balances, post_balances, .. }, | |
inner_instructions, | |
transaction_logs) | |
= bank.load_execute_and_commit_transactions(&batch, std::usize::MAX, true, true, true, &mut timings); | |
let tx_post_token_balances = collect_token_balances(&bank, &batch, &mut mint_decimals); | |
izip!( | |
txs.iter(), | |
execution_results.into_iter(), | |
inner_instructions.into_iter(), | |
pre_balances.into_iter(), | |
post_balances.into_iter(), | |
tx_pre_token_balances.into_iter(), | |
tx_post_token_balances.into_iter(), | |
transaction_logs.into_iter(), | |
).map(|( | |
tx, | |
(execute_result, nonce_rollback), | |
inner_instructions, | |
pre_balances, | |
post_balances, | |
pre_token_balances, | |
post_token_balances, | |
log_messages, | |
)| { | |
let fee_calculator = nonce_rollback | |
.map(|nonce_rollback| nonce_rollback.fee_calculator()) | |
.unwrap_or_else(|| { | |
bank.get_fee_calculator(&tx.message().recent_blockhash) | |
}) | |
.expect("FeeCalculator must exist"); | |
let fee = fee_calculator.calculate_fee(tx.message()); | |
let inner_instructions = inner_instructions.map(|inner_instructions| { | |
inner_instructions | |
.into_iter() | |
.enumerate() | |
.map(|(index, instructions)| InnerInstructions { | |
index: index as u8, | |
instructions | |
}) | |
.filter(|i| !i.instructions.is_empty()) | |
.collect() | |
}); | |
let tx_status_meta = TransactionStatusMeta { | |
status: execute_result, | |
fee, | |
pre_balances, | |
post_balances, | |
pre_token_balances: Some(pre_token_balances), | |
post_token_balances: Some(post_token_balances), | |
inner_instructions, | |
log_messages: Some(log_messages), | |
}; | |
ConfirmedTransaction { | |
slot, | |
transaction: TransactionWithStatusMeta { | |
transaction: tx.clone(), | |
meta: Some(tx_status_meta) | |
} | |
} | |
}).collect() | |
} | |
pub fn execute_as_transaction(bank: &Bank, signers: Vec<&Keypair>, instructions: &[Instruction]) -> ConfirmedTransaction { | |
let tx = tx_with_instructions(signers, bank.last_blockhash(), instructions); | |
return execute_transactions(bank, &[tx]).into_iter().next().expect("must have tx result"); | |
} | |
pub fn print_confirmed_tx(name: &str, confirmed_tx: ConfirmedTransaction) { | |
let tx = confirmed_tx.transaction.transaction.clone(); | |
let encoded = confirmed_tx.encode(UiTransactionEncoding::JsonParsed); | |
println!("EXECUTE {} (slot {})", name, encoded.slot); | |
println_transaction(&tx, &encoded.transaction.meta, " "); | |
} | |
pub fn make_genesis(faucet: &Pubkey, extra_programs: &[(Pubkey, &str)]) -> GenesisConfig { | |
let mut config = GenesisConfig::new( | |
&[ | |
(faucet.clone(), Account::new(1u64 << 48, 0, &system_program::id()),) | |
], | |
&[], | |
); | |
// add spl programs | |
let mut programs = vec![ | |
("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA".parse().unwrap(), "BPFLoader2111111111111111111111111111111111".parse().unwrap(), "spl_token-2.0.6.so".to_string()), | |
("Memo1UhkJRfHyvLMcVucJwxXeuD728EqVDDwQDxFMNo".parse().unwrap(), "BPFLoader1111111111111111111111111111111111".parse().unwrap(), "spl_memo-1.0.0.so".to_string()), | |
("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL".parse().unwrap(), "BPFLoader2111111111111111111111111111111111".parse().unwrap(), "spl_associated-token-account-1.0.1.so".to_string()), | |
("Feat1YXHhH6t1juaWF74WLcfv4XoNocjXA6sPWHNgAse".parse().unwrap(), "BPFLoader2111111111111111111111111111111111".parse().unwrap(), "spl_feature-proposal-1.0.0.so".to_string()), | |
]; | |
for (pk, program) in extra_programs { | |
build_contract(*program); | |
programs.push((*pk, "BPFLoader2111111111111111111111111111111111".parse().unwrap(), format!("target/deploy/{}.so", program).to_string())); | |
} | |
for (id, loader, file_name) in programs.iter() { | |
let mut program_data = Vec::new(); | |
File::open(file_name) | |
.and_then(|mut f| f.read_to_end(&mut program_data)) | |
.unwrap_or_else(|e| { | |
panic!("cannot read genesis program code file {}: {}", file_name, e); | |
}); | |
config.add_account(*id, Account { | |
lamports: config.rent.minimum_balance(program_data.len()), | |
data: program_data, | |
executable: true, | |
owner: *loader, | |
rent_epoch: 0, | |
}); | |
} | |
config | |
} | |
pub fn setup_bank(genesis: &GenesisConfig, dir: &Path) -> Bank { | |
Bank::new_with_paths( | |
genesis, | |
vec![dir.to_path_buf()], | |
&[], | |
None, | |
Some(&Builtins { | |
genesis_builtins: [solana_bpf_loader_upgradeable_program!(), solana_bpf_loader_program!(), solana_bpf_loader_deprecated_program!()].iter().map(|p| { | |
Builtin::new(&p.0, p.1, p.2) | |
}).collect(), | |
feature_builtins: vec![], | |
}), | |
HashSet::new(), | |
false, | |
) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment