Skip to main content

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}