Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

44 changes: 44 additions & 0 deletions crates/floresta-node/src/json_rpc/network.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ use core::net::IpAddr;
use core::net::SocketAddr;

use bitcoin::Network;
use floresta_wire::address_man::ReachableNetworks;
use floresta_wire::node_interface::AddedNodeInfo;
use floresta_wire::node_interface::AddrManInfo;
use floresta_wire::node_interface::NodeAddress;
use floresta_wire::node_interface::PeerInfo;
use serde_json::json;
use serde_json::Value;
Expand Down Expand Up @@ -118,4 +122,44 @@ impl<Blockchain: RpcChain> RpcImpl<Blockchain> {
.await
.map_err(|_| JsonRpcError::Node("Failed to get peer information".to_string()))
}

pub(crate) async fn get_added_node_info(&self) -> Result<Vec<AddedNodeInfo>> {
self.node
.get_added_node_info()
.await
.map_err(|e| JsonRpcError::Node(e.to_string()))
}
Comment on lines +126 to +131
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @jaoleal ..looking at the RPC docs for bitocoin core, https://bitcoincore.org/en/doc/30.0.0/rpc/network/getaddednodeinfo/ the rpc endpoint is meant to take the node address and return info for that address else it returns node information for all nodes...

Image


pub(crate) async fn get_node_addresses(
&self,
count: u32,
network: Option<ReachableNetworks>,
) -> Result<Vec<NodeAddress>> {
self.node
.get_node_addresses(count, network)
.await
.map_err(|e| JsonRpcError::Node(e.to_string()))
}

pub(crate) async fn get_addrman_info(&self) -> Result<AddrManInfo> {
self.node
.get_addrman_info()
.await
.map_err(|e| JsonRpcError::Node(e.to_string()))
}

