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
15 changes: 15 additions & 0 deletions bin/floresta-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,9 @@ fn do_request(cmd: &Cli, client: Client) -> anyhow::Result<String> {
serde_json::to_string_pretty(&client.get_deployment_info(blockhash)?)?
}
Methods::GetAddrManInfo => serde_json::to_string_pretty(&client.get_addrman_info()?)?,
Methods::GetAddedNodeInfo { node } => {
serde_json::to_string_pretty(&client.get_added_node_info(node)?)?
}
})
}

Expand Down Expand Up @@ -456,4 +459,16 @@ pub enum Methods {
disable_help_subcommand = true
)]
GetAddrManInfo,

#[doc = include_str!("../../../doc/rpc/getaddednodeinfo.md")]
#[command(
name = "getaddednodeinfo",
about = "Returns information about manually added nodes",
long_about = Some(include_str!("../../../doc/rpc/getaddednodeinfo.md")),
disable_help_subcommand = true
)]
GetAddedNodeInfo {
/// Filter results to a specific added node (ip or ip:port)
node: Option<String>,
},
}
25 changes: 25 additions & 0 deletions crates/floresta-node/src/json_rpc/network.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use floresta_common::advertised_services;
use floresta_common::service_flags_strings;
use floresta_wire::address_man::NetworkStats;
use floresta_wire::address_man::ReachableNetworks;
use floresta_wire::node_interface::AddedNodeInfo;
use floresta_wire::node_interface::PeerInfo;
use serde_json::Value;
use serde_json::json;
Expand Down Expand Up @@ -190,6 +191,30 @@ impl<Blockchain: RpcChain> RpcImpl<Blockchain> {
Ok(GetAddrManInfo(map))
}

pub(crate) async fn get_added_node_info(
&self,
node: Option<String>,
) -> Result<Vec<AddedNodeInfo>> {
let parsed_node = match node {
Some(addr_str) => {
let (addr, port) = if let Ok(sa) = addr_str.parse::<SocketAddr>() {
(sa.ip(), sa.port())
} else {
let ip = addr_str
.parse::<IpAddr>()
.map_err(|_| JsonRpcError::InvalidAddress)?;
(ip, 8333)
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I need #1116 landed to fix this.

};
Some((addr, port))
}
None => None,
};
self.node
.get_added_node_info(parsed_node)
.await
.map_err(|e| JsonRpcError::Node(e.to_string()))
}

pub(crate) async fn get_network_info(&self) -> Result<GetNetworkInfo> {
// Floresta does not listen for inbound connections, so every peer is outbound.
let connections_in = 0;
Expand Down
8 changes: 8 additions & 0 deletions crates/floresta-node/src/json_rpc/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,14 @@ async fn handle_json_rpc_request(
.await
.map(|v| serde_json::to_value(v).unwrap()),

"getaddednodeinfo" => {
let node = get_optional_field(&params, 0, "node", get_string)?;
state
.get_added_node_info(node)
.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
10 changes: 10 additions & 0 deletions crates/floresta-rpc/src/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,9 @@ pub trait FlorestaRPC {
fn ping(&self) -> Result<()>;
/// Returns address manager statistics broken down by network.
fn get_addrman_info(&self) -> Result<GetAddrManInfo>;

#[doc = include_str!("../../../doc/rpc/getaddednodeinfo.md")]
fn get_added_node_info(&self, node: Option<String>) -> Result<Value>;
}

/// Since the workflow for jsonrpc is the same for all methods, we can implement a trait
Expand Down Expand Up @@ -379,4 +382,11 @@ impl<T: JsonRPCClient> FlorestaRPC for T {
fn get_addrman_info(&self) -> Result<GetAddrManInfo> {
self.call("getaddrmaninfo", &[])
}

fn get_added_node_info(&self, node: Option<String>) -> Result<Value> {
match node {
Some(n) => self.call("getaddednodeinfo", &[Value::String(n)]),
None => self.call("getaddednodeinfo", &[]),
}
}
}
48 changes: 48 additions & 0 deletions crates/floresta-wire/src/p2p_wire/node/peer_man.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ 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::AddedNodeAddress;
use crate::node_interface::AddedNodeInfo;
use crate::node_interface::NodeResponse;
use crate::node_interface::PeerInfo;
use crate::node_interface::UserRequest;
Expand Down Expand Up @@ -830,6 +832,52 @@ where
})
}

pub(crate) fn handle_get_added_node_info(
&self,
node: Option<(IpAddr, u16)>,
) -> Vec<AddedNodeInfo> {
self.added_peers
.iter()
.filter_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),
};

