near_fetch/
query.rs

1//! This module defines a bunch of internal types used solely for querying into RPC
2//! methods to retrieve info about what's on the chain. Note that the types defined
3//! are exposed as-is for users to reference in their own functions or structs as
4//! needed. These types cannot be created outside of near_fetch.
5
6use std::fmt::{Debug, Display};
7
8use near_account_id::AccountId;
9use near_crypto::PublicKey;
10use near_jsonrpc_client::methods::query::RpcQueryResponse;
11use near_jsonrpc_client::methods::{self, RpcMethod};
12use near_jsonrpc_primitives::types::chunks::ChunkReference;
13use near_jsonrpc_primitives::types::query::QueryResponseKind;
14use near_primitives::borsh;
15use near_primitives::hash::CryptoHash;
16use near_primitives::types::{BlockHeight, BlockId, BlockReference, Finality, ShardId, StoreKey};
17use near_primitives::views::{
18    AccessKeyList, AccessKeyView, AccountView, BlockView, CallResult, ChunkView, QueryRequest,
19    ViewStateResult,
20};
21use near_token::NearToken;
22
23use crate::ops::Function;
24use crate::{Client, Error, Result};
25
26/// Intenral type used to represent a boxed future.
27pub(crate) type BoxFuture<'a, T> =
28    std::pin::Pin<Box<dyn std::future::Future<Output = T> + Send + 'a>>;
29
30/// `Query` object allows creating queries into the network of our choice. This object is
31/// usually given from making calls from other functions such as [`view_state`].
32///
33/// [`view_state`]: Client::view_state
34pub struct Query<'a, T> {
35    pub(crate) method: T,
36    pub(crate) client: &'a Client,
37    pub(crate) block_ref: Option<BlockReference>,
38}
39
40impl<'a, T> Query<'a, T> {
41    pub(crate) fn new(client: &'a Client, method: T) -> Self {
42        Self {
43            method,
44            client,
45            block_ref: None,
46        }
47    }
48
49    /// Specify at which block height to query from. Note that only archival
50    /// networks will have the full history while networks like mainnet or testnet will
51    /// only have the history from 5 or less epochs ago.
52    pub fn block_height(mut self, height: BlockHeight) -> Self {
53        self.block_ref = Some(BlockId::Height(height).into());
54        self
55    }
56
57    /// Specify at which block hash to query from. Note that only archival
58    /// networks will have the full history while networks like mainnet or testnet will
59    /// only have the history from 5 or less epochs ago.
60    pub fn block_hash(mut self, hash: CryptoHash) -> Self {
61        self.block_ref = Some(BlockId::Hash(near_primitives::hash::CryptoHash(hash.0)).into());
62        self
63    }
64}
65
66// Constrained to RpcQueryRequest, since methods like GasPrice only take block_id but not Finality.
67impl<'a, T> Query<'a, T>
68where
69    T: ProcessQuery<Method = methods::query::RpcQueryRequest>,
70{
71    /// Specify at which block [`Finality`] to query from.
72    pub fn finality(mut self, value: Finality) -> Self {
73        self.block_ref = Some(value.into());
74        self
75    }
76}
77
78impl<'a, T, R> std::future::IntoFuture for Query<'a, T>
79where
80    T: ProcessQuery<Output = R> + Send + Sync + 'static,
81    <T as ProcessQuery>::Method: RpcMethod + Debug + Send + Sync,
82    <<T as ProcessQuery>::Method as RpcMethod>::Response: Debug + Send + Sync,
83    <<T as ProcessQuery>::Method as RpcMethod>::Error: Debug + Display + Send + Sync,
84{
85    type Output = Result<R>;
86
87    // TODO: boxed future required due to impl Trait as type alias being unstable. So once
88    // https://github.com/rust-lang/rust/issues/63063 is resolved, we can move to that instead.
89    type IntoFuture = BoxFuture<'a, Self::Output>;
90
91    fn into_future(self) -> Self::IntoFuture {
92        Box::pin(async move {
93            let block_reference = self.block_ref.unwrap_or_else(BlockReference::latest);
94            let resp = self
95                .client
96                .send_query(&self.method.into_request(block_reference)?)
97                .await
98                .map_err(|err| Error::Rpc(err.into()))?;
99
100            T::from_response(resp)
101        })
102    }
103}
104
105// Note: this trait is exposed publicly due to constraining with the impl offering `finality`.
106/// Trait used as a high level APIs for consistent usages of block reference. Mostly used
107/// internally to facilitate syntax sugar for performing RPC requests with async builders.
108pub trait ProcessQuery {
109    // TODO: associated default type is unstable. So for now, will require writing
110    // the manual impls for query_request
111    /// Method for doing the internal RPC request to the network of our choosing.
112    type Method: RpcMethod;
113
114    /// Expected output after performing a query.
115    type Output;
116
117    /// Convert into the Request object that is required to perform the RPC request.
118    fn into_request(self, block_ref: BlockReference) -> Result<Self::Method>;
119
120    /// Convert the response from the RPC request to a type of our choosing.
121    fn from_response(resp: <Self::Method as RpcMethod>::Response) -> Result<Self::Output>;
122}
123
124pub struct ViewFunction {
125    pub(crate) account_id: AccountId,
126    pub(crate) function: Function,
127}
128
129pub struct ViewCode {
130    pub(crate) account_id: AccountId,
131}
132
133pub struct ViewAccount {
134    pub(crate) account_id: AccountId,
135}
136
137pub struct ViewBlock;
138
139pub struct ViewState {
140    account_id: AccountId,
141    prefix: Option<Vec<u8>>,
142}
143
144pub struct ViewAccessKey {
145    pub(crate) account_id: AccountId,
146    pub(crate) public_key: PublicKey,
147}
148
149pub struct ViewAccessKeyList {
150    pub(crate) account_id: AccountId,
151}
152
153pub struct GasPrice;
154
155impl ProcessQuery for ViewFunction {
156    type Method = methods::query::RpcQueryRequest;
157    type Output = ViewResult;
158
159    fn into_request(self, block_reference: BlockReference) -> Result<Self::Method> {
160        Ok(Self::Method {
161            block_reference,
162            request: QueryRequest::CallFunction {
163                account_id: self.account_id,
164                method_name: self.function.name,
165                args: self.function.args?.into(),
166            },
167        })
168    }
169
170    fn from_response(resp: RpcQueryResponse) -> Result<Self::Output> {
171        match resp.kind {
172            QueryResponseKind::CallResult(result) => Ok(result.into()),
173            _ => Err(Error::RpcReturnedInvalidData(
174                "while querying account".into(),
175            )),
176        }
177    }
178}
179
180// Specific builder methods attached to a ViewFunction.
181impl Query<'_, ViewFunction> {
182    /// Provide the arguments for the call. These args are serialized bytes from either
183    /// a JSON or Borsh serializable set of arguments. To use the more specific versions
184    /// with better quality of life, use `args_json` or `args_borsh`.
185    pub fn args(mut self, args: Vec<u8>) -> Self {
186        self.method.function = self.method.function.args(args);
187        self
188    }
189
190    /// Similar to `args`, specify an argument that is JSON serializable and can be
191    /// accepted by the equivalent contract. Recommend to use something like
192    /// `serde_json::json!` macro to easily serialize the arguments.
193    pub fn args_json<U: serde::Serialize>(mut self, args: U) -> Self {
194        self.method.function = self.method.function.args_json(args);
195        self
196    }
197
198    /// Similar to `args`, specify an argument that is borsh serializable and can be
199    /// accepted by the equivalent contract.
200    pub fn args_borsh<U: borsh::BorshSerialize>(mut self, args: U) -> Self {
201        self.method.function = self.method.function.args_borsh(args);
202        self
203    }
204}
205
206impl ProcessQuery for ViewCode {
207    type Method = methods::query::RpcQueryRequest;
208    type Output = Vec<u8>;
209
210    fn into_request(self, block_reference: BlockReference) -> Result<Self::Method> {
211        Ok(Self::Method {
212            block_reference,
213            request: QueryRequest::ViewCode {
214                account_id: self.account_id,
215            },
216        })
217    }
218
219    fn from_response(resp: RpcQueryResponse) -> Result<Self::Output> {
220        match resp.kind {
221            QueryResponseKind::ViewCode(contract) => Ok(contract.code),
222            _ => Err(Error::RpcReturnedInvalidData("while querying code".into())),
223        }
224    }
225}
226
227impl ProcessQuery for ViewAccount {
228    type Method = methods::query::RpcQueryRequest;
229    type Output = AccountView;
230
231    fn into_request(self, block_reference: BlockReference) -> Result<Self::Method> {
232        Ok(Self::Method {
233            block_reference,
234            request: QueryRequest::ViewAccount {
235                account_id: self.account_id,
236            },
237        })
238    }
239
240    fn from_response(resp: RpcQueryResponse) -> Result<Self::Output> {
241        match resp.kind {
242            QueryResponseKind::ViewAccount(account) => Ok(account),
243            _ => Err(Error::RpcReturnedInvalidData(
244                "while querying account".into(),
245            )),
246        }
247    }
248}
249
250impl ProcessQuery for ViewBlock {
251    type Method = methods::block::RpcBlockRequest;
252    type Output = BlockView;
253
254    fn into_request(self, block_reference: BlockReference) -> Result<Self::Method> {
255        Ok(Self::Method { block_reference })
256    }
257
258    fn from_response(view: BlockView) -> Result<Self::Output> {
259        Ok(view)
260    }
261}
262
263impl ProcessQuery for ViewState {
264    type Method = methods::query::RpcQueryRequest;
265    type Output = ViewStateResult;
266
267    fn into_request(self, block_reference: BlockReference) -> Result<Self::Method> {
268        Ok(Self::Method {
269            block_reference,
270            request: QueryRequest::ViewState {
271                account_id: self.account_id,
272                prefix: StoreKey::from(self.prefix.unwrap_or_default()),
273                include_proof: false,
274            },
275        })
276    }
277
278    fn from_response(resp: <Self::Method as RpcMethod>::Response) -> Result<Self::Output> {
279        match resp.kind {
280            QueryResponseKind::ViewState(state) => Ok(state),
281            _ => Err(Error::RpcReturnedInvalidData("while querying state".into())),
282        }
283    }
284}
285
286impl<'a> Query<'a, ViewState> {
287    pub(crate) fn view_state(client: &'a Client, id: &AccountId) -> Self {
288        Self::new(
289            client,
290            ViewState {
291                account_id: id.clone(),
292                prefix: None,
293            },
294        )
295    }
296
297    /// Set the prefix for viewing the state.
298    pub fn prefix(mut self, value: &[u8]) -> Self {
299        self.method.prefix = Some(value.into());
300        self
301    }
302}
303
304impl ProcessQuery for ViewAccessKey {
305    type Method = methods::query::RpcQueryRequest;
306    type Output = AccessKeyView;
307
308    fn into_request(self, block_reference: BlockReference) -> Result<Self::Method> {
309        Ok(Self::Method {
310            block_reference,
311            request: QueryRequest::ViewAccessKey {
312                account_id: self.account_id,
313                public_key: self.public_key,
314            },
315        })
316    }
317
318    fn from_response(resp: <Self::Method as RpcMethod>::Response) -> Result<Self::Output> {
319        match resp.kind {
320            QueryResponseKind::AccessKey(key) => Ok(key),
321            _ => Err(Error::RpcReturnedInvalidData(
322                "while querying access key".into(),
323            )),
324        }
325    }
326}
327
328impl ProcessQuery for ViewAccessKeyList {
329    type Method = methods::query::RpcQueryRequest;
330    type Output = AccessKeyList;
331
332    fn into_request(self, block_reference: BlockReference) -> Result<Self::Method> {
333        Ok(Self::Method {
334            block_reference,
335            request: QueryRequest::ViewAccessKeyList {
336                account_id: self.account_id,
337            },
338        })
339    }
340
341    fn from_response(resp: <Self::Method as RpcMethod>::Response) -> Result<Self::Output> {
342        match resp.kind {
343            QueryResponseKind::AccessKeyList(keylist) => Ok(keylist),
344            _ => Err(Error::RpcReturnedInvalidData(
345                "while querying access keys".into(),
346            )),
347        }
348    }
349}
350
351impl ProcessQuery for GasPrice {
352    type Method = methods::gas_price::RpcGasPriceRequest;
353    type Output = NearToken;
354
355    fn into_request(self, block_ref: BlockReference) -> Result<Self::Method> {
356        let block_id = match block_ref {
357            // User provided input via `block_hash` or `block_height` functions.
358            BlockReference::BlockId(block_id) => Some(block_id),
359            // default case, set by `Query` struct via BlockReference::latest.
360            BlockReference::Finality(_finality) => None,
361            // Should not be reachable, unless code got changed.
362            BlockReference::SyncCheckpoint(point) => {
363                return Err(Error::RpcReturnedInvalidData(format!(
364                    "Cannot supply sync checkpoint to gas price: {point:?}. Potential API bug?"
365                )))
366            }
367        };
368
369        Ok(Self::Method { block_id })
370    }
371
372    fn from_response(resp: <Self::Method as RpcMethod>::Response) -> Result<Self::Output> {
373        Ok(NearToken::from_yoctonear(resp.gas_price))
374    }
375}
376
377/// Query object used to query for chunk related details at a specific `ChunkReference` which
378/// consists of either a chunk [`CryptoHash`], or a `BlockShardId` (which consists of [`ShardId`]
379/// and either block [`CryptoHash`] or [`BlockHeight`]).
380///
381/// The default behavior where a `ChunkReference` is not supplied will use a `BlockShardId`
382/// referencing the latest block `CryptoHash` with `ShardId` of 0.
383pub struct QueryChunk<'a> {
384    client: &'a Client,
385    chunk_ref: Option<ChunkReference>,
386}
387
388impl<'a> QueryChunk<'a> {
389    pub(crate) fn new(client: &'a Client) -> Self {
390        Self {
391            client,
392            chunk_ref: None,
393        }
394    }
395
396    /// Specify at which block hash and shard id to query the chunk from. Note that only
397    /// archival networks will have the full history while networks like mainnet or testnet
398    /// will only have the history from 5 or less epochs ago.
399    pub fn block_hash_and_shard(mut self, hash: CryptoHash, shard_id: ShardId) -> Self {
400        self.chunk_ref = Some(ChunkReference::BlockShardId {
401            block_id: BlockId::Hash(near_primitives::hash::CryptoHash(hash.0)),
402            shard_id,
403        });
404        self
405    }
406
407    /// Specify at which block height and shard id to query the chunk from. Note that only
408    /// archival networks will have the full history while networks like mainnet or testnet
409    /// will only have the history from 5 or less epochs ago.
410    pub fn block_height_and_shard(mut self, height: BlockHeight, shard_id: ShardId) -> Self {
411        self.chunk_ref = Some(ChunkReference::BlockShardId {
412            block_id: BlockId::Height(height),
413            shard_id,
414        });
415        self
416    }
417
418    /// Specify at which chunk hash to query the chunk from.
419    pub fn chunk_hash(mut self, hash: CryptoHash) -> Self {
420        self.chunk_ref = Some(ChunkReference::ChunkHash {
421            chunk_id: near_primitives::hash::CryptoHash(hash.0),
422        });
423        self
424    }
425}
426
427impl<'a> std::future::IntoFuture for QueryChunk<'a> {
428    type Output = Result<ChunkView>;
429    type IntoFuture = BoxFuture<'a, Self::Output>;
430
431    fn into_future(self) -> Self::IntoFuture {
432        Box::pin(async move {
433            let chunk_reference = if let Some(chunk_ref) = self.chunk_ref {
434                chunk_ref
435            } else {
436                // Use the latest block hash in the case the user doesn't supply the ChunkReference. Note that
437                // shard_id 0 is used in the default case.
438                let block_view = self.client.view_block().await?;
439                ChunkReference::BlockShardId {
440                    block_id: BlockId::Hash(block_view.header.hash),
441                    shard_id: ShardId::from(0),
442                }
443            };
444
445            let chunk_view = self
446                .client
447                .send_query(&methods::chunk::RpcChunkRequest { chunk_reference })
448                .await?;
449
450            Ok(chunk_view)
451        })
452    }
453}
454
455/// The result from a call into a View function. This contains the contents or
456/// the results from the view function call itself. The consumer of this object
457/// can choose how to deserialize its contents.
458#[derive(Clone, Debug, PartialEq, Eq)]
459#[non_exhaustive]
460pub struct ViewResult {
461    /// Our result from our call into a view function.
462    pub result: Vec<u8>,
463    /// Logs generated from the view function.
464    pub logs: Vec<String>,
465}
466
467impl ViewResult {
468    /// Deserialize an instance of type `T` from bytes of JSON text sourced from the
469    /// execution result of this call. This conversion can fail if the structure of
470    /// the internal state does not meet up with [`serde::de::DeserializeOwned`]'s
471    /// requirements.
472    pub fn json<T: serde::de::DeserializeOwned>(&self) -> Result<T> {
473        Ok(serde_json::from_slice(&self.result)?)
474    }
475
476    /// Deserialize an instance of type `T` from bytes sourced from this view call's
477    /// result. This conversion can fail if the structure of the internal state does
478    /// not meet up with [`borsh::BorshDeserialize`]'s requirements.
479    pub fn borsh<T: borsh::BorshDeserialize>(&self) -> Result<T> {
480        Ok(borsh::BorshDeserialize::try_from_slice(&self.result)?)
481    }
482}
483
484impl From<CallResult> for ViewResult {
485    fn from(result: CallResult) -> Self {
486        Self {
487            result: result.result,
488            logs: result.logs,
489        }
490    }
491}
492
493impl Client {
494    /// Call into a contract's view function. Returns a [`Query`] which allows us
495    /// to specify further details like the arguments of the view call, or at what
496    /// point in the chain we want to view.
497    pub fn view(&self, contract_id: &AccountId, function: &str) -> Query<'_, ViewFunction> {
498        Query::new(
499            self,
500            ViewFunction {
501                account_id: contract_id.clone(),
502                function: Function::new(function),
503            },
504        )
505    }
506
507    /// View the WASM code bytes of a contract on the network.
508    pub fn view_code(&self, contract_id: &AccountId) -> Query<'_, ViewCode> {
509        Query::new(
510            self,
511            ViewCode {
512                account_id: contract_id.clone(),
513            },
514        )
515    }
516
517    /// View the state of a account/contract on the network. This will return the internal
518    /// state of the account in the form of a map of key-value pairs; where STATE contains
519    /// info on a contract's internal data.
520    pub fn view_state(&self, contract_id: &AccountId) -> Query<'_, ViewState> {
521        Query::view_state(self, contract_id)
522    }
523
524    /// View the block from the network. Supply additional parameters such as [`block_height`]
525    /// or [`block_hash`] to get the block.
526    ///
527    /// [`block_height`]: Query::block_height
528    /// [`block_hash`]: Query::block_hash
529    pub fn view_block(&self) -> Query<'_, ViewBlock> {
530        Query::new(self, ViewBlock)
531    }
532
533    /// View the chunk from the network once awaited. Supply additional parameters such as
534    /// [`block_hash_and_shard`], [`block_height_and_shard`] or [`chunk_hash`] to get the
535    /// chunk at a specific reference point. If none of those are supplied, the default
536    /// reference point will be used, which will be the latest block_hash with a shard_id
537    /// of 0.
538    ///
539    /// [`block_hash_and_shard`]: QueryChunk::block_hash_and_shard
540    /// [`block_height_and_shard`]: QueryChunk::block_height_and_shard
541    /// [`chunk_hash`]: QueryChunk::chunk_hash
542    pub fn view_chunk(&self) -> QueryChunk<'_> {
543        QueryChunk::new(self)
544    }
545
546    /// Views the [`AccessKey`] of the account specified by [`AccountId`] associated with
547    /// the [`PublicKey`]
548    ///
549    /// [`AccessKey`]: crate::types::AccessKey
550    pub fn view_access_key(&self, id: &AccountId, pk: &PublicKey) -> Query<'_, ViewAccessKey> {
551        Query::new(
552            self,
553            ViewAccessKey {
554                account_id: id.clone(),
555                public_key: pk.clone(),
556            },
557        )
558    }
559
560    /// Views all the [`AccessKey`]s of the account specified by [`AccountId`]. This will
561    /// return a list of [`AccessKey`]s along with the associated [`PublicKey`].
562    ///
563    /// [`AccessKey`]: crate::types::AccessKey
564    pub fn view_access_keys(&self, id: &AccountId) -> Query<'_, ViewAccessKeyList> {
565        Query::new(
566            self,
567            ViewAccessKeyList {
568                account_id: id.clone(),
569            },
570        )
571    }
572
573    /// View account details of a specific account on the network.
574    pub fn view_account(&self, account_id: &AccountId) -> Query<'_, ViewAccount> {
575        Query::new(
576            self,
577            ViewAccount {
578                account_id: account_id.clone(),
579            },
580        )
581    }
582
583    /// Fetches the latest gas price on the network.
584    pub fn gas_price(&self) -> Query<'_, GasPrice> {
585        Query::new(self, GasPrice)
586    }
587}