2026-03-02 19:47:23 +07:00
|
|
|
use std::{net::SocketAddr, pin::Pin};
|
2026-02-18 13:09:31 +07:00
|
|
|
|
2026-04-20 09:08:36 +02:00
|
|
|
use common_http_client::{
|
|
|
|
|
ApiBlock, BasicAuthCredentials, CommonHttpClient, Error, ProcessedBlockEvent,
|
|
|
|
|
};
|
2026-03-02 19:47:23 +07:00
|
|
|
use futures::Stream;
|
2026-02-18 13:09:31 +07:00
|
|
|
use lb_chain_service::CryptarchiaInfo;
|
2026-04-21 09:22:38 +07:00
|
|
|
use lb_core::{header::HeaderId, mantle::SignedMantleTx, sdp::Declaration};
|
2026-03-19 09:37:58 +02:00
|
|
|
use lb_http_api_common::{
|
|
|
|
|
bodies::wallet::transfer_funds::{
|
|
|
|
|
WalletTransferFundsRequestBody, WalletTransferFundsResponseBody,
|
|
|
|
|
},
|
2026-04-22 17:54:47 +07:00
|
|
|
paths::{DIAL_PEER, MANTLE_SDP_DECLARATIONS, NETWORK_INFO},
|
2026-03-19 09:37:58 +02:00
|
|
|
};
|
2026-04-22 17:54:47 +07:00
|
|
|
use lb_libp2p::{Multiaddr, PeerId};
|
2026-02-18 13:09:31 +07:00
|
|
|
use lb_network_service::backends::libp2p::Libp2pInfo;
|
|
|
|
|
use reqwest::Url;
|
2026-04-22 17:54:47 +07:00
|
|
|
use serde::{Deserialize, Serialize};
|
2026-02-18 13:09:31 +07:00
|
|
|
|
|
|
|
|
#[derive(Clone)]
|
|
|
|
|
pub struct NodeHttpClient {
|
|
|
|
|
base_url: Url,
|
|
|
|
|
testing_url: Option<Url>,
|
|
|
|
|
http_client: CommonHttpClient,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl NodeHttpClient {
|
|
|
|
|
#[must_use]
|
|
|
|
|
pub fn new(base_addr: SocketAddr, testing_addr: Option<SocketAddr>) -> Self {
|
|
|
|
|
let base_url = Url::parse(&format!("http://{base_addr}"))
|
|
|
|
|
.expect("SocketAddr should always render as a valid URL host:port");
|
|
|
|
|
let testing_url = testing_addr.map(|addr| {
|
|
|
|
|
Url::parse(&format!("http://{addr}"))
|
|
|
|
|
.expect("SocketAddr should always render as a valid URL host:port")
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
Self::from_urls(base_url, testing_url)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[must_use]
|
|
|
|
|
pub fn from_urls(base_url: Url, testing_url: Option<Url>) -> Self {
|
2026-02-25 14:13:49 +07:00
|
|
|
Self::from_urls_with_basic_auth(base_url, testing_url, None)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[must_use]
|
|
|
|
|
pub fn from_urls_with_basic_auth(
|
|
|
|
|
base_url: Url,
|
|
|
|
|
testing_url: Option<Url>,
|
|
|
|
|
basic_auth: Option<BasicAuthCredentials>,
|
|
|
|
|
) -> Self {
|
2026-02-18 13:09:31 +07:00
|
|
|
Self {
|
|
|
|
|
base_url,
|
|
|
|
|
testing_url,
|
2026-02-25 14:13:49 +07:00
|
|
|
http_client: CommonHttpClient::new(basic_auth),
|
2026-02-18 13:09:31 +07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub async fn consensus_info(&self) -> Result<CryptarchiaInfo, Error> {
|
|
|
|
|
self.http_client.consensus_info(self.base_url.clone()).await
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub async fn network_info(&self) -> Result<Libp2pInfo, Error> {
|
|
|
|
|
match self.network_info_at(self.base_url.clone()).await {
|
|
|
|
|
Ok(info) => Ok(info),
|
|
|
|
|
Err(base_err) => {
|
|
|
|
|
if let Some(testing_url) = self.testing_url.clone() {
|
|
|
|
|
self.network_info_at(testing_url)
|
|
|
|
|
.await
|
|
|
|
|
.map_err(|_| base_err)
|
|
|
|
|
} else {
|
|
|
|
|
Err(base_err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-20 09:08:36 +02:00
|
|
|
pub async fn block(&self, id: &HeaderId) -> Result<Option<ApiBlock>, Error> {
|
2026-04-16 18:01:53 +02:00
|
|
|
self.http_client
|
|
|
|
|
.get_block_by_id(self.base_url.clone(), *id)
|
|
|
|
|
.await
|
2026-02-18 13:09:31 +07:00
|
|
|
}
|
|
|
|
|
|
2026-03-02 19:47:23 +07:00
|
|
|
/// Opens a processed-block stream from the node HTTP API.
|
|
|
|
|
pub async fn blocks_stream(
|
|
|
|
|
&self,
|
|
|
|
|
) -> Result<Pin<Box<dyn Stream<Item = ProcessedBlockEvent> + Send + '_>>, Error> {
|
|
|
|
|
let stream = self
|
|
|
|
|
.http_client
|
|
|
|
|
.get_blocks_stream(self.base_url.clone())
|
|
|
|
|
.await?;
|
|
|
|
|
Ok(Box::pin(stream))
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-18 13:09:31 +07:00
|
|
|
pub async fn submit_transaction(&self, tx: &SignedMantleTx) -> Result<(), Error> {
|
|
|
|
|
self.http_client
|
|
|
|
|
.post_transaction(self.base_url.clone(), tx.clone())
|
|
|
|
|
.await
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-19 09:37:58 +02:00
|
|
|
pub async fn transfer_funds(
|
|
|
|
|
&self,
|
|
|
|
|
body: WalletTransferFundsRequestBody,
|
|
|
|
|
) -> Result<WalletTransferFundsResponseBody, Error> {
|
|
|
|
|
self.http_client
|
|
|
|
|
.transfer_funds(self.base_url.clone(), body)
|
|
|
|
|
.await
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-21 09:22:38 +07:00
|
|
|
pub async fn get_sdp_declarations(&self) -> Result<Vec<Declaration>, Error> {
|
|
|
|
|
if let Some(testing_url) = self.testing_url.clone()
|
|
|
|
|
&& let Ok(declarations) = self.get_sdp_declarations_at(testing_url).await
|
|
|
|
|
{
|
|
|
|
|
return Ok(declarations);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.get_sdp_declarations_at(self.base_url.clone()).await
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-22 17:54:47 +07:00
|
|
|
pub async fn dial_peer(&self, addr: Multiaddr) -> Result<PeerId, Error> {
|
|
|
|
|
let testing_url = self
|
|
|
|
|
.testing_url
|
|
|
|
|
.clone()
|
|
|
|
|
.ok_or_else(|| Error::Client("testing api unavailable".to_owned()))?;
|
|
|
|
|
let request_url = Self::join_path(&testing_url, DIAL_PEER)?;
|
|
|
|
|
|
|
|
|
|
self.http_client
|
|
|
|
|
.post::<_, PeerId>(request_url, &DialPeerRequestBody { addr })
|
|
|
|
|
.await
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-18 13:09:31 +07:00
|
|
|
#[must_use]
|
|
|
|
|
pub const fn base_url(&self) -> &Url {
|
|
|
|
|
&self.base_url
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[must_use]
|
|
|
|
|
pub const fn testing_url(&self) -> Option<&Url> {
|
|
|
|
|
self.testing_url.as_ref()
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-21 09:22:38 +07:00
|
|
|
/// Fetches network info from one explicit base URL.
|
2026-02-18 13:09:31 +07:00
|
|
|
async fn network_info_at(&self, base_url: Url) -> Result<Libp2pInfo, Error> {
|
2026-04-21 09:22:38 +07:00
|
|
|
let request_url = Self::join_path(&base_url, NETWORK_INFO)?;
|
|
|
|
|
|
2026-02-18 13:09:31 +07:00
|
|
|
self.http_client
|
|
|
|
|
.get::<(), Libp2pInfo>(request_url, None)
|
|
|
|
|
.await
|
|
|
|
|
}
|
2026-04-21 09:22:38 +07:00
|
|
|
|
|
|
|
|
/// Fetches testing-only SDP declarations from one explicit base URL.
|
|
|
|
|
async fn get_sdp_declarations_at(&self, base_url: Url) -> Result<Vec<Declaration>, Error> {
|
|
|
|
|
let request_url = Self::join_path(&base_url, MANTLE_SDP_DECLARATIONS)?;
|
|
|
|
|
|
|
|
|
|
self.http_client
|
|
|
|
|
.get::<(), Vec<Declaration>>(request_url, None)
|
|
|
|
|
.await
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Joins one static API path against a base URL.
|
|
|
|
|
fn join_path(base_url: &Url, path: &str) -> Result<Url, Error> {
|
|
|
|
|
base_url
|
|
|
|
|
.join(path.trim_start_matches('/'))
|
|
|
|
|
.map_err(Error::Url)
|
|
|
|
|
}
|
2026-02-18 13:09:31 +07:00
|
|
|
}
|
2026-04-22 17:54:47 +07:00
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
|
|
|
struct DialPeerRequestBody {
|
|
|
|
|
addr: Multiaddr,
|
|
|
|
|
}
|