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}