Skip to content
Merged
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
8 changes: 8 additions & 0 deletions crates/floresta-cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ Available commands:
- [getroots](#getroots)
- [stop](#stop)
- [addnode](#addnode)
- [ping](#ping)

## getblockchaininfo

Expand Down Expand Up @@ -310,3 +311,10 @@ Adds a new node to our list of peers. This will make our node try to connect to
**Return**

`success`: Whether we successfully added this node to our list of peers

## ping

Sends a ping message to all our peers, to check if they are still alive.

**Args**: None
**Return**: `null`
25 changes: 25 additions & 0 deletions crates/floresta-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ fn do_request(cmd: &Cli, client: Client) -> anyhow::Result<String> {
Methods::GetRpcInfo => serde_json::to_string_pretty(&client.get_rpc_info()?)?,
Methods::Uptime => serde_json::to_string_pretty(&client.uptime()?)?,
Methods::ListDescriptors => serde_json::to_string_pretty(&client.list_descriptors()?)?,
Methods::Ping => serde_json::to_string_pretty(&client.ping()?)?,
})
}

Expand Down Expand Up @@ -149,9 +150,11 @@ pub enum Methods {
/// Returns information about the current state of the blockchain
#[command(name = "getblockchaininfo")]
GetBlockchainInfo,

/// Returns the hash of the block associated with height
#[command(name = "getblockhash")]
GetBlockHash { height: u32 },

/// Returns the proof that one or more transactions were included in a block
#[command(name = "gettxoutproof")]
GetTxOutProof {
Expand All @@ -163,41 +166,52 @@ pub enum Methods {
#[arg(required = false)]
blockhash: Option<BlockHash>,
},

/// Returns the transaction, assuming it is cached by our watch only wallet
#[command(name = "gettransaction")]
GetTransaction { txid: Txid, verbose: Option<bool> },

/// Ask the node to rescan the blockchain for transactions
#[command(name = "rescan")]
RescanBlockchain { start_height: u32 },

/// Submits a raw transaction to the network
#[command(name = "sendrawtransaction")]
SendRawTransaction { tx: String },

/// Returns the block header for the given block hash
#[command(name = "getblockheader")]
GetBlockHeader { hash: BlockHash },

/// Loads a new descriptor to the watch only wallet
#[command(name = "loaddescriptor")]
LoadDescriptor { desc: String },

/// Returns the roots of the current utreexo forest
#[command(name = "getroots")]
GetRoots,

/// Returns a block
#[command(name = "getblock")]
GetBlock {
hash: BlockHash,
verbosity: Option<u32>,
},

/// Returns information about the peers we are connected to
#[command(name = "getpeerinfo")]
GetPeerInfo,

/// Returns the value associated with a UTXO, if it's still not spent.
/// This function only works properly if we have the compact block filters
/// feature enabled
#[command(name = "gettxout")]
GetTxOut { txid: Txid, vout: u32 },

/// Stops the node
#[command(name = "stop")]
Stop,

/// Attempts to add or remove a node from the addnode list.
/// Or try a connection to a node once.
///
Expand All @@ -222,23 +236,34 @@ pub enum Methods {
command: AddNodeCommand,
v2transport: Option<bool>,
},

#[command(name = "findtxout")]
FindTxOut {
txid: Txid,
vout: u32,
script: String,
height_hint: Option<u32>,
},

/// Returns stats about our memory usage
#[command(name = "getmemoryinfo")]
GetMemoryInfo { mode: Option<String> },

/// Returns information about the RPC server
#[command(name = "getrpcinfo")]
GetRpcInfo,

/// Returns for how long the node has been running, in seconds
#[command(name = "uptime")]
Uptime,

/// Returns a list of all descriptors currently loaded in the wallet
#[command(name = "listdescriptors")]
ListDescriptors,

/// Sends a ping to all peers, checking if they are still alive
///
/// Result: json null
#[command(name = "ping")]
Ping,
}
6 changes: 6 additions & 0 deletions crates/floresta-cli/src/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@ pub trait FlorestaRPC {
fn uptime(&self) -> Result<u32>;
/// Returns a list of all descriptors currently loaded in the wallet
fn list_descriptors(&self) -> Result<Vec<String>>;
/// Sends a ping to all peers, checking if they are still alive
fn ping(&self) -> Result<()>;
}

/// Since the workflow for jsonrpc is the same for all methods, we can implement a trait
Expand Down Expand Up @@ -292,4 +294,8 @@ impl<T: JsonRPCClient> FlorestaRPC for T {
fn list_descriptors(&self) -> Result<Vec<String>> {
self.call("listdescriptors", &[])
}

fn ping(&self) -> Result<()> {
self.call("ping", &[])
}
}
37 changes: 36 additions & 1 deletion crates/floresta-wire/src/p2p_wire/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,20 +83,34 @@ pub enum NodeNotification {
pub enum NodeRequest {
/// Get this block's data
GetBlock((Vec<BlockHash>, bool)),

/// Asks peer for headers
GetHeaders(Vec<BlockHash>),

/// Ask for other peers addresses
GetAddresses,

/// Asks this peer to shutdown
Shutdown,

/// Sends a transaction to peers
BroadcastTransaction(Txid),

/// Ask for an unconfirmed transaction
MempoolTransaction(Txid),

/// Sends know addresses to our peers
SendAddresses(Vec<AddrV2Message>),

/// Requests the peer to send us the utreexo state for a given block
GetUtreexoState((BlockHash, u32)),

/// Requests the peer to send us the compact block filters for blocks,
/// starting at a given block hash and height.
GetFilter((BlockHash, u32)),

/// Sends a ping to the peer to check if it's alive
Ping,
}

#[derive(Debug, Hash, PartialEq, Eq, Clone)]
Expand Down Expand Up @@ -384,7 +398,7 @@ where
}

let peers = peers.into_iter().flatten().collect();
responder.send(NodeResponse::GetPeerInfo(peers)).unwrap();
try_and_log!(responder.send(NodeResponse::GetPeerInfo(peers)));
}

// Helper function to resolve an IpAddr to AddrV2
Expand Down Expand Up @@ -491,6 +505,21 @@ where
.await
}

/// Sends the same request to all connected peers
///
/// This function is best-effort, meaning that some peers may not receive the request if they
/// are disconnected or if there is an error sending the request. We intentionally won't
/// propagate the error to the caller, as this would request an early return from the function,
/// which would prevent us from sending the request to the peers the comes after the first
/// erroing one.
async fn broadcast_to_peers(&mut self, request: NodeRequest) {
for peer in self.peers.values() {
if let Err(err) = peer.channel.send(request.clone()) {
warn!("Failed to send request to peer {}: {err}", peer.address);
}
}
}

/// Actually perform the user request
///
/// These are requests made by some consumer of `floresta-wire` using the [`NodeInterface`], and may
Expand All @@ -507,6 +536,12 @@ where
debug!("Performing user request {user_req:?}");

let req = match user_req {
UserRequest::Ping => {
self.broadcast_to_peers(NodeRequest::Ping).await;
try_and_log!(responder.send(NodeResponse::Ping(true)));

return;
}
UserRequest::Block(block) => NodeRequest::GetBlock((vec![block], false)),
UserRequest::UtreexoBlock(block) => NodeRequest::GetBlock((vec![block], true)),
UserRequest::MempoolTransaction(txid) => NodeRequest::MempoolTransaction(txid),
Expand Down
9 changes: 9 additions & 0 deletions crates/floresta-wire/src/p2p_wire/node_interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ pub enum UserRequest {
Add((IpAddr, u16, bool)),
Remove((IpAddr, u16)),
Onetry((IpAddr, u16, bool)),
Ping,
}

#[derive(Debug, Clone, Serialize)]
Expand Down Expand Up @@ -82,6 +83,7 @@ pub enum NodeResponse {
Add(bool),
Remove(bool),
Onetry(bool),
Ping(bool),
}

#[derive(Debug, Clone)]
Expand Down Expand Up @@ -218,6 +220,13 @@ impl NodeInterface {

extract_variant!(GetPeerInfo, val);
}

/// Pings all connected peers to check if they are alive.
pub async fn ping(&self) -> Result<bool, oneshot::error::RecvError> {
let val = self.send_request(UserRequest::Ping).await?;

extract_variant!(Ping, val)
}
}

macro_rules! extract_variant {
Expand Down
5 changes: 5 additions & 0 deletions crates/floresta-wire/src/p2p_wire/peer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,11 @@ impl<T: AsyncWrite + Unpin + Send + Sync> Peer<T> {

self.write(NetworkMessage::GetCFilters(get_filter)).await?;
}
NodeRequest::Ping => {
let nonce = rand::random();
self.last_ping = Some(Instant::now());
self.write(NetworkMessage::Ping(nonce)).await?;
}
}
Ok(())
}
Expand Down
1 change: 1 addition & 0 deletions florestad/src/json_rpc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ pub mod server;
// endpoint impls
mod blockchain;
mod control;
mod network;
14 changes: 14 additions & 0 deletions florestad/src/json_rpc/network.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//! This module holds all RPC server side methods for interacting with our node's network stack.

