From 1b8545128da987ea4b8e68bfa438f86499e84e2b Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Sat, 12 Oct 2024 07:03:21 +1100 Subject: [PATCH 1/5] Do minor cleanup to blockchain docs --- json/src/v17/blockchain.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/json/src/v17/blockchain.rs b/json/src/v17/blockchain.rs index 1731145..91f6890 100644 --- a/json/src/v17/blockchain.rs +++ b/json/src/v17/blockchain.rs @@ -42,8 +42,6 @@ impl GetBestBlockHash { /// Result of JSON-RPC method `getblock` with verbosity set to 0. /// /// A string that is serialized, hex-encoded data for block 'hash'. -/// -/// Method call: `getblock "blockhash" ( verbosity )` #[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] pub struct GetBlockVerbosityZero(pub String); From 6ee7f4339b160163a1a8949b259f664e3d75116f Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Sat, 12 Oct 2024 07:03:49 +1100 Subject: [PATCH 2/5] Update notes --- notes.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/notes.md b/notes.md index 6003af1..374ebc3 100644 --- a/notes.md +++ b/notes.md @@ -2,9 +2,8 @@ ## TODOs -- Consider testing against a mainnet node? +- Scrape all the integration tests from Bitcoin Core python code and + run them here. + +- Change in-specific to non-specific -/// An arbitrary mainnet block -const BLOCK: u64 = 810431; -/// Transaction vout==1 from block 810431. -const TXID = "88177263cf62ce1974465fe5a07adf8e3ee1b2eb784e8f0733d0c5a0ab52fc8c"; From 25b4c7d2734e6a96b9ad5651d680ca8679c9ed58 Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Sat, 12 Oct 2024 07:07:38 +1100 Subject: [PATCH 3/5] v17: Update key Update the key docs now that we worked out that a bunch of RPC methods are either deprecated or do not return anything. We choose not to implement deprecated for v17 because v17.1 is so far back anyways. And obviously if nothing is returned we don't need a type for it. --- json/src/v17/mod.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/json/src/v17/mod.rs b/json/src/v17/mod.rs index ea6c902..0179c35 100644 --- a/json/src/v17/mod.rs +++ b/json/src/v17/mod.rs @@ -8,9 +8,10 @@ //! in the `model` module are version non-specific and are strongly typed using `rust-bitcoin`. //! //! Key: -//! - `[ ]` means not yet done. -//! - `[x]` marks means implemented _and_ tested. -//! - `[-]` means it was considered and intentionally not done. +//! - `[ ]` Not yet done. +//! - `[x]` Implemented _and_ tested. +//! - `[-]` Intentionally not done, typically because method does not return anything, returns +//! a single integer, or is deprecated. //! //! **== Blockchain ==** //! - [x] `getbestblockhash` From 8fcceff0f96f48f35b9df03a206b3fa9508fb160 Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Wed, 16 Oct 2024 08:12:16 +1100 Subject: [PATCH 4/5] Add code comment re control types --- json/src/model/control.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/json/src/model/control.rs b/json/src/model/control.rs index 3f45357..0966c3b 100644 --- a/json/src/model/control.rs +++ b/json/src/model/control.rs @@ -4,3 +4,5 @@ //! //! 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. + +// Control types currently have no need for model equivalents. From c3b9b6d0a8c16839737e32f0e9d4cb068f8d5c5e Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Wed, 16 Oct 2024 08:12:27 +1100 Subject: [PATCH 5/5] v17: Finish network section Finish implementing all JSON types for stuff under the network section. Includes various cleanups and testing. --- client/src/client_sync/v17/mod.rs | 1 + client/src/client_sync/v17/network.rs | 12 +++ integration_test/src/v17/network.rs | 19 +++- integration_test/tests/v17_api.rs | 1 + json/src/lib.rs | 15 +++ json/src/model/mod.rs | 2 + json/src/model/network.rs | 66 ++++++++++++ json/src/v17/mod.rs | 7 +- json/src/v17/network.rs | 141 +++++++++++++++++++++----- 9 files changed, 235 insertions(+), 29 deletions(-) create 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 ddcfb2b..d57507a 100644 --- a/client/src/client_sync/v17/mod.rs +++ b/client/src/client_sync/v17/mod.rs @@ -46,6 +46,7 @@ crate::impl_client_v17__generatetoaddress!(); crate::impl_client_v17__generate!(); // == Network == +crate::impl_client_v17__getaddednodeinfo!(); crate::impl_client_v17__getnettotals!(); crate::impl_client_v17__getnetworkinfo!(); crate::impl_client_v17__getpeerinfo!(); diff --git a/client/src/client_sync/v17/network.rs b/client/src/client_sync/v17/network.rs index cdf1954..e996334 100644 --- a/client/src/client_sync/v17/network.rs +++ b/client/src/client_sync/v17/network.rs @@ -9,6 +9,18 @@ //! //! See, or use the `define_jsonrpc_minreq_client!` macro to define a `Client`. +/// Implements bitcoind JSON-RPC API method `getaddednodeinfo` +#[macro_export] +macro_rules! impl_client_v17__getaddednodeinfo { + () => { + impl Client { + pub fn get_added_node_info(&self) -> Result { + self.call("getaddednodeinfo", &[]) + } + } + }; +} + /// Implements bitcoind JSON-RPC API method `getnettotals` #[macro_export] macro_rules! impl_client_v17__getnettotals { diff --git a/integration_test/src/v17/network.rs b/integration_test/src/v17/network.rs index eaf9e12..13e7d8a 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__getaddednodeinfo { + () => { + #[test] + fn get_added_node_info() { + let bitcoind = $crate::bitcoind_no_wallet(); + let _ = bitcoind.client.get_added_node_info().expect("getaddednodeinfo"); + } + }; +} + /// Requires `Client` to be in scope and to implement `get_network_info`. #[macro_export] macro_rules! impl_test_v17__getnettotals { @@ -17,15 +29,18 @@ macro_rules! impl_test_v17__getnettotals { }; } -/// Requires `Client` to be in scope and to implement `get_network_info`. +/// Requires `Client` to be in scope and to implement `get_network_info` and +/// `check_expected_server_version`. #[macro_export] macro_rules! impl_test_v17__getnetworkinfo { () => { #[test] fn get_network_info() { let bitcoind = $crate::bitcoind_no_wallet(); - let _ = bitcoind.client.get_network_info().expect("getnetworkinfo"); + let json = bitcoind.client.get_network_info().expect("getnetworkinfo"); + assert!(json.into_model().is_ok()); + // Server version is returned as part of the getnetworkinfo method. bitcoind.client.check_expected_server_version().expect("unexpected version"); } }; diff --git a/integration_test/tests/v17_api.rs b/integration_test/tests/v17_api.rs index b246420..a14dd76 100644 --- a/integration_test/tests/v17_api.rs +++ b/integration_test/tests/v17_api.rs @@ -43,6 +43,7 @@ mod generating { mod network { use super::*; + impl_test_v17__getaddednodeinfo!(); impl_test_v17__getnettotals!(); impl_test_v17__getnetworkinfo!(); impl_test_v17__getpeerinfo!(); diff --git a/json/src/lib.rs b/json/src/lib.rs index 83139dd..da9ff7d 100644 --- a/json/src/lib.rs +++ b/json/src/lib.rs @@ -25,6 +25,9 @@ pub mod model; use std::fmt; +use bitcoin::amount::ParseAmountError; +use bitcoin::{Amount, FeeRate}; + /// Converts an `i64` numeric type to a `u32`. /// /// The Bitcoin Core JSONRPC API has fields marked as 'numeric'. It is not obvious what Rust @@ -68,3 +71,15 @@ impl fmt::Display for NumericError { } impl std::error::Error for NumericError {} + +/// Converts `fee_rate` in BTC/kB to `FeeRate`. +fn btc_per_kb(btc_per_kb: f64) -> Result, ParseAmountError> { + let btc_per_byte = btc_per_kb / 1000_f64; + let sats_per_byte = Amount::from_btc(btc_per_byte)?; + + // Virtual bytes equal bytes before segwit. + let rate = FeeRate::from_sat_per_vb(sats_per_byte.to_sat()); + + Ok(rate) +} + diff --git a/json/src/model/mod.rs b/json/src/model/mod.rs index 5bf0469..adb90cc 100644 --- a/json/src/model/mod.rs +++ b/json/src/model/mod.rs @@ -12,6 +12,7 @@ mod blockchain; mod control; mod generating; mod mining; +mod network; mod raw_transactions; mod util; mod wallet; @@ -32,6 +33,7 @@ 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 new file mode 100644 index 0000000..c29683d --- /dev/null +++ b/json/src/model/network.rs @@ -0,0 +1,66 @@ +// 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, + /// `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. + pub relay_fee: Option, // `Some` if parsing succeeds. + /// Minimum fee rate increment for mempool limiting or replacement. + pub incremental_fee: Option, // `Some` if parsing succeeds. + /// 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 0179c35..60e0778 100644 --- a/json/src/v17/mod.rs +++ b/json/src/v17/mod.rs @@ -61,7 +61,7 @@ //! - [-] `addnode "node" "add|remove|onetry"` //! - [-] `clearbanned` //! - [-] `disconnectnode "[address]" [nodeid]` -//! - [ ] `getaddednodeinfo ( "node" )` +//! - [x] `getaddednodeinfo ( "node" )` //! - [-] `getconnectioncount` //! - [x] `getnettotals` //! - [x] `getnetworkinfo` @@ -181,8 +181,9 @@ pub use self::{ control::{GetMemoryInfoStats, Locked, Logging, Uptime}, generating::{Generate, GenerateToAddress}, network::{ - AddedNodeAddress, BytesPerMessage, GetAddedNodeInfo, GetNetTotals, GetNetworkInfo, - GetNetworkInfoAddress, GetNetworkInfoNetwork, GetPeerInfo, PeerInfo, UploadTarget, + AddedNode, AddedNodeAddress, Banned, GetAddedNodeInfo, GetNetTotals, GetNetworkInfo, + GetNetworkInfoAddress, GetNetworkInfoNetwork, GetPeerInfo, ListBanned, PeerInfo, + UploadTarget, }, raw_transactions::SendRawTransaction, wallet::{ diff --git a/json/src/v17/network.rs b/json/src/v17/network.rs index 45721c4..33d7fc0 100644 --- a/json/src/v17/network.rs +++ b/json/src/v17/network.rs @@ -6,8 +6,15 @@ //! /// These types do not implement `into_model` because apart from fee rate there is no additional /// `rust-bitcoin` types needed. +use core::fmt; +use std::collections::BTreeMap; + +use bitcoin::amount::ParseAmountError; +use internals::write_err; use serde::{Deserialize, Serialize}; +use crate::model; + /// Result of JSON-RPC method `getaddednodeinfo`. /// /// > getaddednodeinfo ( "node" ) @@ -18,17 +25,17 @@ use serde::{Deserialize, Serialize}; /// > 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); +pub struct GetAddedNodeInfo(pub Vec); /// 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) + /// The node IP address or name (as provided to addnode). #[serde(rename = "addednode")] pub added_node: String, - /// If connected + /// If connected. pub connected: bool, - /// Only when connected = true + /// Only when connected = true. pub addresses: Vec, } @@ -37,7 +44,7 @@ pub struct AddedNode { pub struct AddedNodeAddress { /// The bitcoin server IP and port we're connected to. pub address: String, - /// connection, inbound or outbound + /// Connection, inbound or outbound. pub connected: String, } @@ -105,8 +112,8 @@ pub struct GetNetworkInfo { pub time_offset: isize, /// The total number of connections. pub connections: usize, - #[serde(rename = "networkactive")] /// Whether p2p networking is enabled. + #[serde(rename = "networkactive")] pub network_active: bool, /// Information per network. pub networks: Vec, @@ -141,14 +148,92 @@ pub struct GetNetworkInfoNetwork { /// 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 + /// Network address. pub address: String, - /// Network port + /// Network port. pub port: u16, - /// Relative score + /// Relative score. 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 = crate::btc_per_kb(self.relay_fee).map_err(E::RelayFee)?; + let incremental_fee = crate::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_relay: self.local_relay, + time_offset: self.time_offset, + connections: self.connections, + network_active: self.network_active, + networks: self.networks.into_iter().map(|n| n.into_model()).collect(), + relay_fee, + incremental_fee, + local_addresses: self.local_addresses.into_iter().map(|a| a.into_model()).collect(), + warnings: self.warnings, + }) + } +} + +/// Error when converting a `GetTransaction` type into the model type. +#[derive(Debug)] +pub enum GetNetworkInfoError { + /// Conversion of the `relay_fee` field failed. + RelayFee(ParseAmountError), + /// Conversion of the `incremental_fee` field failed. + IncrementalFee(ParseAmountError), +} + +impl fmt::Display for GetNetworkInfoError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use GetNetworkInfoError as E; + + match *self { + E::RelayFee(ref e) => write_err!(f, "conversion of the `relay_fee` field failed"; e), + E::IncrementalFee(ref e) => + write_err!(f, "conversion of the `incremental_fee` field failed"; e), + } + } +} + +impl std::error::Error for GetNetworkInfoError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + use GetNetworkInfoError as E; + + match *self { + E::RelayFee(ref e) => Some(e), + E::IncrementalFee(ref e) => Some(e), + } + } +} + +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 } + } +} + /// Result of JSON-RPC method `getpeerinfo`. /// /// > getpeerinfo @@ -160,7 +245,7 @@ pub struct GetPeerInfo(pub Vec); /// An item from the list returned by the JSON-RPC method `getpeerinfo`. #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] pub struct PeerInfo { - /// Peer index + /// Peer index. pub id: u32, /// The IP address and port of the peer ("host:port"). #[serde(rename = "addr")] @@ -176,22 +261,22 @@ pub struct PeerInfo { /// 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 + /// 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 + /// 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 + /// The total bytes sent. #[serde(rename = "bytessent")] pub bytes_sent: u64, - /// The total bytes received + /// The total bytes received. #[serde(rename = "bytesrecv")] pub bytes_received: u64, - /// The connection time in seconds since epoch (Jan 1 1970 GMT) + /// The connection time in seconds since epoch (Jan 1 1970 GMT). #[serde(rename = "conntime")] pub connection_time: u32, - /// The time offset in seconds + /// The time offset in seconds. #[serde(rename = "timeoffset")] pub time_offset: u32, /// Ping time (if available). @@ -206,7 +291,8 @@ pub struct PeerInfo { /// The peer version, such as 70001. pub version: u32, /// The string version (e.g. "/Satoshi:0.8.5/"). - pub subver: String, + #[serde(rename = "subver")] + pub subversion: String, /// Inbound (true) or Outbound (false). pub inbound: bool, /// Whether connection was due to addnode/-connect or if it was an automatic/inbound connection. @@ -227,14 +313,21 @@ pub struct PeerInfo { /// Whether the peer is whitelisted. pub whitelisted: bool, /// The total bytes sent aggregated by message type. - pub bytes_sent_per_message: Vec, + #[serde(rename = "bytessent_per_msg")] + pub bytes_sent_per_message: BTreeMap, /// The total bytes received aggregated by message type. - pub bytes_received_per_message: Vec, + #[serde(rename = "bytesrecv_per_msg")] + pub bytes_received_per_message: BTreeMap, } -/// An item from the list returned by the JSON-RPC method `getpeerinfo`. +/// Result of JSON-RPC method `listbanned`. +/// +/// > listbanned +/// +/// > List all banned IPs/Subnets. #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] -pub struct BytesPerMessage { - #[serde(rename = "addr")] - pub address: u32, // FIXME: This looks wrong. -} +pub struct ListBanned(pub Vec); + +/// An item from the list returned by the JSON-RPC method `listbanned` +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +pub struct Banned(String); // FIXME: The docs are empty so I don't know what shape this is.