From 81afb0da54b5a754ebc26d2594abf9f3fcd9164b Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Wed, 11 Sep 2024 15:54:50 +1000 Subject: [PATCH] v17: Add support for network methods Add support to the `v17` client and integration tests for network section methods. Note, we do not implement `into_model` for the network methods because apart from `FeeRate` there are no `rust-bitcoin` types required. Note also `getaddednodeinfo` is untested because without configuring the network it returns an empty list so a test within the current framework would be smoke and mirrors. --- client/src/client_sync/v17/mod.rs | 4 +- client/src/client_sync/v17/network.rs | 20 +++ integration_test/src/v17/network.rs | 27 ++- integration_test/tests/v17_api.rs | 2 + json/src/model/mod.rs | 2 - json/src/model/network.rs | 68 ------- json/src/v17/mod.rs | 25 +-- json/src/v17/network.rs | 249 ++++++++++++++++---------- 8 files changed, 223 insertions(+), 174 deletions(-) delete mode 100644 json/src/model/network.rs diff --git a/client/src/client_sync/v17/mod.rs b/client/src/client_sync/v17/mod.rs index 1804b35..ddcfb2b 100644 --- a/client/src/client_sync/v17/mod.rs +++ b/client/src/client_sync/v17/mod.rs @@ -19,6 +19,7 @@ use crate::client_sync::{handle_defaults, into_json}; use crate::json::v17::*; crate::define_jsonrpc_minreq_client!("v17"); +crate::impl_client_check_expected_server_version!({ [170100] }); // == Blockchain == crate::impl_client_v17__getblockchaininfo!(); @@ -45,8 +46,9 @@ crate::impl_client_v17__generatetoaddress!(); crate::impl_client_v17__generate!(); // == Network == +crate::impl_client_v17__getnettotals!(); crate::impl_client_v17__getnetworkinfo!(); -crate::impl_client_check_expected_server_version!({ [170100] }); +crate::impl_client_v17__getpeerinfo!(); // == Rawtransactions == crate::impl_client_v17__sendrawtransaction!(); diff --git a/client/src/client_sync/v17/network.rs b/client/src/client_sync/v17/network.rs index 65ebce9..cdf1954 100644 --- a/client/src/client_sync/v17/network.rs +++ b/client/src/client_sync/v17/network.rs @@ -9,6 +9,16 @@ //! //! See, or use the `define_jsonrpc_minreq_client!` macro to define a `Client`. +/// Implements bitcoind JSON-RPC API method `getnettotals` +#[macro_export] +macro_rules! impl_client_v17__getnettotals { + () => { + impl Client { + pub fn get_net_totals(&self) -> Result { self.call("getnettotals", &[]) } + } + }; +} + /// Implements bitcoind JSON-RPC API method `getnetworkinfo` #[macro_export] macro_rules! impl_client_v17__getnetworkinfo { @@ -26,3 +36,13 @@ macro_rules! impl_client_v17__getnetworkinfo { } }; } + +/// Implements bitcoind JSON-RPC API method `getpeerinfo` +#[macro_export] +macro_rules! impl_client_v17__getpeerinfo { + () => { + impl Client { + pub fn get_peer_info(&self) -> Result { self.call("getpeerinfo", &[]) } + } + }; +} diff --git a/integration_test/src/v17/network.rs b/integration_test/src/v17/network.rs index 965266b..eaf9e12 100644 --- a/integration_test/src/v17/network.rs +++ b/integration_test/src/v17/network.rs @@ -5,6 +5,18 @@ //! Specifically this is methods found under the `== Network ==` section of the //! API docs of `bitcoind v0.17.1`. +/// Requires `Client` to be in scope and to implement `get_network_info`. +#[macro_export] +macro_rules! impl_test_v17__getnettotals { + () => { + #[test] + fn get_net_totals() { + let bitcoind = $crate::bitcoind_no_wallet(); + let _ = bitcoind.client.get_net_totals().expect("getnettotals"); + } + }; +} + /// Requires `Client` to be in scope and to implement `get_network_info`. #[macro_export] macro_rules! impl_test_v17__getnetworkinfo { @@ -12,10 +24,21 @@ macro_rules! impl_test_v17__getnetworkinfo { #[test] fn get_network_info() { let bitcoind = $crate::bitcoind_no_wallet(); - let json = bitcoind.client.get_network_info().expect("getnetworkinfo"); - json.into_model().unwrap(); + let _ = bitcoind.client.get_network_info().expect("getnetworkinfo"); bitcoind.client.check_expected_server_version().expect("unexpected version"); } }; } + +/// Requires `Client` to be in scope and to implement `get_peer_info`. +#[macro_export] +macro_rules! impl_test_v17__getpeerinfo { + () => { + #[test] + fn get_peer_info() { + let bitcoind = $crate::bitcoind_no_wallet(); + let _ = bitcoind.client.get_peer_info().expect("getpeerinfo"); + } + }; +} diff --git a/integration_test/tests/v17_api.rs b/integration_test/tests/v17_api.rs index 0c1a4d6..b246420 100644 --- a/integration_test/tests/v17_api.rs +++ b/integration_test/tests/v17_api.rs @@ -43,7 +43,9 @@ mod generating { mod network { use super::*; + impl_test_v17__getnettotals!(); impl_test_v17__getnetworkinfo!(); + impl_test_v17__getpeerinfo!(); } // == Rawtransactions == diff --git a/json/src/model/mod.rs b/json/src/model/mod.rs index adb90cc..5bf0469 100644 --- a/json/src/model/mod.rs +++ b/json/src/model/mod.rs @@ -12,7 +12,6 @@ mod blockchain; mod control; mod generating; mod mining; -mod network; mod raw_transactions; mod util; mod wallet; @@ -33,7 +32,6 @@ pub use self::{ GetMempoolAncestorsVerbose, GetTxOut, Softfork, SoftforkType, }, generating::{Generate, GenerateToAddress}, - network::{GetNetworkInfo, GetNetworkInfoAddress, GetNetworkInfoNetwork}, raw_transactions::SendRawTransaction, wallet::{ CreateWallet, GetBalance, GetBalances, GetBalancesMine, GetBalancesWatchOnly, diff --git a/json/src/model/network.rs b/json/src/model/network.rs deleted file mode 100644 index ec3fdf9..0000000 --- a/json/src/model/network.rs +++ /dev/null @@ -1,68 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -//! Types for methods found under the `== Network ==` section of the API docs. -//! -//! These structs model the types returned by the JSON-RPC API but have concrete types -//! and are not specific to a specific version of Bitcoin Core. - -use bitcoin::FeeRate; -use serde::{Deserialize, Serialize}; - -/// Models the result of JSON-RPC method `getnetworkinfo`. -#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] -pub struct GetNetworkInfo { - /// The server version. - pub version: usize, - /// The server subversion string. - pub subversion: String, - /// The protocol version. - pub protocol_version: usize, - /// The services we offer to the network (hex string). - pub local_services: String, - /// The services we offer to the network, in human-readable form. - pub local_services_names: Vec, - /// `true` if transaction relay is requested from peers. - pub local_relay: bool, - /// The time offset. - pub time_offset: isize, - /// The total number of connections. - pub connections: usize, - /// Whether p2p networking is enabled. - pub network_active: bool, - /// Information per network. - pub networks: Vec, - /// Minimum relay fee rate for transactions in BTC/kvB. - pub relay_fee: FeeRate, - /// Minimum fee rate increment for mempool limiting or replacement in BTC/kvB. - pub incremental_fee: FeeRate, - /// List of local addresses. - pub local_addresses: Vec, - /// Any network and blockchain warnings. - pub warnings: String, -} - -/// Part of the result of the JSON-RPC method `getnetworkinfo` (information per network). -#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] -pub struct GetNetworkInfoNetwork { - /// Network (ipv4, ipv6, onion, i2p, cjdns). - pub name: String, - /// Is the network limited using -onlynet?. - pub limited: bool, - /// Is the network reachable? - pub reachable: bool, - /// ("host:port"): The proxy that is used for this network, or empty if none. - pub proxy: String, - /// Whether randomized credentials are used. - pub proxy_randomize_credentials: bool, -} - -/// Part of the result of the JSON-RPC method `getnetworkinfo` (local address info). -#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] -pub struct GetNetworkInfoAddress { - /// Network address - pub address: String, - /// Network port - pub port: u16, - /// Relative score - pub score: u32, -} diff --git a/json/src/v17/mod.rs b/json/src/v17/mod.rs index 2dd14ce..ea6c902 100644 --- a/json/src/v17/mod.rs +++ b/json/src/v17/mod.rs @@ -57,18 +57,18 @@ //! - [ ] `submitblock "hexdata" ( "dummy" )` //! //! **== Network ==** -//! - [ ] `addnode "node" "add|remove|onetry"` -//! - [ ] `clearbanned` -//! - [ ] `disconnectnode "[address]" [nodeid]` +//! - [-] `addnode "node" "add|remove|onetry"` +//! - [-] `clearbanned` +//! - [-] `disconnectnode "[address]" [nodeid]` //! - [ ] `getaddednodeinfo ( "node" )` -//! - [ ] `getconnectioncount` -//! - [ ] `getnettotals` +//! - [-] `getconnectioncount` +//! - [x] `getnettotals` //! - [x] `getnetworkinfo` -//! - [ ] `getpeerinfo` -//! - [ ] `listbanned` -//! - [ ] `ping` -//! - [ ] `setban "subnet" "add|remove" (bantime) (absolute)` -//! - [ ] `setnetworkactive true|false` +//! - [x] `getpeerinfo` +//! - [-] `listbanned` +//! - [-] `ping` +//! - [-] `setban "subnet" "add|remove" (bantime) (absolute)` +//! - [-] `setnetworkactive true|false` //! //! **== Rawtransactions ==** //! - [ ] `combinepsbt ["psbt",...]` @@ -179,7 +179,10 @@ pub use self::{ }, control::{GetMemoryInfoStats, Locked, Logging, Uptime}, generating::{Generate, GenerateToAddress}, - network::{GetNetworkInfo, GetNetworkInfoAddress, GetNetworkInfoNetwork}, + network::{ + AddedNodeAddress, BytesPerMessage, GetAddedNodeInfo, GetNetTotals, GetNetworkInfo, + GetNetworkInfoAddress, GetNetworkInfoNetwork, GetPeerInfo, PeerInfo, UploadTarget, + }, raw_transactions::SendRawTransaction, wallet::{ CreateWallet, GetBalance, GetNewAddress, GetTransaction, GetTransactionDetail, diff --git a/json/src/v17/network.rs b/json/src/v17/network.rs index b99652a..45721c4 100644 --- a/json/src/v17/network.rs +++ b/json/src/v17/network.rs @@ -3,16 +3,84 @@ //! The JSON-RPC API for Bitcoin Core v0.17.1 - network. //! //! Types for methods found under the `== Network ==` section of the API docs. +//! +/// These types do not implement `into_model` because apart from fee rate there is no additional +/// `rust-bitcoin` types needed. +use serde::{Deserialize, Serialize}; -use core::fmt; +/// Result of JSON-RPC method `getaddednodeinfo`. +/// +/// > getaddednodeinfo ( "node" ) +/// > +/// > Returns information about the given added node, or all added nodes +/// > (note that onetry addnodes are not listed here) +/// > +/// > Arguments: +/// > 1. "node" (string, optional) If provided, return information about this specific node, otherwise all nodes are returned. +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +pub struct GetAddedNodeInfo(Vec); -use bitcoin::{amount, Amount, FeeRate}; -use internals::write_err; -use serde::{Deserialize, Serialize}; +/// An item from the list returned by the JSON-RPC method `getaddednodeinfo`. +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +pub struct AddedNode { + /// The node IP address or name (as provided to addnode) + #[serde(rename = "addednode")] + pub added_node: String, + /// If connected + pub connected: bool, + /// Only when connected = true + pub addresses: Vec, +} + +/// An address returned as part of the JSON-RPC method `getaddednodeinfo`. +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +pub struct AddedNodeAddress { + /// The bitcoin server IP and port we're connected to. + pub address: String, + /// connection, inbound or outbound + pub connected: String, +} -use crate::model; +/// Result of JSON-RPC method `getnettotals`. +/// +/// > getnettotals +/// > +/// > Returns information about network traffic, including bytes in, bytes out, +/// > and current time. +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +pub struct GetNetTotals { + /// Total bytes received. + #[serde(rename = "totalbytesrecv")] + pub total_bytes_recieved: u64, + /// Total bytes sent. + #[serde(rename = "totalbytessent")] + pub total_bytes_sent: u64, + /// Current UNIX time in milliseconds. + #[serde(rename = "timemillis")] + pub time_millis: u64, + /// Upload target totals. + #[serde(rename = "uploadtarget")] + pub upload_target: UploadTarget, +} -/// Result of the JSON-RPC method `getnetworkinfo` +/// The `upload_target` field from the result of JSON-RPC method `getnettotals`. +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +pub struct UploadTarget { + /// Length of the measuring timeframe in seconds. + pub timeframe: u64, + /// Target in bytes. + pub target: u64, + /// True if target is reached. + pub target_reached: bool, + /// True if serving historical blocks. + pub serve_historical_blocks: bool, + /// Bytes left in current time cycle. + pub bytes_left_in_cycle: u64, + /// Seconds left in current time cycle. + pub time_left_in_cycle: u64, +} + +/// Result of the JSON-RPC method `getnetworkinfo`. /// /// > getnetworkinfo /// @@ -81,91 +149,92 @@ pub struct GetNetworkInfoAddress { pub score: u32, } -impl GetNetworkInfo { - /// Converts version specific type to a version in-specific, more strongly typed type. - pub fn into_model(self) -> Result { - use GetNetworkInfoError as E; - - let relay_fee = fee_rate_from_btc_per_kb(self.relay_fee).map_err(E::RelayFee)?; - let incremental_fee = - fee_rate_from_btc_per_kb(self.incremental_fee).map_err(E::IncrementalFee)?; - - Ok(model::GetNetworkInfo { - version: self.version, - subversion: self.subversion, - protocol_version: self.protocol_version, - local_services: self.local_services, - local_services_names: vec![], // TODO: Manually create names? - local_relay: self.local_relay, - time_offset: self.time_offset, - connections: self.connections, - network_active: self.network_active, - networks: self.networks.into_iter().map(|j| j.into_model()).collect(), - relay_fee, - incremental_fee, - local_addresses: self.local_addresses.into_iter().map(|j| j.into_model()).collect(), - warnings: self.warnings, - }) - } -} - -// TODO: Upstream to `rust-bitcoin`. -/// Constructs a `bitcoin::FeeRate` from bitcoin per 1000 bytes. -fn fee_rate_from_btc_per_kb(btc_kb: f64) -> Result { - let amount = Amount::from_btc(btc_kb)?; - let sat_kb = amount.to_sat(); - // There were no virtual bytes in v0.17.1 - Ok(FeeRate::from_sat_per_kwu(sat_kb)) -} - -impl GetNetworkInfoNetwork { - /// Converts version specific type to a version in-specific, more strongly typed type. - pub fn into_model(self) -> model::GetNetworkInfoNetwork { - model::GetNetworkInfoNetwork { - name: self.name, - limited: self.limited, - reachable: self.reachable, - proxy: self.proxy, - proxy_randomize_credentials: self.proxy_randomize_credentials, - } - } -} - -impl GetNetworkInfoAddress { - /// Converts version specific type to a version in-specific, more strongly typed type. - pub fn into_model(self) -> model::GetNetworkInfoAddress { - model::GetNetworkInfoAddress { address: self.address, port: self.port, score: self.score } - } -} - -/// Error when converting to a `v22::GetBlockchainInfo` type to a `concrete` type. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum GetNetworkInfoError { - /// Conversion of the `relay_fee` field failed. - RelayFee(amount::ParseAmountError), - /// Conversion of the `incremental_fee` field failed. - IncrementalFee(amount::ParseAmountError), -} - -impl fmt::Display for GetNetworkInfoError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - use GetNetworkInfoError::*; +/// Result of JSON-RPC method `getpeerinfo`. +/// +/// > getpeerinfo +/// > +/// > Returns data about each connected network node as a json array of objects. +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +pub struct GetPeerInfo(pub Vec); - match *self { - RelayFee(ref e) => write_err!(f, "conversion of the `relay_fee` field failed"; e), - IncrementalFee(ref e) => - write_err!(f, "conversion of the `incremental_fee` field failed"; e), - } - } +/// An item from the list returned by the JSON-RPC method `getpeerinfo`. +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +pub struct PeerInfo { + /// Peer index + pub id: u32, + /// The IP address and port of the peer ("host:port"). + #[serde(rename = "addr")] + pub address: String, + /// Bind address of the connection to the peer ("ip:port"). + #[serde(rename = "addrbind")] + pub address_bind: String, + /// Local address as reported by the peer. + #[serde(rename = "addrlocal")] + pub address_local: String, + /// The services offered. + pub services: String, + /// Whether peer has asked us to relay transactions to it. + #[serde(rename = "relaytxes")] + pub relay_transactions: bool, + /// The time in seconds since epoch (Jan 1 1970 GMT) of the last send + #[serde(rename = "lastsend")] + pub last_send: u32, + /// The time in seconds since epoch (Jan 1 1970 GMT) of the last receive + #[serde(rename = "lastrecv")] + pub last_received: u32, + /// The total bytes sent + #[serde(rename = "bytessent")] + pub bytes_sent: u64, + /// The total bytes received + #[serde(rename = "bytesrecv")] + pub bytes_received: u64, + /// The connection time in seconds since epoch (Jan 1 1970 GMT) + #[serde(rename = "conntime")] + pub connection_time: u32, + /// The time offset in seconds + #[serde(rename = "timeoffset")] + pub time_offset: u32, + /// Ping time (if available). + #[serde(rename = "pingtime")] + pub ping_time: u32, + /// Minimum observed ping time (if any at all). + #[serde(rename = "minping")] + pub minimum_ping: u32, + /// Ping wait (if non-zero). + #[serde(rename = "pingwait")] + pub ping_wait: u32, + /// The peer version, such as 70001. + pub version: u32, + /// The string version (e.g. "/Satoshi:0.8.5/"). + pub subver: String, + /// Inbound (true) or Outbound (false). + pub inbound: bool, + /// Whether connection was due to addnode/-connect or if it was an automatic/inbound connection. + #[serde(rename = "addnode")] + pub add_node: bool, + /// The starting height (block) of the peer. + #[serde(rename = "startingheight")] + pub starting_height: u32, + /// The ban score. + #[serde(rename = "banscore")] + pub ban_score: u64, + /// The last header we have in common with this peer. + pub synced_headers: u32, + /// The last block we have in common with this peer. + pub synced_blocks: u32, + /// The heights of blocks we're currently asking from this peer. + pub inflight: Vec, + /// Whether the peer is whitelisted. + pub whitelisted: bool, + /// The total bytes sent aggregated by message type. + pub bytes_sent_per_message: Vec, + /// The total bytes received aggregated by message type. + pub bytes_received_per_message: Vec, } -impl std::error::Error for GetNetworkInfoError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - use GetNetworkInfoError::*; - - match *self { - RelayFee(ref e) => Some(e), - IncrementalFee(ref e) => Some(e), - } - } +/// An item from the list returned by the JSON-RPC method `getpeerinfo`. +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +pub struct BytesPerMessage { + #[serde(rename = "addr")] + pub address: u32, // FIXME: This looks wrong. }