use super::res::Error as RpcError;
use super::server::RpcChain;
use super::server::RpcImpl;

impl<Blockchain: RpcChain> RpcImpl<Blockchain> {
pub(crate) async fn ping(&self) -> Result<bool, RpcError> {
self.node
.ping()
.await
.map_err(|e| RpcError::Node(e.to_string()))
}
}
6 changes: 6 additions & 0 deletions florestad/src/json_rpc/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,12 @@ async fn handle_json_rpc_request(
.map(|v| ::serde_json::to_value(v).unwrap())
}

"ping" => {
state.ping().await?;

Ok(serde_json::json!(null))
}

// wallet
"loaddescriptor" => {
let descriptor = params[0].as_str().ok_or(Error::InvalidDescriptor)?;
Expand Down
51 changes: 51 additions & 0 deletions tests/floresta-cli/ping-test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
"""
A test that creates a florestad and a bitcoind node, and connects them. We then
send a ping to bitcoind and check if bitcoind receives it, by calling
`getpeerinfo` and checking that we've received a ping from floresta.
"""

from test_framework import FlorestaRPC, BitcoinRPC, FlorestaTestFramework
from test_framework.rpc.floresta import REGTEST_RPC_SERVER as florestad_rpc
from test_framework.rpc.bitcoin import REGTEST_RPC_SERVER as bitcoind_rpc


class PingTest(FlorestaTestFramework):
index = [-1, -1]
expected_chain = "regtest"

def set_test_params(self):
PingTest.index[0] = self.add_node(variant="florestad", rpcserver=florestad_rpc)
PingTest.index[1] = self.add_node(variant="bitcoind", rpcserver=bitcoind_rpc)

def run_test(self):
# Start the nodes
self.run_node(PingTest.index[0])
self.run_node(PingTest.index[1])

bitcoind: BitcoinRPC = self.get_node(PingTest.index[1]).rpc
florestad: FlorestaRPC = self.get_node(PingTest.index[0]).rpc

# Connect floresta to bitcoind
florestad.addnode(
f"{bitcoind_rpc['host']}:{bitcoind_rpc['ports']['p2p']}", "onetry"
)

# Check that we have a connection, but no ping yet
peer_info = bitcoind.get_peerinfo()
self.assertTrue(
"ping" not in peer_info[0]["bytesrecv_per_msg"],
)

# Send a ping to bitcoind
self.log("Sending ping to bitcoind...")
florestad.ping()

# Check that bitcoind received the ping
peer_info = bitcoind.get_peerinfo()
self.assertTrue(peer_info[0]["bytesrecv_per_msg"]["ping"])

self.stop()


if __name__ == "__main__":
PingTest().main()
6 changes: 6 additions & 0 deletions tests/test_framework/rpc/bitcoin.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,9 @@ def stop(self):
result = self.perform_request("stop")
self.wait_for_connections(opened=False)
return result

def get_peerinfo(self) -> dict:
"""
Get the peer information by performing `perform_request('getpeerinfo')`
"""
return self.perform_request("getpeerinfo")
6 changes: 6 additions & 0 deletions tests/test_framework/rpc/floresta.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,12 @@ def addnode(self, node: str, command: str, v2transport: bool = False):

return self.perform_request("addnode", params=[node, command, v2transport])

def ping(self):
"""
Tells our node to send a ping to all its peers
"""
return self.perform_request("ping")

def get_roots(self):
"""
Returns the roots of our current floresta state performing
Expand Down
Loading