Skip to main content

bee/debug/
node.rs

1//! Health, versions, and chain-state endpoints.
2
3use num_bigint::BigInt;
4use reqwest::Method;
5use serde::{Deserialize, Deserializer};
6
7use crate::client::{Inner, MAX_JSON_RESPONSE_BYTES, request};
8use crate::swarm::Error;
9
10use super::DebugApi;
11
12/// `GET /health` response. Mirrors bee-js `Health`.
13#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
14#[serde(rename_all = "camelCase")]
15pub struct Health {
16    /// Reported status (`"ok"` when ready).
17    pub status: String,
18    /// Bee build version (e.g. `"2.7.1-61fab37b"`).
19    pub version: String,
20    /// API version this Bee implements.
21    pub api_version: String,
22}
23
24/// `GET /node` / `GET /addresses` plus health give the full picture.
25/// `Versions` is the structured triple bee-js exposes from `/health`.
26#[derive(Clone, Debug, PartialEq, Eq)]
27pub struct BeeVersions {
28    /// Full Bee version including git suffix.
29    pub bee_version: String,
30    /// Just the API version field.
31    pub bee_api_version: String,
32    /// Supported API version this client claims to follow.
33    pub supported_api_version: String,
34    /// Supported exact Bee version this client targets.
35    pub supported_bee_version_exact: String,
36}
37
38/// `GET /chainstate` response. Mirrors bee-go's `ChainStateResponse`,
39/// including the bigint-as-string custom decode for `currentPrice` /
40/// `totalAmount` (one of the three live-Bee bug fixes bee-go hit).
41#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
42#[serde(rename_all = "camelCase")]
43pub struct ChainState {
44    /// Latest block number Bee has settled to.
45    pub block: u64,
46    /// Highest block number Bee has observed (head of chain).
47    pub chain_tip: u64,
48    /// Per-chunk price in PLUR/block.
49    #[serde(default, deserialize_with = "deserialize_bigint_string")]
50    pub current_price: BigInt,
51    /// Total accumulated price (PLUR).
52    #[serde(default, deserialize_with = "deserialize_bigint_string")]
53    pub total_amount: BigInt,
54}
55
56fn deserialize_bigint_string<'de, D>(d: D) -> Result<BigInt, D::Error>
57where
58    D: Deserializer<'de>,
59{
60    let s: String = Deserialize::deserialize(d)?;
61    if s.is_empty() {
62        return Ok(BigInt::from(0));
63    }
64    s.parse::<BigInt>().map_err(serde::de::Error::custom)
65}
66
67/// Supported API version this client claims compatibility with.
68pub const SUPPORTED_API_VERSION: &str = "8.0.0";
69/// Supported exact Bee version this client targets.
70pub const SUPPORTED_BEE_VERSION_EXACT: &str = "2.7.2-rc1-83612d37";
71
72/// `GET /node` response — operator-mode flags. Mirrors bee-go `NodeInfo`.
73#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
74#[serde(rename_all = "camelCase")]
75pub struct NodeInfo {
76    /// Bee mode (`"full"`, `"light"`, `"ultra-light"`, `"dev"`).
77    pub bee_mode: String,
78    /// Whether the chequebook subsystem is enabled.
79    pub chequebook_enabled: bool,
80    /// Whether SWAP settlement is enabled.
81    pub swap_enabled: bool,
82}
83
84/// `GET /status` response — operational status snapshot. Mirrors
85/// bee-go `StatusResponse`.
86#[derive(Clone, Debug, PartialEq, Default, Deserialize)]
87#[serde(rename_all = "camelCase")]
88pub struct Status {
89    /// Overlay address.
90    pub overlay: String,
91    /// Proximity order vs. the network root.
92    pub proximity: i64,
93    /// Bee mode.
94    pub bee_mode: String,
95    /// Reserve size (chunks).
96    pub reserve_size: i64,
97    /// Reserve size within radius.
98    pub reserve_size_within_radius: i64,
99    /// Pull-sync rate (chunks/sec).
100    pub pullsync_rate: f64,
101    /// Storage radius.
102    pub storage_radius: i64,
103    /// Currently connected peers.
104    pub connected_peers: i64,
105    /// Neighbourhood size.
106    pub neighborhood_size: i64,
107    /// Batch commitment.
108    pub batch_commitment: i64,
109    /// Reachability flag.
110    pub is_reachable: bool,
111    /// Last block synced.
112    pub last_synced_block: i64,
113    /// Committed depth.
114    pub committed_depth: i64,
115    /// Whether the node is still warming up.
116    pub is_warming_up: bool,
117}
118
119/// `GET /status/peers` row — like [`Status`] but per-peer, with a
120/// `request_failed` flag set when the snapshot couldn't be gathered.
121#[derive(Clone, Debug, PartialEq, Default, Deserialize)]
122#[serde(rename_all = "camelCase")]
123pub struct PeerStatus {
124    /// Inner per-peer status snapshot (zero-valued if the request failed).
125    #[serde(flatten)]
126    pub status: Status,
127    /// True when Bee couldn't reach this peer in time.
128    #[serde(default)]
129    pub request_failed: bool,
130}
131
132/// `GET /status/neighborhoods` row — per-neighbourhood reserve stats.
133#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
134#[serde(rename_all = "camelCase")]
135pub struct Neighborhood {
136    /// Binary prefix string (e.g. `"01101"`).
137    pub neighborhood: String,
138    /// Reserve size within the neighbourhood radius.
139    pub reserve_size_within_radius: i64,
140    /// Proximity order.
141    pub proximity: u8,
142}
143
144impl DebugApi {
145    /// `GET /health` — basic liveness + version info.
146    pub async fn health(&self) -> Result<Health, Error> {
147        let builder = request(&self.inner, Method::GET, "health")?;
148        self.inner.send_json(builder).await
149    }
150
151    /// Structured version triple derived from `/health`.
152    pub async fn versions(&self) -> Result<BeeVersions, Error> {
153        let h = self.health().await?;
154        Ok(BeeVersions {
155            bee_version: h.version,
156            bee_api_version: h.api_version,
157            supported_api_version: SUPPORTED_API_VERSION.to_string(),
158            supported_bee_version_exact: SUPPORTED_BEE_VERSION_EXACT.to_string(),
159        })
160    }
161
162    /// True iff the Bee node's reported API version equals
163    /// [`SUPPORTED_API_VERSION`].
164    pub async fn is_supported_api_version(&self) -> Result<bool, Error> {
165        let h = self.health().await?;
166        Ok(h.api_version == SUPPORTED_API_VERSION)
167    }
168
169    /// True iff the Bee node's reported version equals
170    /// [`SUPPORTED_BEE_VERSION_EXACT`].
171    pub async fn is_supported_exact_version(&self) -> Result<bool, Error> {
172        let h = self.health().await?;
173        Ok(h.version == SUPPORTED_BEE_VERSION_EXACT)
174    }
175
176    /// `GET /chainstate` — current price and total amount as bigints.
177    pub async fn chain_state(&self) -> Result<ChainState, Error> {
178        let builder = request(&self.inner, Method::GET, "chainstate")?;
179        self.inner.send_json(builder).await
180    }
181
182    /// `GET /node` — operator-mode flags.
183    pub async fn node_info(&self) -> Result<NodeInfo, Error> {
184        let builder = request(&self.inner, Method::GET, "node")?;
185        self.inner.send_json(builder).await
186    }
187
188    /// `GET /status` — operational snapshot (reserve size, sync, peers).
189    pub async fn status(&self) -> Result<Status, Error> {
190        let builder = request(&self.inner, Method::GET, "status")?;
191        self.inner.send_json(builder).await
192    }
193
194    /// `GET /status/peers` — per-peer status snapshots gathered in
195    /// parallel by the Bee node. Peers that don't respond have
196    /// `request_failed = true`.
197    pub async fn status_peers(&self) -> Result<Vec<PeerStatus>, Error> {
198        let builder = request(&self.inner, Method::GET, "status/peers")?;
199        #[derive(Deserialize)]
200        struct Resp {
201            snapshots: Vec<PeerStatus>,
202        }
203        let r: Resp = self.inner.send_json(builder).await?;
204        Ok(r.snapshots)
205    }
206
207    /// `GET /status/neighborhoods` — reserve statistics per
208    /// neighbourhood.
209    pub async fn status_neighborhoods(&self) -> Result<Vec<Neighborhood>, Error> {
210        let builder = request(&self.inner, Method::GET, "status/neighborhoods")?;
211        #[derive(Deserialize)]
212        struct Resp {
213            neighborhoods: Vec<Neighborhood>,
214        }
215        let r: Resp = self.inner.send_json(builder).await?;
216        Ok(r.neighborhoods)
217    }
218
219    /// `GET /readiness` — true if the node returns 2xx, false on 404,
220    /// otherwise the underlying error.
221    pub async fn readiness(&self) -> Result<bool, Error> {
222        let builder = request(&self.inner, Method::GET, "readiness")?;
223        match self.inner.send(builder).await {
224            Ok(_) => Ok(true),
225            Err(e) if e.status() == Some(404) || e.status() == Some(503) => Ok(false),
226            Err(e) => Err(e),
227        }
228    }
229
230    /// `GET /gateway` — true when Bee is running in gateway mode. A
231    /// 404 (gateway disabled) becomes `Ok(false)` — matching bee-js.
232    pub async fn is_gateway(&self) -> Result<bool, Error> {
233        let builder = request(&self.inner, Method::GET, "gateway")?;
234        match self.inner.send(builder).await {
235            Ok(resp) => {
236                #[derive(Deserialize)]
237                struct Resp {
238                    gateway: bool,
239                }
240                let r: Resp = serde_json::from_slice(&Inner::read_capped(resp, MAX_JSON_RESPONSE_BYTES).await?)?;
241                Ok(r.gateway)
242            }
243            Err(e) if e.status() == Some(404) => Ok(false),
244            Err(e) => Err(e),
245        }
246    }
247
248    /// Ping the base URL — true on any 2xx response. Mirrors bee-js
249    /// `Bee.isConnected`.
250    pub async fn is_connected(&self) -> bool {
251        self.check_connection().await.is_ok()
252    }
253
254    /// Same as [`DebugApi::is_connected`] but returns the underlying
255    /// error if the node is unreachable.
256    pub async fn check_connection(&self) -> Result<(), Error> {
257        let builder = self
258            .inner
259            .http
260            .request(Method::GET, self.inner.base_url.clone());
261        self.inner.send(builder).await?;
262        Ok(())
263    }
264}