use crate::network::NetworkManager;
use crate::rpc::errors::{RpcError, RpcResult};
use crate::rpc::params::{
param_bool, param_bool_default, param_str, param_str_required, param_u64_default,
};
use crate::utils::current_timestamp;
use serde_json::{json, Value};
use std::net::SocketAddr;
use std::sync::Arc;
use tracing::debug;
#[derive(Clone)]
pub struct NetworkRpc {
network_manager: Option<Arc<NetworkManager>>,
}
impl NetworkRpc {
pub fn new() -> Self {
Self {
network_manager: None,
}
}
pub fn with_dependencies(network_manager: Arc<NetworkManager>) -> Self {
Self {
network_manager: Some(network_manager),
}
}
pub async fn get_network_info(&self) -> RpcResult<Value> {
#[cfg(debug_assertions)]
debug!("RPC: getnetworkinfo");
use std::sync::OnceLock;
static CACHED_NETWORK_INFO: OnceLock<Value> = OnceLock::new();
if let Some(ref network) = self.network_manager {
let peer_count = network.peer_count();
let base_info = CACHED_NETWORK_INFO.get_or_init(|| {
json!({
"version": 70015,
"subversion": format!("/BitcoinCommons:{}/", env!("CARGO_PKG_VERSION")),
"protocolversion": 70015,
"localservices": "0000000000000001",
"localrelay": true,
"timeoffset": 0,
"networkactive": true,
"connections": 0,
"networks": [
{
"name": "ipv4",
"limited": false,
"reachable": true,
"proxy": "",
"proxy_randomize_credentials": false
},
{
"name": "ipv6",
"limited": false,
"reachable": true,
"proxy": "",
"proxy_randomize_credentials": false
}
],
"relayfee": 0.00001000,
"incrementalfee": 0.00001000,
"localaddresses": [],
"warnings": ""
})
});
let mut result = base_info.clone();
result["connections"] = json!(peer_count);
Ok(result)
} else {
Ok(json!({
"version": 70015,
"subversion": "/blvm-node:0.1.0/",
"protocolversion": 70015,
"localservices": "0000000000000001",
"localrelay": true,
"timeoffset": 0,
"networkactive": true,
"connections": 0,
"networks": [
{
"name": "ipv4",
"limited": false,
"reachable": true,
"proxy": "",
"proxy_randomize_credentials": false
},
{
"name": "ipv6",
"limited": false,
"reachable": true,
"proxy": "",
"proxy_randomize_credentials": false
}
],
"relayfee": 0.00001000,
"incrementalfee": 0.00001000,
"localaddresses": [],
"warnings": ""
}))
}
}
pub async fn get_peer_info(&self) -> RpcResult<Value> {
debug!("RPC: getpeerinfo");
if let Some(ref network) = self.network_manager {
let peer_manager = network.peer_manager().await;
let mut peers = Vec::new();
for addr in peer_manager.peer_addresses() {
if let Some(peer) = peer_manager.get_peer(&addr) {
peers.push(json!({
"id": match addr {
crate::network::transport::TransportAddr::Tcp(sock) => sock.port() as u64,
#[cfg(feature = "quinn")]
crate::network::transport::TransportAddr::Quinn(sock) => sock.port() as u64,
#[cfg(feature = "iroh")]
crate::network::transport::TransportAddr::Iroh(_) => 0u64,
},
"addr": addr.to_string(),
"addrlocal": "",
"services": "0000000000000001",
"relaytxes": true,
"lastsend": peer.last_send(),
"lastrecv": peer.last_recv(),
"bytessent": peer.bytes_sent(),
"bytesrecv": peer.bytes_recv(),
"conntime": peer.conntime(),
"timeoffset": 0,
"pingtime": 0.0,
"minping": 0.0,
"version": peer.version() as i64,
"subver": peer.user_agent().unwrap_or(&"/unknown/".to_string()).clone(),
"inbound": false,
"addnode": false,
"startingheight": 0,
"synced_headers": -1,
"synced_blocks": -1,
"inflight": [],
"whitelisted": false,
"minfeefilter": 0.00001000,
"bytessent_per_msg": {},
"bytesrecv_per_msg": {}
}));
}
}
Ok(json!(peers))
} else {
Ok(json!([]))
}
}
pub async fn get_connection_count(&self, _params: &Value) -> RpcResult<Value> {
#[cfg(debug_assertions)]
debug!("RPC: getconnectioncount");
if let Some(ref network) = self.network_manager {
Ok(Value::Number(serde_json::Number::from(
network.peer_count(),
)))
} else {
Ok(Value::Number(serde_json::Number::from(0)))
}
}
pub async fn ping(&self, _params: &Value) -> RpcResult<Value> {
#[cfg(debug_assertions)]
debug!("RPC: ping");
Ok(Value::Null)
}
pub async fn add_node(&self, params: &Value) -> RpcResult<Value> {
debug!("RPC: addnode");
let node = param_str_required(params, 0, "addnode")?;
let command = param_str(params, 1).unwrap_or("add");
let addr: SocketAddr = node.parse().map_err(|_| {
RpcError::invalid_params_with_fields(
format!("Invalid node address: {node}"),
vec![("node", "Must be in format IP:port (e.g., 192.168.1.1:8333)")],
Some(json!([
"Format: IPv4:port or [IPv6]:port",
"Example: 192.168.1.1:8333 or [2001:db8::1]:8333"
])),
)
})?;
if let Some(ref mut network) = self.network_manager.as_ref() {
match command {
"add" => {
network.add_persistent_peer(addr);
debug!("Added node {} to persistent peer list", addr);
Ok(Value::Null)
}
"remove" => {
network.remove_persistent_peer(addr);
debug!("Removed node {} from persistent peer list", addr);
Ok(Value::Null)
}
"onetry" => {
if let Err(e) = network.connect_to_peer(addr).await {
return Err(RpcError::internal_error(format!(
"Failed to connect to {addr}: {e}"
)));
}
debug!("Connected to node {} (onetry)", addr);
Ok(Value::Null)
}
_ => Err(RpcError::invalid_params(format!(
"Invalid command: {command}. Must be 'add', 'remove', or 'onetry'"
))),
}
} else {
match command {
"add" | "remove" | "onetry" => Ok(Value::Null),
_ => Err(RpcError::invalid_params(format!(
"Invalid command: {command}. Must be 'add', 'remove', or 'onetry'"
))),
}
}
}
pub async fn disconnect_node(&self, params: &Value) -> RpcResult<Value> {
debug!("RPC: disconnectnode");
let address = params
.get(0)
.and_then(|p| p.as_str())
.ok_or_else(|| RpcError::missing_parameter("address", Some("string (IP:port)")))?;
let addr: SocketAddr = address.parse().map_err(|_| {
RpcError::invalid_params_with_fields(
format!("Invalid address: {address}"),
vec![(
"address",
"Must be in format IP:port (e.g., 192.168.1.1:8333)",
)],
Some(json!([
"Format: IPv4:port or [IPv6]:port",
"Example: 192.168.1.1:8333 or [2001:db8::1]:8333"
])),
)
})?;
if let Some(ref network) = self.network_manager {
let peer_manager = network.peer_manager().await;
use crate::network::transport::TransportAddr;
let transport_addr = TransportAddr::Tcp(addr);
if peer_manager.get_peer(&transport_addr).is_some() {
debug!("Disconnect peer {} requested", addr);
} else {
debug!("Peer {} not found", addr);
}
}
Ok(Value::Null)
}
pub async fn get_net_totals(&self, _params: &Value) -> RpcResult<Value> {
#[cfg(debug_assertions)]
debug!("RPC: getnettotals");
if let Some(ref network) = self.network_manager {
let stats = network.get_network_stats().await;
Ok(json!({
"totalbytesrecv": stats.bytes_received,
"totalbytessent": stats.bytes_sent,
"activeconnections": stats.active_connections,
"bannedpeers": stats.banned_peers,
"messagequeuesize": 0, "timemillis": current_timestamp() as u128 * 1000
}))
} else {
Ok(json!({
"totalbytesrecv": 0,
"totalbytessent": 0,
"activeconnections": 0,
"bannedpeers": 0,
"messagequeuesize": 0,
"timemillis": current_timestamp() * 1000
}))
}
}
pub async fn get_dos_protection_info(&self, _params: &Value) -> RpcResult<Value> {
debug!("RPC: getdosprotectioninfo");
if let Some(network) = self.network_manager.as_ref() {
let dos_protection = network.dos_protection();
let dos_metrics = dos_protection.get_dos_metrics().await;
let dos_config = dos_protection.get_config().await;
let metrics = crate::node::metrics::DosMetrics {
connection_rate_violations: dos_metrics.connection_rate_violations,
auto_bans: dos_metrics.auto_bans_applied,
message_queue_overflows: dos_metrics.message_queue_overflows,
active_connection_limit_hits: dos_metrics.active_connection_limit_hits,
resource_exhaustion_events: dos_metrics.resource_exhaustion_events,
};
Ok(json!({
"metrics": {
"connection_rate_violations": metrics.connection_rate_violations,
"auto_bans": metrics.auto_bans,
"message_queue_overflows": metrics.message_queue_overflows,
"active_connection_limit_hits": metrics.active_connection_limit_hits,
"resource_exhaustion_events": metrics.resource_exhaustion_events,
},
"config": {
"max_connections_per_window": dos_config.max_connections_per_window,
"window_seconds": dos_config.window_seconds,
"max_message_queue_size": dos_config.max_message_queue_size,
"max_active_connections": dos_config.max_active_connections,
"auto_ban_connection_violations": dos_config.auto_ban_connection_violations,
}
}))
} else {
Ok(json!({
"error": "Network manager not available"
}))
}
}
pub async fn clear_banned(&self, _params: &Value) -> RpcResult<Value> {
debug!("RPC: clearbanned");
if let Some(ref network) = self.network_manager {
network.clear_bans();
debug!("Cleared all bans");
}
Ok(Value::Null)
}
pub async fn set_ban(&self, params: &Value) -> RpcResult<Value> {
debug!("RPC: setban");
let subnet = param_str_required(params, 0, "setban")?;
let command = param_str(params, 1).unwrap_or("add");
let addr: SocketAddr = subnet.parse().map_err(|_| {
RpcError::invalid_params_with_fields(
format!("Invalid address/subnet: {subnet}"),
vec![(
"subnet",
"Must be in format IP:port (e.g., 192.168.1.1:8333)",
)],
Some(json!([
"Format: IPv4:port or [IPv6]:port",
"Example: 192.168.1.1:8333 or [2001:db8::1]:8333"
])),
)
})?;
let bantime = param_u64_default(params, 2, 86400);
let absolute = param_bool_default(params, 3, false);
if let Some(ref network) = self.network_manager {
let now = current_timestamp();
let unban_timestamp = if absolute {
bantime } else if bantime == 0 {
0 } else {
now + bantime };
match command {
"add" => {
network.ban_peer(addr, unban_timestamp);
debug!("Banned peer {} until {}", addr, unban_timestamp);
Ok(Value::Null)
}
"remove" => {
network.unban_peer(addr);
debug!("Unbanned peer {}", addr);
Ok(Value::Null)
}
_ => Err(RpcError::invalid_params(format!(
"Invalid command: {command}. Must be 'add' or 'remove'"
))),
}
} else {
match command {
"add" | "remove" => Ok(json!(null)),
_ => Err(RpcError::invalid_params(format!(
"Invalid command: {command}. Must be 'add' or 'remove'"
))),
}
}
}
pub async fn list_banned(&self, _params: &Value) -> RpcResult<Value> {
debug!("RPC: listbanned");
if let Some(ref network) = self.network_manager {
let banned = network.get_banned_peers();
let result: Vec<Value> = banned
.iter()
.map(|(addr, unban_timestamp)| {
json!({
"address": addr.to_string(),
"banned_until": if *unban_timestamp == u64::MAX {
serde_json::Value::Null } else {
serde_json::Value::Number((*unban_timestamp).into())
},
"banned_until_absolute": *unban_timestamp == u64::MAX
})
})
.collect();
Ok(json!(result))
} else {
Ok(json!([]))
}
}
pub async fn getaddednodeinfo(&self, params: &Value) -> RpcResult<Value> {
debug!("RPC: getaddednodeinfo");
let node = param_str_required(params, 0, "getaddednodeinfo")?;
let dns = param_bool_default(params, 1, false);
if let Some(ref network) = self.network_manager {
let addr: SocketAddr = node
.parse()
.map_err(|e| RpcError::invalid_params(format!("Invalid node address: {e}")))?;
let persistent_peers = network.get_persistent_peers().await;
let _is_added = persistent_peers.contains(&addr);
let peer_count = network.peer_count();
let is_connected = peer_count > 0;
Ok(json!([{
"addednode": node,
"connected": is_connected,
"addresses": if dns {
vec![json!({
"address": node,
"connected": is_connected
})]
} else {
vec![json!({
"address": addr.to_string(),
"connected": is_connected
})]
}
}]))
} else {
Ok(json!([{
"addednode": node,
"connected": false,
"addresses": []
}]))
}
}
pub async fn getnodeaddresses(&self, params: &Value) -> RpcResult<Value> {
debug!("RPC: getnodeaddresses");
let count = param_u64_default(params, 0, 1).min(100) as usize;
if let Some(ref network) = self.network_manager {
let peer_addrs = network.get_peer_addresses().await;
let mut addresses = Vec::new();
for addr in peer_addrs.into_iter().take(count) {
match addr {
crate::network::transport::TransportAddr::Tcp(sock) => {
addresses.push(json!({
"time": current_timestamp(),
"services": "0000000000000001",
"address": sock.ip().to_string(),
"port": sock.port(),
"network": if sock.is_ipv4() { "ipv4" } else { "ipv6" }
}));
}
#[cfg(feature = "quinn")]
crate::network::transport::TransportAddr::Quinn(sock) => {
addresses.push(json!({
"time": current_timestamp(),
"services": "0000000000000001",
"address": sock.ip().to_string(),
"port": sock.port(),
"network": if sock.is_ipv4() { "ipv4" } else { "ipv6" }
}));
}
#[cfg(feature = "iroh")]
crate::network::transport::TransportAddr::Iroh(_) => {
}
}
}
Ok(json!(addresses))
} else {
Ok(json!([]))
}
}
pub async fn setnetworkactive(&self, params: &Value) -> RpcResult<Value> {
debug!("RPC: setnetworkactive");
let state = param_bool(params, 0).ok_or_else(|| {
RpcError::invalid_params("State parameter required (true/false)".to_string())
})?;
if let Some(ref network) = self.network_manager {
network.set_network_active(state).await.map_err(|e| {
RpcError::internal_error(format!("Failed to set network active: {e}"))
})?;
Ok(json!(state))
} else {
Err(RpcError::internal_error(
"Network manager not available".to_string(),
))
}
}
}
impl Default for NetworkRpc {
fn default() -> Self {
Self::new()
}
}