use crate::primitives::{ FetchLatestFinalBlockInfo, IsSyncing, QueryViewFunction, SubmitSignedTransaction, }; use crate::types::{LatestFinalBlockInfo, ObservedState}; use near_account_id::AccountId; use near_indexer::near_primitives::transaction::SignedTransaction; use std::sync::{Arc, Mutex}; use std::time::Duration; use thiserror::Error; use tokio::sync::Notify; #[derive(Clone)] pub struct MockChainState { sync_response: Arc>>, query_view_function_submitter_state: Arc>, latest_final_block: Arc>>, signed_transaction_submitter_state: Arc>, read_notify: Arc, } pub struct MockQueryViewFunctionState { pub response: Result, pub submitted: Vec, } #[derive(Debug, Clone, PartialEq, Eq)] pub struct Call { pub contract_id: AccountId, pub method_name: String, pub args: Vec, } pub struct MockSignedTransactionSubmitterState { pub response: Result<(), MockError>, pub submitted: Vec, } impl MockChainState { pub fn builder() -> MockChainStateBuilder { MockChainStateBuilder::new() } pub fn set_sync_response(&self, value: Result) { *self.sync_response.lock().unwrap() = value; } /// Update the view function query response. pub async fn set_view_response(&self, value: Result) { let mut inner = self.query_view_function_submitter_state.lock().unwrap(); inner.response = value; } /// Wait for the next query_view_function call (polls submitted.len() every 10ms). pub async fn await_next_view_call(&self, max_wait_duration: Duration) -> Result<(), MockError> { tokio::time::timeout(max_wait_duration, self.read_notify.notified()) .await .map_err(|_| MockError::Timeout) } /// Returns a snapshot of all recorded view function calls. pub async fn view_calls(&self) -> Vec { let inner = self.query_view_function_submitter_state.lock().unwrap(); inner.submitted.clone() } /// Returns a snapshot of all recorded signed transactions. pub async fn signed_transactions(&self) -> Vec { let inner = self.signed_transaction_submitter_state.lock().unwrap(); inner.submitted.clone() } } pub struct MockChainStateBuilder { sync_response: Result, query_view_function_response: Result, latest_final_block: Result, signed_transaction_submitter_response: Result<(), MockError>, } impl Default for MockChainStateBuilder { fn default() -> Self { Self::new() } } impl MockChainStateBuilder { pub fn new() -> Self { Self { sync_response: Err(MockError::NotInitialized), query_view_function_response: Err(MockError::NotInitialized), latest_final_block: Err(MockError::NotInitialized), signed_transaction_submitter_response: Err(MockError::NotInitialized), } } pub fn with_syncing_status(mut self, s: Result) -> Self { self.sync_response = s; self } pub fn with_latest_block(mut self, b: Result) -> Self { self.latest_final_block = b; self } pub fn with_signed_transaction_submitter_response(mut self, r: Result<(), MockError>) -> Self { self.signed_transaction_submitter_response = r; self } pub fn with_query_view_function_response( mut self, r: Result, ) -> Self { self.query_view_function_response = r; self } pub fn build(self) -> MockChainState { MockChainState { sync_response: Arc::new(Mutex::new(self.sync_response)), query_view_function_submitter_state: Arc::new(Mutex::new(MockQueryViewFunctionState { response: self.query_view_function_response, submitted: Vec::new(), })), read_notify: Arc::new(Notify::new()), latest_final_block: Arc::new(Mutex::new(self.latest_final_block)), signed_transaction_submitter_state: Arc::new(Mutex::new( MockSignedTransactionSubmitterState { response: self.signed_transaction_submitter_response, submitted: Vec::new(), }, )), } } } impl IsSyncing for MockChainState { type Error = MockError; async fn is_syncing(&self) -> Result { self.sync_response.lock().unwrap().clone() } } impl QueryViewFunction for MockChainState { type Error = MockError; async fn query_view_function( &self, contract_id: &AccountId, method_name: &str, args: &[u8], ) -> Result { let mut inner = self.query_view_function_submitter_state.lock().unwrap(); inner.submitted.push(Call { contract_id: contract_id.clone(), method_name: method_name.to_string(), args: args.to_vec(), }); let response = inner.response.clone(); drop(inner); self.read_notify.notify_waiters(); response } } impl FetchLatestFinalBlockInfo for MockChainState { type Error = MockError; async fn fetch_latest_final_block_info(&self) -> Result { self.latest_final_block.lock().unwrap().clone() } } impl SubmitSignedTransaction for MockChainState { type Error = MockError; async fn submit_signed_transaction( &self, transaction: SignedTransaction, ) -> Result<(), Self::Error> { let mut inner = self.signed_transaction_submitter_state.lock().unwrap(); inner.submitted.push(transaction); inner.response.clone() } } #[derive(Debug, Error, Clone, PartialEq, Eq)] pub enum MockError { #[error("failed to sync")] SyncError, #[error("failed to fetch latest final block")] LatestFinalBlockError, #[error("mock field not initialized")] NotInitialized, #[error("mock view client error")] ViewClientError, #[error("timed out")] Timeout, #[error("rpc error")] RpcError, }