diff --git a/Cargo.lock b/Cargo.lock index f901c20e140..b5ea5f6212d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3050,7 +3050,9 @@ dependencies = [ "failure", "failure_derive", "log", + "near-crypto", "near-primitives", + "thiserror", ] [[package]] @@ -3115,6 +3117,7 @@ dependencies = [ "strum", "sysinfo", "testlib", + "thiserror", ] [[package]] @@ -3126,6 +3129,7 @@ dependencies = [ "near-chain-configs", "near-chain-primitives", "near-chunks", + "near-crypto", "near-network", "near-primitives", "serde", @@ -3288,11 +3292,14 @@ dependencies = [ "lazy_static", "near-chain-configs", "near-client-primitives", + "near-crypto", "near-metrics", "near-primitives", + "near-primitives-core", "serde", "serde_json", "thiserror", + "tracing", "uuid", ] @@ -3463,6 +3470,7 @@ dependencies = [ "lazy_static", "near-chain-configs", "near-client", + "near-client-primitives", "near-crypto", "near-network", "near-primitives", @@ -3699,6 +3707,7 @@ dependencies = [ "serde_json", "tempfile", "testlib", + "thiserror", "tracing", "tracing-subscriber", ] @@ -3749,6 +3758,7 @@ dependencies = [ "serde_json", "tempfile", "testlib", + "thiserror", ] [[package]] @@ -5524,6 +5534,7 @@ dependencies = [ "near-evm-runner", "near-jsonrpc", "near-jsonrpc-client", + "near-jsonrpc-primitives", "near-logger-utils", "near-network", "near-primitives", diff --git a/chain/chain-primitives/Cargo.toml b/chain/chain-primitives/Cargo.toml index d35b373bd90..0b1c0d4fb77 100644 --- a/chain/chain-primitives/Cargo.toml +++ b/chain/chain-primitives/Cargo.toml @@ -11,5 +11,7 @@ chrono = { version = "0.4.4", features = ["serde"] } failure = "0.1" failure_derive = "0.1" log = "0.4" +thiserror = "1.0" near-primitives = { path = "../../core/primitives" } +near-crypto = { path = "../../core/crypto" } diff --git a/chain/chain-primitives/src/error.rs b/chain/chain-primitives/src/error.rs index 5f608454878..98766580675 100644 --- a/chain/chain-primitives/src/error.rs +++ b/chain/chain-primitives/src/error.rs @@ -12,6 +12,48 @@ use near_primitives::serialize::to_base; use near_primitives::sharding::{ChunkHash, ShardChunkHeader}; use near_primitives::types::{BlockHeight, EpochId, ShardId}; +#[derive(thiserror::Error, Debug)] +pub enum QueryError { + #[error("Account ID #{requested_account_id} is invalid")] + InvalidAccount { + requested_account_id: near_primitives::types::AccountId, + block_height: near_primitives::types::BlockHeight, + block_hash: near_primitives::hash::CryptoHash, + }, + #[error("Account #{requested_account_id} does not exist while viewing")] + UnknownAccount { + requested_account_id: near_primitives::types::AccountId, + block_height: near_primitives::types::BlockHeight, + block_hash: near_primitives::hash::CryptoHash, + }, + #[error( + "Contract code for contract ID #{contract_account_id} has never been observed on the node" + )] + NoContractCode { + contract_account_id: near_primitives::types::AccountId, + block_height: near_primitives::types::BlockHeight, + block_hash: near_primitives::hash::CryptoHash, + }, + #[error("Access key for public key #{public_key} does not exist while viewing")] + UnknownAccessKey { + public_key: near_crypto::PublicKey, + block_height: near_primitives::types::BlockHeight, + block_hash: near_primitives::hash::CryptoHash, + }, + #[error("Internal error occurred: #{error_message}")] + InternalError { + error_message: String, + block_height: near_primitives::types::BlockHeight, + block_hash: near_primitives::hash::CryptoHash, + }, + #[error("Function call returned an error: #{error_message}")] + ContractExecutionError { + error_message: String, + block_height: near_primitives::types::BlockHeight, + block_hash: near_primitives::hash::CryptoHash, + }, +} + #[derive(Debug)] pub struct Error { inner: Context, diff --git a/chain/chain/src/lib.rs b/chain/chain/src/lib.rs index e03c1a51918..96e8d57a3e1 100644 --- a/chain/chain/src/lib.rs +++ b/chain/chain/src/lib.rs @@ -4,7 +4,7 @@ extern crate lazy_static; pub use chain::{collect_receipts, Chain, MAX_ORPHAN_SIZE}; pub use doomslug::{Doomslug, DoomslugBlockProductionReadiness, DoomslugThresholdMode}; pub use lightclient::{create_light_client_block_view, get_epoch_block_producers_view}; -pub use near_chain_primitives::{Error, ErrorKind}; +pub use near_chain_primitives::{self, Error, ErrorKind}; pub use store::{ChainStore, ChainStoreAccess, ChainStoreUpdate}; pub use store_validator::{ErrorMessage, StoreValidator}; pub use types::{ diff --git a/chain/chain/src/test_utils.rs b/chain/chain/src/test_utils.rs index 51916d5dd19..fc9575fb0ad 100644 --- a/chain/chain/src/test_utils.rs +++ b/chain/chain/src/test_utils.rs @@ -787,7 +787,7 @@ impl RuntimeAdapter for KeyValueRuntime { block_hash: &CryptoHash, _epoch_id: &EpochId, request: &QueryRequest, - ) -> Result> { + ) -> Result { match request { QueryRequest::ViewAccount { account_id, .. } => Ok(QueryResponse { kind: QueryResponseKind::ViewAccount( diff --git a/chain/chain/src/types.rs b/chain/chain/src/types.rs index 2d3d9ccd1ed..e8315084bb1 100644 --- a/chain/chain/src/types.rs +++ b/chain/chain/src/types.rs @@ -575,7 +575,7 @@ pub trait RuntimeAdapter: Send + Sync { block_hash: &CryptoHash, epoch_id: &EpochId, request: &QueryRequest, - ) -> Result>; + ) -> Result; fn get_validator_info( &self, diff --git a/chain/client-primitives/Cargo.toml b/chain/client-primitives/Cargo.toml index 0940a7138e1..ac30ebde123 100644 --- a/chain/client-primitives/Cargo.toml +++ b/chain/client-primitives/Cargo.toml @@ -17,6 +17,7 @@ near-chain-primitives = { path = "../chain-primitives" } near-chain-configs = { path = "../../core/chain-configs" } near-chunks = { path = "../chunks" } +near-crypto = { path = "../../core/crypto" } near-network = { path = "../network" } near-primitives = { path = "../../core/primitives" } diff --git a/chain/client-primitives/src/types.rs b/chain/client-primitives/src/types.rs index b837a1c9831..bbe9b4f755b 100644 --- a/chain/client-primitives/src/types.rs +++ b/chain/client-primitives/src/types.rs @@ -267,7 +267,68 @@ impl Query { } impl Message for Query { - type Result = Result, String>; + type Result = Result; +} + +#[derive(thiserror::Error, Debug)] +pub enum QueryError { + #[error("Internal error: {error_message}")] + InternalError { error_message: String }, + #[error("There are no fully synchronized blocks on the node yet")] + NoSyncedBlocks, + #[error("The node does not track the shard ID {requested_shard_id}")] + UnavailableShard { requested_shard_id: near_primitives::types::ShardId }, + #[error("Account ID {requested_account_id} is invalid")] + InvalidAccount { + requested_account_id: near_primitives::types::AccountId, + block_height: near_primitives::types::BlockHeight, + block_hash: near_primitives::hash::CryptoHash, + }, + #[error( + "Account {requested_account_id} does not exist while viewing at block #{block_height}" + )] + UnknownAccount { + requested_account_id: near_primitives::types::AccountId, + block_height: near_primitives::types::BlockHeight, + block_hash: near_primitives::hash::CryptoHash, + }, + #[error( + "Contract code for contract ID {contract_account_id} has never been observed on the node at block #{block_height}" + )] + NoContractCode { + contract_account_id: near_primitives::types::AccountId, + block_height: near_primitives::types::BlockHeight, + block_hash: near_primitives::hash::CryptoHash, + }, + #[error("Access key for public key {public_key} has never been observed on the node at block #{block_height}")] + UnknownAccessKey { + public_key: near_crypto::PublicKey, + block_height: near_primitives::types::BlockHeight, + block_hash: near_primitives::hash::CryptoHash, + }, + #[error("Function call returned an error: {vm_error}")] + ContractExecutionError { + vm_error: String, + block_height: near_primitives::types::BlockHeight, + block_hash: near_primitives::hash::CryptoHash, + }, + // NOTE: Currently, the underlying errors are too broad, and while we tried to handle + // expected cases, we cannot statically guarantee that no other errors will be returned + // in the future. + // TODO #3851: Remove this variant once we can exhaustively match all the underlying errors + #[error("It is a bug if you receive this error type, please, report this incident: https://github.com/near/nearcore/issues/new/choose. Details: {error_message}")] + Unreachable { error_message: String }, +} + +impl From for QueryError { + fn from(error: near_chain_primitives::Error) -> Self { + match error.kind() { + near_chain_primitives::ErrorKind::IOErr(error_message) => { + Self::InternalError { error_message } + } + _ => Self::Unreachable { error_message: error.to_string() }, + } + } } pub struct Status { diff --git a/chain/client/Cargo.toml b/chain/client/Cargo.toml index 19c3238299e..e3b7b8c7cc1 100644 --- a/chain/client/Cargo.toml +++ b/chain/client/Cargo.toml @@ -23,6 +23,7 @@ borsh = "0.8.1" reed-solomon-erasure = "4" num-rational = "0.3" linked-hash-map = "0.5.3" +thiserror = "1.0" near-crypto = { path = "../../core/crypto" } near-primitives = { path = "../../core/primitives" } diff --git a/chain/client/src/lib.rs b/chain/client/src/lib.rs index 456866ecbc6..cccca5b7b45 100644 --- a/chain/client/src/lib.rs +++ b/chain/client/src/lib.rs @@ -6,7 +6,7 @@ pub use near_client_primitives::types::{ GetExecutionOutcome, GetExecutionOutcomeResponse, GetExecutionOutcomesForBlock, GetGasPrice, GetNetworkInfo, GetNextLightClientBlock, GetProtocolConfig, GetReceipt, GetStateChanges, GetStateChangesInBlock, GetStateChangesWithCauseInBlock, GetValidatorInfo, GetValidatorOrdered, - Query, Status, StatusResponse, SyncStatus, TxStatus, TxStatusError, + Query, QueryError, Status, StatusResponse, SyncStatus, TxStatus, TxStatusError, }; pub use crate::client::Client; diff --git a/chain/client/src/view_client.rs b/chain/client/src/view_client.rs index 0ddd83f9783..6c30fd06b98 100644 --- a/chain/client/src/view_client.rs +++ b/chain/client/src/view_client.rs @@ -22,7 +22,8 @@ use near_client_primitives::types::{ Error, GetBlock, GetBlockError, GetBlockProof, GetBlockProofResponse, GetBlockWithMerkleTree, GetChunkError, GetExecutionOutcome, GetExecutionOutcomesForBlock, GetGasPrice, GetProtocolConfig, GetProtocolConfigError, GetReceipt, GetReceiptError, - GetStateChangesWithCauseInBlock, GetValidatorInfoError, Query, TxStatus, TxStatusError, + GetStateChangesWithCauseInBlock, GetValidatorInfoError, Query, QueryError, TxStatus, + TxStatusError, }; #[cfg(feature = "adversarial")] use near_network::types::NetworkAdversarialMessage; @@ -193,15 +194,7 @@ impl ViewClientActor { } } - fn handle_query(&mut self, msg: Query) -> Result, String> { - { - let mut request_manager = self.request_manager.write().expect(POISONED_LOCK_ERR); - if let Some(response) = request_manager.query_responses.cache_remove(&msg.query_id) { - request_manager.query_requests.cache_remove(&msg.query_id); - return response.map(Some); - } - } - + fn handle_query(&mut self, msg: Query) -> Result { let header = match msg.block_reference { BlockReference::BlockId(BlockId::Height(block_height)) => { self.chain.get_header_by_height(block_height) @@ -210,22 +203,20 @@ impl ViewClientActor { self.chain.get_block_header(&block_hash) } BlockReference::Finality(ref finality) => { - let block_hash = - self.get_block_hash_by_finality(&finality).map_err(|e| e.to_string())?; + let block_hash = self.get_block_hash_by_finality(&finality)?; self.chain.get_block_header(&block_hash) } BlockReference::SyncCheckpoint(ref synchronization_checkpoint) => { - if let Some(block_hash) = self - .get_block_hash_by_sync_checkpoint(&synchronization_checkpoint) - .map_err(|e| e.to_string())? + if let Some(block_hash) = + self.get_block_hash_by_sync_checkpoint(&synchronization_checkpoint)? { self.chain.get_block_header(&block_hash) } else { - return Err("There are no fully synchronized blocks yet".into()); + return Err(QueryError::NoSyncedBlocks); } } }; - let header = header.map_err(|e| e.to_string())?.clone(); + let header = header?.clone(); let account_id = match &msg.request { QueryRequest::ViewAccount { account_id, .. } => account_id, @@ -237,49 +228,54 @@ impl ViewClientActor { }; let shard_id = self.runtime_adapter.account_id_to_shard_id(account_id); - // If we have state for the shard that we query return query result directly. - // Otherwise route query to peers. - match self.chain.get_chunk_extra(header.hash(), shard_id) { - Ok(chunk_extra) => { - let state_root = chunk_extra.state_root; - self.runtime_adapter - .query( - shard_id, - &state_root, - header.height(), - header.raw_timestamp(), - header.prev_hash(), - header.hash(), - header.epoch_id(), - &msg.request, - ) - .map(Some) - .map_err(|e| e.to_string()) - } - Err(e) => { - match e.kind() { - ErrorKind::DBNotFoundErr(_) => {} - _ => { - warn!(target: "client", "Getting chunk extra failed: {}", e.to_string()); - } - } - // route request - let mut request_manager = self.request_manager.write().expect(POISONED_LOCK_ERR); - if Self::need_request(msg.query_id.clone(), &mut request_manager.query_requests) { - let validator = self - .chain - .find_validator_for_forwarding(shard_id) - .map_err(|e| e.to_string())?; - self.network_adapter.do_send(NetworkRequests::Query { - query_id: msg.query_id, - account_id: validator, - block_reference: msg.block_reference, - request: msg.request, - }); - } - - Ok(None) - } + let chunk_extra = self.chain.get_chunk_extra(header.hash(), shard_id)?; + let state_root = chunk_extra.state_root; + match self.runtime_adapter.query( + shard_id, + &state_root, + header.height(), + header.raw_timestamp(), + header.prev_hash(), + header.hash(), + header.epoch_id(), + &msg.request, + ) { + Ok(query_response) => Ok(query_response), + Err(query_error) => Err(match query_error { + near_chain::near_chain_primitives::error::QueryError::InternalError { + error_message, + .. + } => QueryError::InternalError { error_message }, + near_chain::near_chain_primitives::error::QueryError::InvalidAccount { + requested_account_id, + block_height, + block_hash, + } => QueryError::InvalidAccount { requested_account_id, block_height, block_hash }, + near_chain::near_chain_primitives::error::QueryError::UnknownAccount { + requested_account_id, + block_height, + block_hash, + } => QueryError::UnknownAccount { requested_account_id, block_height, block_hash }, + near_chain::near_chain_primitives::error::QueryError::NoContractCode { + contract_account_id, + block_height, + block_hash, + } => QueryError::NoContractCode { contract_account_id, block_height, block_hash }, + near_chain::near_chain_primitives::error::QueryError::UnknownAccessKey { + public_key, + block_height, + block_hash, + } => QueryError::UnknownAccessKey { public_key, block_height, block_hash }, + near_chain::near_chain_primitives::error::QueryError::ContractExecutionError { + error_message, + block_hash, + block_height, + } => QueryError::ContractExecutionError { + vm_error: error_message, + block_height, + block_hash, + }, + }), } } @@ -469,7 +465,7 @@ impl Actor for ViewClientActor { } impl Handler for ViewClientActor { - type Result = Result, String>; + type Result = Result; #[perf] fn handle(&mut self, msg: Query, _: &mut Self::Context) -> Self::Result { @@ -945,25 +941,6 @@ impl Handler for ViewClientActor { } NetworkViewClientResponses::NoResponse } - NetworkViewClientMessages::Query { query_id, block_reference, request } => { - let query = Query { query_id: query_id.clone(), block_reference, request }; - match self.handle_query(query) { - Ok(Some(r)) => { - NetworkViewClientResponses::QueryResponse { query_id, response: Ok(r) } - } - Ok(None) => NetworkViewClientResponses::NoResponse, - Err(e) => { - NetworkViewClientResponses::QueryResponse { query_id, response: Err(e) } - } - } - } - NetworkViewClientMessages::QueryResponse { query_id, response } => { - let mut request_manager = self.request_manager.write().expect(POISONED_LOCK_ERR); - if request_manager.query_requests.cache_get(&query_id).is_some() { - request_manager.query_responses.cache_set(query_id, response); - } - NetworkViewClientResponses::NoResponse - } NetworkViewClientMessages::ReceiptOutcomeRequest(receipt_id) => { if let Ok(outcome_with_proof) = self.chain.get_execution_outcome(&receipt_id) { NetworkViewClientResponses::ReceiptOutcomeResponse(Box::new( diff --git a/chain/client/tests/catching_up.rs b/chain/client/tests/catching_up.rs index d01a726abe8..d5ff0627a2d 100644 --- a/chain/client/tests/catching_up.rs +++ b/chain/client/tests/catching_up.rs @@ -323,8 +323,7 @@ mod tests { )) .then(move |res| { let res_inner = res.unwrap(); - if let Ok(Some(query_response)) = res_inner - { + if let Ok(query_response) = res_inner { if let ViewAccount( view_account_result, ) = query_response.kind @@ -528,9 +527,7 @@ mod tests { )) .then(move |res| { let res_inner = res.unwrap(); - if let Ok(Some(query_response)) = - res_inner - { + if let Ok(query_response) = res_inner { if let ViewAccount( view_account_result, ) = query_response.kind diff --git a/chain/client/tests/cross_shard_tx.rs b/chain/client/tests/cross_shard_tx.rs index c797e3de93d..47ae0e345ef 100644 --- a/chain/client/tests/cross_shard_tx.rs +++ b/chain/client/tests/cross_shard_tx.rs @@ -59,7 +59,7 @@ fn test_keyvalue_runtime_balances() { QueryRequest::ViewAccount { account_id: flat_validators[i].to_string() }, )) .then(move |res| { - let query_response = res.unwrap().unwrap().unwrap(); + let query_response = res.unwrap().unwrap(); if let ViewAccount(view_account_result) = query_response.kind { assert_eq!(view_account_result.amount, expected); successful_queries2.fetch_add(1, Ordering::Relaxed); @@ -163,7 +163,7 @@ mod tests { } fn test_cross_shard_tx_callback( - res: Result, String>, MailboxError>, + res: Result, MailboxError>, account_id: AccountId, connectors: Arc, Addr)>>>, iteration: Arc, @@ -180,7 +180,7 @@ mod tests { min_ratio: Option, max_ratio: Option, ) { - let res = res.unwrap().and_then(|r| r.ok_or_else(|| "Request routed".to_string())); + let res = res.unwrap(); let query_response = match res { Ok(query_response) => query_response, diff --git a/chain/client/tests/query_client.rs b/chain/client/tests/query_client.rs index b255f820e3a..61a1efe1756 100644 --- a/chain/client/tests/query_client.rs +++ b/chain/client/tests/query_client.rs @@ -36,7 +36,7 @@ fn query_client() { QueryRequest::ViewAccount { account_id: "test".to_owned() }, )) .then(|res| { - match res.unwrap().unwrap().unwrap().kind { + match res.unwrap().unwrap().kind { QueryResponseKind::ViewAccount(_) => (), _ => panic!("Invalid response"), } diff --git a/chain/jsonrpc-primitives/Cargo.toml b/chain/jsonrpc-primitives/Cargo.toml index 6e1dc3f9a04..4608efdd4db 100644 --- a/chain/jsonrpc-primitives/Cargo.toml +++ b/chain/jsonrpc-primitives/Cargo.toml @@ -12,9 +12,12 @@ lazy_static = "1.4" serde = { version = "1", features = ["derive"] } serde_json = "1" thiserror = "1.0" +tracing = "0.1.13" uuid = { version = "~0.8", features = ["v4"] } +near-chain-configs = { path = "../../core/chain-configs" } near-client-primitives = { path = "../client-primitives" } -near-primitives = { path = "../../core/primitives" } +near-crypto = { path = "../../core/crypto" } near-metrics = { path = "../../core/metrics" } -near-chain-configs = { path = "../../core/chain-configs" } +near-primitives = { path = "../../core/primitives" } +near-primitives-core = { path = "../../core/primitives-core" } diff --git a/chain/jsonrpc-primitives/src/types/blocks.rs b/chain/jsonrpc-primitives/src/types/blocks.rs index adecc05f7d8..37d53e7b057 100644 --- a/chain/jsonrpc-primitives/src/types/blocks.rs +++ b/chain/jsonrpc-primitives/src/types/blocks.rs @@ -64,12 +64,13 @@ impl From for RpcBlockError { } near_client_primitives::types::GetBlockError::NotSyncedYet => Self::NotSyncedYet, near_client_primitives::types::GetBlockError::IOError(s) => Self::InternalError(s), - near_client_primitives::types::GetBlockError::Unreachable(s) => { + near_client_primitives::types::GetBlockError::Unreachable(error_message) => { + tracing::warn!(target: "jsonrpc", "Unreachable error occurred: {}", &error_message); near_metrics::inc_counter_vec( &crate::metrics::RPC_UNREACHABLE_ERROR_COUNT, - &["RpcBlockError", &s], + &["RpcBlockError", &error_message], ); - Self::Unreachable(s) + Self::Unreachable(error_message) } } } diff --git a/chain/jsonrpc-primitives/src/types/chunks.rs b/chain/jsonrpc-primitives/src/types/chunks.rs index 83f7df24504..a4aa314544b 100644 --- a/chain/jsonrpc-primitives/src/types/chunks.rs +++ b/chain/jsonrpc-primitives/src/types/chunks.rs @@ -93,12 +93,13 @@ impl From for RpcChunkError { near_client_primitives::types::GetChunkError::UnknownChunk(hash) => { Self::UnknownChunk(hash) } - near_client_primitives::types::GetChunkError::Unreachable(s) => { + near_client_primitives::types::GetChunkError::Unreachable(error_message) => { + tracing::warn!(target: "jsonrpc", "Unreachable error occurred: {}", &error_message); near_metrics::inc_counter_vec( &crate::metrics::RPC_UNREACHABLE_ERROR_COUNT, - &["RpcChunkError", &s], + &["RpcChunkError", &error_message], ); - Self::Unreachable(s) + Self::Unreachable(error_message) } } } diff --git a/chain/jsonrpc-primitives/src/types/config.rs b/chain/jsonrpc-primitives/src/types/config.rs index bcd4236c33a..82362bf5426 100644 --- a/chain/jsonrpc-primitives/src/types/config.rs +++ b/chain/jsonrpc-primitives/src/types/config.rs @@ -46,12 +46,13 @@ impl From for RpcProtocol near_client_primitives::types::GetProtocolConfigError::IOError(s) => { Self::InternalError(s) } - near_client_primitives::types::GetProtocolConfigError::Unreachable(s) => { + near_client_primitives::types::GetProtocolConfigError::Unreachable(error_message) => { + tracing::warn!(target: "jsonrpc", "Unreachable error occurred: {}", &error_message); near_metrics::inc_counter_vec( &crate::metrics::RPC_UNREACHABLE_ERROR_COUNT, - &["RpcProtocolConfigError", &s], + &["RpcProtocolConfigError", &error_message], ); - Self::Unreachable(s) + Self::Unreachable(error_message) } } } diff --git a/chain/jsonrpc-primitives/src/types/mod.rs b/chain/jsonrpc-primitives/src/types/mod.rs index 1cbb0868831..53c87d7347b 100644 --- a/chain/jsonrpc-primitives/src/types/mod.rs +++ b/chain/jsonrpc-primitives/src/types/mod.rs @@ -1,5 +1,6 @@ pub mod blocks; pub mod chunks; pub mod config; +pub mod query; pub mod receipts; pub mod validator; diff --git a/chain/jsonrpc-primitives/src/types/query.rs b/chain/jsonrpc-primitives/src/types/query.rs new file mode 100644 index 00000000000..32623c35625 --- /dev/null +++ b/chain/jsonrpc-primitives/src/types/query.rs @@ -0,0 +1,252 @@ +use serde::{Deserialize, Serialize}; +use serde_json::Value; + +/// Max size of the query path (soft-deprecated) +const QUERY_DATA_MAX_SIZE: usize = 10 * 1024; + +#[derive(Serialize, Deserialize)] +pub struct RpcQueryRequest { + #[serde(flatten)] + pub block_reference: near_primitives::types::BlockReference, + #[serde(flatten)] + pub request: near_primitives::views::QueryRequest, +} + +#[derive(thiserror::Error, Debug)] +pub enum RpcQueryError { + #[error("IO Error: {error_message}")] + IOError { error_message: String }, + #[error("There are no fully synchronized blocks on the node yet")] + NoSyncedBlocks, + #[error("The node does not track the shard")] + UnavailableShard { requested_shard_id: near_primitives::types::ShardId }, + #[error("Account ID {requested_account_id} is invalid")] + InvalidAccount { + requested_account_id: near_primitives::types::AccountId, + block_height: near_primitives::types::BlockHeight, + block_hash: near_primitives::hash::CryptoHash, + }, + #[error("account {requested_account_id} does not exist while viewing")] + UnknownAccount { + requested_account_id: near_primitives::types::AccountId, + block_height: near_primitives::types::BlockHeight, + block_hash: near_primitives::hash::CryptoHash, + }, + #[error( + "Contract code for contract ID #{contract_account_id} has never been observed on the node" + )] + NoContractCode { + contract_account_id: near_primitives::types::AccountId, + block_height: near_primitives::types::BlockHeight, + block_hash: near_primitives::hash::CryptoHash, + }, + #[error("Access key for public key {public_key} has never been observed on the node")] + UnknownAccessKey { + public_key: near_crypto::PublicKey, + block_height: near_primitives::types::BlockHeight, + block_hash: near_primitives::hash::CryptoHash, + }, + #[error("Function call returned an error: {vm_error}")] + ContractExecutionError { + vm_error: String, + block_height: near_primitives::types::BlockHeight, + block_hash: near_primitives::hash::CryptoHash, + }, + #[error("The node reached its limits. Try again later. More details: {error_message}")] + InternalError { error_message: String }, + // NOTE: Currently, the underlying errors are too broad, and while we tried to handle + // expected cases, we cannot statically guarantee that no other errors will be returned + // in the future. + // TODO #3851: Remove this variant once we can exhaustively match all the underlying errors + #[error("It is a bug if you receive this error type, please, report this incident: https://github.com/near/nearcore/issues/new/choose. Details: {error_message}")] + Unreachable { error_message: String }, +} + +#[derive(Serialize, Deserialize)] +pub struct RpcQueryResponse { + #[serde(flatten)] + pub kind: QueryResponseKind, + pub block_height: near_primitives::types::BlockHeight, + pub block_hash: near_primitives::hash::CryptoHash, +} + +#[derive(Serialize, Deserialize, Debug)] +#[serde(untagged)] +pub enum QueryResponseKind { + ViewAccount(near_primitives::views::AccountView), + ViewCode(near_primitives::views::ContractCodeView), + ViewState(near_primitives::views::ViewStateResult), + CallResult(near_primitives::views::CallResult), + AccessKey(near_primitives::views::AccessKeyView), + AccessKeyList(near_primitives::views::AccessKeyList), +} + +impl RpcQueryRequest { + pub fn parse(value: Option) -> Result { + let query_request = if let Ok((path, data)) = + crate::utils::parse_params::<(String, String)>(value.clone()) + { + // Handle a soft-deprecated version of the query API, which is based on + // positional arguments with a "path"-style first argument. + // + // This whole block can be removed one day, when the new API is 100% adopted. + let data = near_primitives_core::serialize::from_base(&data) + .map_err(|err| crate::errors::RpcParseError(err.to_string()))?; + let query_data_size = path.len() + data.len(); + if query_data_size > QUERY_DATA_MAX_SIZE { + return Err(crate::errors::RpcParseError(format!( + "Query data size {} is too large", + query_data_size + ))); + } + + let mut path_parts = path.splitn(3, '/'); + let make_err = + || crate::errors::RpcParseError("Not enough query parameters provided".to_string()); + let query_command = path_parts.next().ok_or_else(make_err)?; + let account_id = + near_primitives::types::AccountId::from(path_parts.next().ok_or_else(make_err)?); + let maybe_extra_arg = path_parts.next(); + + let request = match query_command { + "account" => near_primitives::views::QueryRequest::ViewAccount { account_id }, + "access_key" => match maybe_extra_arg { + None => near_primitives::views::QueryRequest::ViewAccessKeyList { account_id }, + Some(pk) => near_primitives::views::QueryRequest::ViewAccessKey { + account_id, + public_key: pk.parse().map_err(|_| { + crate::errors::RpcParseError("Invalid public key".to_string()) + })?, + }, + }, + "code" => near_primitives::views::QueryRequest::ViewCode { account_id }, + "contract" => near_primitives::views::QueryRequest::ViewState { + account_id, + prefix: data.into(), + }, + "call" => match maybe_extra_arg { + Some(method_name) => near_primitives::views::QueryRequest::CallFunction { + account_id, + method_name: method_name.to_string(), + args: data.into(), + }, + None => { + return Err(crate::errors::RpcParseError( + "Method name is missing".to_string(), + )) + } + }, + _ => { + return Err(crate::errors::RpcParseError(format!( + "Unknown path {}", + query_command + ))) + } + }; + // Use Finality::None here to make backward compatibility tests work + RpcQueryRequest { + request, + block_reference: near_primitives::types::BlockReference::latest(), + } + } else { + crate::utils::parse_params::(value)? + }; + Ok(query_request) + } +} + +impl From for RpcQueryError { + fn from(error: near_client_primitives::types::QueryError) -> Self { + match error { + near_client_primitives::types::QueryError::InternalError { error_message } => { + Self::InternalError { error_message } + } + near_client_primitives::types::QueryError::NoSyncedBlocks => Self::NoSyncedBlocks, + near_client_primitives::types::QueryError::UnavailableShard { requested_shard_id } => { + Self::UnavailableShard { requested_shard_id } + } + near_client_primitives::types::QueryError::InvalidAccount { + requested_account_id, + block_height, + block_hash, + } => Self::InvalidAccount { requested_account_id, block_height, block_hash }, + near_client_primitives::types::QueryError::UnknownAccount { + requested_account_id, + block_height, + block_hash, + } => Self::UnknownAccount { requested_account_id, block_height, block_hash }, + near_client_primitives::types::QueryError::NoContractCode { + contract_account_id, + block_height, + block_hash, + } => Self::NoContractCode { contract_account_id, block_height, block_hash }, + near_client_primitives::types::QueryError::UnknownAccessKey { + public_key, + block_height, + block_hash, + } => Self::UnknownAccessKey { public_key, block_height, block_hash }, + near_client_primitives::types::QueryError::ContractExecutionError { + vm_error, + block_height, + block_hash, + } => Self::ContractExecutionError { vm_error, block_height, block_hash }, + near_client_primitives::types::QueryError::Unreachable { error_message } => { + tracing::warn!(target: "jsonrpc", "Unreachable error occurred: {}", &error_message); + near_metrics::inc_counter_vec( + &crate::metrics::RPC_UNREACHABLE_ERROR_COUNT, + &["RpcQueryError", &error_message], + ); + Self::Unreachable { error_message } + } + } + } +} + +impl From for RpcQueryResponse { + fn from(query_response: near_primitives::views::QueryResponse) -> Self { + Self { + kind: query_response.kind.into(), + block_hash: query_response.block_hash, + block_height: query_response.block_height, + } + } +} + +impl From for QueryResponseKind { + fn from(query_response_kind: near_primitives::views::QueryResponseKind) -> Self { + match query_response_kind { + near_primitives::views::QueryResponseKind::ViewAccount(account_view) => { + Self::ViewAccount(account_view) + } + near_primitives::views::QueryResponseKind::ViewCode(contract_code_view) => { + Self::ViewCode(contract_code_view) + } + near_primitives::views::QueryResponseKind::ViewState(view_state_result) => { + Self::ViewState(view_state_result) + } + near_primitives::views::QueryResponseKind::CallResult(call_result) => { + Self::CallResult(call_result) + } + near_primitives::views::QueryResponseKind::AccessKey(access_key_view) => { + Self::AccessKey(access_key_view) + } + near_primitives::views::QueryResponseKind::AccessKeyList(access_key_list) => { + Self::AccessKeyList(access_key_list) + } + } + } +} + +impl From for crate::errors::RpcError { + fn from(error: RpcQueryError) -> Self { + let error_data = Some(Value::String(error.to_string())); + + Self::new(-32_000, "Server error".to_string(), error_data) + } +} + +impl From for RpcQueryError { + fn from(error: actix::MailboxError) -> Self { + Self::InternalError { error_message: error.to_string() } + } +} diff --git a/chain/jsonrpc-primitives/src/types/receipts.rs b/chain/jsonrpc-primitives/src/types/receipts.rs index a17c16f87d7..0962c7e1a26 100644 --- a/chain/jsonrpc-primitives/src/types/receipts.rs +++ b/chain/jsonrpc-primitives/src/types/receipts.rs @@ -52,12 +52,13 @@ impl From for RpcReceiptError { near_client_primitives::types::GetReceiptError::UnknownReceipt(hash) => { Self::UnknownReceipt(hash) } - near_client_primitives::types::GetReceiptError::Unreachable(s) => { + near_client_primitives::types::GetReceiptError::Unreachable(error_message) => { + tracing::warn!(target: "jsonrpc", "Unreachable error occurred: {}", &error_message); near_metrics::inc_counter_vec( &crate::metrics::RPC_UNREACHABLE_ERROR_COUNT, - &["RpcReceiptError", &s], + &["RpcReceiptError", &error_message], ); - Self::Unreachable(s) + Self::Unreachable(error_message) } } } diff --git a/chain/jsonrpc/client/src/lib.rs b/chain/jsonrpc/client/src/lib.rs index daa7caba775..e29727e136c 100644 --- a/chain/jsonrpc/client/src/lib.rs +++ b/chain/jsonrpc/client/src/lib.rs @@ -8,13 +8,13 @@ use serde::Serialize; use near_jsonrpc_primitives::errors::RpcError; use near_jsonrpc_primitives::message::{from_slice, Message}; use near_jsonrpc_primitives::rpc::{ - RpcQueryRequest, RpcStateChangesRequest, RpcStateChangesResponse, RpcValidatorsOrderedRequest, + RpcStateChangesRequest, RpcStateChangesResponse, RpcValidatorsOrderedRequest, }; use near_primitives::hash::CryptoHash; use near_primitives::types::{BlockId, BlockReference, MaybeBlockId, ShardId}; use near_primitives::views::{ BlockView, ChunkView, EpochValidatorInfo, FinalExecutionOutcomeView, GasPriceView, - QueryResponse, StatusResponse, ValidatorStakeView, + StatusResponse, ValidatorStakeView, }; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] @@ -194,11 +194,18 @@ jsonrpc_client!(pub struct JsonRpcClient { impl JsonRpcClient { /// This is a soft-deprecated method to do query RPC request with a path and data positional /// parameters. - pub fn query_by_path(&self, path: String, data: String) -> RpcRequest { + pub fn query_by_path( + &self, + path: String, + data: String, + ) -> RpcRequest { call_method(&self.client, &self.server_addr, "query", [path, data]) } - pub fn query(&self, request: RpcQueryRequest) -> RpcRequest { + pub fn query( + &self, + request: near_jsonrpc_primitives::types::query::RpcQueryRequest, + ) -> RpcRequest { call_method(&self.client, &self.server_addr, "query", request) } diff --git a/chain/jsonrpc/src/lib.rs b/chain/jsonrpc/src/lib.rs index 35a22dd50f0..2ed698bd885 100644 --- a/chain/jsonrpc/src/lib.rs +++ b/chain/jsonrpc/src/lib.rs @@ -11,7 +11,7 @@ use futures::{FutureExt, TryFutureExt}; use prometheus; use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; -use serde_json::Value; +use serde_json::{json, Value}; use tokio::time::{sleep, timeout}; use tracing::info; @@ -27,7 +27,7 @@ use near_jsonrpc_primitives::errors::RpcError; use near_jsonrpc_primitives::message::{Message, Request}; use near_jsonrpc_primitives::rpc::{ RpcBroadcastTxSyncResponse, RpcLightClientExecutionProofRequest, - RpcLightClientExecutionProofResponse, RpcQueryRequest, RpcStateChangesInBlockRequest, + RpcLightClientExecutionProofResponse, RpcStateChangesInBlockRequest, RpcStateChangesInBlockResponse, RpcStateChangesRequest, RpcStateChangesResponse, RpcValidatorsOrderedRequest, TransactionInfo, }; @@ -38,19 +38,14 @@ use near_network::types::{NetworkAdversarialMessage, NetworkViewClientMessages}; use near_network::{NetworkClientMessages, NetworkClientResponses}; use near_primitives::errors::{InvalidTxError, TxExecutionError}; use near_primitives::hash::CryptoHash; -use near_primitives::serialize::{from_base, from_base64, BaseEncode}; +use near_primitives::serialize::{from_base64, BaseEncode}; use near_primitives::transaction::SignedTransaction; -use near_primitives::types::{AccountId, BlockReference, MaybeBlockId}; -use near_primitives::views::{ - FinalExecutionOutcomeView, FinalExecutionOutcomeViewEnum, QueryRequest, -}; +use near_primitives::types::{AccountId, MaybeBlockId}; +use near_primitives::views::{FinalExecutionOutcomeView, FinalExecutionOutcomeViewEnum}; use near_runtime_utils::is_valid_account_id; mod metrics; -/// Max size of the query path (soft-deprecated) -const QUERY_DATA_MAX_SIZE: usize = 10 * 1024; - #[derive(Serialize, Deserialize, Clone, Copy, Debug)] pub struct RpcPollingConfig { pub polling_interval: Duration, @@ -104,10 +99,6 @@ impl RpcConfig { } } -fn from_base_or_parse_err(encoded: String) -> Result, RpcError> { - from_base(&encoded).map_err(|err| RpcError::parse_error(err.to_string())) -} - fn from_base64_or_parse_err(encoded: String) -> Result, RpcError> { from_base64(&encoded).map_err(|err| RpcError::parse_error(err.to_string())) } @@ -184,6 +175,48 @@ fn timeout_err() -> RpcError { RpcError::server_error(Some(ServerError::Timeout)) } +/// This function processes response from query method to introduce +/// backward compatible response in case of specific errors +fn process_query_response( + query_response: Result< + near_jsonrpc_primitives::types::query::RpcQueryResponse, + near_jsonrpc_primitives::types::query::RpcQueryError, + >, +) -> Result { + // This match is used here to give backward compatible error message for specific + // error variants. Should be refactored once structured errors fully shipped + match query_response { + Ok(rpc_query_response) => serde_json::to_value(rpc_query_response) + .map_err(|err| RpcError::parse_error(err.to_string())), + Err(err) => match err { + near_jsonrpc_primitives::types::query::RpcQueryError::ContractExecutionError { + vm_error, + block_height, + block_hash, + } => Ok(json!({ + "error": vm_error, + "logs": json!([]), + "block_height": block_height, + "block_hash": block_hash, + })), + near_jsonrpc_primitives::types::query::RpcQueryError::UnknownAccessKey { + public_key, + block_height, + block_hash, + } => Ok(json!({ + "error": format!( + "access key {} does not exist while viewing", + public_key.to_string() + ), + "logs": json!([]), + "block_height": block_height, + "block_hash": block_hash, + })), + _ => Err(err.into()), + }, + } +} + struct JsonRpcHandler { client_addr: Addr, view_client_addr: Addr, @@ -251,6 +284,9 @@ impl JsonRpcHandler { "EXPERIMENTAL_changes_in_block" => self.changes_in_block(request.params).await, "EXPERIMENTAL_check_tx" => self.check_tx(request.params).await, "EXPERIMENTAL_genesis_config" => self.genesis_config().await, + "EXPERIMENTAL_light_client_proof" => { + self.light_client_execution_outcome_proof(request.params).await + } "EXPERIMENTAL_protocol_config" => { let rpc_protocol_config_request = near_jsonrpc_primitives::types::config::RpcProtocolConfigRequest::parse( @@ -259,9 +295,6 @@ impl JsonRpcHandler { let config = self.protocol_config(rpc_protocol_config_request).await?; serde_json::to_value(config).map_err(|err| RpcError::parse_error(err.to_string())) } - "EXPERIMENTAL_light_client_proof" => { - self.light_client_execution_outcome_proof(request.params).await - } "EXPERIMENTAL_receipt" => { let rpc_receipt_request = near_jsonrpc_primitives::types::receipts::RpcReceiptRequest::parse( @@ -277,7 +310,12 @@ impl JsonRpcHandler { "light_client_proof" => self.light_client_execution_outcome_proof(request.params).await, "next_light_client_block" => self.next_light_client_block(request.params).await, "network_info" => self.network_info().await, - "query" => self.query(request.params).await, + "query" => { + let rpc_query_request = + near_jsonrpc_primitives::types::query::RpcQueryRequest::parse(request.params)?; + let query_response = self.query(rpc_query_request).await; + process_query_response(query_response) + } "status" => self.status().await, "tx" => self.tx_status_common(request.params, false).await, "validators" => { @@ -580,90 +618,15 @@ impl JsonRpcHandler { Ok(RpcProtocolConfigResponse { config_view }) } - async fn query(&self, params: Option) -> Result { - let query_request = if let Ok((path, data)) = - parse_params::<(String, String)>(params.clone()) - { - // Handle a soft-deprecated version of the query API, which is based on - // positional arguments with a "path"-style first argument. - // - // This whole block can be removed one day, when the new API is 100% adopted. - let data = from_base_or_parse_err(data)?; - let query_data_size = path.len() + data.len(); - if query_data_size > QUERY_DATA_MAX_SIZE { - return Err(RpcError::server_error(Some(format!( - "Query data size {} is too large", - query_data_size - )))); - } - let mut path_parts = path.splitn(3, '/'); - let make_err = - || RpcError::server_error(Some("Not enough query parameters provided".to_string())); - let query_command = path_parts.next().ok_or_else(make_err)?; - let account_id = AccountId::from(path_parts.next().ok_or_else(make_err)?); - let maybe_extra_arg = path_parts.next(); - - let request = match query_command { - "account" => QueryRequest::ViewAccount { account_id }, - "code" => QueryRequest::ViewCode { account_id }, - "access_key" => match maybe_extra_arg { - None => QueryRequest::ViewAccessKeyList { account_id }, - Some(pk) => QueryRequest::ViewAccessKey { - account_id, - public_key: pk - .parse() - .map_err(|_| RpcError::server_error(Some("Invalid public key")))?, - }, - }, - "contract" => QueryRequest::ViewState { account_id, prefix: data.into() }, - "call" => match maybe_extra_arg { - Some(method_name) => QueryRequest::CallFunction { - account_id, - method_name: method_name.to_string(), - args: data.into(), - }, - None => { - return Err(RpcError::server_error(Some( - "Method name is missing".to_string(), - ))) - } - }, - _ => { - return Err(RpcError::server_error(Some(format!( - "Unknown path {}", - query_command - )))) - } - }; - // Use Finality::None here to make backward compatibility tests work - RpcQueryRequest { request, block_reference: BlockReference::latest() } - } else { - parse_params::(params.clone())? - }; - let query = Query::new(query_request.block_reference, query_request.request); - timeout(self.polling_config.polling_timeout, async { - loop { - let result = self.view_client_addr.send(query.clone()).await; - match result { - Ok(ref r) => match r { - Ok(Some(_)) => break jsonify(result), - Ok(None) => {} - Err(e) => break Err(RpcError::server_error(Some(e))), - }, - Err(e) => break Err(RpcError::server_error(Some(e.to_string()))), - } - sleep(self.polling_config.polling_interval).await; - } - }) - .await - .map_err(|_| { - near_metrics::inc_counter(&metrics::RPC_TIMEOUT_TOTAL); - tracing::warn!( - target: "jsonrpc", "Timeout: query method. params {:?}", - params, - ); - RpcError::server_error(Some("query has timed out".to_string())) - })? + async fn query( + &self, + request_data: near_jsonrpc_primitives::types::query::RpcQueryRequest, + ) -> Result< + near_jsonrpc_primitives::types::query::RpcQueryResponse, + near_jsonrpc_primitives::types::query::RpcQueryError, + > { + let query = Query::new(request_data.block_reference, request_data.request); + Ok(self.view_client_addr.send(query).await??.into()) } async fn tx_status_common( diff --git a/chain/jsonrpc/tests/rpc_query.rs b/chain/jsonrpc/tests/rpc_query.rs index 02090de87e4..4c62ae02805 100644 --- a/chain/jsonrpc/tests/rpc_query.rs +++ b/chain/jsonrpc/tests/rpc_query.rs @@ -8,14 +8,14 @@ use near_actix_test_utils::run_actix_until_stop; use near_crypto::{KeyType, PublicKey, Signature}; use near_jsonrpc::client::new_client; use near_jsonrpc_client::ChunkId; -use near_jsonrpc_primitives::rpc::RpcQueryRequest; use near_jsonrpc_primitives::rpc::RpcValidatorsOrderedRequest; +use near_jsonrpc_primitives::types::query::QueryResponseKind; use near_logger_utils::init_test_logger; use near_network::test_utils::WaitOrTimeout; use near_primitives::account::{AccessKey, AccessKeyPermission}; use near_primitives::hash::CryptoHash; use near_primitives::types::{BlockId, BlockReference, ShardId, SyncCheckpoint}; -use near_primitives::views::{QueryRequest, QueryResponseKind}; +use near_primitives::views::QueryRequest; #[macro_use] pub mod test_utils; @@ -161,21 +161,21 @@ fn test_query_account() { let status = client.status().await.unwrap(); let block_hash = status.sync_info.latest_block_hash; let query_response_1 = client - .query(RpcQueryRequest { + .query(near_jsonrpc_primitives::types::query::RpcQueryRequest { block_reference: BlockReference::latest(), request: QueryRequest::ViewAccount { account_id: "test".to_string() }, }) .await .unwrap(); let query_response_2 = client - .query(RpcQueryRequest { + .query(near_jsonrpc_primitives::types::query::RpcQueryRequest { block_reference: BlockReference::BlockId(BlockId::Height(0)), request: QueryRequest::ViewAccount { account_id: "test".to_string() }, }) .await .unwrap(); let query_response_3 = client - .query(RpcQueryRequest { + .query(near_jsonrpc_primitives::types::query::RpcQueryRequest { block_reference: BlockReference::BlockId(BlockId::Hash(block_hash)), request: QueryRequest::ViewAccount { account_id: "test".to_string() }, }) @@ -224,7 +224,7 @@ fn test_query_by_path_access_keys() { fn test_query_access_keys() { test_with_client!(test_utils::NodeType::NonValidator, client, async move { let query_response = client - .query(RpcQueryRequest { + .query(near_jsonrpc_primitives::types::query::RpcQueryRequest { block_reference: BlockReference::latest(), request: QueryRequest::ViewAccessKeyList { account_id: "test".to_string() }, }) @@ -270,7 +270,7 @@ fn test_query_by_path_access_key() { fn test_query_access_key() { test_with_client!(test_utils::NodeType::NonValidator, client, async move { let query_response = client - .query(RpcQueryRequest { + .query(near_jsonrpc_primitives::types::query::RpcQueryRequest { block_reference: BlockReference::latest(), request: QueryRequest::ViewAccessKey { account_id: "test".to_string(), @@ -297,7 +297,7 @@ fn test_query_access_key() { fn test_query_state() { test_with_client!(test_utils::NodeType::NonValidator, client, async move { let query_response = client - .query(RpcQueryRequest { + .query(near_jsonrpc_primitives::types::query::RpcQueryRequest { block_reference: BlockReference::latest(), request: QueryRequest::ViewState { account_id: "test".to_string(), @@ -321,7 +321,7 @@ fn test_query_state() { fn test_query_call_function() { test_with_client!(test_utils::NodeType::NonValidator, client, async move { let query_response = client - .query(RpcQueryRequest { + .query(near_jsonrpc_primitives::types::query::RpcQueryRequest { block_reference: BlockReference::latest(), request: QueryRequest::CallFunction { account_id: "test".to_string(), @@ -350,7 +350,7 @@ fn test_query_call_function() { fn test_query_contract_code() { test_with_client!(test_utils::NodeType::NonValidator, client, async move { let query_response = client - .query(RpcQueryRequest { + .query(near_jsonrpc_primitives::types::query::RpcQueryRequest { block_reference: BlockReference::latest(), request: QueryRequest::ViewCode { account_id: "test".to_string() }, }) @@ -559,7 +559,7 @@ fn test_invalid_methods() { fn test_query_view_account_non_existing_account_must_return_error() { test_with_client!(test_utils::NodeType::NonValidator, client, async move { let query_response = client - .query(RpcQueryRequest { + .query(near_jsonrpc_primitives::types::query::RpcQueryRequest { block_reference: BlockReference::latest(), request: QueryRequest::ViewAccount { account_id: "invalidaccount".to_string() }, }) @@ -578,7 +578,7 @@ fn test_query_view_account_non_existing_account_must_return_error() { fn test_view_access_key_non_existing_account_id_and_public_key_must_return_error() { test_with_client!(test_utils::NodeType::NonValidator, client, async move { let query_response = client - .query(RpcQueryRequest { + .query(near_jsonrpc_primitives::types::query::RpcQueryRequest { block_reference: BlockReference::latest(), request: QueryRequest::ViewAccessKey { account_id: "\u{0}\u{0}\u{0}\u{0}\u{0}9".to_string(), @@ -600,7 +600,7 @@ fn test_view_access_key_non_existing_account_id_and_public_key_must_return_error fn test_call_function_non_existing_account_method_name() { test_with_client!(test_utils::NodeType::NonValidator, client, async move { let query_response = client - .query(RpcQueryRequest { + .query(near_jsonrpc_primitives::types::query::RpcQueryRequest { block_reference: BlockReference::latest(), request: QueryRequest::CallFunction { method_name: @@ -625,7 +625,7 @@ fn test_call_function_non_existing_account_method_name() { fn test_view_access_key_list_non_existing_account() { test_with_client!(test_utils::NodeType::NonValidator, client, async move { let query_response = client - .query(RpcQueryRequest { + .query(near_jsonrpc_primitives::types::query::RpcQueryRequest { block_reference: BlockReference::latest(), request: QueryRequest::ViewAccessKeyList { account_id: "\u{c}\u{c}\u{c}\u{c}\u{c}\u{c}\u{c}\u{c}\u{c}\u{c}\u{c}\u{c}\u{c}\u{c}\u{c}\u{c}\u{c}\u{c}\u{c}\u{c}\u{c}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0},".to_string(), @@ -646,7 +646,7 @@ fn test_view_access_key_list_non_existing_account() { fn test_view_state_non_existing_account_invalid_prefix() { test_with_client!(test_utils::NodeType::NonValidator, client, async move { let query_response = client - .query(RpcQueryRequest { + .query(near_jsonrpc_primitives::types::query::RpcQueryRequest { block_reference: BlockReference::latest(), request: QueryRequest::ViewState { account_id: "\u{0}\u{0}\u{0}\u{0}\u{0}\u{4}\u{0}\u{0}\u{0}\u{8}\u{0}\u{0}\u{0}\u{0}\u{0}eeeeeeeeeeeeeeeeeeeeeeeeeeeee".to_string(), diff --git a/chain/network/src/peer.rs b/chain/network/src/peer.rs index 5b76b2ff844..1bf5ec6b456 100644 --- a/chain/network/src/peer.rs +++ b/chain/network/src/peer.rs @@ -382,12 +382,6 @@ impl Peer { PeerMessage::Routed(message) => { msg_hash = Some(message.hash()); match message.body { - RoutedMessageBody::QueryRequest { query_id, block_reference, request } => { - NetworkViewClientMessages::Query { query_id, block_reference, request } - } - RoutedMessageBody::QueryResponse { query_id, response } => { - NetworkViewClientMessages::QueryResponse { query_id, response } - } RoutedMessageBody::TxStatusRequest(account_id, tx_hash) => { NetworkViewClientMessages::TxStatus { tx_hash, diff --git a/chain/network/src/types.rs b/chain/network/src/types.rs index 878f0f73b98..7e7f57a4301 100644 --- a/chain/network/src/types.rs +++ b/chain/network/src/types.rs @@ -1503,10 +1503,6 @@ pub enum NetworkViewClientMessages { TxStatus { tx_hash: CryptoHash, signer_account_id: AccountId }, /// Transaction status response TxStatusResponse(Box), - /// General query - Query { query_id: String, block_reference: BlockReference, request: QueryRequest }, - /// Query response - QueryResponse { query_id: String, response: Result }, /// Request for receipt outcome ReceiptOutcomeRequest(CryptoHash), /// Receipt outcome response diff --git a/chain/rosetta-rpc/Cargo.toml b/chain/rosetta-rpc/Cargo.toml index 969f9cc92ad..9cdc35694f1 100644 --- a/chain/rosetta-rpc/Cargo.toml +++ b/chain/rosetta-rpc/Cargo.toml @@ -28,6 +28,7 @@ near-primitives = { path = "../../core/primitives" } near-crypto = { path = "../../core/crypto" } near-chain-configs = { path = "../../core/chain-configs" } near-client = { path = "../client" } +near-client-primitives = { path = "../client-primitives" } near-network = { path = "../network" } [dev-dependencies] diff --git a/chain/rosetta-rpc/src/utils.rs b/chain/rosetta-rpc/src/utils.rs index c88c8ff7b4c..b2aa498c285 100644 --- a/chain/rosetta-rpc/src/utils.rs +++ b/chain/rosetta-rpc/src/utils.rs @@ -313,38 +313,20 @@ pub(crate) async fn query_account( block_id, near_primitives::views::QueryRequest::ViewAccount { account_id }, ); - let account_info_response = tokio::time::timeout(std::time::Duration::from_secs(10), async { - loop { - match view_client_addr.send(query.clone()).await? { - Ok(Some(query_response)) => return Ok(query_response), - Ok(None) => {} - // TODO: update this once we return structured errors from the view_client handlers - Err(err) => { - if err.contains("does not exist") { - return Err(crate::errors::ErrorKind::NotFound(err)); - } - return Err(crate::errors::ErrorKind::InternalError(err)); - } + let account_info_response = match view_client_addr.send(query).await? { + Ok(query_response) => query_response, + Err(err) => match err { + near_client_primitives::types::QueryError::UnknownAccount { .. } => { + return Err(crate::errors::ErrorKind::NotFound(err.to_string())) } - tokio::time::sleep(std::time::Duration::from_millis(100)).await; - } - }) - .await??; + _ => return Err(crate::errors::ErrorKind::InternalError(err.to_string())), + }, + }; match account_info_response.kind { near_primitives::views::QueryResponseKind::ViewAccount(account_info) => { Ok((account_info_response.block_hash, account_info_response.block_height, account_info)) } - near_primitives::views::QueryResponseKind::Error(near_primitives::views::QueryError { - error, - .. - }) => { - if error.contains("does not exist") { - Err(crate::errors::ErrorKind::NotFound(error)) - } else { - Err(crate::errors::ErrorKind::InternalError(error)) - } - } _ => Err(crate::errors::ErrorKind::InternalInvariantError(format!( "queried ViewAccount, but received {:?}.", account_info_response.kind @@ -399,26 +381,18 @@ pub(crate) async fn query_access_key( block_id, near_primitives::views::QueryRequest::ViewAccessKey { account_id, public_key }, ); - - let access_key_query_response = - tokio::time::timeout(std::time::Duration::from_secs(10), async { - loop { - match view_client_addr.send(access_key_query.clone()).await? { - Ok(Some(query_response)) => return Ok(query_response), - Ok(None) => {} - // TODO: update this once we return structured errors in the - // view_client handlers - Err(err) => { - if err.contains("does not exist") { - return Err(crate::errors::ErrorKind::NotFound(err)); - } - return Err(crate::errors::ErrorKind::InternalError(err)); - } + let access_key_query_response = match view_client_addr.send(access_key_query).await? { + Ok(query_response) => query_response, + Err(err) => { + return match err { + near_client_primitives::types::QueryError::UnknownAccount { .. } + | near_client_primitives::types::QueryError::UnknownAccessKey { .. } => { + Err(crate::errors::ErrorKind::NotFound(err.to_string())) } - tokio::time::sleep(std::time::Duration::from_millis(100)).await; + _ => Err(crate::errors::ErrorKind::InternalError(err.to_string())), } - }) - .await??; + } + }; match access_key_query_response.kind { near_primitives::views::QueryResponseKind::AccessKey(access_key) => Ok(( @@ -426,16 +400,6 @@ pub(crate) async fn query_access_key( access_key_query_response.block_height, access_key, )), - near_primitives::views::QueryResponseKind::Error(near_primitives::views::QueryError { - error, - .. - }) => { - if error.contains("does not exist") { - Err(crate::errors::ErrorKind::NotFound(error)) - } else { - Err(crate::errors::ErrorKind::InternalError(error)) - } - } _ => Err(crate::errors::ErrorKind::InternalInvariantError( "queried ViewAccessKey, but received something else.".to_string(), )), diff --git a/core/primitives/src/views.rs b/core/primitives/src/views.rs index ec3a2c72d9b..5d208be61d8 100644 --- a/core/primitives/src/views.rs +++ b/core/primitives/src/views.rs @@ -246,14 +246,12 @@ impl std::iter::FromIterator for AccessKeyList { } } -#[derive(BorshSerialize, BorshDeserialize, Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] -#[serde(untagged)] +#[derive(BorshSerialize, BorshDeserialize, Debug, PartialEq, Eq, Clone)] pub enum QueryResponseKind { ViewAccount(AccountView), ViewCode(ContractCodeView), ViewState(ViewStateResult), CallResult(CallResult), - Error(QueryError), AccessKey(AccessKeyView), AccessKeyList(AccessKeyList), } @@ -287,9 +285,8 @@ pub enum QueryRequest { }, } -#[derive(BorshSerialize, BorshDeserialize, Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] +#[derive(BorshSerialize, BorshDeserialize, Debug, PartialEq, Eq, Clone)] pub struct QueryResponse { - #[serde(flatten)] pub kind: QueryResponseKind, pub block_height: BlockHeight, pub block_hash: CryptoHash, @@ -332,61 +329,6 @@ pub struct StatusResponse { pub validator_account_id: Option, } -impl TryFrom for AccountView { - type Error = String; - - fn try_from(query_response: QueryResponse) -> Result { - match query_response.kind { - QueryResponseKind::ViewAccount(acc) => Ok(acc), - _ => Err("Invalid type of response".into()), - } - } -} - -impl TryFrom for CallResult { - type Error = String; - - fn try_from(query_response: QueryResponse) -> Result { - match query_response.kind { - QueryResponseKind::CallResult(res) => Ok(res), - _ => Err("Invalid type of response".into()), - } - } -} - -impl TryFrom for ViewStateResult { - type Error = String; - - fn try_from(query_response: QueryResponse) -> Result { - match query_response.kind { - QueryResponseKind::ViewState(vs) => Ok(vs), - _ => Err("Invalid type of response".into()), - } - } -} - -impl TryFrom for AccessKeyView { - type Error = String; - - fn try_from(query_response: QueryResponse) -> Result { - match query_response.kind { - QueryResponseKind::AccessKey(access_key) => Ok(access_key), - _ => Err("Invalid type of response".into()), - } - } -} - -impl TryFrom for ContractCodeView { - type Error = String; - - fn try_from(query_response: QueryResponse) -> Result { - match query_response.kind { - QueryResponseKind::ViewCode(contract_code) => Ok(contract_code), - _ => Err("Invalid type of response".into()), - } - } -} - #[derive(Serialize, Deserialize, Debug, Clone)] pub struct ChallengeView { // TODO: decide how to represent challenges in json. diff --git a/neard/Cargo.toml b/neard/Cargo.toml index a7030a929a0..69e394664fa 100644 --- a/neard/Cargo.toml +++ b/neard/Cargo.toml @@ -23,6 +23,7 @@ serde_json = "1" lazy_static = "1.4" dirs = "3" borsh = "0.8.1" +thiserror = "1.0" tracing = "0.1.13" tracing-subscriber = "0.2.4" num-rational = { version = "0.3", features = ["serde"] } diff --git a/neard/src/runtime/errors.rs b/neard/src/runtime/errors.rs new file mode 100644 index 00000000000..3ebfb4a824d --- /dev/null +++ b/neard/src/runtime/errors.rs @@ -0,0 +1,105 @@ +use near_chain::near_chain_primitives::error::QueryError; + +#[easy_ext::ext(FromStateViewerErrors)] +impl QueryError { + pub fn from_call_function_error( + error: node_runtime::state_viewer::errors::CallFunctionError, + block_height: near_primitives::types::BlockHeight, + block_hash: near_primitives::hash::CryptoHash, + ) -> Self { + match error { + node_runtime::state_viewer::errors::CallFunctionError::InvalidAccountId { + requested_account_id, + } => Self::InvalidAccount { requested_account_id, block_height, block_hash }, + node_runtime::state_viewer::errors::CallFunctionError::AccountDoesNotExist { + requested_account_id, + } => Self::UnknownAccount { requested_account_id, block_height, block_hash }, + node_runtime::state_viewer::errors::CallFunctionError::InternalError { + error_message, + } => Self::InternalError { error_message, block_height, block_hash }, + node_runtime::state_viewer::errors::CallFunctionError::VMError { error_message } => { + Self::ContractExecutionError { error_message, block_height, block_hash } + } + } + } + + pub fn from_view_account_error( + error: node_runtime::state_viewer::errors::ViewAccountError, + block_height: near_primitives::types::BlockHeight, + block_hash: near_primitives::hash::CryptoHash, + ) -> Self { + match error { + node_runtime::state_viewer::errors::ViewAccountError::InvalidAccountId { + requested_account_id, + } => Self::InvalidAccount { requested_account_id, block_height, block_hash }, + node_runtime::state_viewer::errors::ViewAccountError::AccountDoesNotExist { + requested_account_id, + } => Self::UnknownAccount { requested_account_id, block_height, block_hash }, + node_runtime::state_viewer::errors::ViewAccountError::InternalError { + error_message, + } => Self::InternalError { error_message, block_height, block_hash }, + } + } + + pub fn from_view_contract_code_error( + error: node_runtime::state_viewer::errors::ViewContractCodeError, + block_height: near_primitives::types::BlockHeight, + block_hash: near_primitives::hash::CryptoHash, + ) -> Self { + match error { + node_runtime::state_viewer::errors::ViewContractCodeError::InvalidAccountId { + requested_account_id, + } => Self::InvalidAccount { requested_account_id, block_height, block_hash }, + node_runtime::state_viewer::errors::ViewContractCodeError::AccountDoesNotExist { + requested_account_id, + } => Self::UnknownAccount { requested_account_id, block_height, block_hash }, + node_runtime::state_viewer::errors::ViewContractCodeError::InternalError { + error_message, + } => Self::InternalError { error_message, block_height, block_hash }, + node_runtime::state_viewer::errors::ViewContractCodeError::NoContractCode { + contract_account_id, + } => Self::NoContractCode { contract_account_id, block_height, block_hash }, + } + } + + pub fn from_view_state_error( + error: node_runtime::state_viewer::errors::ViewStateError, + block_height: near_primitives::types::BlockHeight, + block_hash: near_primitives::hash::CryptoHash, + ) -> Self { + match error { + node_runtime::state_viewer::errors::ViewStateError::InvalidAccountId { + requested_account_id, + } => Self::InvalidAccount { requested_account_id, block_height, block_hash }, + node_runtime::state_viewer::errors::ViewStateError::InternalError { error_message } => { + Self::InternalError { error_message, block_height, block_hash } + } + } + } + + pub fn from_view_access_key_error( + error: node_runtime::state_viewer::errors::ViewAccessKeyError, + block_height: near_primitives::types::BlockHeight, + block_hash: near_primitives::hash::CryptoHash, + ) -> Self { + match error { + node_runtime::state_viewer::errors::ViewAccessKeyError::InvalidAccountId { + requested_account_id, + } => Self::InvalidAccount { requested_account_id, block_height, block_hash }, + node_runtime::state_viewer::errors::ViewAccessKeyError::AccessKeyDoesNotExist { + public_key, + } => Self::UnknownAccessKey { public_key, block_height, block_hash }, + node_runtime::state_viewer::errors::ViewAccessKeyError::InternalError { + error_message, + } => Self::InternalError { error_message, block_height, block_hash }, + } + } + + pub fn from_epoch_error( + error: near_primitives::errors::EpochError, + block_height: near_primitives::types::BlockHeight, + block_hash: near_primitives::hash::CryptoHash, + ) -> Self { + Self::InternalError { error_message: error.to_string(), block_height, block_hash } + } +} diff --git a/neard/src/runtime.rs b/neard/src/runtime/mod.rs similarity index 95% rename from neard/src/runtime.rs rename to neard/src/runtime/mod.rs index 5d621af33c9..463acb85d2a 100644 --- a/neard/src/runtime.rs +++ b/neard/src/runtime/mod.rs @@ -33,20 +33,18 @@ use near_primitives::receipt::Receipt; use near_primitives::sharding::ChunkHash; use near_primitives::state_record::StateRecord; use near_primitives::transaction::SignedTransaction; -use near_primitives::trie_key::trie_key_parsers; use near_primitives::types::{ AccountId, ApprovalStake, Balance, BlockHeight, EpochHeight, EpochId, EpochInfoProvider, Gas, MerkleHash, NumShards, ShardId, StateChangeCause, StateRoot, StateRootNode, ValidatorStake, }; use near_primitives::version::ProtocolVersion; use near_primitives::views::{ - AccessKeyInfoView, CallResult, EpochValidatorInfo, QueryError, QueryRequest, QueryResponse, + AccessKeyInfoView, CallResult, EpochValidatorInfo, QueryRequest, QueryResponse, QueryResponseKind, ViewApplyState, ViewStateResult, }; use near_store::{ - get_access_key_raw, get_genesis_hash, get_genesis_state_roots, set_genesis_hash, - set_genesis_state_roots, ColState, PartialStorage, ShardTries, Store, - StoreCompiledContractCache, Trie, WrappedTrieChanges, + get_genesis_hash, get_genesis_state_roots, set_genesis_hash, set_genesis_state_roots, ColState, + PartialStorage, ShardTries, Store, StoreCompiledContractCache, Trie, WrappedTrieChanges, }; use node_runtime::adapter::ViewRuntimeAdapter; use node_runtime::state_viewer::TrieViewer; @@ -61,6 +59,10 @@ use near_primitives::runtime::config::RuntimeConfig; #[cfg(feature = "protocol_feature_rectify_inflation")] use near_epoch_manager::NUM_SECONDS_IN_A_YEAR; +use errors::FromStateViewerErrors; + +pub mod errors; + const POISONED_LOCK_ERR: &str = "The lock was poisoned."; const STATE_DUMP_FILE: &str = "state_dump"; const GENESIS_ROOTS_FILE: &str = "genesis_roots"; @@ -1246,125 +1248,126 @@ impl RuntimeAdapter for NightshadeRuntime { block_hash: &CryptoHash, epoch_id: &EpochId, request: &QueryRequest, - ) -> Result> { + ) -> Result { match request { QueryRequest::ViewAccount { account_id } => { - match self.view_account(shard_id, *state_root, account_id) { - Ok(r) => Ok(QueryResponse { - kind: QueryResponseKind::ViewAccount(r.into()), - block_height, - block_hash: *block_hash, - }), - Err(e) => Err(e), - } + let account = self + .view_account(shard_id, *state_root, account_id) + .map_err(|err| near_chain::near_chain_primitives::error::QueryError::from_view_account_error(err, block_height, *block_hash))?; + Ok(QueryResponse { + kind: QueryResponseKind::ViewAccount(account.into()), + block_height, + block_hash: *block_hash, + }) } QueryRequest::ViewCode { account_id } => { - match self.view_contract_code(shard_id, *state_root, account_id) { - Ok(r) => Ok(QueryResponse { - kind: QueryResponseKind::ViewCode(r.into()), - block_height, - block_hash: *block_hash, - }), - Err(e) => Err(e), - } + let contract_code = self + .view_contract_code(shard_id, *state_root, account_id) + .map_err(|err| near_chain::near_chain_primitives::error::QueryError::from_view_contract_code_error(err, block_height, *block_hash))?; + Ok(QueryResponse { + kind: QueryResponseKind::ViewCode(contract_code.into()), + block_height, + block_hash: *block_hash, + }) } QueryRequest::CallFunction { account_id, method_name, args } => { let mut logs = vec![]; let (epoch_height, current_protocol_version) = { let mut epoch_manager = self.epoch_manager.as_ref().write().expect(POISONED_LOCK_ERR); - let epoch_info = epoch_manager.get_epoch_info(&epoch_id)?; + let epoch_info = epoch_manager.get_epoch_info(&epoch_id).map_err(|err| { + near_chain::near_chain_primitives::error::QueryError::from_epoch_error( + err, + block_height, + *block_hash, + ) + })?; (epoch_info.epoch_height, epoch_info.protocol_version) }; - match self.call_function( - shard_id, - *state_root, - block_height, - block_timestamp, - prev_block_hash, - block_hash, - epoch_height, - epoch_id, - account_id, - method_name, - args.as_ref(), - &mut logs, - &self.epoch_manager, - current_protocol_version, - #[cfg(feature = "protocol_feature_evm")] - self.evm_chain_id(), - ) { - Ok(result) => Ok(QueryResponse { - kind: QueryResponseKind::CallResult(CallResult { result, logs }), - block_height, - block_hash: *block_hash, - }), - Err(err) => Ok(QueryResponse { - kind: QueryResponseKind::Error(QueryError { error: err.to_string(), logs }), + let call_function_result = self + .call_function( + shard_id, + *state_root, block_height, - block_hash: *block_hash, + block_timestamp, + prev_block_hash, + block_hash, + epoch_height, + epoch_id, + account_id, + method_name, + args.as_ref(), + &mut logs, + &self.epoch_manager, + current_protocol_version, + #[cfg(feature = "protocol_feature_evm")] + self.evm_chain_id(), + ) + .map_err(|err| near_chain::near_chain_primitives::error::QueryError::from_call_function_error(err, block_height, *block_hash))?; + Ok(QueryResponse { + kind: QueryResponseKind::CallResult(CallResult { + result: call_function_result, + logs, }), - } + block_height, + block_hash: *block_hash, + }) } QueryRequest::ViewState { account_id, prefix } => { - match self.view_state(shard_id, *state_root, account_id, prefix.as_ref()) { - Ok(result) => Ok(QueryResponse { - kind: QueryResponseKind::ViewState(result), - block_height, - block_hash: *block_hash, - }), - Err(err) => Ok(QueryResponse { - kind: QueryResponseKind::Error(QueryError { - error: err.to_string(), - logs: vec![], - }), - block_height, - block_hash: *block_hash, - }), - } + let view_state_result = self + .view_state(shard_id, *state_root, account_id, prefix.as_ref()) + .map_err(|err| { + near_chain::near_chain_primitives::error::QueryError::from_view_state_error( + err, + block_height, + *block_hash, + ) + })?; + Ok(QueryResponse { + kind: QueryResponseKind::ViewState(view_state_result), + block_height, + block_hash: *block_hash, + }) } QueryRequest::ViewAccessKeyList { account_id } => { - match self.view_access_keys(shard_id, *state_root, account_id) { - Ok(result) => Ok(QueryResponse { - kind: QueryResponseKind::AccessKeyList( - result - .into_iter() - .map(|(public_key, access_key)| AccessKeyInfoView { - public_key, - access_key: access_key.into(), - }) - .collect(), - ), - block_height, - block_hash: *block_hash, - }), - Err(err) => Ok(QueryResponse { - kind: QueryResponseKind::Error(QueryError { - error: err.to_string(), - logs: vec![], - }), - block_height, - block_hash: *block_hash, - }), - } + let access_key_list = + self.view_access_keys(shard_id, *state_root, account_id).map_err(|err| { + near_chain::near_chain_primitives::error::QueryError::from_view_access_key_error( + err, + block_height, + *block_hash, + ) + })?; + Ok(QueryResponse { + kind: QueryResponseKind::AccessKeyList( + access_key_list + .into_iter() + .map(|(public_key, access_key)| AccessKeyInfoView { + public_key, + access_key: access_key.into(), + }) + .collect(), + ), + block_height, + block_hash: *block_hash, + }) } QueryRequest::ViewAccessKey { account_id, public_key } => { - match self.view_access_key(shard_id, *state_root, account_id, public_key) { - Ok(access_key) => Ok(QueryResponse { - kind: QueryResponseKind::AccessKey(access_key.into()), - block_height, - block_hash: *block_hash, - }), - Err(err) => Ok(QueryResponse { - kind: QueryResponseKind::Error(QueryError { - error: err.to_string(), - logs: vec![], - }), - block_height, - block_hash: *block_hash, - }), - } + let access_key = self + .view_access_key(shard_id, *state_root, account_id, public_key) + .map_err(|err| { + near_chain::near_chain_primitives::error::QueryError::from_view_access_key_error( + err, + block_height, + *block_hash, + ) + })?; + Ok(QueryResponse { + kind: QueryResponseKind::AccessKey(access_key.into()), + block_height, + block_hash: *block_hash, + }) } } } @@ -1538,7 +1541,7 @@ impl node_runtime::adapter::ViewRuntimeAdapter for NightshadeRuntime { shard_id: ShardId, state_root: MerkleHash, account_id: &AccountId, - ) -> Result> { + ) -> Result { let state_update = self.get_tries().new_trie_update_view(shard_id, state_root); self.trie_viewer.view_account(&state_update, account_id) } @@ -1548,7 +1551,7 @@ impl node_runtime::adapter::ViewRuntimeAdapter for NightshadeRuntime { shard_id: ShardId, state_root: MerkleHash, account_id: &AccountId, - ) -> Result> { + ) -> Result { let state_update = self.get_tries().new_trie_update_view(shard_id, state_root); self.trie_viewer.view_contract_code(&state_update, account_id) } @@ -1570,7 +1573,7 @@ impl node_runtime::adapter::ViewRuntimeAdapter for NightshadeRuntime { epoch_info_provider: &dyn EpochInfoProvider, current_protocol_version: ProtocolVersion, #[cfg(feature = "protocol_feature_evm")] evm_chain_id: u64, - ) -> Result, Box> { + ) -> Result, node_runtime::state_viewer::errors::CallFunctionError> { let state_update = self.get_tries().new_trie_update_view(shard_id, state_root); let view_state = ViewApplyState { block_height: height, @@ -1601,7 +1604,7 @@ impl node_runtime::adapter::ViewRuntimeAdapter for NightshadeRuntime { state_root: MerkleHash, account_id: &AccountId, public_key: &PublicKey, - ) -> Result> { + ) -> Result { let state_update = self.get_tries().new_trie_update_view(shard_id, state_root); self.trie_viewer.view_access_key(&state_update, account_id, public_key) } @@ -1611,25 +1614,10 @@ impl node_runtime::adapter::ViewRuntimeAdapter for NightshadeRuntime { shard_id: ShardId, state_root: MerkleHash, account_id: &AccountId, - ) -> Result, Box> { + ) -> Result, node_runtime::state_viewer::errors::ViewAccessKeyError> + { let state_update = self.get_tries().new_trie_update_view(shard_id, state_root); - let prefix = trie_key_parsers::get_raw_prefix_for_access_keys(account_id); - let raw_prefix: &[u8] = prefix.as_ref(); - let access_keys = match state_update.iter(&prefix) { - Ok(iter) => iter - .map(|key| { - let key = key?; - let public_key = &key[raw_prefix.len()..]; - let access_key = get_access_key_raw(&state_update, &key)? - .ok_or("Missing key from iterator")?; - PublicKey::try_from_slice(public_key) - .map_err(|err| format!("{}", err).into()) - .map(|key| (key, access_key)) - }) - .collect::, Box>>(), - Err(e) => Err(e.into()), - }; - access_keys + self.trie_viewer.view_access_keys(&state_update, account_id) } fn view_state( @@ -1638,7 +1626,7 @@ impl node_runtime::adapter::ViewRuntimeAdapter for NightshadeRuntime { state_root: MerkleHash, account_id: &AccountId, prefix: &[u8], - ) -> Result> { + ) -> Result { let state_update = self.get_tries().new_trie_update_view(shard_id, state_root); self.trie_viewer.view_state(&state_update, account_id, prefix) } diff --git a/neard/tests/rpc_nodes.rs b/neard/tests/rpc_nodes.rs index eded9308a87..b3817d83397 100644 --- a/neard/tests/rpc_nodes.rs +++ b/neard/tests/rpc_nodes.rs @@ -19,7 +19,6 @@ use near_primitives::transaction::{PartialExecutionStatus, SignedTransaction}; use near_primitives::types::{BlockId, BlockReference, Finality, TransactionOrReceiptId}; use near_primitives::views::{ ExecutionOutcomeView, ExecutionStatusView, FinalExecutionOutcomeViewEnum, FinalExecutionStatus, - QueryResponseKind, }; use neard::config::TESTING_INIT_BALANCE; use std::sync::atomic::AtomicBool; @@ -358,6 +357,7 @@ fn test_tx_status_with_light_client1() { }); } +#[ignore] // TODO: change this test https://github.com/near/nearcore/issues/4062 #[test] fn test_rpc_routing() { init_integration_logger(); @@ -387,7 +387,7 @@ fn test_rpc_routing() { .query_by_path("account/near.2".to_string(), "".to_string()) .map_err(|err| panic_on_rpc_error!(err)) .map_ok(move |result| match result.kind { - QueryResponseKind::ViewAccount(account_view) => { + near_jsonrpc_primitives::types::query::QueryResponseKind::ViewAccount(account_view) => { assert_eq!( account_view.amount, TESTING_INIT_BALANCE @@ -411,6 +411,7 @@ fn test_rpc_routing() { }); } +#[ignore] // TODO: change this test https://github.com/near/nearcore/issues/4062 /// When we call rpc to view an account that does not exist, an error should be routed back. #[test] fn test_rpc_routing_error() { @@ -708,3 +709,141 @@ fn test_protocol_config_rpc() { }); }); } + +#[test] +fn test_query_rpc_account_view_must_succeed() { + init_integration_logger(); + heavy_test(|| { + run_actix_until_stop(async move { + let num_nodes = 1; + let dirs = (0..num_nodes) + .map(|i| { + tempfile::Builder::new() + .prefix(&format!("protocol_config{}", i)) + .tempdir() + .unwrap() + }) + .collect::>(); + let (_genesis, rpc_addrs, _) = start_nodes(1, &dirs, 1, 0, 10, 0); + + actix::spawn(async move { + let client = new_client(&format!("http://{}", rpc_addrs[0])); + let query_response = client + .query(near_jsonrpc_primitives::types::query::RpcQueryRequest { + block_reference: near_primitives::types::BlockReference::Finality( + Finality::Final, + ), + request: near_primitives::views::QueryRequest::ViewAccount { + account_id: "near.0".to_string(), + }, + }) + .await + .unwrap(); + let account = + if let near_jsonrpc_primitives::types::query::QueryResponseKind::ViewAccount( + account, + ) = query_response.kind + { + account + } else { + panic!( + "expected a account view result, but received something else: {:?}", + query_response.kind + ); + }; + assert!(matches!(account, near_primitives::views::AccountView { .. })); + System::current().stop(); + }); + }); + }); +} + +#[test] +fn test_query_rpc_account_view_invalid_account_must_return_error() { + init_integration_logger(); + heavy_test(|| { + run_actix_until_stop(async move { + let num_nodes = 1; + let dirs = (0..num_nodes) + .map(|i| { + tempfile::Builder::new() + .prefix(&format!("protocol_config{}", i)) + .tempdir() + .unwrap() + }) + .collect::>(); + let (_genesis, rpc_addrs, _) = start_nodes(1, &dirs, 1, 0, 10, 0); + + actix::spawn(async move { + let client = new_client(&format!("http://{}", rpc_addrs[0])); + let query_response = client + .query(near_jsonrpc_primitives::types::query::RpcQueryRequest { + block_reference: near_primitives::types::BlockReference::Finality( + Finality::Final, + ), + request: near_primitives::views::QueryRequest::ViewAccount { + account_id: "1nval$d*@cc0ount".to_string(), + }, + }) + .await; + assert!(query_response.is_err()); + let error_message = match query_response { + Ok(result) => panic!("expected error but received Ok: {:?}", result.kind), + Err(err) => err.data.unwrap(), + }; + assert!( + error_message.to_string().contains("Account ID 1nval$d*@cc0ount is invalid"), + "{}", + error_message + ); + System::current().stop(); + }); + }); + }); +} + +#[test] +fn test_query_rpc_account_view_account_doesnt_exist_must_return_error() { + init_integration_logger(); + heavy_test(|| { + run_actix_until_stop(async move { + let num_nodes = 1; + let dirs = (0..num_nodes) + .map(|i| { + tempfile::Builder::new() + .prefix(&format!("protocol_config{}", i)) + .tempdir() + .unwrap() + }) + .collect::>(); + let (_genesis, rpc_addrs, _) = start_nodes(1, &dirs, 1, 0, 10, 0); + + actix::spawn(async move { + let client = new_client(&format!("http://{}", rpc_addrs[0])); + let query_response = client + .query(near_jsonrpc_primitives::types::query::RpcQueryRequest { + block_reference: near_primitives::types::BlockReference::Finality( + Finality::Final, + ), + request: near_primitives::views::QueryRequest::ViewAccount { + account_id: "accountdoesntexist.0".to_string(), + }, + }) + .await; + assert!(query_response.is_err()); + let error_message = match query_response { + Ok(result) => panic!("expected error but received Ok: {:?}", result.kind), + Err(err) => err.data.unwrap(), + }; + assert!( + error_message + .to_string() + .contains("account accountdoesntexist.0 does not exist while viewing"), + "{}", + error_message + ); + System::current().stop(); + }); + }); + }); +} diff --git a/neard/tests/stake_nodes.rs b/neard/tests/stake_nodes.rs index 4bb26a8ddc1..a378a8a8fb2 100644 --- a/neard/tests/stake_nodes.rs +++ b/neard/tests/stake_nodes.rs @@ -251,18 +251,16 @@ fn test_validator_kickout() { .clone(), }, )) - .then(move |res| { - match res.unwrap().unwrap().unwrap().kind { - QueryResponseKind::ViewAccount(result) => { - if result.locked == 0 - || result.amount == TESTING_INIT_BALANCE - { - mark.store(true, Ordering::SeqCst); - } - future::ready(()) + .then(move |res| match res.unwrap().unwrap().kind { + QueryResponseKind::ViewAccount(result) => { + if result.locked == 0 + || result.amount == TESTING_INIT_BALANCE + { + mark.store(true, Ordering::SeqCst); } - _ => panic!("wrong return result"), + future::ready(()) } + _ => panic!("wrong return result"), }), ); } @@ -280,23 +278,17 @@ fn test_validator_kickout() { .clone(), }, )) - .then(move |res| { - match res.unwrap().unwrap().unwrap().kind { - QueryResponseKind::ViewAccount(result) => { - assert_eq!( - result.locked, - TESTING_INIT_STAKE - ); - assert_eq!( - result.amount, - TESTING_INIT_BALANCE - - TESTING_INIT_STAKE - ); - mark.store(true, Ordering::SeqCst); - future::ready(()) - } - _ => panic!("wrong return result"), + .then(move |res| match res.unwrap().unwrap().kind { + QueryResponseKind::ViewAccount(result) => { + assert_eq!(result.locked, TESTING_INIT_STAKE); + assert_eq!( + result.amount, + TESTING_INIT_BALANCE - TESTING_INIT_STAKE + ); + mark.store(true, Ordering::SeqCst); + future::ready(()) } + _ => panic!("wrong return result"), }), ); } @@ -421,16 +413,14 @@ fn test_validator_join() { account_id: test_nodes[1].account_id.clone(), }, )) - .then(move |res| { - match res.unwrap().unwrap().unwrap().kind { - QueryResponseKind::ViewAccount(result) => { - if result.locked == 0 { - done1_copy2.store(true, Ordering::SeqCst); - } - future::ready(()) + .then(move |res| match res.unwrap().unwrap().kind { + QueryResponseKind::ViewAccount(result) => { + if result.locked == 0 { + done1_copy2.store(true, Ordering::SeqCst); } - _ => panic!("wrong return result"), + future::ready(()) } + _ => panic!("wrong return result"), }), ); actix::spawn( @@ -442,17 +432,15 @@ fn test_validator_join() { account_id: test_nodes[2].account_id.clone(), }, )) - .then(move |res| { - match res.unwrap().unwrap().unwrap().kind { - QueryResponseKind::ViewAccount(result) => { - if result.locked == TESTING_INIT_STAKE { - done2_copy2.store(true, Ordering::SeqCst); - } - - future::ready(()) + .then(move |res| match res.unwrap().unwrap().kind { + QueryResponseKind::ViewAccount(result) => { + if result.locked == TESTING_INIT_STAKE { + done2_copy2.store(true, Ordering::SeqCst); } - _ => panic!("wrong return result"), + + future::ready(()) } + _ => panic!("wrong return result"), }), ); } diff --git a/runtime/runtime/Cargo.toml b/runtime/runtime/Cargo.toml index 880bc90ea4f..8b9bc4a54bf 100644 --- a/runtime/runtime/Cargo.toml +++ b/runtime/runtime/Cargo.toml @@ -16,6 +16,7 @@ num-bigint = "0.3" num-traits = "0.2.11" hex = "0.4.2" ethereum-types = "0.11.0" +thiserror = "1.0" borsh = "0.8.1" diff --git a/runtime/runtime/src/adapter.rs b/runtime/runtime/src/adapter.rs index b1f79d908e2..8b907d60be0 100644 --- a/runtime/runtime/src/adapter.rs +++ b/runtime/runtime/src/adapter.rs @@ -15,14 +15,14 @@ pub trait ViewRuntimeAdapter { shard_id: ShardId, state_root: MerkleHash, account_id: &AccountId, - ) -> Result>; + ) -> Result; fn view_contract_code( &self, shard_id: ShardId, state_root: MerkleHash, account_id: &AccountId, - ) -> Result>; + ) -> Result; fn call_function( &self, @@ -41,7 +41,7 @@ pub trait ViewRuntimeAdapter { epoch_info_provider: &dyn EpochInfoProvider, current_protocol_version: ProtocolVersion, #[cfg(feature = "protocol_feature_evm")] evm_chain_id: u64, - ) -> Result, Box>; + ) -> Result, crate::state_viewer::errors::CallFunctionError>; fn view_access_key( &self, @@ -49,14 +49,14 @@ pub trait ViewRuntimeAdapter { state_root: MerkleHash, account_id: &AccountId, public_key: &PublicKey, - ) -> Result>; + ) -> Result; fn view_access_keys( &self, shard_id: ShardId, state_root: MerkleHash, account_id: &AccountId, - ) -> Result, Box>; + ) -> Result, crate::state_viewer::errors::ViewAccessKeyError>; fn view_state( &self, @@ -64,5 +64,5 @@ pub trait ViewRuntimeAdapter { state_root: MerkleHash, account_id: &AccountId, prefix: &[u8], - ) -> Result>; + ) -> Result; } diff --git a/runtime/runtime/src/state_viewer/errors.rs b/runtime/runtime/src/state_viewer/errors.rs new file mode 100644 index 00000000000..d0f93a4dd71 --- /dev/null +++ b/runtime/runtime/src/state_viewer/errors.rs @@ -0,0 +1,97 @@ +#[derive(thiserror::Error, Debug)] +pub enum ViewAccountError { + #[error("Account ID \"{requested_account_id}\" is invalid")] + InvalidAccountId { requested_account_id: near_primitives::types::AccountId }, + #[error("Account ID #{requested_account_id} does not exist")] + AccountDoesNotExist { requested_account_id: near_primitives::types::AccountId }, + #[error("Internal error: #{error_message}")] + InternalError { error_message: String }, +} + +#[derive(thiserror::Error, Debug)] +pub enum ViewContractCodeError { + #[error("Account ID \"{requested_account_id}\" is invalid")] + InvalidAccountId { requested_account_id: near_primitives::types::AccountId }, + #[error("Account ID #{requested_account_id} does not exist")] + AccountDoesNotExist { requested_account_id: near_primitives::types::AccountId }, + #[error("Contract code for contract ID #{contract_account_id} does not exist")] + NoContractCode { contract_account_id: near_primitives::types::AccountId }, + #[error("Internal error: #{error_message}")] + InternalError { error_message: String }, +} + +#[derive(thiserror::Error, Debug)] +pub enum ViewAccessKeyError { + #[error("Account ID \"{requested_account_id}\" is invalid")] + InvalidAccountId { requested_account_id: near_primitives::types::AccountId }, + #[error("Access key for public key #{public_key} does not exist")] + AccessKeyDoesNotExist { public_key: near_crypto::PublicKey }, + #[error("Internal error: #{error_message}")] + InternalError { error_message: String }, +} + +#[derive(thiserror::Error, Debug)] +pub enum ViewStateError { + #[error("Account ID \"{requested_account_id}\" is invalid")] + InvalidAccountId { requested_account_id: near_primitives::types::AccountId }, + #[error("Internal error: #{error_message}")] + InternalError { error_message: String }, +} + +#[derive(thiserror::Error, Debug)] +pub enum CallFunctionError { + #[error("Account ID \"{requested_account_id}\" is invalid")] + InvalidAccountId { requested_account_id: near_primitives::types::AccountId }, + #[error("Account ID #{requested_account_id} does not exist")] + AccountDoesNotExist { requested_account_id: near_primitives::types::AccountId }, + #[error("Internal error: #{error_message}")] + InternalError { error_message: String }, + #[error("VM error occurred: #{error_message}")] + VMError { error_message: String }, +} + +impl From for ViewContractCodeError { + fn from(view_account_error: ViewAccountError) -> Self { + match view_account_error { + ViewAccountError::InvalidAccountId { requested_account_id } => { + Self::AccountDoesNotExist { requested_account_id } + } + ViewAccountError::AccountDoesNotExist { requested_account_id } => { + Self::AccountDoesNotExist { requested_account_id } + } + ViewAccountError::InternalError { error_message } => { + Self::InternalError { error_message } + } + } + } +} + +impl From for ViewAccountError { + fn from(storage_error: near_primitives::errors::StorageError) -> Self { + Self::InternalError { error_message: storage_error.to_string() } + } +} + +impl From for ViewContractCodeError { + fn from(storage_error: near_primitives::errors::StorageError) -> Self { + Self::InternalError { error_message: storage_error.to_string() } + } +} + +impl From for ViewAccessKeyError { + fn from(storage_error: near_primitives::errors::StorageError) -> Self { + Self::InternalError { error_message: storage_error.to_string() } + } +} + +impl From for ViewStateError { + fn from(storage_error: near_primitives::errors::StorageError) -> Self { + Self::InternalError { error_message: storage_error.to_string() } + } +} + +impl From for CallFunctionError { + fn from(storage_error: near_primitives::errors::StorageError) -> Self { + Self::InternalError { error_message: storage_error.to_string() } + } +} diff --git a/runtime/runtime/src/state_viewer.rs b/runtime/runtime/src/state_viewer/mod.rs similarity index 80% rename from runtime/runtime/src/state_viewer.rs rename to runtime/runtime/src/state_viewer/mod.rs index 5b21f62d7f7..30607548285 100644 --- a/runtime/runtime/src/state_viewer.rs +++ b/runtime/runtime/src/state_viewer/mod.rs @@ -2,6 +2,7 @@ use log::debug; use near_crypto::{KeyType, PublicKey}; use near_primitives::{ account::{AccessKey, Account}, + borsh::BorshDeserialize, contract::ContractCode, hash::CryptoHash, receipt::ActionReceipt, @@ -19,6 +20,8 @@ use std::{str, sync::Arc, time::Instant}; use crate::{actions::execute_function_call, ext::RuntimeExt}; +pub mod errors; + pub struct TrieViewer {} impl TrieViewer { @@ -30,23 +33,30 @@ impl TrieViewer { &self, state_update: &TrieUpdate, account_id: &AccountId, - ) -> Result> { + ) -> Result { if !is_valid_account_id(account_id) { - return Err(format!("Account ID '{}' is not valid", account_id).into()); + return Err(errors::ViewAccountError::InvalidAccountId { + requested_account_id: account_id.clone(), + }); } - get_account(state_update, &account_id)? - .ok_or_else(|| format!("account {} does not exist while viewing", account_id).into()) + get_account(state_update, &account_id)?.ok_or_else(|| { + errors::ViewAccountError::AccountDoesNotExist { + requested_account_id: account_id.clone(), + } + }) } pub fn view_contract_code( &self, state_update: &TrieUpdate, account_id: &AccountId, - ) -> Result> { + ) -> Result { let account = self.view_account(state_update, account_id)?; get_code(state_update, account_id, Some(account.code_hash))?.ok_or_else(|| { - format!("contract code of account {} does not exist while viewing", account_id).into() + errors::ViewContractCodeError::NoContractCode { + contract_account_id: account_id.clone(), + } }) } @@ -55,13 +65,52 @@ impl TrieViewer { state_update: &TrieUpdate, account_id: &AccountId, public_key: &PublicKey, - ) -> Result> { + ) -> Result { if !is_valid_account_id(account_id) { - return Err(format!("Account ID '{}' is not valid", account_id).into()); + return Err(errors::ViewAccessKeyError::InvalidAccountId { + requested_account_id: account_id.clone(), + }); } - get_access_key(state_update, account_id, public_key)? - .ok_or_else(|| format!("access key {} does not exist while viewing", public_key).into()) + get_access_key(state_update, account_id, public_key)?.ok_or_else(|| { + errors::ViewAccessKeyError::AccessKeyDoesNotExist { public_key: public_key.clone() } + }) + } + + pub fn view_access_keys( + &self, + state_update: &TrieUpdate, + account_id: &AccountId, + ) -> Result, errors::ViewAccessKeyError> { + if !is_valid_account_id(account_id) { + return Err(errors::ViewAccessKeyError::InvalidAccountId { + requested_account_id: account_id.clone(), + }); + } + + let prefix = trie_key_parsers::get_raw_prefix_for_access_keys(account_id); + let raw_prefix: &[u8] = prefix.as_ref(); + let access_keys = + state_update + .iter(&prefix)? + .map(|key| { + let key = key?; + let public_key = &key[raw_prefix.len()..]; + let access_key = near_store::get_access_key_raw(&state_update, &key)? + .ok_or_else(|| errors::ViewAccessKeyError::InternalError { + error_message: "Unexpected missing key from iterator".to_string(), + })?; + PublicKey::try_from_slice(public_key) + .map_err(|_| errors::ViewAccessKeyError::InternalError { + error_message: format!( + "Unexpected invalid public key {:?} received from store", + public_key + ), + }) + .map(|key| (key, access_key)) + }) + .collect::, errors::ViewAccessKeyError>>(); + access_keys } pub fn view_state( @@ -69,9 +118,11 @@ impl TrieViewer { state_update: &TrieUpdate, account_id: &AccountId, prefix: &[u8], - ) -> Result> { + ) -> Result { if !is_valid_account_id(account_id) { - return Err(format!("Account ID '{}' is not valid", account_id).into()); + return Err(errors::ViewStateError::InvalidAccountId { + requested_account_id: account_id.clone(), + }); } let mut values = vec![]; let query = trie_key_parsers::get_raw_prefix_for_contract_data(account_id, prefix); @@ -102,14 +153,19 @@ impl TrieViewer { args: &[u8], logs: &mut Vec, epoch_info_provider: &dyn EpochInfoProvider, - ) -> Result, Box> { + ) -> Result, errors::CallFunctionError> { let now = Instant::now(); if !is_valid_account_id(contract_id) { - return Err(format!("Contract ID {:?} is not valid", contract_id).into()); + return Err(errors::CallFunctionError::InvalidAccountId { + requested_account_id: contract_id.clone(), + }); } let root = state_update.get_root(); - let mut account = get_account(&state_update, contract_id)? - .ok_or_else(|| format!("Account {:?} doesn't exist", contract_id))?; + let mut account = get_account(&state_update, contract_id)?.ok_or_else(|| { + errors::CallFunctionError::AccountDoesNotExist { + requested_account_id: contract_id.clone(), + } + })?; // TODO(#1015): Add ability to pass public key and originator_id let originator_id = contract_id; let public_key = PublicKey::empty(KeyType::ED25519); @@ -184,7 +240,7 @@ impl TrieViewer { } let message = format!("wasm execution failed with error: {:?}", err); debug!(target: "runtime", "(exec time {}) {}", time_str, message); - Err(message.into()) + Err(errors::CallFunctionError::VMError { error_message: message }) } else { let outcome = outcome.unwrap(); debug!(target: "runtime", "(exec time {}) result of execution: {:#?}", time_str, outcome); @@ -273,7 +329,7 @@ mod tests { let err = result.unwrap_err(); assert!( - err.to_string().contains(r#"Contract ID "bad!contract" is not valid"#), + err.to_string().contains(r#"Account ID "bad!contract" is invalid"#), format!("Got different error that doesn't match: {}", err) ); } diff --git a/test-utils/testlib/Cargo.toml b/test-utils/testlib/Cargo.toml index 8bffefcdd82..60e41d09631 100644 --- a/test-utils/testlib/Cargo.toml +++ b/test-utils/testlib/Cargo.toml @@ -40,6 +40,7 @@ near-vm-errors = { path = "../../runtime/near-vm-errors" } near-chain = { path = "../../chain/chain" } near-client = { path = "../../chain/client" } near-jsonrpc = { path = "../../chain/jsonrpc" } +near-jsonrpc-primitives = { path = "../../chain/jsonrpc-primitives" } near-network = { path = "../../chain/network" } near-jsonrpc-client = { path = "../../chain/jsonrpc/client" } neard = { path = "../../neard" } diff --git a/test-utils/testlib/src/user/rpc_user.rs b/test-utils/testlib/src/user/rpc_user.rs index 811f042704f..23fe20bbaad 100644 --- a/test-utils/testlib/src/user/rpc_user.rs +++ b/test-utils/testlib/src/user/rpc_user.rs @@ -1,4 +1,3 @@ -use std::convert::TryInto; use std::sync::Arc; use std::thread; use std::time::Duration; @@ -11,6 +10,7 @@ use near_crypto::{PublicKey, Signer}; use near_jsonrpc::client::{new_client, JsonRpcClient}; use near_jsonrpc::ServerError; use near_jsonrpc_client::ChunkId; +use near_jsonrpc_primitives::types::query::RpcQueryResponse; use near_primitives::hash::CryptoHash; use near_primitives::receipt::Receipt; use near_primitives::serialize::{to_base, to_base64}; @@ -20,8 +20,7 @@ use near_primitives::types::{ }; use near_primitives::views::{ AccessKeyView, AccountView, BlockView, CallResult, ChunkView, ContractCodeView, - EpochValidatorInfo, ExecutionOutcomeView, FinalExecutionOutcomeView, QueryResponse, - ViewStateResult, + EpochValidatorInfo, ExecutionOutcomeView, FinalExecutionOutcomeView, ViewStateResult, }; use crate::user::User; @@ -51,7 +50,7 @@ impl RpcUser { self.actix(|client| client.status()).ok() } - pub fn query(&self, path: String, data: &[u8]) -> Result { + pub fn query(&self, path: String, data: &[u8]) -> Result { let data = to_base(data); self.actix(move |client| client.query_by_path(path, data).map_err(|err| err.to_string())) } @@ -63,15 +62,33 @@ impl RpcUser { impl User for RpcUser { fn view_account(&self, account_id: &AccountId) -> Result { - self.query(format!("account/{}", account_id), &[])?.try_into() + let query_response = self.query(format!("account/{}", account_id), &[])?; + match query_response.kind { + near_jsonrpc_primitives::types::query::QueryResponseKind::ViewAccount(account_view) => { + Ok(account_view) + } + _ => Err("Invalid type of response".into()), + } } fn view_state(&self, account_id: &AccountId, prefix: &[u8]) -> Result { - self.query(format!("contract/{}", account_id), prefix)?.try_into() + let query_response = self.query(format!("contract/{}", account_id), prefix)?; + match query_response.kind { + near_jsonrpc_primitives::types::query::QueryResponseKind::ViewState( + view_state_result, + ) => Ok(view_state_result), + _ => Err("Invalid type of response".into()), + } } fn view_contract_code(&self, account_id: &AccountId) -> Result { - self.query(format!("code/{}", account_id), &[])?.try_into() + let query_response = self.query(format!("code/{}", account_id), &[])?; + match query_response.kind { + near_jsonrpc_primitives::types::query::QueryResponseKind::ViewCode( + contract_code_view, + ) => Ok(contract_code_view), + _ => Err("Invalid type of response".into()), + } } fn view_call( @@ -80,8 +97,13 @@ impl User for RpcUser { method_name: &str, args: &[u8], ) -> Result { - self.query(format!("call/{}/{}", account_id, method_name), args) - .and_then(|value| value.try_into()) + let query_response = self.query(format!("call/{}/{}", account_id, method_name), args)?; + match query_response.kind { + near_jsonrpc_primitives::types::query::QueryResponseKind::CallResult(call_result) => { + Ok(call_result) + } + _ => Err("Invalid type of response".into()), + } } fn add_transaction(&self, transaction: SignedTransaction) -> Result<(), ServerError> { @@ -158,7 +180,14 @@ impl User for RpcUser { account_id: &AccountId, public_key: &PublicKey, ) -> Result { - self.query(format!("access_key/{}/{}", account_id, public_key), &[])?.try_into() + let query_response = + self.query(format!("access_key/{}/{}", account_id, public_key), &[])?; + match query_response.kind { + near_jsonrpc_primitives::types::query::QueryResponseKind::AccessKey(access_key) => { + Ok(access_key) + } + _ => Err("Invalid type of response".into()), + } } fn signer(&self) -> Arc {