near_workspaces/rpc/
query.rs

1//! This module defines a bunch of internal types used solely for querying into
2//! RPC methods to get info about what's on the chain.
3//!
4//! Note that the types defined are exposed as-is for users to reference in their own
5//! functions or structs as needed. These types cannot be created outside of workspaces.
6//! To use them, refer to surface level types like [`Account`], [`Contract`] and [`Worker`].
7//!
8//! For example, to query into downloading contract state:
9//! ```
10//! use near_workspaces::{AccountId, Network, Worker};
11//! use near_workspaces::rpc::query::{Query, ViewState};
12//!
13//! async fn my_func(worker: &Worker<impl Network>) -> anyhow::Result<()> {
14//!     let contract_id: AccountId = "some-contract.near".parse()?;
15//!     let query: Query<'_, ViewState> = worker.view_state(&contract_id);
16//!     let bytes = query.await?;
17//!     Ok(())
18//! }
19//! ```
20//! But most of the time, we do not need to worry about these types as they are
21//! meant to be transitory, and only exist while calling into their immediate
22//! methods. So the above example should look more like the following:
23//! ```ignore
24//! async fn my_func(worker: &Worker<impl Network>) -> anyhow::Result<()> {
25//!     let contract_id: AccountId = "some-contract.near".parse()?;
26//!     let bytes = worker.view_state(&contract_id).await?;
27//!     Ok(())
28//! }
29//! ```
30//!
31//! [`Account`]: crate::Account
32//! [`Contract`]: crate::Contract
33//! [`Worker`]: crate::Worker
34
35use std::collections::HashMap;
36use std::fmt::{Debug, Display};
37
38use near_account_id::AccountId;
39use near_jsonrpc_client::methods::query::RpcQueryResponse;
40use near_jsonrpc_client::methods::{self, RpcMethod};
41use near_jsonrpc_primitives::types::chunks::ChunkReference;
42use near_jsonrpc_primitives::types::query::QueryResponseKind;
43use near_primitives::types::{BlockId, BlockReference, StoreKey};
44use near_primitives::views::{BlockView, QueryRequest};
45use near_token::NearToken;
46
47use crate::error::RpcErrorCode;
48use crate::operations::Function;
49use crate::result::ViewResultDetails;
50use crate::rpc::client::Client;
51use crate::rpc::{tool, BoxFuture};
52use crate::types::account::AccountDetails;
53use crate::types::{AccessKey, AccessKeyInfo, BlockHeight, Finality, PublicKey, ShardId};
54use crate::{Block, Chunk, CryptoHash, Result};
55
56/// `Query` object allows creating queries into the network of our choice. This object is
57/// usually given from making calls from other functions such as [`view_state`].
58///
59/// [`view_state`]: crate::worker::Worker::view_state
60pub struct Query<'a, T> {
61    pub(crate) method: T,
62    pub(crate) client: &'a Client,
63    pub(crate) block_ref: Option<BlockReference>,
64}
65
66impl<'a, T> Query<'a, T> {
67    pub(crate) fn new(client: &'a Client, method: T) -> Self {
68        Self {
69            method,
70            client,
71            block_ref: None,
72        }
73    }
74
75    /// Specify at which block height to query from. Note that only archival
76    /// networks will have the full history while networks like mainnet or testnet will
77    /// only have the history from 5 or less epochs ago.
78    pub fn block_height(mut self, height: BlockHeight) -> Self {
79        self.block_ref = Some(BlockId::Height(height).into());
80        self
81    }
82
83    /// Specify at which block hash to query from. Note that only archival
84    /// networks will have the full history while networks like mainnet or testnet will
85    /// only have the history from 5 or less epochs ago.
86    pub fn block_hash(mut self, hash: CryptoHash) -> Self {
87        self.block_ref = Some(BlockId::Hash(near_primitives::hash::CryptoHash(hash.0)).into());
88        self
89    }
90}
91
92// Constrained to RpcQueryRequest, since methods like GasPrice only take block_id but not Finality.
93impl<T> Query<'_, T>
94where
95    T: ProcessQuery<Method = methods::query::RpcQueryRequest>,
96{
97    /// Specify at which block [`Finality`] to query from.
98    pub fn finality(mut self, value: Finality) -> Self {
99        self.block_ref = Some(value.into());
100        self
101    }
102
103    pub(crate) fn block_reference(mut self, value: BlockReference) -> Self {
104        self.block_ref = Some(value);
105        self
106    }
107}
108
109impl<'a, T, R> std::future::IntoFuture for Query<'a, T>
110where
111    T: ProcessQuery<Output = R> + Send + Sync + 'static,
112    <T as ProcessQuery>::Method: RpcMethod + Debug + Send + Sync,
113    <<T as ProcessQuery>::Method as RpcMethod>::Response: Debug + Send + Sync,
114    <<T as ProcessQuery>::Method as RpcMethod>::Error: Debug + Display + Send + Sync,
115{
116    type Output = Result<R>;
117
118    // TODO: boxed future required due to impl Trait as type alias being unstable. So once
119    // https://github.com/rust-lang/rust/issues/63063 is resolved, we can move to that instead.
120    type IntoFuture = BoxFuture<'a, Self::Output>;
121
122    fn into_future(self) -> Self::IntoFuture {
123        Box::pin(async move {
124            let block_reference = self.block_ref.unwrap_or_else(BlockReference::latest);
125            let resp = self
126                .client
127                .query(self.method.into_request(block_reference)?)
128                .await
129                .map_err(|e| RpcErrorCode::QueryFailure.custom(e))?;
130
131            T::from_response(resp)
132        })
133    }
134}
135
136// Note: this trait is exposed publicly due to constraining with the impl offering `finality`.
137/// Trait used as a converter from WorkspaceRequest to near-rpc request,
138/// and from near-rpc response to a WorkspaceResult.
139///
140/// Mostly used internally to facilitate syntax sugar for performing RPC requests with async builders.
141pub trait ProcessQuery {
142    // TODO: associated default type is unstable. So for now, will require writing
143    // the manual impls for query_request
144    /// Method for doing the internal RPC request to the network of our choosing.
145    type Method: RpcMethod;
146
147    /// Expected output after performing a query. This is mainly to convert over
148    /// the type from near-primitives to a workspace type.
149    type Output;
150
151    /// Convert into the Request object that is required to perform the RPC request.
152    fn into_request(self, block_ref: BlockReference) -> Result<Self::Method>;
153
154    /// Convert the response from the RPC request to a type of our choosing, mainly to conform
155    /// to workspaces related types from the near-primitives or json types from the network.
156    fn from_response(resp: <Self::Method as RpcMethod>::Response) -> Result<Self::Output>;
157}
158
159pub struct ViewFunction {
160    pub(crate) account_id: AccountId,
161    pub(crate) function: Function,
162}
163
164pub struct ViewCode {
165    pub(crate) account_id: AccountId,
166}
167
168pub struct ViewAccount {
169    pub(crate) account_id: AccountId,
170}
171
172pub struct ViewBlock;
173
174pub struct ViewState {
175    account_id: AccountId,
176    prefix: Option<Vec<u8>>,
177}
178
179pub struct ViewAccessKey {
180    pub(crate) account_id: AccountId,
181    pub(crate) public_key: PublicKey,
182}
183
184pub struct ViewAccessKeyList {
185    pub(crate) account_id: AccountId,
186}
187
188pub struct GasPrice;
189
190impl ProcessQuery for ViewFunction {
191    type Method = methods::query::RpcQueryRequest;
192    type Output = ViewResultDetails;
193
194    fn into_request(self, block_reference: BlockReference) -> Result<Self::Method> {
195        Ok(Self::Method {
196            block_reference,
197            request: QueryRequest::CallFunction {
198                account_id: self.account_id,
199                method_name: self.function.name,
200                args: self.function.args?.into(),
201            },
202        })
203    }
204
205    fn from_response(resp: RpcQueryResponse) -> Result<Self::Output> {
206        match resp.kind {
207            QueryResponseKind::CallResult(result) => Ok(result.into()),
208            _ => Err(RpcErrorCode::QueryReturnedInvalidData.message("while querying account")),
209        }
210    }
211}
212
213// Specific builder methods attached to a ViewFunction.
214impl Query<'_, ViewFunction> {
215    /// Provide the arguments for the call. These args are serialized bytes from either
216    /// a JSON or Borsh serializable set of arguments. To use the more specific versions
217    /// with better quality of life, use `args_json` or `args_borsh`.
218    pub fn args(mut self, args: Vec<u8>) -> Self {
219        self.method.function = self.method.function.args(args);
220        self
221    }
222
223    /// Similar to `args`, specify an argument that is JSON serializable and can be
224    /// accepted by the equivalent contract. Recommend to use something like
225    /// `serde_json::json!` macro to easily serialize the arguments.
226    pub fn args_json<U: serde::Serialize>(mut self, args: U) -> Self {
227        self.method.function = self.method.function.args_json(args);
228        self
229    }
230
231    /// Similar to `args`, specify an argument that is borsh serializable and can be
232    /// accepted by the equivalent contract.
233    pub fn args_borsh<U: near_primitives::borsh::BorshSerialize>(mut self, args: U) -> Self {
234        self.method.function = self.method.function.args_borsh(args);
235        self
236    }
237}
238
239impl ProcessQuery for ViewCode {
240    type Method = methods::query::RpcQueryRequest;
241    type Output = Vec<u8>;
242
243    fn into_request(self, block_reference: BlockReference) -> Result<Self::Method> {
244        Ok(Self::Method {
245            block_reference,
246            request: QueryRequest::ViewCode {
247                account_id: self.account_id,
248            },
249        })
250    }
251
252    fn from_response(resp: RpcQueryResponse) -> Result<Self::Output> {
253        match resp.kind {
254            QueryResponseKind::ViewCode(contract) => Ok(contract.code),
255            _ => Err(RpcErrorCode::QueryReturnedInvalidData.message("while querying code")),
256        }
257    }
258}
259
260impl ProcessQuery for ViewAccount {
261    type Method = methods::query::RpcQueryRequest;
262    type Output = AccountDetails;
263
264    fn into_request(self, block_reference: BlockReference) -> Result<Self::Method> {
265        Ok(Self::Method {
266            block_reference,
267            request: QueryRequest::ViewAccount {
268                account_id: self.account_id,
269            },
270        })
271    }
272
273    fn from_response(resp: RpcQueryResponse) -> Result<Self::Output> {
274        match resp.kind {
275            QueryResponseKind::ViewAccount(account) => Ok(account.into()),
276            _ => Err(RpcErrorCode::QueryReturnedInvalidData.message("while querying account")),
277        }
278    }
279}
280
281impl ProcessQuery for ViewBlock {
282    type Method = methods::block::RpcBlockRequest;
283    type Output = Block;
284
285    fn into_request(self, block_reference: BlockReference) -> Result<Self::Method> {
286        Ok(Self::Method { block_reference })
287    }
288
289    fn from_response(view: BlockView) -> Result<Self::Output> {
290        Ok(view.into())
291    }
292}
293
294impl ProcessQuery for ViewState {
295    type Method = methods::query::RpcQueryRequest;
296    type Output = HashMap<Vec<u8>, Vec<u8>>;
297
298    fn into_request(self, block_reference: BlockReference) -> Result<Self::Method> {
299        Ok(Self::Method {
300            block_reference,
301            request: QueryRequest::ViewState {
302                account_id: self.account_id,
303                prefix: StoreKey::from(self.prefix.unwrap_or_default()),
304                include_proof: false,
305            },
306        })
307    }
308
309    fn from_response(resp: <Self::Method as RpcMethod>::Response) -> Result<Self::Output> {
310        match resp.kind {
311            QueryResponseKind::ViewState(state) => Ok(tool::into_state_map(state.values)),
312            _ => Err(RpcErrorCode::QueryReturnedInvalidData.message("while querying state")),
313        }
314    }
315}
316
317impl<'a> Query<'a, ViewState> {
318    pub(crate) fn view_state(client: &'a Client, id: &AccountId) -> Self {
319        Self::new(
320            client,
321            ViewState {
322                account_id: id.clone(),
323                prefix: None,
324            },
325        )
326    }
327
328    /// Set the prefix for viewing the state.
329    pub fn prefix(mut self, value: &[u8]) -> Self {
330        self.method.prefix = Some(value.into());
331        self
332    }
333}
334
335impl ProcessQuery for ViewAccessKey {
336    type Method = methods::query::RpcQueryRequest;
337    type Output = AccessKey;
338
339    fn into_request(self, block_reference: BlockReference) -> Result<Self::Method> {
340        Ok(Self::Method {
341            block_reference,
342            request: QueryRequest::ViewAccessKey {
343                account_id: self.account_id,
344                public_key: self.public_key.into(),
345            },
346        })
347    }
348
349    fn from_response(resp: <Self::Method as RpcMethod>::Response) -> Result<Self::Output> {
350        match resp.kind {
351            QueryResponseKind::AccessKey(key) => Ok(key.into()),
352            _ => Err(RpcErrorCode::QueryReturnedInvalidData.message("while querying access key")),
353        }
354    }
355}
356
357impl ProcessQuery for ViewAccessKeyList {
358    type Method = methods::query::RpcQueryRequest;
359    type Output = Vec<AccessKeyInfo>;
360
361    fn into_request(self, block_reference: BlockReference) -> Result<Self::Method> {
362        Ok(Self::Method {
363            block_reference,
364            request: QueryRequest::ViewAccessKeyList {
365                account_id: self.account_id,
366            },
367        })
368    }
369
370    fn from_response(resp: <Self::Method as RpcMethod>::Response) -> Result<Self::Output> {
371        match resp.kind {
372            QueryResponseKind::AccessKeyList(keylist) => {
373                Ok(keylist.keys.into_iter().map(Into::into).collect())
374            }
375            _ => Err(RpcErrorCode::QueryReturnedInvalidData.message("while querying access keys")),
376        }
377    }
378}
379
380impl ProcessQuery for GasPrice {
381    type Method = methods::gas_price::RpcGasPriceRequest;
382    type Output = NearToken;
383
384    fn into_request(self, block_ref: BlockReference) -> Result<Self::Method> {
385        let block_id = match block_ref {
386            // User provided input via `block_hash` or `block_height` functions.
387            BlockReference::BlockId(block_id) => Some(block_id),
388            // default case, set by `Query` struct via BlockReference::latest.
389            BlockReference::Finality(_finality) => None,
390            // Should not be reachable, unless code got changed.
391            BlockReference::SyncCheckpoint(point) => {
392                return Err(RpcErrorCode::QueryFailure.message(format!(
393                    "Cannot supply sync checkpoint to gas price: {point:?}. Potential API bug?"
394                )))
395            }
396        };
397
398        Ok(Self::Method { block_id })
399    }
400
401    fn from_response(resp: <Self::Method as RpcMethod>::Response) -> Result<Self::Output> {
402        Ok(resp.gas_price)
403    }
404}
405
406/// Query object to query for chunk related details at a specific `ChunkReference` which
407/// consists of either a chunk [`CryptoHash`], or a `BlockShardId`.
408///
409/// `BlockShardId` consists of [`ShardId`] and either block [`CryptoHash`] or [`BlockHeight`]
410/// The default behavior where a `ChunkReference` is not supplied will use a `BlockShardId`
411/// referencing the latest block `CryptoHash` with `ShardId` of 0.
412pub struct QueryChunk<'a> {
413    client: &'a Client,
414    chunk_ref: Option<ChunkReference>,
415}
416
417impl<'a> QueryChunk<'a> {
418    pub(crate) fn new(client: &'a Client) -> Self {
419        Self {
420            client,
421            chunk_ref: None,
422        }
423    }
424
425    /// Specify at which block hash and shard id to query the chunk from. Note that only
426    /// archival networks will have the full history while networks like mainnet or testnet
427    /// will only have the history from 5 or less epochs ago.
428    pub fn block_hash_and_shard(mut self, hash: CryptoHash, shard_id: ShardId) -> Self {
429        self.chunk_ref = Some(ChunkReference::BlockShardId {
430            block_id: BlockId::Hash(near_primitives::hash::CryptoHash(hash.0)),
431            shard_id: shard_id.into(),
432        });
433        self
434    }
435
436    /// Specify at which block height and shard id to query the chunk from. Note that only
437    /// archival networks will have the full history while networks like mainnet or testnet
438    /// will only have the history from 5 or less epochs ago.
439    pub fn block_height_and_shard(mut self, height: BlockHeight, shard_id: ShardId) -> Self {
440        self.chunk_ref = Some(ChunkReference::BlockShardId {
441            block_id: BlockId::Height(height),
442            shard_id: shard_id.into(),
443        });
444        self
445    }
446
447    /// Specify at which chunk hash to query the chunk from.
448    pub fn chunk_hash(mut self, hash: CryptoHash) -> Self {
449        self.chunk_ref = Some(ChunkReference::ChunkHash {
450            chunk_id: near_primitives::hash::CryptoHash(hash.0),
451        });
452        self
453    }
454}
455
456impl<'a> std::future::IntoFuture for QueryChunk<'a> {
457    type Output = Result<Chunk>;
458    type IntoFuture = BoxFuture<'a, Self::Output>;
459
460    fn into_future(self) -> Self::IntoFuture {
461        Box::pin(async move {
462            let chunk_reference = if let Some(chunk_ref) = self.chunk_ref {
463                chunk_ref
464            } else {
465                // Use the latest block hash in the case the user doesn't supply the ChunkReference. Note that
466                // shard_id 0 is used in the default case.
467                let block_view = self.client.view_block(None).await?;
468                ChunkReference::BlockShardId {
469                    block_id: BlockId::Hash(block_view.header.hash),
470                    shard_id: 0.into(),
471                }
472            };
473
474            let chunk_view = self
475                .client
476                .query(methods::chunk::RpcChunkRequest { chunk_reference })
477                .await
478                .map_err(|e| RpcErrorCode::QueryFailure.custom(e))?;
479
480            Ok(chunk_view.into())
481        })
482    }
483}