use near_crypto::{InMemorySigner, KeyType}; use near_primitives::account::{AccessKey, Account}; use near_primitives::hash::{hash, CryptoHash}; use near_primitives::receipt::Receipt; use near_primitives::state_record::StateRecord; use near_primitives::test_utils::MockEpochInfoProvider; use near_primitives::transaction::{ExecutionOutcomeWithId, SignedTransaction}; use near_primitives::types::{AccountId, Balance}; use near_primitives::version::PROTOCOL_VERSION; use near_store::test_utils::create_tries; use near_store::ShardTries; use node_runtime::{ApplyState, Runtime}; use random_config::random_config; use std::collections::HashMap; use std::sync::{Arc, Condvar, Mutex}; use std::thread; use std::thread::JoinHandle; pub mod random_config; /// Initial balance used in tests. pub const TESTING_INIT_BALANCE: Balance = 1_000_000_000 * NEAR_BASE; /// Validator's stake used in tests. pub const TESTING_INIT_STAKE: Balance = 50_000_000 * NEAR_BASE; /// One NEAR, divisible by 10^24. pub const NEAR_BASE: Balance = 1_000_000_000_000_000_000_000_000; pub struct StandaloneRuntime { pub apply_state: ApplyState, pub runtime: Runtime, pub tries: ShardTries, pub signer: InMemorySigner, pub root: CryptoHash, pub epoch_info_provider: MockEpochInfoProvider, } impl StandaloneRuntime { pub fn account_id(&self) -> String { self.signer.account_id.clone() } pub fn new(signer: InMemorySigner, state_records: &[StateRecord], tries: ShardTries) -> Self { let mut runtime_config = random_config(); // Bumping costs to avoid inflation overflows. runtime_config.wasm_config.limit_config.max_total_prepaid_gas = 10u64.pow(15); runtime_config.transaction_costs.action_receipt_creation_config.execution = runtime_config.wasm_config.limit_config.max_total_prepaid_gas / 64; runtime_config.transaction_costs.data_receipt_creation_config.base_cost.execution = runtime_config.wasm_config.limit_config.max_total_prepaid_gas / 64; let runtime = Runtime::new(); let (store_update, root) = runtime.apply_genesis_state(tries.clone(), 0, &[], state_records, &runtime_config); store_update.commit().unwrap(); let apply_state = ApplyState { block_index: 0, prev_block_hash: Default::default(), block_hash: Default::default(), epoch_id: Default::default(), epoch_height: 0, gas_price: 100, block_timestamp: 0, gas_limit: None, random_seed: Default::default(), current_protocol_version: PROTOCOL_VERSION, config: Arc::new(runtime_config), cache: None, #[cfg(feature = "protocol_feature_evm")] evm_chain_id: near_chain_configs::TESTNET_EVM_CHAIN_ID, profile: Default::default(), }; Self { apply_state, runtime, tries, signer, root, epoch_info_provider: MockEpochInfoProvider::default(), } } pub fn process_block( &mut self, receipts: &[Receipt], transactions: &[SignedTransaction], ) -> (Vec, Vec) { let apply_result = self .runtime .apply( self.tries.get_trie_for_shard(0), self.root, &None, &self.apply_state, receipts, transactions, &self.epoch_info_provider, ) .unwrap(); let (store_update, root) = self.tries.apply_all(&apply_result.trie_changes, 0).unwrap(); self.root = root; store_update.commit().unwrap(); self.apply_state.block_index += 1; (apply_result.outgoing_receipts, apply_result.outcomes) } } #[derive(Default)] pub struct RuntimeMailbox { pub incoming_transactions: Vec, pub incoming_receipts: Vec, } impl RuntimeMailbox { pub fn is_emtpy(&self) -> bool { self.incoming_receipts.is_empty() && self.incoming_transactions.is_empty() } } #[derive(Default)] pub struct RuntimeGroup { pub mailboxes: (Mutex>, Condvar), pub state_records: Arc>, pub signers: Vec, /// Account id of the runtime on which the transaction was executed mapped to the transactions. pub executed_transactions: Mutex>>, /// Account id of the runtime on which the receipt was executed mapped to the list of the receipts. pub executed_receipts: Mutex>>, /// List of the transaction logs. pub transaction_logs: Mutex>, } impl RuntimeGroup { pub fn new_with_account_ids( account_ids: Vec, num_existing_accounts: u64, contract_code: &[u8], ) -> Arc { let mut res = Self::default(); assert!(num_existing_accounts <= account_ids.len() as u64); let (state_records, signers) = Self::state_records_signers(account_ids, num_existing_accounts, contract_code); Arc::make_mut(&mut res.state_records).extend(state_records); for signer in signers { res.signers.push(signer.clone()); res.mailboxes.0.lock().unwrap().insert(signer.account_id.clone(), Default::default()); } Arc::new(res) } pub fn new(num_runtimes: u64, num_existing_accounts: u64, contract_code: &[u8]) -> Arc { let account_ids = (0..num_runtimes).map(|i| format!("near_{}", i)).collect(); Self::new_with_account_ids(account_ids, num_existing_accounts, contract_code) } /// Get state records and signers for standalone runtimes. fn state_records_signers( account_ids: Vec, num_existing_accounts: u64, contract_code: &[u8], ) -> (Vec, Vec) { let code_hash = hash(contract_code); let mut state_records = vec![]; let mut signers = vec![]; for (i, account_id) in account_ids.into_iter().enumerate() { let signer = InMemorySigner::from_seed(&account_id, KeyType::ED25519, &account_id); if (i as u64) < num_existing_accounts { state_records.push(StateRecord::Account { account_id: account_id.to_string(), account: Account { amount: TESTING_INIT_BALANCE, locked: TESTING_INIT_STAKE, code_hash, storage_usage: 0, }, }); state_records.push(StateRecord::AccessKey { account_id: account_id.to_string(), public_key: signer.public_key.clone(), access_key: AccessKey::full_access().into(), }); state_records .push(StateRecord::Contract { account_id, code: contract_code.to_vec() }); } signers.push(signer); } (state_records, signers) } pub fn start_runtimes( group: Arc, transactions: Vec, ) -> Vec> { for transaction in transactions { group .mailboxes .0 .lock() .unwrap() .get_mut(&transaction.transaction.signer_id) .unwrap() .incoming_transactions .push(transaction); } let mut handles = vec![]; for signer in &group.signers { let signer = signer.clone(); let state_records = Arc::clone(&group.state_records); let runtime_factory = move || StandaloneRuntime::new(signer, &state_records, create_tries()); handles.push(Self::start_runtime_in_thread(group.clone(), runtime_factory)); } handles } fn start_runtime_in_thread(group: Arc, runtime_factory: F) -> JoinHandle<()> where F: FnOnce() -> StandaloneRuntime + Send + 'static, { thread::spawn(move || { let mut runtime = runtime_factory(); loop { let account_id = runtime.account_id(); let mut mailboxes = group.mailboxes.0.lock().unwrap(); loop { if !mailboxes.get(&account_id).unwrap().is_emtpy() { break; } if mailboxes.values().all(|m| m.is_emtpy()) { return; } mailboxes = group.mailboxes.1.wait(mailboxes).unwrap(); } let mailbox = mailboxes.get_mut(&account_id).unwrap(); group .executed_receipts .lock() .unwrap() .entry(account_id.clone()) .or_insert_with(Vec::new) .extend(mailbox.incoming_receipts.clone()); group .executed_transactions .lock() .unwrap() .entry(account_id.clone()) .or_insert_with(Vec::new) .extend(mailbox.incoming_transactions.clone()); let (new_receipts, transaction_results) = runtime .process_block(&mailbox.incoming_receipts, &mailbox.incoming_transactions); mailbox.incoming_receipts.clear(); mailbox.incoming_transactions.clear(); group.transaction_logs.lock().unwrap().extend(transaction_results); for new_receipt in new_receipts { let locked_other_mailbox = mailboxes.get_mut(&new_receipt.receiver_id).unwrap(); locked_other_mailbox.incoming_receipts.push(new_receipt); } group.mailboxes.1.notify_all(); } }) } /// Get receipt that was executed by the given runtime based on hash. pub fn get_receipt(&self, executing_runtime: &str, hash: &CryptoHash) -> Receipt { self.executed_receipts .lock() .unwrap() .get(executing_runtime) .expect("Runtime not found") .iter() .find_map(|r| if &r.get_hash() == hash { Some(r.clone()) } else { None }) .expect("Runtime does not contain the receipt with the given hash.") } /// Get transaction log produced by the execution of given transaction/receipt /// identified by `producer_hash`. pub fn get_transaction_log(&self, producer_hash: &CryptoHash) -> ExecutionOutcomeWithId { self.transaction_logs .lock() .unwrap() .iter() .find_map(|tl| if &tl.id == producer_hash { Some(tl.clone()) } else { None }) .expect("The execution log of the given receipt is missing") } pub fn get_receipt_debug(&self, hash: &CryptoHash) -> (String, Receipt) { for (executed_runtime, tls) in self.executed_receipts.lock().unwrap().iter() { if let Some(res) = tls.iter().find_map(|r| if &r.get_hash() == hash { Some(r.clone()) } else { None }) { return (executed_runtime.clone(), res); } } unimplemented!() } } /// Binds a tuple to a vector. /// # Examples: /// /// ``` /// let v = vec![1,2,3]; /// tuplet!((a,b,c) = v); /// assert_eq!(a, &1); /// assert_eq!(b, &2); /// assert_eq!(c, &3); /// ``` #[macro_export] macro_rules! tuplet { {() = $v:expr, $message:expr } => { assert!($v.is_empty(), "{}", $message); }; {($y:ident) = $v:expr, $message:expr } => { assert_eq!($v.len(), 1, "{}", $message); let $y = &$v[0]; }; { ($y:ident $(, $x:ident)*) = $v:expr, $message:expr } => { let ($y, $($x),*) = tuplet!($v ; 1 ; ($($x),*) ; (&$v[0]), $message ); }; { $v:expr ; $j:expr ; ($y:ident $(, $x:ident)*) ; ($($a:expr),*), $message:expr } => { tuplet!( $v ; $j+1 ; ($($x),*) ; ($($a),*,&$v[$j]), $message ) }; { $v:expr ; $j:expr ; () ; $accu:expr, $message:expr } => { { assert_eq!($v.len(), $j, "{}", $message); $accu } } } #[macro_export] macro_rules! assert_receipts { ($group:ident, $transaction:ident => [ $($receipt:ident),* ] ) => { let transaction_log = $group.get_transaction_log(&$transaction.get_hash()); tuplet!(( $($receipt),* ) = transaction_log.outcome.receipt_ids, "Incorrect number of produced receipts for transaction"); }; ($group:ident, $from:expr => $receipt:ident @ $to:expr, $receipt_pat:pat, $receipt_assert:block, $actions_name:ident, $($action_name:ident, $action_pat:pat, $action_assert:block ),+ => [ $($produced_receipt:ident),*] ) => { let r = $group.get_receipt($to, $receipt); assert_eq!(r.predecessor_id, $from.to_string()); assert_eq!(r.receiver_id, $to.to_string()); match &r.receipt { $receipt_pat => { $receipt_assert tuplet!(( $($action_name),* ) = $actions_name, "Incorrect number of actions"); $( match $action_name { $action_pat => { $action_assert } _ => panic!("Action {:#?} does not satisfy the pattern {}", $action_name, stringify!($action_pat)), } )* } _ => panic!("Receipt {:#?} does not satisfy the pattern {}", r, stringify!($receipt_pat)), } let receipt_log = $group.get_transaction_log(&r.get_hash()); tuplet!(( $($produced_receipt),* ) = receipt_log.outcome.receipt_ids, "Incorrect number of produced receipts for a receipt"); }; } /// A short form for refunds. /// ``` /// assert_refund!(group, ref1 @ "near_0"); /// ``` /// expands into: /// ``` /// assert_receipts!(group, "system" => ref1 @ "near_0", /// ReceiptEnum::Action(ActionReceipt{actions, ..}), {}, /// actions, /// a0, Action::Transfer(TransferAction{..}), {} /// => []); /// ``` #[macro_export] macro_rules! assert_refund { ($group:ident, $receipt:ident @ $to:expr) => { assert_receipts!($group, "system" => $receipt @ $to, ReceiptEnum::Action(ActionReceipt{actions, ..}), {}, actions, a0, Action::Transfer(TransferAction{..}), {} => []); } }