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//! ```
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//! ```
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//! ```
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//! include_proof: false,
88//! }
89//! };
90//!
91//! let response = client.call(request).await?;
92//!
93//! assert!(matches!(
94//! response,
95//! methods::query::RpcQueryResponse { .. }
96//! ));
97//! # Ok(())
98//! # }
99//! ```
100//!
101//! ### Returns information about a single access key for given account
102//!
103//! ```
104//! use near_jsonrpc_client::{methods, JsonRpcClient};
105//! use near_primitives::{types::{BlockReference, BlockId}, views::QueryRequest};
106//!
107//! # #[tokio::main]
108//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
109//! let client = JsonRpcClient::connect("https://archival-rpc.testnet.fastnear.com");
110//!
111//! let request = methods::query::RpcQueryRequest {
112//! // block_reference: BlockReference::BlockId(BlockId::Hash("CA9bigchLBUYKaHKz3vQxK3Z7Fae2gnVabGrrLJrQEzp".parse()?)),
113//! block_reference: BlockReference::latest(),
114//! request: QueryRequest::ViewAccessKey {
115//! account_id: "fido.testnet".parse()?,
116//! public_key: "ed25519:GwRkfEckaADh5tVxe3oMfHBJZfHAJ55TRWqJv9hSpR38".parse()?
117//! }
118//! };
119//!
120//! let response = client.call(request).await?;
121//!
122//! assert!(matches!(
123//! response,
124//! methods::query::RpcQueryResponse { .. }
125//! ));
126//! # Ok(())
127//! # }
128//! ```
129//!
130//! ### Returns all access keys for a given account.
131//!
132//! ```
133//! use near_jsonrpc_client::{methods, JsonRpcClient};
134//! use near_primitives::{types::{BlockReference, BlockId}, views::QueryRequest};
135//!
136//! # #[tokio::main]
137//! # async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
138//! let client = JsonRpcClient::connect("https://archival-rpc.testnet.fastnear.com");
139//!
140//! let request = methods::query::RpcQueryRequest {
141//! block_reference: BlockReference::BlockId(BlockId::Hash("AUDcb2iNUbsmCsmYGfGuKzyXKimiNcCZjBKTVsbZGnoH".parse()?)),
142//! request: QueryRequest::ViewAccessKeyList {
143//! account_id: "nosedive.testnet".parse()?,
144//! }
145//! };
146//!
147//! let response = client.call(request).await?;
148//!
149//! assert!(matches!(
150//! response,
151//! methods::query::RpcQueryResponse { .. }
152//! ));
153//! # Ok(())
154//! # }
155//! ```
156//!
157//! ### Call a function in a contract deployed on the network
158//!
159//! ```
160//! use near_jsonrpc_client::{methods, JsonRpcClient};
161//! use near_primitives::{types::{BlockReference, BlockId, FunctionArgs}, views::QueryRequest};
162//! use serde_json::json;
163//!
164//! # #[tokio::main]
165//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
166//! let client = JsonRpcClient::connect("https://archival-rpc.testnet.fastnear.com");
167//!
168//! let request = methods::query::RpcQueryRequest {
169//! // block_reference: BlockReference::BlockId(BlockId::Hash("CA9bigchLBUYKaHKz3vQxK3Z7Fae2gnVabGrrLJrQEzp".parse()?)),
170//! block_reference: BlockReference::latest(),
171//! request: QueryRequest::CallFunction {
172//! account_id: "nosedive.testnet".parse()?,
173//! method_name: "status".parse()?,
174//! args: FunctionArgs::from(
175//! json!({
176//! "account_id": "miraclx.testnet",
177//! })
178//! .to_string()
179//! .into_bytes(),
180//! )
181//! }
182//! };
183//!
184//! let response = client.call(request).await?;
185//!
186//! assert!(matches!(
187//! response,
188//! methods::query::RpcQueryResponse { .. }
189//! ));
190//! # Ok(())
191//! # }
192//! ```
193use super::*;
194
195pub use near_jsonrpc_primitives::types::query::{RpcQueryError, RpcQueryRequest, RpcQueryResponse};
196
197impl RpcHandlerResponse for RpcQueryResponse {}
198
199impl RpcHandlerError for RpcQueryError {}
200
201impl private::Sealed for RpcQueryRequest {}
202
203impl RpcMethod for RpcQueryRequest {
204 type Response = RpcQueryResponse;
205 type Error = RpcQueryError;
206
207 fn method_name(&self) -> &str {
208 "query"
209 }
210
211 fn params(&self) -> Result<serde_json::Value, io::Error> {
212 Ok(json!(self))
213 }
214
215 fn parse_handler_response(
216 response: serde_json::Value,
217 ) -> Result<Result<Self::Response, Self::Error>, serde_json::Error> {
218 match serde_json::from_value::<QueryResponse>(response)? {
219 QueryResponse::HandlerResponse(r) => Ok(Ok(r)),
220 QueryResponse::HandlerError(LegacyQueryError {
221 error,
222 block_height,
223 block_hash,
224 }) => {
225 let mut err_parts = error.split(' ');
226 let query_error = if let (
227 Some("access"),
228 Some("key"),
229 Some(pk),
230 Some("does"),
231 Some("not"),
232 Some("exist"),
233 Some("while"),
234 Some("viewing"),
235 None,
236 ) = (
237 err_parts.next(),
238 err_parts.next(),
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 ) {
247 let public_key = pk
248 .parse::<near_crypto::PublicKey>()
249 .map_err(serde::de::Error::custom)?;
250 RpcQueryError::UnknownAccessKey {
251 public_key,
252 block_height,
253 block_hash,
254 }
255 } else {
256 RpcQueryError::ContractExecutionError {
257 vm_error: error,
258 block_height,
259 block_hash,
260 }
261 };
262
263 Ok(Err(query_error))
264 }
265 }
266 }
267}
268
269#[derive(serde::Deserialize)]
270#[serde(untagged)]
271enum QueryResponse {
272 HandlerResponse(RpcQueryResponse),
273 HandlerError(LegacyQueryError),
274}
275
276#[derive(serde::Deserialize)]
277struct LegacyQueryError {
278 error: String,
279 block_height: near_primitives::types::BlockHeight,
280 block_hash: near_primitives::hash::CryptoHash,
281}
282
283#[cfg(test)]
284mod tests {
285 use {super::*, crate::*};
286
287 /// This test is to make sure the method executor treats `&RpcMethod`s the same as `RpcMethod`s.
288 #[tokio::test]
289 async fn test_unknown_method() -> Result<(), Box<dyn std::error::Error>> {
290 let client = JsonRpcClient::connect("https://rpc.testnet.near.org");
291
292 let request = RpcQueryRequest {
293 block_reference: near_primitives::types::BlockReference::latest(),
294 request: near_primitives::views::QueryRequest::CallFunction {
295 account_id: "testnet".parse()?,
296 method_name: "some_unavailable_method".to_string(),
297 args: vec![].into(),
298 },
299 };
300
301 let response_err = client.call(&request).await.unwrap_err();
302
303 assert!(
304 matches!(
305 response_err.handler_error(),
306 Some(RpcQueryError::ContractExecutionError {
307 ref vm_error,
308 ..
309 }) if vm_error.contains("MethodResolveError(MethodNotFound)")
310 ),
311 "this is unexpected: {:#?}",
312 response_err
313 );
314
315 Ok(())
316 }
317
318 #[tokio::test]
319 async fn test_unknown_access_key() -> Result<(), Box<dyn std::error::Error>> {
320 let client = JsonRpcClient::connect("https://archival-rpc.testnet.fastnear.com");
321
322 let request = RpcQueryRequest {
323 block_reference: near_primitives::types::BlockReference::BlockId(
324 near_primitives::types::BlockId::Height(63503911),
325 ),
326 request: near_primitives::views::QueryRequest::ViewAccessKey {
327 account_id: "miraclx.testnet".parse()?,
328 public_key: "ed25519:9KnjTjL6vVoM8heHvCcTgLZ67FwFkiLsNtknFAVsVvYY".parse()?,
329 },
330 };
331
332 let response_err = client.call(request).await.unwrap_err();
333
334 assert!(
335 matches!(
336 response_err.handler_error(),
337 Some(RpcQueryError::UnknownAccessKey {
338 ref public_key,
339 block_height: 63503911,
340 ..
341 }) if public_key.to_string() == "ed25519:9KnjTjL6vVoM8heHvCcTgLZ67FwFkiLsNtknFAVsVvYY"
342 ),
343 "this is unexpected: {:#?}",
344 response_err
345 );
346
347 Ok(())
348 }
349
350 #[tokio::test]
351 async fn test_contract_execution_error() -> Result<(), Box<dyn std::error::Error>> {
352 let client = JsonRpcClient::connect("https://archival-rpc.testnet.fastnear.com");
353
354 let request = RpcQueryRequest {
355 block_reference: near_primitives::types::BlockReference::BlockId(
356 near_primitives::types::BlockId::Height(63503911),
357 ),
358 request: near_primitives::views::QueryRequest::CallFunction {
359 account_id: "miraclx.testnet".parse()?,
360 method_name: "".to_string(),
361 args: vec![].into(),
362 },
363 };
364
365 let response_err = client.call(request).await.unwrap_err();
366
367 assert!(
368 matches!(
369 response_err.handler_error(),
370 Some(RpcQueryError::ContractExecutionError {
371 ref vm_error,
372 block_height: 63503911,
373 ..
374 }) if vm_error.contains("MethodResolveError(MethodEmptyName)")
375 ),
376 "this is unexpected: {:#?}",
377 response_err
378 );
379
380 Ok(())
381 }
382}