near_jsonrpc_client/methods/query.rs
1//! This module allows you to make generic requests to the network.
2//!
3//! The `RpcQueryRequest` struct takes in a [`BlockReference`](https://docs.rs/near-primitives/0.12.0/near_primitives/types/enum.BlockReference.html) and a [`QueryRequest`](https://docs.rs/near-primitives/0.12.0/near_primitives/views/enum.QueryRequest.html).
4//!
5//! The `BlockReference` enum allows you to specify a block by `Finality`, `BlockId` or `SyncCheckpoint`.
6//!
7//! The `QueryRequest` enum provides multiple variaints for performing the following actions:
8//! - View an account's details
9//! - View a contract's code
10//! - View the state of an account
11//! - View the `AccessKey` of an account
12//! - View the `AccessKeyList` of an account
13//! - Call a function in a contract deployed on the network.
14//!
15//! ## Examples
16//!
17//! ### Returns basic account information.
18//!
19//! ```no_run
20//! use near_jsonrpc_client::{methods, JsonRpcClient};
21//! use near_primitives::{types::{BlockReference, BlockId}, views::QueryRequest};
22//!
23//! # #[tokio::main]
24//! # async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
25//! let client = JsonRpcClient::connect("https://archival-rpc.mainnet.fastnear.com");
26//!
27//! let request = methods::query::RpcQueryRequest {
28//! block_reference: BlockReference::BlockId(BlockId::Hash("6Qq9hYG7vQhnje4iC1hfbyhh9vNQoNem7j8Dxi7EVSdN".parse()?)),
29//! request: QueryRequest::ViewAccount {
30//! account_id: "itranscend.near".parse()?,
31//! }
32//! };
33//!
34//! let response = client.call(request).await?;
35//!
36//! assert!(matches!(
37//! response,
38//! methods::query::RpcQueryResponse { .. }
39//! ));
40//! # Ok(())
41//! # }
42//! ```
43//!
44//! ### Returns the contract code (Wasm binary) deployed to the account. The returned code will be encoded in base64.
45//!
46//! ```no_run
47//! use near_jsonrpc_client::{methods, JsonRpcClient};
48//! use near_primitives::{types::{BlockReference, BlockId}, views::QueryRequest};
49//!
50//! # #[tokio::main]
51//! # async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
52//! let client = JsonRpcClient::connect("https://archival-rpc.testnet.fastnear.com");
53//!
54//! let request = methods::query::RpcQueryRequest {
55//! block_reference: BlockReference::BlockId(BlockId::Hash("CrYzVUyam5TMJTcJDJMSJ7Fzc79SDTgtK1SfVpEnteZF".parse()?)),
56//! request: QueryRequest::ViewCode {
57//! account_id: "nosedive.testnet".parse()?,
58//! }
59//! };
60//!
61//! let response = client.call(request).await?;
62//!
63//! assert!(matches!(
64//! response,
65//! methods::query::RpcQueryResponse { .. }
66//! ));
67//! # Ok(())
68//! # }
69//! ```
70//!
71//! ### Returns the account state
72//!
73//! ```no_run
74//! use near_jsonrpc_client::{methods, JsonRpcClient};
75//! use near_primitives::{types::{BlockReference, BlockId, StoreKey}, views::QueryRequest};
76//!
77//! # #[tokio::main]
78//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
79//! let client = JsonRpcClient::connect("https://archival-rpc.testnet.fastnear.com");
80//!
81//! let request = methods::query::RpcQueryRequest {
82//! // block_reference: BlockReference::BlockId(BlockId::Hash("AUDcb2iNUbsmCsmYGfGuKzyXKimiNcCZjBKTVsbZGnoH".parse()?)),
83//! block_reference: BlockReference::latest(),
84//! request: QueryRequest::ViewState {
85//! account_id: "nosedive.testnet".parse()?,
86//! prefix: StoreKey::from(vec![]),
87//! after_key: None,
88//! limit: None,
89//! include_proof: false,
90//! }
91//! };
92//!
93//! let response = client.call(request).await?;
94//!
95//! assert!(matches!(
96//! response,
97//! methods::query::RpcQueryResponse { .. }
98//! ));
99//! # Ok(())
100//! # }
101//! ```
102//!
103//! ### Returns information about a single access key for given account
104//!
105//! ```no_run
106//! use near_jsonrpc_client::{methods, JsonRpcClient};
107//! use near_primitives::{types::{BlockReference, BlockId}, views::QueryRequest};
108//!
109//! # #[tokio::main]
110//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
111//! let client = JsonRpcClient::connect("https://archival-rpc.testnet.fastnear.com");
112//!
113//! let request = methods::query::RpcQueryRequest {
114//! // block_reference: BlockReference::BlockId(BlockId::Hash("CA9bigchLBUYKaHKz3vQxK3Z7Fae2gnVabGrrLJrQEzp".parse()?)),
115//! block_reference: BlockReference::latest(),
116//! request: QueryRequest::ViewAccessKey {
117//! account_id: "fido.testnet".parse()?,
118//! public_key: "ed25519:GwRkfEckaADh5tVxe3oMfHBJZfHAJ55TRWqJv9hSpR38".parse()?
119//! }
120//! };
121//!
122//! let response = client.call(request).await?;
123//!
124//! assert!(matches!(
125//! response,
126//! methods::query::RpcQueryResponse { .. }
127//! ));
128//! # Ok(())
129//! # }
130//! ```
131//!
132//! ### Returns all access keys for a given account.
133//!
134//! ```no_run
135//! use near_jsonrpc_client::{methods, JsonRpcClient};
136//! use near_primitives::{types::{BlockReference, BlockId}, views::QueryRequest};
137//!
138//! # #[tokio::main]
139//! # async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
140//! let client = JsonRpcClient::connect("https://archival-rpc.testnet.fastnear.com");
141//!
142//! let request = methods::query::RpcQueryRequest {
143//! block_reference: BlockReference::BlockId(BlockId::Hash("AUDcb2iNUbsmCsmYGfGuKzyXKimiNcCZjBKTVsbZGnoH".parse()?)),
144//! request: QueryRequest::ViewAccessKeyList {
145//! account_id: "nosedive.testnet".parse()?,
146//! }
147//! };
148//!
149//! let response = client.call(request).await?;
150//!
151//! assert!(matches!(
152//! response,
153//! methods::query::RpcQueryResponse { .. }
154//! ));
155//! # Ok(())
156//! # }
157//! ```
158//!
159//! ### Call a function in a contract deployed on the network
160//!
161//! ```no_run
162//! use near_jsonrpc_client::{methods, JsonRpcClient};
163//! use near_primitives::{types::{BlockReference, BlockId, FunctionArgs}, views::QueryRequest};
164//! use serde_json::json;
165//!
166//! # #[tokio::main]
167//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
168//! let client = JsonRpcClient::connect("https://archival-rpc.testnet.fastnear.com");
169//!
170//! let request = methods::query::RpcQueryRequest {
171//! // block_reference: BlockReference::BlockId(BlockId::Hash("CA9bigchLBUYKaHKz3vQxK3Z7Fae2gnVabGrrLJrQEzp".parse()?)),
172//! block_reference: BlockReference::latest(),
173//! request: QueryRequest::CallFunction {
174//! account_id: "nosedive.testnet".parse()?,
175//! method_name: "status".parse()?,
176//! args: FunctionArgs::from(
177//! json!({
178//! "account_id": "miraclx.testnet",
179//! })
180//! .to_string()
181//! .into_bytes(),
182//! )
183//! }
184//! };
185//!
186//! let response = client.call(request).await?;
187//!
188//! assert!(matches!(
189//! response,
190//! methods::query::RpcQueryResponse { .. }
191//! ));
192//! # Ok(())
193//! # }
194//! ```
195use super::*;
196
197pub use near_jsonrpc_primitives::types::query::{RpcQueryError, RpcQueryRequest, RpcQueryResponse};
198
199impl RpcHandlerResponse for RpcQueryResponse {}
200
201impl RpcHandlerError for RpcQueryError {}
202
203impl private::Sealed for RpcQueryRequest {}
204
205impl RpcMethod for RpcQueryRequest {
206 type Response = RpcQueryResponse;
207 type Error = RpcQueryError;
208
209 fn method_name(&self) -> &str {
210 "query"
211 }
212
213 fn params(&self) -> Result<serde_json::Value, io::Error> {
214 Ok(json!(self))
215 }
216
217 fn parse_handler_response(
218 response: serde_json::Value,
219 ) -> Result<Result<Self::Response, Self::Error>, serde_json::Error> {
220 match serde_json::from_value::<QueryResponse>(response)? {
221 QueryResponse::HandlerResponse(r) => Ok(Ok(r)),
222 QueryResponse::HandlerError(LegacyQueryError {
223 error,
224 block_height,
225 block_hash,
226 }) => {
227 let mut err_parts = error.split(' ');
228 let query_error = if let (
229 Some("access"),
230 Some("key"),
231 Some(pk),
232 Some("does"),
233 Some("not"),
234 Some("exist"),
235 Some("while"),
236 Some("viewing"),
237 None,
238 ) = (
239 err_parts.next(),
240 err_parts.next(),
241 err_parts.next(),
242 err_parts.next(),
243 err_parts.next(),
244 err_parts.next(),
245 err_parts.next(),
246 err_parts.next(),
247 err_parts.next(),
248 ) {
249 let public_key = pk
250 .parse::<near_crypto::PublicKey>()
251 .map_err(serde::de::Error::custom)?;
252 RpcQueryError::UnknownAccessKey {
253 public_key,
254 block_height,
255 block_hash,
256 }
257 } else {
258 RpcQueryError::ContractExecutionError {
259 vm_error: error.clone(),
260 error: near_primitives::errors::FunctionCallError::ExecutionError(error),
261 block_height,
262 block_hash,
263 }
264 };
265
266 Ok(Err(query_error))
267 }
268 }
269 }
270}
271
272#[derive(serde::Deserialize)]
273#[serde(untagged)]
274enum QueryResponse {
275 HandlerResponse(RpcQueryResponse),
276 HandlerError(LegacyQueryError),
277}
278
279#[derive(serde::Deserialize)]
280struct LegacyQueryError {
281 error: String,
282 block_height: near_primitives::types::BlockHeight,
283 block_hash: near_primitives::hash::CryptoHash,
284}
285
286#[cfg(test)]
287mod tests {
288 use {super::*, crate::*};
289
290 /// This test is to make sure the method executor treats `&RpcMethod`s the same as `RpcMethod`s.
291 #[tokio::test]
292 async fn test_unknown_method() -> Result<(), Box<dyn std::error::Error>> {
293 let client = JsonRpcClient::connect("https://rpc.testnet.near.org");
294
295 let request = RpcQueryRequest {
296 block_reference: near_primitives::types::BlockReference::latest(),
297 request: near_primitives::views::QueryRequest::CallFunction {
298 account_id: "testnet".parse()?,
299 method_name: "some_unavailable_method".to_string(),
300 args: vec![].into(),
301 },
302 };
303
304 let response_err = client.call(&request).await.unwrap_err();
305
306 assert!(
307 matches!(
308 response_err.handler_error(),
309 Some(RpcQueryError::ContractExecutionError {
310 vm_error,
311 ..
312 }) if vm_error.contains("MethodResolveError(MethodNotFound)")
313 ),
314 "this is unexpected: {:#?}",
315 response_err
316 );
317
318 Ok(())
319 }
320
321 #[tokio::test]
322 async fn test_unknown_access_key() -> Result<(), Box<dyn std::error::Error>> {
323 let client = JsonRpcClient::connect("https://archival-rpc.testnet.fastnear.com");
324
325 let request = RpcQueryRequest {
326 block_reference: near_primitives::types::BlockReference::BlockId(
327 near_primitives::types::BlockId::Height(63503911),
328 ),
329 request: near_primitives::views::QueryRequest::ViewAccessKey {
330 account_id: "miraclx.testnet".parse()?,
331 public_key: "ed25519:9KnjTjL6vVoM8heHvCcTgLZ67FwFkiLsNtknFAVsVvYY".parse()?,
332 },
333 };
334
335 let response_err = client.call(request).await.unwrap_err();
336
337 assert!(
338 matches!(
339 response_err.handler_error(),
340 Some(RpcQueryError::UnknownAccessKey {
341 public_key,
342 block_height: 63503911,
343 ..
344 }) if public_key.to_string() == "ed25519:9KnjTjL6vVoM8heHvCcTgLZ67FwFkiLsNtknFAVsVvYY"
345 ),
346 "this is unexpected: {:#?}",
347 response_err
348 );
349
350 Ok(())
351 }
352
353 #[tokio::test]
354 async fn test_contract_execution_error() -> Result<(), Box<dyn std::error::Error>> {
355 let client = JsonRpcClient::connect("https://archival-rpc.testnet.fastnear.com");
356
357 let request = RpcQueryRequest {
358 block_reference: near_primitives::types::BlockReference::BlockId(
359 near_primitives::types::BlockId::Height(63503911),
360 ),
361 request: near_primitives::views::QueryRequest::CallFunction {
362 account_id: "miraclx.testnet".parse()?,
363 method_name: "".to_string(),
364 args: vec![].into(),
365 },
366 };
367
368 let response_err = client.call(request).await.unwrap_err();
369
370 assert!(
371 matches!(
372 response_err.handler_error(),
373 Some(RpcQueryError::ContractExecutionError {
374 vm_error,
375 block_height: 63503911,
376 ..
377 }) if vm_error.contains("MethodResolveError(MethodEmptyName)")
378 ),
379 "this is unexpected: {:#?}",
380 response_err
381 );
382
383 Ok(())
384 }
385}