pub(crate) async fn add_peer_address(
&self,
address: String,
port: u16,
tried: bool,
) -> Result<Value> {
let success = self
.node
.add_peer_address(address, port, tried)
.await
.map_err(|e| JsonRpcError::Node(e.to_string()))?;

Ok(json!({ "success": success }))
}
}
43 changes: 43 additions & 0 deletions crates/floresta-node/src/json_rpc/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ use floresta_compact_filters::network_filters::NetworkFilters;
use floresta_watch_only::kv_database::KvDatabase;
use floresta_watch_only::AddressCache;
use floresta_watch_only::CachedTransaction;
use floresta_wire::address_man::ReachableNetworks;
use floresta_wire::node_interface::NodeInterface;
use serde_json::json;
use serde_json::Value;
Expand Down Expand Up @@ -356,6 +357,48 @@ async fn handle_json_rpc_request(
.await
.map(|v| serde_json::to_value(v).unwrap()),

"getaddednodeinfo" => state
.get_added_node_info()
.await
.map(|v| serde_json::to_value(v).unwrap()),
Comment on lines +360 to +363
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should take something like let node = get_optional_field(&params, 0, "node", get_string)?;


"getnodeaddresses" => {
let count = get_optional_field(&params, 0, "count", get_numeric)?.unwrap_or(1);
let network = get_optional_field(&params, 1, "network", get_string)?
.map(|s| match s.as_str() {
"ipv4" => Ok(ReachableNetworks::IPv4),
"ipv6" => Ok(ReachableNetworks::IPv6),
"onion" => Ok(ReachableNetworks::TorV3),
"i2p" => Ok(ReachableNetworks::I2P),
"cjdns" => Ok(ReachableNetworks::Cjdns),
other => Err(JsonRpcError::InvalidParameterType(format!(
"Unknown network '{other}'. Expected one of: ipv4, ipv6, onion, i2p, cjdns"
))),
})
.transpose()?;

state
.get_node_addresses(count, network)
.await
.map(|v| serde_json::to_value(v).unwrap())
}

"getaddrmaninfo" => state
.get_addrman_info()
.await
.map(|v| serde_json::to_value(v).unwrap()),

"addpeeraddress" => {
let address = get_string(&params, 0, "address")?;
let port: u16 = get_numeric(&params, 1, "port")?;
let tried = get_optional_field(&params, 2, "tried", get_bool)?.unwrap_or(false);

state
.add_peer_address(address, port, tried)
.await
.map(|v| serde_json::to_value(v).unwrap())
}

"addnode" => {
let node = get_string(&params, 0, "node")?;
let command = get_string(&params, 1, "command")?;
Expand Down
1 change: 1 addition & 0 deletions crates/floresta-wire/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ categories = ["cryptography::cryptocurrencies", "network-programming"]
[dependencies]
bip324 = { version = "=0.7.0", features = [ "tokio" ] }
bitcoin = { workspace = true }
hex = { workspace = true }
dns-lookup = { workspace = true }
rand = { workspace = true }
rustls = "=0.23.27"
Expand Down
50 changes: 50 additions & 0 deletions crates/floresta-wire/src/p2p_wire/address_man.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,27 @@ pub enum ReachableNetworks {
Cjdns,
}

/// Per-network address manager statistics.
#[derive(Debug, Default, Clone, Copy)]
pub struct NetworkStats {
/// Number of new (untried) addresses.
pub new: usize,
/// Number of tried addresses.
pub tried: usize,
/// Total number of addresses (new + tried).
pub total: usize,
}

/// Address manager statistics broken down by network.
#[derive(Debug, Default, Clone, Copy)]
pub struct ConnectionStats {
pub ipv4: NetworkStats,
pub ipv6: NetworkStats,
pub onion: NetworkStats,
pub i2p: NetworkStats,
pub cjdns: NetworkStats,
}

#[derive(Debug, Clone, PartialEq)]
/// How do we store peers locally
pub struct LocalAddress {
Expand Down Expand Up @@ -383,6 +404,35 @@ impl AddressMan {
.as_secs()
}

/// Returns the total number of addresses stored in the address manager.
pub fn address_count(&self) -> usize {
self.addresses.len()
}

/// Returns address manager statistics broken down by network type.
pub fn get_addrman_info(&self) -> ConnectionStats {
let mut stats = ConnectionStats::default();

for addr in self.addresses.values() {
let bucket = match &addr.address {
AddrV2::Ipv4(_) => &mut stats.ipv4,
AddrV2::Ipv6(_) => &mut stats.ipv6,
AddrV2::TorV3(_) => &mut stats.onion,
AddrV2::I2p(_) => &mut stats.i2p,
AddrV2::Cjdns(_) => &mut stats.cjdns,
_ => continue,
};

bucket.total += 1;
match addr.state {
AddressState::Tried(_) | AddressState::Connected => bucket.tried += 1,
_ => bucket.new += 1,
}
}

stats
}
Comment on lines +413 to +434
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
pub fn get_addrman_info(&self) -> ConnectionStats {
let mut stats = ConnectionStats::default();
for addr in self.addresses.values() {
let bucket = match &addr.address {
AddrV2::Ipv4(_) => &mut stats.ipv4,
AddrV2::Ipv6(_) => &mut stats.ipv6,
AddrV2::TorV3(_) => &mut stats.onion,
AddrV2::I2p(_) => &mut stats.i2p,
AddrV2::Cjdns(_) => &mut stats.cjdns,
_ => continue,
};
bucket.total += 1;
match addr.state {
AddressState::Tried(_) | AddressState::Connected => bucket.tried += 1,
_ => bucket.new += 1,
}
}
stats
}
pub fn get_addrman_info(&self) -> AddrManInfo {
let mut stats = AddrManInfo::default();
for addr in self.addresses.values() {
let bucket = match &addr.address {
AddrV2::Ipv4(_) => &mut stats.ipv4,
AddrV2::Ipv6(_) => &mut stats.ipv6,
AddrV2::TorV3(_) => &mut stats.onion,
AddrV2::I2p(_) => &mut stats.i2p,
AddrV2::Cjdns(_) => &mut stats.cjdns,
_ => continue,
};
bucket.total += 1;
stats.all_networks.total += 1;
match addr.state {
AddressState::Tried(_) | AddressState::Connected => {
bucket.tried += 1;
stats.all_networks.tried += 1;
}
_ => {
bucket.new += 1;
stats.all_networks.new += 1;
}
}
}
stats
}

Consider removing ConnectionStats and using AddrManInfo directly here


/// Add a new address to our list of known address
pub fn push_addresses(&mut self, addresses: &[LocalAddress]) {
for address in addresses {
Expand Down
150 changes: 150 additions & 0 deletions crates/floresta-wire/src/p2p_wire/node/peer_man.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// SPDX-License-Identifier: MIT OR Apache-2.0

use core::net::IpAddr;
use core::net::Ipv4Addr;
use core::net::Ipv6Addr;
use core::net::SocketAddr;
use std::time::Instant;
use std::time::SystemTime;
Expand All @@ -13,6 +15,7 @@ use bitcoin::p2p::ServiceFlags;
use bitcoin::Transaction;
use floresta_chain::ChainBackend;
use floresta_common::service_flags;
use hex;
use rand::distributions::Distribution;
use rand::distributions::WeightedIndex;
use rand::prelude::SliceRandom;
Expand All @@ -29,10 +32,15 @@ use super::PeerStatus;
use super::UtreexoNode;
use crate::address_man::AddressState;
use crate::address_man::LocalAddress;
use crate::address_man::ReachableNetworks;
use crate::block_proof::Bitmap;
use crate::node::running_ctx::RunningNode;
use crate::node_context::NodeContext;
use crate::node_context::PeerId;
use crate::node_interface::AddedNodeInfo;
use crate::node_interface::AddrManInfo;
use crate::node_interface::AddrManNetworkInfo;
use crate::node_interface::NodeAddress;
use crate::node_interface::NodeResponse;
use crate::node_interface::PeerInfo;
use crate::node_interface::UserRequest;
Expand Down Expand Up @@ -817,6 +825,148 @@ where
})
}

pub(crate) fn handle_get_added_node_info(&self) -> Vec<AddedNodeInfo> {
self.added_peers
.iter()
.map(|added| {
let added_addr = match &added.address {
AddrV2::Ipv4(ip) => IpAddr::V4(*ip),
AddrV2::Ipv6(ip) => IpAddr::V6(*ip),
_ => IpAddr::V4(core::net::Ipv4Addr::UNSPECIFIED),
};

let connected = self.peers.values().any(|peer| {
peer.address == added_addr
&& peer.port == added.port
&& peer.state == PeerStatus::Ready
});

AddedNodeInfo {
addednode: format!("{}:{}", added_addr, added.port),
connected,
}
})
.collect()
}

pub(crate) fn handle_get_node_addresses(
&self,
count: u32,
network: Option<ReachableNetworks>,
) -> Vec<NodeAddress> {
let addresses = self.address_man.get_addresses_to_send();
// count=0 means return all known addresses, matching Bitcoin Core behavior
let count = if count == 0 {
addresses.len()
} else {
(count as usize).min(addresses.len())
};

addresses
.into_iter()
.filter(|(addr, _, _, _)| match &network {
None => true,
Some(ReachableNetworks::IPv4) => matches!(addr, AddrV2::Ipv4(_)),
Some(ReachableNetworks::IPv6) => matches!(addr, AddrV2::Ipv6(_)),
Some(ReachableNetworks::TorV3) => matches!(addr, AddrV2::TorV3(_)),
Some(ReachableNetworks::I2P) => matches!(addr, AddrV2::I2p(_)),
Some(ReachableNetworks::Cjdns) => matches!(addr, AddrV2::Cjdns(_)),
})
.take(count)
.filter_map(|(addr, time, services, port)| {
let (address, network) = match &addr {
AddrV2::Ipv4(ip) => (ip.to_string(), "ipv4"),
AddrV2::Ipv6(ip) => (ip.to_string(), "ipv6"),
AddrV2::Cjdns(ip) => (ip.to_string(), "cjdns"),
AddrV2::TorV3(key) => (hex::encode(key), "onion"),
AddrV2::I2p(key) => (hex::encode(key), "i2p"),
_ => return None,
};

Some(NodeAddress {
time,
services: services.to_u64(),
address,
port,
network: network.to_string(),
})
})
.collect()
Comment on lines +863 to +892
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

7c82897

Wouldn't it be better to use a single filter_map instead of a filter followed by a filter_map?

}

pub(crate) fn handle_get_addrman_info(&self) -> AddrManInfo {
let stats = self.address_man.get_addrman_info();

let to_info = |s: crate::p2p_wire::address_man::NetworkStats| AddrManNetworkInfo {
total: s.total,
new: s.new,
tried: s.tried,
};

let all_networks = AddrManNetworkInfo {
total: stats.ipv4.total
+ stats.ipv6.total
+ stats.onion.total
+ stats.i2p.total
+ stats.cjdns.total,
new: stats.ipv4.new
+ stats.ipv6.new
+ stats.onion.new
+ stats.i2p.new
+ stats.cjdns.new,
tried: stats.ipv4.tried
+ stats.ipv6.tried
+ stats.onion.tried
+ stats.i2p.tried
+ stats.cjdns.tried,
};

AddrManInfo {
all_networks,
ipv4: to_info(stats.ipv4),
ipv6: to_info(stats.ipv6),
onion: to_info(stats.onion),
i2p: to_info(stats.i2p),
cjdns: to_info(stats.cjdns),
}
}

pub(crate) fn handle_add_peer_address(
&mut self,
address: String,
port: u16,
tried: bool,
) -> bool {
let addr = if let Ok(ipv4) = address.parse::<Ipv4Addr>() {
AddrV2::Ipv4(ipv4)
} else if let Ok(ipv6) = address.parse::<Ipv6Addr>() {
AddrV2::Ipv6(ipv6)
} else {
return false;
};

let state = if tried {
AddressState::Tried(
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs(),
)
} else {
AddressState::NeverTried
};

let services = ServiceFlags::WITNESS | ServiceFlags::NETWORK_LIMITED;
let id = rand::random::<usize>();
let local_addr = LocalAddress::new(addr, 0, state, services, port, id);

let count_before = self.address_man.address_count();
self.address_man.push_addresses(&[local_addr]);
let count_after = self.address_man.address_count();

count_after > count_before
}

// === ADDNODE ===

// TODO: remove this after bitcoin-0.33.0
Expand Down
Loading
Loading