use packable::PackableExt;
use serde::{Deserialize, Serialize};
use url::Url;
use crate::{
client::{
constants::{DEFAULT_API_TIMEOUT, DEFAULT_USER_AGENT},
node_manager::node::{Node, NodeAuth},
Client, ClientInner, Error, Result,
},
types::{
api::core::response::{
BlockMetadataResponse, InfoResponse, OutputWithMetadataResponse, PeerResponse, ReceiptResponse,
ReceiptsResponse, RoutesResponse, SubmitBlockResponse, TipsResponse, TreasuryResponse, UtxoChangesResponse,
},
block::{
output::{Output, OutputId, OutputMetadata, OutputWithMetadata},
payload::{
milestone::{dto::MilestonePayloadDto, MilestoneId, MilestonePayload},
transaction::TransactionId,
},
Block, BlockDto, BlockId,
},
TryFromDto,
},
};
pub(crate) static INFO_PATH: &str = "api/core/v2/info";
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct NodeInfoWrapper {
pub node_info: InfoResponse,
pub url: String,
}
impl ClientInner {
pub async fn get_health(&self, url: &str) -> Result<bool> {
let path = "health";
let mut url = Url::parse(url)?;
url.set_path(path);
let status = crate::client::node_manager::http_client::HttpClient::new(DEFAULT_USER_AGENT.to_string())
.get(
Node {
url,
auth: None,
disabled: false,
},
DEFAULT_API_TIMEOUT,
)
.await?
.status();
match status {
200 => Ok(true),
_ => Ok(false),
}
}
pub async fn get_routes(&self) -> Result<RoutesResponse> {
let path = "api/routes";
self.node_manager
.read()
.await
.get_request(path, None, self.get_timeout().await, false, false)
.await
}
pub async fn get_info(&self) -> Result<NodeInfoWrapper> {
self.node_manager
.read()
.await
.get_request(INFO_PATH, None, self.get_timeout().await, false, false)
.await
}
pub async fn get_tips(&self) -> Result<Vec<BlockId>> {
let path = "api/core/v2/tips";
let response = self
.node_manager
.read()
.await
.get_request::<TipsResponse>(path, None, self.get_timeout().await, false, false)
.await?;
Ok(response.tips)
}
pub async fn post_block(&self, block: &Block) -> Result<BlockId> {
let path = "api/core/v2/blocks";
let local_pow = self.get_local_pow().await;
let timeout = if local_pow {
self.get_timeout().await
} else {
self.get_remote_pow_timeout().await
};
let block_dto = BlockDto::from(block);
let response = match self
.node_manager
.read()
.await
.post_request_json::<SubmitBlockResponse>(path, timeout, serde_json::to_value(block_dto)?, local_pow)
.await
{
Ok(res) => res,
Err(Error::Node(crate::client::node_api::error::Error::UnavailablePow)) => {
if !self.get_fallback_to_local_pow().await {
return Err(Error::Node(crate::client::node_api::error::Error::UnavailablePow));
}
self.network_info.write().await.local_pow = true;
let block_res = self.finish_block_builder(None, block.payload().cloned()).await;
let block_with_local_pow = match block_res {
Ok(block) => {
self.network_info.write().await.local_pow = false;
block
}
Err(e) => {
self.network_info.write().await.local_pow = false;
return Err(e);
}
};
let block_dto = BlockDto::from(&block_with_local_pow);
self.node_manager
.read()
.await
.post_request_json(path, timeout, serde_json::to_value(block_dto)?, true)
.await?
}
Err(e) => return Err(e),
};
Ok(response.block_id)
}
pub async fn post_block_raw(&self, block: &Block) -> Result<BlockId> {
let path = "api/core/v2/blocks";
let local_pow = self.get_local_pow().await;
let timeout = if local_pow {
self.get_timeout().await
} else {
self.get_remote_pow_timeout().await
};
let response = match self
.node_manager
.read()
.await
.post_request_bytes::<SubmitBlockResponse>(path, timeout, &block.pack_to_vec(), local_pow)
.await
{
Ok(res) => res,
Err(Error::Node(crate::client::node_api::error::Error::UnavailablePow)) => {
if !self.get_fallback_to_local_pow().await {
return Err(Error::Node(crate::client::node_api::error::Error::UnavailablePow));
}
self.network_info.write().await.local_pow = true;
let block_res = self.finish_block_builder(None, block.payload().cloned()).await;
let block_with_local_pow = match block_res {
Ok(block) => {
self.network_info.write().await.local_pow = false;
block
}
Err(e) => {
self.network_info.write().await.local_pow = false;
return Err(e);
}
};
self.node_manager
.read()
.await
.post_request_bytes(path, timeout, &block_with_local_pow.pack_to_vec(), true)
.await?
}
Err(e) => return Err(e),
};
Ok(response.block_id)
}
pub async fn get_block(&self, block_id: &BlockId) -> Result<Block> {
let path = &format!("api/core/v2/blocks/{block_id}");
let dto = self
.node_manager
.read()
.await
.get_request::<BlockDto>(path, None, self.get_timeout().await, false, true)
.await?;
Ok(Block::try_from_dto_with_params(
dto,
self.get_protocol_parameters().await?,
)?)
}
pub async fn get_block_raw(&self, block_id: &BlockId) -> Result<Vec<u8>> {
let path = &format!("api/core/v2/blocks/{block_id}");
self.node_manager
.read()
.await
.get_request_bytes(path, None, self.get_timeout().await)
.await
}
pub async fn get_block_metadata(&self, block_id: &BlockId) -> Result<BlockMetadataResponse> {
let path = &format!("api/core/v2/blocks/{block_id}/metadata");
self.node_manager
.read()
.await
.get_request(path, None, self.get_timeout().await, true, true)
.await
}
pub async fn get_output(&self, output_id: &OutputId) -> Result<OutputWithMetadata> {
let path = &format!("api/core/v2/outputs/{output_id}");
let response: OutputWithMetadataResponse = self
.node_manager
.read()
.await
.get_request(path, None, self.get_timeout().await, false, true)
.await?;
let token_supply = self.get_token_supply().await?;
let output = Output::try_from_dto_with_params(response.output, token_supply)?;
Ok(OutputWithMetadata::new(output, response.metadata))
}
pub async fn get_output_raw(&self, output_id: &OutputId) -> Result<Vec<u8>> {
let path = &format!("api/core/v2/outputs/{output_id}");
self.node_manager
.read()
.await
.get_request_bytes(path, None, self.get_timeout().await)
.await
}
pub async fn get_output_metadata(&self, output_id: &OutputId) -> Result<OutputMetadata> {
let path = &format!("api/core/v2/outputs/{output_id}/metadata");
self.node_manager
.read()
.await
.get_request::<OutputMetadata>(path, None, self.get_timeout().await, false, true)
.await
}
pub async fn get_receipts(&self) -> Result<Vec<ReceiptResponse>> {
let path = &"api/core/v2/receipts";
let resp = self
.node_manager
.read()
.await
.get_request::<ReceiptsResponse>(path, None, DEFAULT_API_TIMEOUT, false, false)
.await?;
Ok(resp.receipts)
}
pub async fn get_receipts_migrated_at(&self, milestone_index: u32) -> Result<Vec<ReceiptResponse>> {
let path = &format!("api/core/v2/receipts/{milestone_index}");
let resp = self
.node_manager
.read()
.await
.get_request::<ReceiptsResponse>(path, None, DEFAULT_API_TIMEOUT, false, false)
.await?;
Ok(resp.receipts)
}
pub async fn get_treasury(&self) -> Result<TreasuryResponse> {
let path = "api/core/v2/treasury";
self.node_manager
.read()
.await
.get_request(path, None, DEFAULT_API_TIMEOUT, false, false)
.await
}
pub async fn get_included_block(&self, transaction_id: &TransactionId) -> Result<Block> {
let path = &format!("api/core/v2/transactions/{transaction_id}/included-block");
let dto = self
.node_manager
.read()
.await
.get_request::<BlockDto>(path, None, self.get_timeout().await, true, true)
.await?;
Ok(Block::try_from_dto_with_params(
dto,
self.get_protocol_parameters().await?,
)?)
}
pub async fn get_included_block_raw(&self, transaction_id: &TransactionId) -> Result<Vec<u8>> {
let path = &format!("api/core/v2/transactions/{transaction_id}/included-block");
self.node_manager
.read()
.await
.get_request_bytes(path, None, self.get_timeout().await)
.await
}
pub async fn get_included_block_metadata(&self, transaction_id: &TransactionId) -> Result<BlockMetadataResponse> {
let path = &format!("api/core/v2/transactions/{transaction_id}/included-block/metadata");
self.node_manager
.read()
.await
.get_request(path, None, self.get_timeout().await, true, true)
.await
}
pub async fn get_milestone_by_id(&self, milestone_id: &MilestoneId) -> Result<MilestonePayload> {
let path = &format!("api/core/v2/milestones/{milestone_id}");
let dto = self
.node_manager
.read()
.await
.get_request::<MilestonePayloadDto>(path, None, self.get_timeout().await, false, true)
.await?;
Ok(MilestonePayload::try_from_dto_with_params(
dto,
self.get_protocol_parameters().await?,
)?)
}
pub async fn get_milestone_by_id_raw(&self, milestone_id: &MilestoneId) -> Result<Vec<u8>> {
let path = &format!("api/core/v2/milestones/{milestone_id}");
self.node_manager
.read()
.await
.get_request_bytes(path, None, self.get_timeout().await)
.await
}
pub async fn get_utxo_changes_by_id(&self, milestone_id: &MilestoneId) -> Result<UtxoChangesResponse> {
let path = &format!("api/core/v2/milestones/{milestone_id}/utxo-changes");
self.node_manager
.read()
.await
.get_request(path, None, self.get_timeout().await, false, false)
.await
}
pub async fn get_milestone_by_index(&self, index: u32) -> Result<MilestonePayload> {
let path = &format!("api/core/v2/milestones/by-index/{index}");
let dto = self
.node_manager
.read()
.await
.get_request::<MilestonePayloadDto>(path, None, self.get_timeout().await, false, true)
.await?;
Ok(MilestonePayload::try_from_dto_with_params(
dto,
self.get_protocol_parameters().await?,
)?)
}
pub async fn get_milestone_by_index_raw(&self, index: u32) -> Result<Vec<u8>> {
let path = &format!("api/core/v2/milestones/by-index/{index}");
self.node_manager
.read()
.await
.get_request_bytes(path, None, self.get_timeout().await)
.await
}
pub async fn get_utxo_changes_by_index(&self, index: u32) -> Result<UtxoChangesResponse> {
let path = &format!("api/core/v2/milestones/by-index/{index}/utxo-changes");
self.node_manager
.read()
.await
.get_request(path, None, self.get_timeout().await, false, false)
.await
}
pub async fn get_peers(&self) -> Result<Vec<PeerResponse>> {
let path = "api/core/v2/peers";
let resp = self
.node_manager
.read()
.await
.get_request::<Vec<PeerResponse>>(path, None, self.get_timeout().await, false, false)
.await?;
Ok(resp)
}
}
impl Client {
pub async fn get_node_info(url: &str, auth: Option<NodeAuth>) -> Result<InfoResponse> {
let mut url = crate::client::node_manager::builder::validate_url(Url::parse(url)?)?;
if let Some(auth) = &auth {
if let Some((name, password)) = &auth.basic_auth_name_pwd {
url.set_username(name)
.map_err(|_| crate::client::Error::UrlAuth("username"))?;
url.set_password(Some(password))
.map_err(|_| crate::client::Error::UrlAuth("password"))?;
}
}
let path = "api/core/v2/info";
url.set_path(path);
let resp: InfoResponse =
crate::client::node_manager::http_client::HttpClient::new(DEFAULT_USER_AGENT.to_string())
.get(
Node {
url,
auth,
disabled: false,
},
DEFAULT_API_TIMEOUT,
)
.await?
.into_json()
.await?;
Ok(resp)
}
}