// If a node filter is specified, skip entries that don't match
if let Some((filter_ip, filter_port)) = &node {
if added_addr != *filter_ip || added.port != *filter_port {
return None;
}
}

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

let addr_str = format!("{added_addr}:{}", added.port);

let addresses = if connected {
vec![AddedNodeAddress {
address: addr_str.clone(),
connected: "outbound".to_string(),
}]
} else {
vec![]
};

Some(AddedNodeInfo {
addednode: addr_str,
connected,
addresses,
})
})
.collect()
}

// === ADDNODE ===

// TODO: remove this after bitcoin-0.33.0
Expand Down
6 changes: 6 additions & 0 deletions crates/floresta-wire/src/p2p_wire/node/user_req.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,12 @@ where
return;
}

UserRequest::GetAddedNodeInfo(node) => {
let info = self.handle_get_added_node_info(node);
try_and_log!(responder.send(NodeResponse::GetAddedNodeInfo(info)));
return;
}

UserRequest::SendTransaction(transaction) => {
let txid = transaction.compute_txid();
let mut mempool = self.mempool.lock().await;
Expand Down
42 changes: 42 additions & 0 deletions crates/floresta-wire/src/p2p_wire/node_interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,33 @@ pub enum UserRequest {

/// Return address manager statistics.
GetAddrManInfo,

/// Return information about manually added nodes, optionally filtered by address.
GetAddedNodeInfo(Option<(IpAddr, u16)>),
}

#[derive(Debug, Clone, Serialize)]
/// Information about a manually added node (via `addnode`).
pub struct AddedNodeInfo {
/// The address of the added node in "ip:port" format.
pub addednode: String,

/// Whether we are currently connected to this node.
pub connected: bool,

/// Connection details. Only populated when `connected` is true.
#[serde(skip_serializing_if = "Vec::is_empty")]
pub addresses: Vec<AddedNodeAddress>,
}

#[derive(Debug, Clone, Serialize)]
/// Address information for a connected added node.
pub struct AddedNodeAddress {
/// The peer address in "ip:port" format.
pub address: String,

/// The connection direction: "outbound" (Floresta does not accept inbound).
pub connected: String,
}

#[derive(Debug, Clone, Serialize)]
Expand Down Expand Up @@ -165,6 +192,9 @@ pub enum NodeResponse {

/// Address manager statistics.
GetAddrManInfo(ConnectionStats),

/// Information about all manually added nodes.
GetAddedNodeInfo(Vec<AddedNodeInfo>),
}

#[derive(Debug, Clone)]
Expand Down Expand Up @@ -342,6 +372,18 @@ impl NodeInterface {

extract_variant!(GetAddrManInfo, val)
}

/// Returns information about manually added nodes, optionally filtered by address.
pub async fn get_added_node_info(
&self,
node: Option<(IpAddr, u16)>,
) -> Result<Vec<AddedNodeInfo>, oneshot::error::RecvError> {
let val = self
.send_request(UserRequest::GetAddedNodeInfo(node))
.await?;

extract_variant!(GetAddedNodeInfo, val)
}
}

fn serialize_service_flags<S>(flags: &ServiceFlags, serializer: S) -> Result<S::Ok, S::Error>
Expand Down
44 changes: 44 additions & 0 deletions doc/rpc/getaddednodeinfo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# `getaddednodeinfo`

Return information about nodes that were manually added.

## Usage

### Synopsis

```text
floresta-cli getaddednodeinfo [node]
```

### Examples

```bash
floresta-cli getaddednodeinfo
floresta-cli getaddednodeinfo 192.168.0.1:8333
```

## Arguments

- `node` - (string, optional) If provided, return information only about this added node. The value should be an IP address or `ip:port`. If only an IP is given, port 8333 is assumed.

## Returns

### Ok Response

A JSON array of objects, one per added node:

- `addednode` - (string) The address of the node in `ip:port` format
- `connected` - (boolean) Whether the node is currently connected
- `addresses` - (array, only when connected) Connection details:
- `address` - (string) The peer address in `ip:port` format
- `connected` - (string) The connection direction (`"outbound"`)

### Error Response

- `Node` - Failed to retrieve added node information
- `InvalidAddress` - The provided node address could not be parsed

## Notes

- Only manually nodes added will appear here; peers discovered automatically are not included.
- Floresta does not accept inbound connections yet, so the `connected` field in the `addresses` array is always `"outbound"`.
Loading
Loading