use std::{convert::TryFrom, str};
use futures::{future::BoxFuture, FutureExt};
use http::Response;
use hyper::Body;
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, Key, ProtocolVersion, URef, U512};
use super::{ApiRequest, Error, ErrorCode, ReactorEventT, RpcWithParams, RpcWithParamsExt};
use crate::{
components::api_server::CLIENT_API_VERSION,
crypto::hash::Digest,
effect::EffectBuilder,
reactor::QueueKind,
rpcs::{RpcWithoutParams, RpcWithoutParamsExt},
types::{
json_compatibility::{AuctionState, StoredValue},
Block,
},
};
#[derive(Serialize, Deserialize, Debug)]
pub struct GetItemParams {
pub state_root_hash: Digest,
pub key: String,
pub path: Vec<String>,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct GetItemResult {
pub api_version: Version,
pub stored_value: StoredValue,
pub merkle_proof: String,
}
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| ApiRequest::QueryGlobalState {
state_root_hash: params.state_root_hash,
base_key,
path: params.path,
responder,
},
QueueKind::Api,
)
.await;
let (value, proof) = match query_result {
Ok(QueryResult::Success { value, proofs }) => (value, proofs),
Ok(query_result) => {
let error_msg = format!("state query failed: {:?}", query_result);
info!("{}", error_msg);
return Ok(response_builder.error(warp_json_rpc::Error::custom(
ErrorCode::QueryFailed as i64,
error_msg,
))?);
}
Err(error) => {
let error_msg = format!("state query failed to execute: {:?}", error);
info!("{}", error_msg);
return Ok(response_builder.error(warp_json_rpc::Error::custom(
ErrorCode::QueryFailedToExecute as i64,
error_msg,
))?);
}
};
let value_compat = match StoredValue::try_from(&*value) {
Ok(value_compat) => value_compat,
Err(error) => {
info!("failed to encode stored value: {}", error);
return Ok(response_builder.error(warp_json_rpc::Error::INTERNAL_ERROR)?);
}
};
let proof_bytes = match 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 result = Self::ResponseResult {
api_version: CLIENT_API_VERSION.clone(),
stored_value: value_compat,
merkle_proof: hex::encode(proof_bytes),
};
Ok(response_builder.success(result)?)
}
.boxed()
}
}
#[derive(Serialize, Deserialize, Debug)]
pub struct GetBalanceParams {
pub state_root_hash: Digest,
pub purse_uref: String,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct GetBalanceResult {
pub api_version: Version,
pub balance_value: U512,
pub merkle_proof: String,
}
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| ApiRequest::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)]
pub struct GetAuctionInfoResult {
pub api_version: Version,
pub auction_state: AuctionState,
}
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| ApiRequest::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| ApiRequest::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| ApiRequest::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| ApiRequest::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, bids, era_validators);
debug!("responding to client with: {:?}", auction_state);
Ok(response_builder.success(auction_state)?)
}
.boxed()
}
}