use std::collections::HashMap;
use std::fmt::{Debug, Display};
use near_account_id::AccountId;
use near_jsonrpc_client::methods::query::RpcQueryResponse;
use near_jsonrpc_client::methods::{self, RpcMethod};
use near_jsonrpc_primitives::types::chunks::ChunkReference;
use near_jsonrpc_primitives::types::query::QueryResponseKind;
use near_primitives::types::{BlockId, BlockReference, StoreKey};
use near_primitives::views::{BlockView, QueryRequest};
use near_token::NearToken;
use crate::error::RpcErrorCode;
use crate::operations::Function;
use crate::result::ViewResultDetails;
use crate::rpc::client::Client;
use crate::rpc::{tool, BoxFuture};
use crate::types::account::AccountDetails;
use crate::types::{AccessKey, AccessKeyInfo, BlockHeight, Finality, PublicKey, ShardId};
use crate::{Block, Chunk, CryptoHash, Result};
pub struct Query<'a, T> {
pub(crate) method: T,
pub(crate) client: &'a Client,
pub(crate) block_ref: Option<BlockReference>,
}
impl<'a, T> Query<'a, T> {
pub(crate) fn new(client: &'a Client, method: T) -> Self {
Self {
method,
client,
block_ref: None,
}
}
pub fn block_height(mut self, height: BlockHeight) -> Self {
self.block_ref = Some(BlockId::Height(height).into());
self
}
pub fn block_hash(mut self, hash: CryptoHash) -> Self {
self.block_ref = Some(BlockId::Hash(near_primitives::hash::CryptoHash(hash.0)).into());
self
}
}
impl<'a, T> Query<'a, T>
where
T: ProcessQuery<Method = methods::query::RpcQueryRequest>,
{
pub fn finality(mut self, value: Finality) -> Self {
self.block_ref = Some(value.into());
self
}
pub(crate) fn block_reference(mut self, value: BlockReference) -> Self {
self.block_ref = Some(value);
self
}
}
impl<'a, T, R> std::future::IntoFuture for Query<'a, T>
where
T: ProcessQuery<Output = R> + Send + Sync + 'static,
<T as ProcessQuery>::Method: RpcMethod + Debug + Send + Sync,
<<T as ProcessQuery>::Method as RpcMethod>::Response: Debug + Send + Sync,
<<T as ProcessQuery>::Method as RpcMethod>::Error: Debug + Display + Send + Sync,
{
type Output = Result<R>;
type IntoFuture = BoxFuture<'a, Self::Output>;
fn into_future(self) -> Self::IntoFuture {
Box::pin(async move {
let block_reference = self.block_ref.unwrap_or_else(BlockReference::latest);
let resp = self
.client
.query(self.method.into_request(block_reference)?)
.await
.map_err(|e| RpcErrorCode::QueryFailure.custom(e))?;
T::from_response(resp)
})
}
}
pub trait ProcessQuery {
type Method: RpcMethod;
type Output;
fn into_request(self, block_ref: BlockReference) -> Result<Self::Method>;
fn from_response(resp: <Self::Method as RpcMethod>::Response) -> Result<Self::Output>;
}
pub struct ViewFunction {
pub(crate) account_id: AccountId,
pub(crate) function: Function,
}
pub struct ViewCode {
pub(crate) account_id: AccountId,
}
pub struct ViewAccount {
pub(crate) account_id: AccountId,
}
pub struct ViewBlock;
pub struct ViewState {
account_id: AccountId,
prefix: Option<Vec<u8>>,
}
pub struct ViewAccessKey {
pub(crate) account_id: AccountId,
pub(crate) public_key: PublicKey,
}
pub struct ViewAccessKeyList {
pub(crate) account_id: AccountId,
}
pub struct GasPrice;
impl ProcessQuery for ViewFunction {
type Method = methods::query::RpcQueryRequest;
type Output = ViewResultDetails;
fn into_request(self, block_reference: BlockReference) -> Result<Self::Method> {
Ok(Self::Method {
block_reference,
request: QueryRequest::CallFunction {
account_id: self.account_id,
method_name: self.function.name,
args: self.function.args?.into(),
},
})
}
fn from_response(resp: RpcQueryResponse) -> Result<Self::Output> {
match resp.kind {
QueryResponseKind::CallResult(result) => Ok(result.into()),
_ => Err(RpcErrorCode::QueryReturnedInvalidData.message("while querying account")),
}
}
}
impl Query<'_, ViewFunction> {
pub fn args(mut self, args: Vec<u8>) -> Self {
self.method.function = self.method.function.args(args);
self
}
pub fn args_json<U: serde::Serialize>(mut self, args: U) -> Self {
self.method.function = self.method.function.args_json(args);
self
}
pub fn args_borsh<U: borsh::BorshSerialize>(mut self, args: U) -> Self {
self.method.function = self.method.function.args_borsh(args);
self
}
}
impl ProcessQuery for ViewCode {
type Method = methods::query::RpcQueryRequest;
type Output = Vec<u8>;
fn into_request(self, block_reference: BlockReference) -> Result<Self::Method> {
Ok(Self::Method {
block_reference,
request: QueryRequest::ViewCode {
account_id: self.account_id,
},
})
}
fn from_response(resp: RpcQueryResponse) -> Result<Self::Output> {
match resp.kind {
QueryResponseKind::ViewCode(contract) => Ok(contract.code),
_ => Err(RpcErrorCode::QueryReturnedInvalidData.message("while querying code")),
}
}
}
impl ProcessQuery for ViewAccount {
type Method = methods::query::RpcQueryRequest;
type Output = AccountDetails;
fn into_request(self, block_reference: BlockReference) -> Result<Self::Method> {
Ok(Self::Method {
block_reference,
request: QueryRequest::ViewAccount {
account_id: self.account_id,
},
})
}
fn from_response(resp: RpcQueryResponse) -> Result<Self::Output> {
match resp.kind {
QueryResponseKind::ViewAccount(account) => Ok(account.into()),
_ => Err(RpcErrorCode::QueryReturnedInvalidData.message("while querying account")),
}
}
}
impl ProcessQuery for ViewBlock {
type Method = methods::block::RpcBlockRequest;
type Output = Block;
fn into_request(self, block_reference: BlockReference) -> Result<Self::Method> {
Ok(Self::Method { block_reference })
}
fn from_response(view: BlockView) -> Result<Self::Output> {
Ok(view.into())
}
}
impl ProcessQuery for ViewState {
type Method = methods::query::RpcQueryRequest;
type Output = HashMap<Vec<u8>, Vec<u8>>;
fn into_request(self, block_reference: BlockReference) -> Result<Self::Method> {
Ok(Self::Method {
block_reference,
request: QueryRequest::ViewState {
account_id: self.account_id,
prefix: StoreKey::from(self.prefix.map(Vec::from).unwrap_or_default()),
include_proof: false,
},
})
}
fn from_response(resp: <Self::Method as RpcMethod>::Response) -> Result<Self::Output> {
match resp.kind {
QueryResponseKind::ViewState(state) => Ok(tool::into_state_map(state.values)),
_ => Err(RpcErrorCode::QueryReturnedInvalidData.message("while querying state")),
}
}
}
impl<'a> Query<'a, ViewState> {
pub(crate) fn view_state(client: &'a Client, id: &AccountId) -> Self {
Self::new(
client,
ViewState {
account_id: id.clone(),
prefix: None,
},
)
}
pub fn prefix(mut self, value: &[u8]) -> Self {
self.method.prefix = Some(value.into());
self
}
}
impl ProcessQuery for ViewAccessKey {
type Method = methods::query::RpcQueryRequest;
type Output = AccessKey;
fn into_request(self, block_reference: BlockReference) -> Result<Self::Method> {
Ok(Self::Method {
block_reference,
request: QueryRequest::ViewAccessKey {
account_id: self.account_id,
public_key: self.public_key.into(),
},
})
}
fn from_response(resp: <Self::Method as RpcMethod>::Response) -> Result<Self::Output> {
match resp.kind {
QueryResponseKind::AccessKey(key) => Ok(key.into()),
_ => Err(RpcErrorCode::QueryReturnedInvalidData.message("while querying access key")),
}
}
}
impl ProcessQuery for ViewAccessKeyList {
type Method = methods::query::RpcQueryRequest;
type Output = Vec<AccessKeyInfo>;
fn into_request(self, block_reference: BlockReference) -> Result<Self::Method> {
Ok(Self::Method {
block_reference,
request: QueryRequest::ViewAccessKeyList {
account_id: self.account_id,
},
})
}
fn from_response(resp: <Self::Method as RpcMethod>::Response) -> Result<Self::Output> {
match resp.kind {
QueryResponseKind::AccessKeyList(keylist) => {
Ok(keylist.keys.into_iter().map(Into::into).collect())
}
_ => Err(RpcErrorCode::QueryReturnedInvalidData.message("while querying access keys")),
}
}
}
impl ProcessQuery for GasPrice {
type Method = methods::gas_price::RpcGasPriceRequest;
type Output = NearToken;
fn into_request(self, block_ref: BlockReference) -> Result<Self::Method> {
let block_id = match block_ref {
BlockReference::BlockId(block_id) => Some(block_id),
BlockReference::Finality(_finality) => None,
BlockReference::SyncCheckpoint(point) => {
return Err(RpcErrorCode::QueryFailure.message(format!(
"Cannot supply sync checkpoint to gas price: {point:?}. Potential API bug?"
)))
}
};
Ok(Self::Method { block_id })
}
fn from_response(resp: <Self::Method as RpcMethod>::Response) -> Result<Self::Output> {
Ok(NearToken::from_yoctonear(resp.gas_price))
}
}
pub struct QueryChunk<'a> {
client: &'a Client,
chunk_ref: Option<ChunkReference>,
}
impl<'a> QueryChunk<'a> {
pub(crate) fn new(client: &'a Client) -> Self {
Self {
client,
chunk_ref: None,
}
}
pub fn block_hash_and_shard(mut self, hash: CryptoHash, shard_id: ShardId) -> Self {
self.chunk_ref = Some(ChunkReference::BlockShardId {
block_id: BlockId::Hash(near_primitives::hash::CryptoHash(hash.0)),
shard_id,
});
self
}
pub fn block_height_and_shard(mut self, height: BlockHeight, shard_id: ShardId) -> Self {
self.chunk_ref = Some(ChunkReference::BlockShardId {
block_id: BlockId::Height(height),
shard_id,
});
self
}
pub fn chunk_hash(mut self, hash: CryptoHash) -> Self {
self.chunk_ref = Some(ChunkReference::ChunkHash {
chunk_id: near_primitives::hash::CryptoHash(hash.0),
});
self
}
}
impl<'a> std::future::IntoFuture for QueryChunk<'a> {
type Output = Result<Chunk>;
type IntoFuture = BoxFuture<'a, Self::Output>;
fn into_future(self) -> Self::IntoFuture {
Box::pin(async move {
let chunk_reference = if let Some(chunk_ref) = self.chunk_ref {
chunk_ref
} else {
let block_view = self.client.view_block(None).await?;
ChunkReference::BlockShardId {
block_id: BlockId::Hash(block_view.header.hash),
shard_id: 0,
}
};
let chunk_view = self
.client
.query(methods::chunk::RpcChunkRequest { chunk_reference })
.await
.map_err(|e| RpcErrorCode::QueryFailure.custom(e))?;
Ok(chunk_view.into())
})
}
}