mirror of
https://github.com/logos-co/nomos-node.git
synced 2026-08-31 03:21:15 +00:00
323 lines
9.5 KiB
Rust
323 lines
9.5 KiB
Rust
use std::{collections::HashMap, net::SocketAddr, pin::Pin, time::Duration};
|
|
|
|
use common_http_client::{
|
|
ApiBlock, BasicAuthCredentials, CommonHttpClient, Error, ProcessedBlockEvent,
|
|
};
|
|
use futures::Stream;
|
|
use lb_blend_service::message::NetworkInfo as BlendNetworkInfo;
|
|
use lb_chain_service::ChainServiceInfo;
|
|
use lb_core::{
|
|
header::HeaderId,
|
|
mantle::{
|
|
NoteId, SignedMantleTx,
|
|
transactions::{hash::TxHash, states::VerificationState},
|
|
},
|
|
sdp::{Declaration, DeclarationId, Locator},
|
|
};
|
|
use lb_http_api_common::{
|
|
bodies::{
|
|
blend::JoinBlendRequestBody,
|
|
mantle::GasPricesResponseBody,
|
|
wallet::{
|
|
balance::WalletBalanceResponseBody,
|
|
fund::{WalletFundRequestBody, WalletFundResponseBody},
|
|
transfer_funds::{WalletTransferFundsRequestBody, WalletTransferFundsResponseBody},
|
|
},
|
|
},
|
|
paths::{
|
|
BLEND_NETWORK_INFO, DIAL_PEER, MANTLE_METRICS, MANTLE_SDP_DECLARATIONS, MEMPOOL_VIEW,
|
|
NETWORK_INFO,
|
|
},
|
|
queries::BlocksStreamQuery,
|
|
};
|
|
use lb_key_management_system_service::keys::ZkPublicKey;
|
|
use lb_libp2p::{Multiaddr, PeerId};
|
|
use lb_network_service::backends::libp2p::Libp2pInfo;
|
|
use lb_tx_service::MempoolMetrics;
|
|
use reqwest::Url;
|
|
use serde::{Deserialize, Serialize};
|
|
use tokio::time::timeout;
|
|
|
|
#[derive(Clone)]
|
|
pub struct NodeHttpClient {
|
|
base_url: Url,
|
|
http_client: CommonHttpClient,
|
|
/// Default timeout for all requests
|
|
timeout: Duration,
|
|
}
|
|
|
|
impl NodeHttpClient {
|
|
#[must_use]
|
|
pub fn new(base_addr: SocketAddr) -> Self {
|
|
let base_url = Url::parse(&format!("http://{base_addr}"))
|
|
.expect("SocketAddr should always render as a valid URL host:port");
|
|
|
|
Self::from_url(base_url)
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn from_url(base_url: Url) -> Self {
|
|
Self::from_url_with_basic_auth(base_url, None)
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn from_url_with_basic_auth(
|
|
base_url: Url,
|
|
basic_auth: Option<BasicAuthCredentials>,
|
|
) -> Self {
|
|
Self {
|
|
base_url,
|
|
http_client: CommonHttpClient::new(basic_auth),
|
|
timeout: Duration::from_secs(15),
|
|
}
|
|
}
|
|
|
|
pub async fn consensus_info(&self) -> Result<ChainServiceInfo, Error> {
|
|
self.with_timeout(
|
|
"Consensus info request",
|
|
self.http_client.consensus_info(self.base_url.clone()),
|
|
)
|
|
.await
|
|
}
|
|
|
|
pub async fn gas_prices(&self, tip: Option<HeaderId>) -> Result<GasPricesResponseBody, Error> {
|
|
self.with_timeout(
|
|
"Gas prices request",
|
|
self.http_client.gas_prices(self.base_url.clone(), tip),
|
|
)
|
|
.await
|
|
}
|
|
|
|
pub async fn network_info(&self) -> Result<Libp2pInfo, Error> {
|
|
self.network_info_at(self.base_url.clone()).await
|
|
}
|
|
|
|
pub async fn block(&self, id: &HeaderId) -> Result<Option<ApiBlock>, Error> {
|
|
self.with_timeout(
|
|
"Get block by ID request",
|
|
self.http_client.get_block_by_id(self.base_url.clone(), *id),
|
|
)
|
|
.await
|
|
}
|
|
|
|
pub async fn wallet_balance(
|
|
&self,
|
|
zk_pk: ZkPublicKey,
|
|
tip: Option<HeaderId>,
|
|
) -> Result<WalletBalanceResponseBody, Error> {
|
|
self.with_timeout(
|
|
"Wallet balance request",
|
|
self.http_client
|
|
.get_wallet_balance(self.base_url.clone(), zk_pk, tip),
|
|
)
|
|
.await
|
|
}
|
|
|
|
pub async fn blend_info(&self) -> Result<Option<BlendNetworkInfo<PeerId>>, Error> {
|
|
let request_url = Self::join_path(&self.base_url, BLEND_NETWORK_INFO)?;
|
|
|
|
self.with_timeout(
|
|
"Blend info request",
|
|
self.http_client
|
|
.get::<(), Option<BlendNetworkInfo<PeerId>>>(request_url, None),
|
|
)
|
|
.await
|
|
}
|
|
|
|
pub async fn mantle_metrics(&self) -> Result<MempoolMetrics, Error> {
|
|
let request_url = Self::join_path(&self.base_url, MANTLE_METRICS)?;
|
|
|
|
self.with_timeout(
|
|
"Mantle metrics request",
|
|
self.http_client
|
|
.get::<(), MempoolMetrics>(request_url, None),
|
|
)
|
|
.await
|
|
}
|
|
|
|
/// 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
|
|
.with_timeout(
|
|
"Blocks stream request",
|
|
self.http_client.get_blocks_stream(self.base_url.clone()),
|
|
)
|
|
.await?;
|
|
|
|
Ok(Box::pin(stream))
|
|
}
|
|
|
|
/// Opens a processed-block stream from the node HTTP API with a limited
|
|
/// range.
|
|
pub async fn blocks_range_stream(
|
|
&self,
|
|
params: BlocksStreamQuery,
|
|
) -> Result<Pin<Box<dyn Stream<Item = ProcessedBlockEvent> + Send + '_>>, Error> {
|
|
let stream = self
|
|
.with_timeout(
|
|
"Blocks range stream request",
|
|
self.http_client
|
|
.get_blocks_range_stream(self.base_url.clone(), params),
|
|
)
|
|
.await?;
|
|
|
|
Ok(Box::pin(stream))
|
|
}
|
|
|
|
pub async fn submit_transaction<State>(&self, tx: &SignedMantleTx<State>) -> Result<(), Error>
|
|
where
|
|
State: VerificationState + Send + Sync + Clone + 'static,
|
|
{
|
|
self.with_timeout(
|
|
"Submit transaction request",
|
|
self.http_client
|
|
.post_transaction(self.base_url.clone(), tx.clone()),
|
|
)
|
|
.await
|
|
}
|
|
|
|
pub async fn blend_transaction<State>(
|
|
&self,
|
|
tx: &SignedMantleTx<State>,
|
|
) -> Result<TxHash, Error>
|
|
where
|
|
State: VerificationState + Send + Sync + Clone + 'static,
|
|
{
|
|
self.with_timeout(
|
|
"Blend transaction request",
|
|
self.http_client
|
|
.blend_transaction(self.base_url.clone(), tx.clone()),
|
|
)
|
|
.await
|
|
}
|
|
|
|
pub async fn blend_pending_transactions(&self) -> Result<Vec<TxHash>, Error> {
|
|
self.with_timeout(
|
|
"Blend pending transactions request",
|
|
self.http_client
|
|
.blend_pending_transactions(self.base_url.clone()),
|
|
)
|
|
.await
|
|
}
|
|
|
|
pub async fn transfer_funds(
|
|
&self,
|
|
body: WalletTransferFundsRequestBody,
|
|
) -> Result<WalletTransferFundsResponseBody, Error> {
|
|
self.with_timeout(
|
|
"Transfer funds request",
|
|
self.http_client.transfer_funds(self.base_url.clone(), body),
|
|
)
|
|
.await
|
|
}
|
|
|
|
pub async fn fund_tx(
|
|
&self,
|
|
body: WalletFundRequestBody,
|
|
) -> Result<WalletFundResponseBody, Error> {
|
|
self.with_timeout(
|
|
"Fund transaction request",
|
|
self.http_client.fund_tx(self.base_url.clone(), body),
|
|
)
|
|
.await
|
|
}
|
|
|
|
pub async fn get_sdp_declarations(&self) -> Result<HashMap<DeclarationId, Declaration>, Error> {
|
|
self.get_sdp_declarations_at(self.base_url.clone()).await
|
|
}
|
|
|
|
pub async fn test_mempool_view(&self) -> Result<Vec<TxHash>, Error> {
|
|
let request_url = Self::join_path(&self.base_url, MEMPOOL_VIEW)?;
|
|
|
|
self.http_client
|
|
.get::<(), Vec<TxHash>>(request_url, None)
|
|
.await
|
|
}
|
|
|
|
pub async fn dial_peer(&self, addr: Multiaddr) -> Result<PeerId, Error> {
|
|
let request_url = Self::join_path(&self.base_url, DIAL_PEER)?;
|
|
|
|
self.with_timeout(
|
|
"Dial peer request",
|
|
self.http_client
|
|
.post::<_, PeerId>(request_url, &DialPeerRequestBody { addr }),
|
|
)
|
|
.await
|
|
}
|
|
|
|
#[must_use]
|
|
pub const fn base_url(&self) -> &Url {
|
|
&self.base_url
|
|
}
|
|
|
|
/// Fetches network info from one explicit base URL.
|
|
async fn network_info_at(&self, base_url: Url) -> Result<Libp2pInfo, Error> {
|
|
let request_url = Self::join_path(&base_url, NETWORK_INFO)?;
|
|
|
|
self.with_timeout(
|
|
"Network info request",
|
|
self.http_client.get::<(), Libp2pInfo>(request_url, None),
|
|
)
|
|
.await
|
|
}
|
|
|
|
/// Fetches testing-only SDP declarations from one explicit base URL.
|
|
async fn get_sdp_declarations_at(
|
|
&self,
|
|
base_url: Url,
|
|
) -> Result<HashMap<DeclarationId, Declaration>, Error> {
|
|
let request_url = Self::join_path(&base_url, MANTLE_SDP_DECLARATIONS)?;
|
|
|
|
self.with_timeout(
|
|
"SDP declarations request",
|
|
self.http_client
|
|
.get::<(), HashMap<DeclarationId, Declaration>>(request_url, None),
|
|
)
|
|
.await
|
|
}
|
|
|
|
async fn with_timeout<T, F>(&self, operation_name: &str, operation: F) -> Result<T, Error>
|
|
where
|
|
F: Future<Output = Result<T, Error>>,
|
|
{
|
|
let operation_timeout = self.timeout;
|
|
timeout(operation_timeout, operation)
|
|
.await
|
|
.unwrap_or_else(|_| {
|
|
Err(Error::Server(format!(
|
|
"{operation_name} timed out after {operation_timeout:?}"
|
|
)))
|
|
})
|
|
}
|
|
|
|
/// 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)
|
|
}
|
|
|
|
pub async fn join_blend_network(
|
|
&self,
|
|
locator: Locator,
|
|
locked_note_id: NoteId,
|
|
) -> Result<DeclarationId, Error> {
|
|
self.http_client
|
|
.join_blend_network(
|
|
&self.base_url,
|
|
JoinBlendRequestBody {
|
|
locator,
|
|
locked_note_id,
|
|
},
|
|
)
|
|
.await
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
struct DialPeerRequestBody {
|
|
addr: Multiaddr,
|
|
}
|