#![allow(clippy::field_reassign_with_default)]
use std::str;
use futures::{future::BoxFuture, FutureExt};
use http::Response;
use hyper::Body;
use once_cell::sync::Lazy;
use schemars::JsonSchema;
use semver::Version;
use serde::{Deserialize, Serialize};
use tracing::{debug, info};
use warp_json_rpc::Builder;
use casper_execution_engine::{
core::engine_state::{BalanceResult, QueryResult},
storage::protocol_data::ProtocolData,
};
use casper_types::{bytesrepr::ToBytes, CLValue, Key, ProtocolVersion, URef, U512};
use super::{
docs::DocExample, Error, ErrorCode, ReactorEventT, RpcRequest, RpcWithParams, RpcWithParamsExt,
};
use crate::{
components::CLIENT_API_VERSION,
crypto::hash::Digest,
effect::EffectBuilder,
reactor::QueueKind,
rpcs::{
common::{self, MERKLE_PROOF},
RpcWithoutParams, RpcWithoutParamsExt,
},
types::{
json_compatibility::{AuctionState, StoredValue},
Block,
},
};
static GET_ITEM_PARAMS: Lazy<GetItemParams> = Lazy::new(|| GetItemParams {
state_root_hash: *Block::doc_example().header().state_root_hash(),
key: "deploy-af684263911154d26fa05be9963171802801a0b6aff8f199b7391eacb8edc9e1".to_string(),
path: vec!["inner".to_string()],
});
static GET_ITEM_RESULT: Lazy<GetItemResult> = Lazy::new(|| GetItemResult {
api_version: CLIENT_API_VERSION.clone(),
stored_value: StoredValue::CLValue(CLValue::from_t(1u64).unwrap()),
merkle_proof: MERKLE_PROOF.clone(),
});
static GET_BALANCE_PARAMS: Lazy<GetBalanceParams> = Lazy::new(|| GetBalanceParams {
state_root_hash: *Block::doc_example().header().state_root_hash(),
purse_uref: "uref-09480c3248ef76b603d386f3f4f8a5f87f597d4eaffd475433f861af187ab5db-007"
.to_string(),
});
static GET_BALANCE_RESULT: Lazy<GetBalanceResult> = Lazy::new(|| GetBalanceResult {
api_version: CLIENT_API_VERSION.clone(),
balance_value: U512::from(123_456),
merkle_proof: MERKLE_PROOF.clone(),
});
static GET_AUCTION_INFO_RESULT: Lazy<GetAuctionInfoResult> = Lazy::new(|| GetAuctionInfoResult {
api_version: CLIENT_API_VERSION.clone(),
auction_state: AuctionState::doc_example().clone(),
});
#[derive(Serialize, Deserialize, Debug, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct GetItemParams {
pub state_root_hash: Digest,
pub key: String,
#[serde(default)]
pub path: Vec<String>,
}
impl DocExample for GetItemParams {
fn doc_example() -> &'static Self {
&*GET_ITEM_PARAMS
}
}
#[derive(Serialize, Deserialize, Debug, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct GetItemResult {
#[schemars(with = "String")]
pub api_version: Version,
pub stored_value: StoredValue,
pub merkle_proof: String,
}
impl DocExample for GetItemResult {
fn doc_example() -> &'static Self {
&*GET_ITEM_RESULT
}
}
pub struct GetItem {}
impl RpcWithParams for GetItem {
const METHOD: &'static str = "state_get_item";
type RequestParams = GetItemParams;
type ResponseResult = GetItemResult;
}
impl RpcWithParamsExt for GetItem {
fn handle_request<REv: ReactorEventT>(
effect_builder: EffectBuilder<REv>,
response_builder: Builder,
params: Self::RequestParams,
) -> BoxFuture<'static, Result<Response<Body>, Error>> {
async move {
let base_key = match Key::from_formatted_str(¶ms.key)
.map_err(|error| format!("failed to parse key: {:?}", error))
{
Ok(key) => key,
Err(error_msg) => {
info!("{}", error_msg);
return Ok(response_builder.error(warp_json_rpc::Error::custom(
ErrorCode::ParseQueryKey as i64,
error_msg,
))?);
}
};
let query_result = effect_builder
.make_request(
|responder| RpcRequest::QueryGlobalState {
state_root_hash: params.state_root_hash,
base_key,
path: params.path,
responder,
},
QueueKind::Api,
)
.await;
let (stored_value, proof_bytes) = match common::extract_query_result(query_result) {
Ok(tuple) => tuple,
Err((error_code, error_msg)) => {
info!("{}", error_msg);
return Ok(response_builder
.error(warp_json_rpc::Error::custom(error_code as i64, error_msg))?);
}
};
let result = Self::ResponseResult {
api_version: CLIENT_API_VERSION.clone(),
stored_value,
merkle_proof: hex::encode(proof_bytes),
};
Ok(response_builder.success(result)?)
}
.boxed()
}
}
#[derive(Serialize, Deserialize, Debug, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct GetBalanceParams {
pub state_root_hash: Digest,
pub purse_uref: String,
}
impl DocExample for GetBalanceParams {
fn doc_example() -> &'static Self {
&*GET_BALANCE_PARAMS
}
}
#[derive(Serialize, Deserialize, Debug, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct GetBalanceResult {
#[schemars(with = "String")]
pub api_version: Version,
pub balance_value: U512,
pub merkle_proof: String,
}
impl DocExample for GetBalanceResult {
fn doc_example() -> &'static Self {
&*GET_BALANCE_RESULT
}
}
pub struct GetBalance {}
impl RpcWithParams for GetBalance {
const METHOD: &'static str = "state_get_balance";
type RequestParams = GetBalanceParams;
type ResponseResult = GetBalanceResult;
}
impl RpcWithParamsExt for GetBalance {
fn handle_request<REv: ReactorEventT>(
effect_builder: EffectBuilder<REv>,
response_builder: Builder,
params: Self::RequestParams,
) -> BoxFuture<'static, Result<Response<Body>, Error>> {
async move {
let purse_uref = match URef::from_formatted_str(¶ms.purse_uref)
.map_err(|error| format!("failed to parse purse_uref: {:?}", error))
{
Ok(uref) => uref,
Err(error_msg) => {
info!("{}", error_msg);
return Ok(response_builder.error(warp_json_rpc::Error::custom(
ErrorCode::ParseGetBalanceURef as i64,
error_msg,
))?);
}
};
let balance_result = effect_builder
.make_request(
|responder| RpcRequest::GetBalance {
state_root_hash: params.state_root_hash,
purse_uref,
responder,
},
QueueKind::Api,
)
.await;
let (balance_value, purse_proof, balance_proof) = match balance_result {
Ok(BalanceResult::Success {
motes,
purse_proof,
balance_proof,
}) => (motes, purse_proof, balance_proof),
Ok(balance_result) => {
let error_msg = format!("get-balance failed: {:?}", balance_result);
info!("{}", error_msg);
return Ok(response_builder.error(warp_json_rpc::Error::custom(
ErrorCode::GetBalanceFailed as i64,
error_msg,
))?);
}
Err(error) => {
let error_msg = format!("get-balance failed to execute: {}", error);
info!("{}", error_msg);
return Ok(response_builder.error(warp_json_rpc::Error::custom(
ErrorCode::GetBalanceFailedToExecute as i64,
error_msg,
))?);
}
};
let proof_bytes = match (*purse_proof, *balance_proof).to_bytes() {
Ok(proof_bytes) => proof_bytes,
Err(error) => {
info!("failed to encode stored value: {}", error);
return Ok(response_builder.error(warp_json_rpc::Error::INTERNAL_ERROR)?);
}
};
let merkle_proof = hex::encode(proof_bytes);
let result = Self::ResponseResult {
api_version: CLIENT_API_VERSION.clone(),
balance_value,
merkle_proof,
};
Ok(response_builder.success(result)?)
}
.boxed()
}
}
#[derive(Serialize, Deserialize, Debug, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct GetAuctionInfoResult {
#[schemars(with = "String")]
pub api_version: Version,
pub auction_state: AuctionState,
}
impl DocExample for GetAuctionInfoResult {
fn doc_example() -> &'static Self {
&*GET_AUCTION_INFO_RESULT
}
}
pub struct GetAuctionInfo {}
impl RpcWithoutParams for GetAuctionInfo {
const METHOD: &'static str = "state_get_auction_info";
type ResponseResult = GetAuctionInfoResult;
}
impl RpcWithoutParamsExt for GetAuctionInfo {
fn handle_request<REv: ReactorEventT>(
effect_builder: EffectBuilder<REv>,
response_builder: Builder,
) -> BoxFuture<'static, Result<Response<Body>, Error>> {
async move {
let block: Block = {
let maybe_block = effect_builder
.make_request(
|responder| RpcRequest::GetBlock {
maybe_id: None,
responder,
},
QueueKind::Api,
)
.await;
match maybe_block {
None => {
let error_msg =
"get-auction-info failed to get last added block".to_string();
info!("{}", error_msg);
return Ok(response_builder.error(warp_json_rpc::Error::custom(
ErrorCode::NoSuchBlock as i64,
error_msg,
))?);
}
Some(block) => block,
}
};
let protocol_version = ProtocolVersion::V1_0_0;
let protocol_version_result = effect_builder
.make_request(
|responder| RpcRequest::QueryProtocolData {
protocol_version,
responder,
},
QueueKind::Api,
)
.await;
let protocol_data = {
if let Ok(Some(protocol_data)) = protocol_version_result {
protocol_data
} else {
Box::new(ProtocolData::default())
}
};
let base_key = protocol_data.auction().into();
let path = vec![casper_types::auction::BIDS_KEY.to_string()];
let state_root_hash = *block.header().state_root_hash();
let block_height = block.header().height();
let query_result = effect_builder
.make_request(
|responder| RpcRequest::QueryGlobalState {
state_root_hash,
base_key,
path,
responder,
},
QueueKind::Api,
)
.await;
let bids = if let Ok(QueryResult::Success { value, .. }) = query_result {
value
.as_cl_value()
.and_then(|cl_value| cl_value.to_owned().into_t().ok())
} else {
None
};
let era_validators_result = effect_builder
.make_request(
|responder| RpcRequest::QueryEraValidators {
state_root_hash,
protocol_version,
responder,
},
QueueKind::Api,
)
.await;
let era_validators = era_validators_result.ok();
let auction_state =
AuctionState::new(state_root_hash, block_height, era_validators, bids);
debug!("responding to client with: {:?}", auction_state);
Ok(response_builder.success(auction_state)?)
}
.boxed()
}
}