use std::collections::HashMap;
use bytes::Bytes;
use num_bigint::BigInt;
use reqwest::Method;
use serde::{Deserialize, Deserializer};
use crate::client::{Inner, MAX_JSON_RESPONSE_BYTES, request};
use crate::swarm::Error;
use super::DebugApi;
#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
pub struct Balance {
pub peer: String,
#[serde(deserialize_with = "deserialize_bigint")]
pub balance: BigInt,
}
fn deserialize_bigint<'de, D>(d: D) -> Result<BigInt, D::Error>
where
D: Deserializer<'de>,
{
let s: String = Deserialize::deserialize(d)?;
if s.is_empty() {
return Ok(BigInt::from(0));
}
s.parse::<BigInt>().map_err(serde::de::Error::custom)
}
fn deserialize_opt_bigint<'de, D>(d: D) -> Result<Option<BigInt>, D::Error>
where
D: Deserializer<'de>,
{
let s: String = Deserialize::deserialize(d)?;
if s.is_empty() {
return Ok(None);
}
s.parse::<BigInt>()
.map(Some)
.map_err(serde::de::Error::custom)
}
#[derive(Clone, Debug, PartialEq, Eq, Default, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PeerAccounting {
#[serde(default, deserialize_with = "deserialize_opt_bigint")]
pub balance: Option<BigInt>,
#[serde(default, deserialize_with = "deserialize_opt_bigint")]
pub consumed_balance: Option<BigInt>,
#[serde(default, deserialize_with = "deserialize_opt_bigint")]
pub threshold_received: Option<BigInt>,
#[serde(default, deserialize_with = "deserialize_opt_bigint")]
pub threshold_given: Option<BigInt>,
#[serde(default, deserialize_with = "deserialize_opt_bigint")]
pub current_threshold_received: Option<BigInt>,
#[serde(default, deserialize_with = "deserialize_opt_bigint")]
pub current_threshold_given: Option<BigInt>,
#[serde(default, deserialize_with = "deserialize_opt_bigint")]
pub surplus_balance: Option<BigInt>,
#[serde(default, deserialize_with = "deserialize_opt_bigint")]
pub reserved_balance: Option<BigInt>,
#[serde(default, deserialize_with = "deserialize_opt_bigint")]
pub shadow_reserved_balance: Option<BigInt>,
#[serde(default, deserialize_with = "deserialize_opt_bigint")]
pub ghost_balance: Option<BigInt>,
}
#[derive(Clone, Debug, PartialEq, Default, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RedistributionState {
#[serde(default, deserialize_with = "deserialize_opt_bigint")]
pub minimum_gas_funds: Option<BigInt>,
pub has_sufficient_funds: bool,
pub is_frozen: bool,
pub is_fully_synced: bool,
pub phase: String,
pub round: u64,
pub last_won_round: u64,
pub last_played_round: u64,
pub last_frozen_round: u64,
pub last_selected_round: u64,
pub last_sample_duration_seconds: f64,
pub block: u64,
#[serde(default, deserialize_with = "deserialize_opt_bigint")]
pub reward: Option<BigInt>,
#[serde(default, deserialize_with = "deserialize_opt_bigint")]
pub fees: Option<BigInt>,
pub is_healthy: bool,
}
#[derive(Clone, Debug, PartialEq, Default, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RCHashResponse {
pub duration_seconds: f64,
pub hash: String,
#[serde(default)]
pub proofs: ChunkInclusionProofs,
}
#[derive(Clone, Debug, PartialEq, Default, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ChunkInclusionProofs {
#[serde(default)]
pub proof1: ChunkInclusionProof,
#[serde(default)]
pub proof2: ChunkInclusionProof,
#[serde(default)]
pub proof_last: ChunkInclusionProof,
}
#[derive(Clone, Debug, PartialEq, Default, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ChunkInclusionProof {
#[serde(default)]
pub chunk_span: u64,
#[serde(default)]
pub postage_proof: PostageProof,
pub proof_segments: Option<Vec<String>>,
pub proof_segments2: Option<Vec<String>>,
pub proof_segments3: Option<Vec<String>>,
#[serde(default)]
pub prove_segment: String,
#[serde(default)]
pub prove_segment2: String,
pub soc_proof: Option<Vec<SocProof>>,
}
#[derive(Clone, Debug, PartialEq, Default, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PostageProof {
#[serde(default)]
pub index: String,
#[serde(default)]
pub postage_id: String,
#[serde(default)]
pub signature: String,
#[serde(default)]
pub time_stamp: String,
}
#[derive(Clone, Debug, PartialEq, Default, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SocProof {
#[serde(default)]
pub chunk_addr: String,
#[serde(default)]
pub identifier: String,
#[serde(default)]
pub signature: String,
#[serde(default)]
pub signer: String,
}
impl DebugApi {
pub async fn balances(&self) -> Result<Vec<Balance>, Error> {
let builder = request(&self.inner, Method::GET, "balances")?;
#[derive(Deserialize)]
struct Resp {
balances: Vec<Balance>,
}
let r: Resp = self.inner.send_json(builder).await?;
Ok(r.balances)
}
pub async fn peer_balance(&self, address: &str) -> Result<Balance, Error> {
let path = format!("balances/{address}");
let builder = request(&self.inner, Method::GET, &path)?;
self.inner.send_json(builder).await
}
pub async fn consumed_balances(&self) -> Result<Vec<Balance>, Error> {
let builder = request(&self.inner, Method::GET, "consumed")?;
#[derive(Deserialize)]
struct Resp {
balances: Vec<Balance>,
}
let r: Resp = self.inner.send_json(builder).await?;
Ok(r.balances)
}
pub async fn peer_consumed_balance(&self, address: &str) -> Result<Balance, Error> {
let path = format!("consumed/{address}");
let builder = request(&self.inner, Method::GET, &path)?;
self.inner.send_json(builder).await
}
pub async fn accounting(&self) -> Result<HashMap<String, PeerAccounting>, Error> {
let builder = request(&self.inner, Method::GET, "accounting")?;
#[derive(Deserialize)]
struct Resp {
#[serde(rename = "peerData")]
peer_data: HashMap<String, PeerAccounting>,
}
let r: Resp = self.inner.send_json(builder).await?;
Ok(r.peer_data)
}
pub async fn stake(&self) -> Result<BigInt, Error> {
let builder = request(&self.inner, Method::GET, "stake")?;
#[derive(Deserialize)]
struct Resp {
#[serde(rename = "stakedAmount")]
staked_amount: String,
}
let r: Resp = self.inner.send_json(builder).await?;
r.staked_amount.parse::<BigInt>().map_err(|e| {
Error::argument(format!("invalid stakedAmount {:?}: {e}", r.staked_amount))
})
}
pub async fn deposit_stake(&self, amount: &BigInt) -> Result<String, Error> {
let path = format!("stake/{amount}");
let builder = request(&self.inner, Method::POST, &path)?;
tx_hash_response(&self.inner, builder).await
}
pub async fn withdrawable_stake(&self) -> Result<BigInt, Error> {
let builder = request(&self.inner, Method::GET, "stake/withdrawable")?;
#[derive(Deserialize)]
struct Resp {
#[serde(rename = "withdrawableAmount")]
withdrawable_amount: String,
}
let r: Resp = self.inner.send_json(builder).await?;
r.withdrawable_amount.parse::<BigInt>().map_err(|e| {
Error::argument(format!(
"invalid withdrawableAmount {:?}: {e}",
r.withdrawable_amount
))
})
}
pub async fn withdraw_surplus_stake(&self) -> Result<String, Error> {
let builder = request(&self.inner, Method::DELETE, "stake/withdrawable")?;
tx_hash_response(&self.inner, builder).await
}
pub async fn migrate_stake(&self) -> Result<String, Error> {
let builder = request(&self.inner, Method::DELETE, "stake")?;
tx_hash_response(&self.inner, builder).await
}
pub async fn redistribution_state(&self) -> Result<RedistributionState, Error> {
let builder = request(&self.inner, Method::GET, "redistributionstate")?;
self.inner.send_json(builder).await
}
pub async fn r_chash(
&self,
depth: u8,
anchor1: &str,
anchor2: &str,
) -> Result<RCHashResponse, Error> {
let path = format!("rchash/{depth}/{anchor1}/{anchor2}");
let builder = request(&self.inner, Method::GET, &path)?;
self.inner.send_json(builder).await
}
}
async fn tx_hash_response(
inner: &crate::client::Inner,
builder: reqwest::RequestBuilder,
) -> Result<String, Error> {
#[derive(Deserialize)]
struct Resp {
#[serde(rename = "txHash")]
tx_hash: String,
}
let resp = inner.send(builder).await?;
let bytes: Bytes = Inner::read_capped(resp, MAX_JSON_RESPONSE_BYTES).await?;
let r: Resp = serde_json::from_slice(&bytes)?;
Ok(r.tx_hash)
}