1use std::collections::HashMap;
35use std::fmt::{Debug, Display};
36
37use unc_account_id::AccountId;
38use unc_jsonrpc_client::methods::query::RpcQueryResponse;
39use unc_jsonrpc_client::methods::{self, RpcMethod};
40use unc_jsonrpc_primitives::types::chunks::ChunkReference;
41use unc_jsonrpc_primitives::types::query::QueryResponseKind;
42use unc_primitives::types::{BlockId, BlockReference, StoreKey};
43use unc_primitives::views::{BlockView, QueryRequest};
44use unc_token::UncToken;
45
46use crate::error::RpcErrorCode;
47use crate::operations::Function;
48use crate::result::ViewResultDetails;
49use crate::rpc::client::Client;
50use crate::rpc::{tool, BoxFuture};
51use crate::types::account::AccountDetails;
52use crate::types::{AccessKey, AccessKeyInfo, BlockHeight, Finality, PublicKey, ShardId};
53use crate::{Block, Chunk, CryptoHash, Result};
54
55pub struct Query<'a, T> {
60 pub(crate) method: T,
61 pub(crate) client: &'a Client,
62 pub(crate) block_ref: Option<BlockReference>,
63}
64
65impl<'a, T> Query<'a, T> {
66 pub(crate) fn new(client: &'a Client, method: T) -> Self {
67 Self {
68 method,
69 client,
70 block_ref: None,
71 }
72 }
73
74 pub fn block_height(mut self, height: BlockHeight) -> Self {
78 self.block_ref = Some(BlockId::Height(height).into());
79 self
80 }
81
82 pub fn block_hash(mut self, hash: CryptoHash) -> Self {
86 self.block_ref = Some(BlockId::Hash(unc_primitives::hash::CryptoHash(hash.0)).into());
87 self
88 }
89}
90
91impl<'a, T> Query<'a, T>
93where
94 T: ProcessQuery<Method = methods::query::RpcQueryRequest>,
95{
96 pub fn finality(mut self, value: Finality) -> Self {
98 self.block_ref = Some(value.into());
99 self
100 }
101
102 pub(crate) fn block_reference(mut self, value: BlockReference) -> Self {
103 self.block_ref = Some(value);
104 self
105 }
106}
107
108impl<'a, T, R> std::future::IntoFuture for Query<'a, T>
109where
110 T: ProcessQuery<Output = R> + Send + Sync + 'static,
111 <T as ProcessQuery>::Method: RpcMethod + Debug + Send + Sync,
112 <<T as ProcessQuery>::Method as RpcMethod>::Response: Debug + Send + Sync,
113 <<T as ProcessQuery>::Method as RpcMethod>::Error: Debug + Display + Send + Sync,
114{
115 type Output = Result<R>;
116
117 type IntoFuture = BoxFuture<'a, Self::Output>;
120
121 fn into_future(self) -> Self::IntoFuture {
122 Box::pin(async move {
123 let block_reference = self.block_ref.unwrap_or_else(BlockReference::latest);
124 let resp = self
125 .client
126 .query(self.method.into_request(block_reference)?)
127 .await
128 .map_err(|e| RpcErrorCode::QueryFailure.custom(e))?;
129
130 T::from_response(resp)
131 })
132 }
133}
134
135pub trait ProcessQuery {
140 type Method: RpcMethod;
144
145 type Output;
148
149 fn into_request(self, block_ref: BlockReference) -> Result<Self::Method>;
151
152 fn from_response(resp: <Self::Method as RpcMethod>::Response) -> Result<Self::Output>;
155}
156
157pub struct ViewFunction {
158 pub(crate) account_id: AccountId,
159 pub(crate) function: Function,
160}
161
162pub struct ViewCode {
163 pub(crate) account_id: AccountId,
164}
165
166pub struct ViewAccount {
167 pub(crate) account_id: AccountId,
168}
169
170pub struct ViewBlock;
171
172pub struct ViewState {
173 account_id: AccountId,
174 prefix: Option<Vec<u8>>,
175}
176
177pub struct ViewAccessKey {
178 pub(crate) account_id: AccountId,
179 pub(crate) public_key: PublicKey,
180}
181
182pub struct ViewAccessKeyList {
183 pub(crate) account_id: AccountId,
184}
185
186pub struct GasPrice;
187
188impl ProcessQuery for ViewFunction {
189 type Method = methods::query::RpcQueryRequest;
190 type Output = ViewResultDetails;
191
192 fn into_request(self, block_reference: BlockReference) -> Result<Self::Method> {
193 Ok(Self::Method {
194 block_reference,
195 request: QueryRequest::CallFunction {
196 account_id: self.account_id,
197 method_name: self.function.name,
198 args: self.function.args?.into(),
199 },
200 })
201 }
202
203 fn from_response(resp: RpcQueryResponse) -> Result<Self::Output> {
204 match resp.kind {
205 QueryResponseKind::CallResult(result) => Ok(result.into()),
206 _ => Err(RpcErrorCode::QueryReturnedInvalidData.message("while querying account")),
207 }
208 }
209}
210
211impl Query<'_, ViewFunction> {
213 pub fn args(mut self, args: Vec<u8>) -> Self {
217 self.method.function = self.method.function.args(args);
218 self
219 }
220
221 pub fn args_json<U: serde::Serialize>(mut self, args: U) -> Self {
225 self.method.function = self.method.function.args_json(args);
226 self
227 }
228
229 pub fn args_borsh<U: unc_primitives::borsh::BorshSerialize>(mut self, args: U) -> Self {
232 self.method.function = self.method.function.args_borsh(args);
233 self
234 }
235}
236
237impl ProcessQuery for ViewCode {
238 type Method = methods::query::RpcQueryRequest;
239 type Output = Vec<u8>;
240
241 fn into_request(self, block_reference: BlockReference) -> Result<Self::Method> {
242 Ok(Self::Method {
243 block_reference,
244 request: QueryRequest::ViewCode {
245 account_id: self.account_id,
246 },
247 })
248 }
249
250 fn from_response(resp: RpcQueryResponse) -> Result<Self::Output> {
251 match resp.kind {
252 QueryResponseKind::ViewCode(contract) => Ok(contract.code),
253 _ => Err(RpcErrorCode::QueryReturnedInvalidData.message("while querying code")),
254 }
255 }
256}
257
258impl ProcessQuery for ViewAccount {
259 type Method = methods::query::RpcQueryRequest;
260 type Output = AccountDetails;
261
262 fn into_request(self, block_reference: BlockReference) -> Result<Self::Method> {
263 Ok(Self::Method {
264 block_reference,
265 request: QueryRequest::ViewAccount {
266 account_id: self.account_id,
267 },
268 })
269 }
270
271 fn from_response(resp: RpcQueryResponse) -> Result<Self::Output> {
272 match resp.kind {
273 QueryResponseKind::ViewAccount(account) => Ok(account.into()),
274 _ => Err(RpcErrorCode::QueryReturnedInvalidData.message("while querying account")),
275 }
276 }
277}
278
279impl ProcessQuery for ViewBlock {
280 type Method = methods::block::RpcBlockRequest;
281 type Output = Block;
282
283 fn into_request(self, block_reference: BlockReference) -> Result<Self::Method> {
284 Ok(Self::Method { block_reference })
285 }
286
287 fn from_response(view: BlockView) -> Result<Self::Output> {
288 Ok(view.into())
289 }
290}
291
292impl ProcessQuery for ViewState {
293 type Method = methods::query::RpcQueryRequest;
294 type Output = HashMap<Vec<u8>, Vec<u8>>;
295
296 fn into_request(self, block_reference: BlockReference) -> Result<Self::Method> {
297 Ok(Self::Method {
298 block_reference,
299 request: QueryRequest::ViewState {
300 account_id: self.account_id,
301 prefix: StoreKey::from(self.prefix.map(Vec::from).unwrap_or_default()),
302 include_proof: false,
303 },
304 })
305 }
306
307 fn from_response(resp: <Self::Method as RpcMethod>::Response) -> Result<Self::Output> {
308 match resp.kind {
309 QueryResponseKind::ViewState(state) => Ok(tool::into_state_map(state.values)),
310 _ => Err(RpcErrorCode::QueryReturnedInvalidData.message("while querying state")),
311 }
312 }
313}
314
315impl<'a> Query<'a, ViewState> {
316 pub(crate) fn view_state(client: &'a Client, id: &AccountId) -> Self {
317 Self::new(
318 client,
319 ViewState {
320 account_id: id.clone(),
321 prefix: None,
322 },
323 )
324 }
325
326 pub fn prefix(mut self, value: &[u8]) -> Self {
328 self.method.prefix = Some(value.into());
329 self
330 }
331}
332
333impl ProcessQuery for ViewAccessKey {
334 type Method = methods::query::RpcQueryRequest;
335 type Output = AccessKey;
336
337 fn into_request(self, block_reference: BlockReference) -> Result<Self::Method> {
338 Ok(Self::Method {
339 block_reference,
340 request: QueryRequest::ViewAccessKey {
341 account_id: self.account_id,
342 public_key: self.public_key.into(),
343 },
344 })
345 }
346
347 fn from_response(resp: <Self::Method as RpcMethod>::Response) -> Result<Self::Output> {
348 match resp.kind {
349 QueryResponseKind::AccessKey(key) => Ok(key.into()),
350 _ => Err(RpcErrorCode::QueryReturnedInvalidData.message("while querying access key")),
351 }
352 }
353}
354
355impl ProcessQuery for ViewAccessKeyList {
356 type Method = methods::query::RpcQueryRequest;
357 type Output = Vec<AccessKeyInfo>;
358
359 fn into_request(self, block_reference: BlockReference) -> Result<Self::Method> {
360 Ok(Self::Method {
361 block_reference,
362 request: QueryRequest::ViewAccessKeyList {
363 account_id: self.account_id,
364 },
365 })
366 }
367
368 fn from_response(resp: <Self::Method as RpcMethod>::Response) -> Result<Self::Output> {
369 match resp.kind {
370 QueryResponseKind::AccessKeyList(keylist) => {
371 Ok(keylist.keys.into_iter().map(Into::into).collect())
372 }
373 _ => Err(RpcErrorCode::QueryReturnedInvalidData.message("while querying access keys")),
374 }
375 }
376}
377
378impl ProcessQuery for GasPrice {
379 type Method = methods::gas_price::RpcGasPriceRequest;
380 type Output = UncToken;
381
382 fn into_request(self, block_ref: BlockReference) -> Result<Self::Method> {
383 let block_id = match block_ref {
384 BlockReference::BlockId(block_id) => Some(block_id),
386 BlockReference::Finality(_finality) => None,
388 BlockReference::SyncCheckpoint(point) => {
390 return Err(RpcErrorCode::QueryFailure.message(format!(
391 "Cannot supply sync checkpoint to gas price: {point:?}. Potential API bug?"
392 )))
393 }
394 };
395
396 Ok(Self::Method { block_id })
397 }
398
399 fn from_response(resp: <Self::Method as RpcMethod>::Response) -> Result<Self::Output> {
400 Ok(UncToken::from_attounc(resp.gas_price))
401 }
402}
403
404pub struct QueryChunk<'a> {
411 client: &'a Client,
412 chunk_ref: Option<ChunkReference>,
413}
414
415impl<'a> QueryChunk<'a> {
416 pub(crate) fn new(client: &'a Client) -> Self {
417 Self {
418 client,
419 chunk_ref: None,
420 }
421 }
422
423 pub fn block_hash_and_shard(mut self, hash: CryptoHash, shard_id: ShardId) -> Self {
427 self.chunk_ref = Some(ChunkReference::BlockShardId {
428 block_id: BlockId::Hash(unc_primitives::hash::CryptoHash(hash.0)),
429 shard_id,
430 });
431 self
432 }
433
434 pub fn block_height_and_shard(mut self, height: BlockHeight, shard_id: ShardId) -> Self {
438 self.chunk_ref = Some(ChunkReference::BlockShardId {
439 block_id: BlockId::Height(height),
440 shard_id,
441 });
442 self
443 }
444
445 pub fn chunk_hash(mut self, hash: CryptoHash) -> Self {
447 self.chunk_ref = Some(ChunkReference::ChunkHash {
448 chunk_id: unc_primitives::hash::CryptoHash(hash.0),
449 });
450 self
451 }
452}
453
454impl<'a> std::future::IntoFuture for QueryChunk<'a> {
455 type Output = Result<Chunk>;
456 type IntoFuture = BoxFuture<'a, Self::Output>;
457
458 fn into_future(self) -> Self::IntoFuture {
459 Box::pin(async move {
460 let chunk_reference = if let Some(chunk_ref) = self.chunk_ref {
461 chunk_ref
462 } else {
463 let block_view = self.client.view_block(None).await?;
466 ChunkReference::BlockShardId {
467 block_id: BlockId::Hash(block_view.header.hash),
468 shard_id: 0,
469 }
470 };
471
472 let chunk_view = self
473 .client
474 .query(methods::chunk::RpcChunkRequest { chunk_reference })
475 .await
476 .map_err(|e| RpcErrorCode::QueryFailure.custom(e))?;
477
478 Ok(chunk_view.into())
479 })
480 }
481}