From 06823c696c222d55258fa0478c6c6dc009fbf8a0 Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Mon, 17 Jun 2024 15:27:10 +1000 Subject: [PATCH 1/2] Add a len/is_empty functions to GenerateToAddress Add a convenience functions to the `GenerateToAddress` model type to get the number of blocks generated. --- json/src/model/generating.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/json/src/model/generating.rs b/json/src/model/generating.rs index c188830..724b690 100644 --- a/json/src/model/generating.rs +++ b/json/src/model/generating.rs @@ -11,3 +11,11 @@ use serde::{Deserialize, Serialize}; /// Models the result of JSON-RPC method `generatetoaddress`. #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] pub struct GenerateToAddress(pub Vec); + +impl GenerateToAddress { + /// Returns the number of blocks generated. + pub fn len(&self) -> usize { self.0.len() } + + /// Returns true if 0 blocks were generated. + pub fn is_empty(&self) -> bool { self.0.is_empty() } +} From d0e052d3a584a5ac90649e4e5c2d62d14adb460d Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Fri, 21 Jun 2024 03:31:46 +1000 Subject: [PATCH 2/2] Implement into_model For all `json` types implement an inherent `into_model` function and remove the `TryInto` implementations. Done to improve ergonomics. Patch includes a bunch of other cleanups and improvements on the way. --- client/src/client_sync/error.rs | 15 +- client/src/client_sync/v17/blockchain.rs | 6 +- client/src/client_sync/v17/mod.rs | 4 +- client/src/client_sync/v17/wallet.rs | 2 +- client/src/client_sync/v23.rs | 4 +- integration_test/src/v17/blockchain.rs | 46 +- integration_test/src/v17/control.rs | 1 + integration_test/src/v17/generating.rs | 3 +- integration_test/src/v17/network.rs | 4 +- integration_test/src/v17/wallet.rs | 42 +- integration_test/src/v19/wallet.rs | 3 +- integration_test/src/v22/blockchain.rs | 25 - integration_test/src/v22/mod.rs | 1 - integration_test/src/v22/wallet.rs | 7 + integration_test/tests/v17_api.rs | 4 +- integration_test/tests/v18_api.rs | 3 +- integration_test/tests/v19_api.rs | 3 +- integration_test/tests/v20_api.rs | 3 +- integration_test/tests/v21_api.rs | 3 +- integration_test/tests/v22_api.rs | 6 +- integration_test/tests/v23_api.rs | 5 +- integration_test/tests/v24_api.rs | 5 +- integration_test/tests/v25_api.rs | 5 +- integration_test/tests/v26_api.rs | 3 +- json/src/model/blockchain.rs | 31 +- json/src/v17/blockchain.rs | 502 ++++++++++++++++++ json/src/v17/blockchain/convert.rs | 198 ------- json/src/v17/blockchain/mod.rs | 220 -------- json/src/v17/{control/mod.rs => control.rs} | 0 .../v17/{generating/mod.rs => generating.rs} | 13 +- json/src/v17/generating/convert.rs | 23 - json/src/v17/{mining/mod.rs => mining.rs} | 0 json/src/v17/mining/convert.rs | 0 json/src/v17/network.rs | 173 ++++++ json/src/v17/network/convert.rs | 103 ---- json/src/v17/network/mod.rs | 78 --- .../mod.rs => raw_transactions.rs} | 19 +- json/src/v17/raw_transactions/convert.rs | 18 - json/src/v17/{util/mod.rs => util.rs} | 0 json/src/v17/util/convert.rs | 0 json/src/v17/wallet.rs | 363 +++++++++++++ json/src/v17/wallet/convert.rs | 211 -------- json/src/v17/wallet/mod.rs | 143 ----- json/src/v17/{zmq/mod.rs => zmq.rs} | 0 .../v19/{blockchain/mod.rs => blockchain.rs} | 76 ++- json/src/v19/blockchain/convert.rs | 143 ----- json/src/v19/wallet.rs | 91 ++++ json/src/v19/wallet/convert.rs | 54 -- json/src/v19/wallet/mod.rs | 48 -- json/src/v22/mod.rs | 6 +- json/src/v22/{wallet/mod.rs => wallet.rs} | 27 +- json/src/v22/wallet/convert.rs | 22 - json/src/v23/mod.rs | 2 +- json/src/v24/mod.rs | 2 +- json/src/v25/mod.rs | 2 +- json/src/v25/{wallet/mod.rs => wallet.rs} | 24 +- json/src/v26/mod.rs | 2 +- regtest/src/lib.rs | 13 +- 58 files changed, 1423 insertions(+), 1387 deletions(-) delete mode 100644 integration_test/src/v22/blockchain.rs create mode 100644 json/src/v17/blockchain.rs delete mode 100644 json/src/v17/blockchain/convert.rs delete mode 100644 json/src/v17/blockchain/mod.rs rename json/src/v17/{control/mod.rs => control.rs} (100%) rename json/src/v17/{generating/mod.rs => generating.rs} (68%) delete mode 100644 json/src/v17/generating/convert.rs rename json/src/v17/{mining/mod.rs => mining.rs} (100%) delete mode 100644 json/src/v17/mining/convert.rs create mode 100644 json/src/v17/network.rs delete mode 100644 json/src/v17/network/convert.rs delete mode 100644 json/src/v17/network/mod.rs rename json/src/v17/{raw_transactions/mod.rs => raw_transactions.rs} (60%) delete mode 100644 json/src/v17/raw_transactions/convert.rs rename json/src/v17/{util/mod.rs => util.rs} (100%) delete mode 100644 json/src/v17/util/convert.rs create mode 100644 json/src/v17/wallet.rs delete mode 100644 json/src/v17/wallet/convert.rs delete mode 100644 json/src/v17/wallet/mod.rs rename json/src/v17/{zmq/mod.rs => zmq.rs} (100%) rename json/src/v19/{blockchain/mod.rs => blockchain.rs} (67%) delete mode 100644 json/src/v19/blockchain/convert.rs create mode 100644 json/src/v19/wallet.rs delete mode 100644 json/src/v19/wallet/convert.rs delete mode 100644 json/src/v19/wallet/mod.rs rename json/src/v22/{wallet/mod.rs => wallet.rs} (55%) delete mode 100644 json/src/v22/wallet/convert.rs rename json/src/v25/{wallet/mod.rs => wallet.rs} (79%) diff --git a/client/src/client_sync/error.rs b/client/src/client_sync/error.rs index 6b43c72..112f3ca 100644 --- a/client/src/client_sync/error.rs +++ b/client/src/client_sync/error.rs @@ -8,7 +8,8 @@ use bitcoin::{hex, secp256k1}; #[derive(Debug)] pub enum Error { JsonRpc(jsonrpc::error::Error), - Hex(hex::HexToBytesError), + HexToArray(hex::HexToArrayError), + HexToBytes(hex::HexToBytesError), Json(serde_json::error::Error), BitcoinSerialization(bitcoin::consensus::encode::FromHexError), Secp256k1(secp256k1::Error), @@ -29,8 +30,12 @@ impl From for Error { fn from(e: jsonrpc::error::Error) -> Error { Error::JsonRpc(e) } } +impl From for Error { + fn from(e: hex::HexToArrayError) -> Self { Self::HexToArray(e) } +} + impl From for Error { - fn from(e: hex::HexToBytesError) -> Error { Error::Hex(e) } + fn from(e: hex::HexToBytesError) -> Self { Self::HexToBytes(e) } } impl From for Error { @@ -59,7 +64,8 @@ impl fmt::Display for Error { match *self { JsonRpc(ref e) => write!(f, "JSON-RPC error: {}", e), - Hex(ref e) => write!(f, "hex decode error: {}", e), + HexToArray(ref e) => write!(f, "hex to array decode error: {}", e), + HexToBytes(ref e) => write!(f, "hex to bytes decode error: {}", e), Json(ref e) => write!(f, "JSON error: {}", e), BitcoinSerialization(ref e) => write!(f, "Bitcoin serialization error: {}", e), Secp256k1(ref e) => write!(f, "secp256k1 error: {}", e), @@ -80,7 +86,8 @@ impl error::Error for Error { match *self { JsonRpc(ref e) => Some(e), - Hex(ref e) => Some(e), + HexToArray(ref e) => Some(e), + HexToBytes(ref e) => Some(e), Json(ref e) => Some(e), BitcoinSerialization(ref e) => Some(e), Secp256k1(ref e) => Some(e), diff --git a/client/src/client_sync/v17/blockchain.rs b/client/src/client_sync/v17/blockchain.rs index 9ae7858..0ab48bb 100644 --- a/client/src/client_sync/v17/blockchain.rs +++ b/client/src/client_sync/v17/blockchain.rs @@ -29,8 +29,7 @@ macro_rules! impl_client_v17__getbestblockhash { /// Gets the blockhash of the current chain tip. pub fn best_block_hash(&self) -> Result { let json = self.get_best_block_hash()?; - let concrete: $crate::json::model::GetBestBlockHash = json.try_into().unwrap(); - Ok(concrete.0) + Ok(json.block_hash()?) } pub fn get_best_block_hash(&self) -> Result { @@ -48,8 +47,7 @@ macro_rules! impl_client_v17__getblock { /// Gets a block by blockhash. pub fn get_block(&self, hash: &BlockHash) -> Result { let json = self.get_block_verbosity_zero(hash)?; - let concrete: $crate::json::model::GetBlockVerbosityZero = json.try_into()?; - Ok(concrete.0) + Ok(json.block()?) } // FIXME(getblock): This handling of optional args is ugly as hell but because the returned json diff --git a/client/src/client_sync/v17/mod.rs b/client/src/client_sync/v17/mod.rs index 4b9a8f2..8d8438c 100644 --- a/client/src/client_sync/v17/mod.rs +++ b/client/src/client_sync/v17/mod.rs @@ -53,7 +53,7 @@ crate::impl_client_v17__gettransaction!(); #[serde(rename_all = "kebab-case")] pub enum AddressType { Legacy, - P2ShSegwit, + P2shSegwit, Bech32, } @@ -63,7 +63,7 @@ impl fmt::Display for AddressType { let s = match *self { Legacy => "legacy", - P2ShSegwit => "p2sh-segwit", + P2shSegwit => "p2sh-segwit", Bech32 => "bech32", }; fmt::Display::fmt(s, f) diff --git a/client/src/client_sync/v17/wallet.rs b/client/src/client_sync/v17/wallet.rs index 01aeb09..eddb480 100644 --- a/client/src/client_sync/v17/wallet.rs +++ b/client/src/client_sync/v17/wallet.rs @@ -102,7 +102,7 @@ macro_rules! impl_client_v17__sendtoaddress { &self, address: &Address, amount: Amount, - ) -> Result { + ) -> Result { let mut args = [address.to_string().into(), into_json(amount.to_btc())?]; self.call("sendtoaddress", handle_defaults(&mut args, &["".into(), "".into()])) } diff --git a/client/src/client_sync/v23.rs b/client/src/client_sync/v23.rs index 3e3ac8f..20f28da 100644 --- a/client/src/client_sync/v23.rs +++ b/client/src/client_sync/v23.rs @@ -47,7 +47,7 @@ crate::impl_client_v17__gettransaction!(); #[serde(rename_all = "kebab-case")] pub enum AddressType { Legacy, - P2ShSegwit, + P2shSegwit, Bech32, Bech32m, } @@ -58,7 +58,7 @@ impl fmt::Display for AddressType { let s = match *self { Legacy => "legacy", - P2ShSegwit => "p2sh-segwit", + P2shSegwit => "p2sh-segwit", Bech32 => "bech32", Bech32m => "bech32m", }; diff --git a/integration_test/src/v17/blockchain.rs b/integration_test/src/v17/blockchain.rs index e7b0be0..6fc418b 100644 --- a/integration_test/src/v17/blockchain.rs +++ b/integration_test/src/v17/blockchain.rs @@ -12,7 +12,8 @@ macro_rules! impl_test_v17__getblockchaininfo { #[test] fn get_blockchain_info() { let bitcoind = $crate::bitcoind_no_wallet(); - let _ = bitcoind.client.get_blockchain_info().expect("getblockchaininfo"); + let json = bitcoind.client.get_blockchain_info().expect("getblockchaininfo"); + assert!(json.into_model().is_ok()); } }; } @@ -29,24 +30,53 @@ macro_rules! impl_test_v17__getbestblockhash { #[test] fn get_best_block_hash() { let bitcoind = $crate::bitcoind_no_wallet(); - let _ = bitcoind.client.get_best_block_hash().expect("getbestblockhash"); + let json = bitcoind.client.get_best_block_hash().expect("getbestblockhash"); + assert!(json.into_model().is_ok()); + } + }; +} + +/// Requires `Client` to be in scope and to implement `get_block 0`. +#[macro_export] +macro_rules! impl_test_v17__getblock_verbosity_0 { + () => { + #[test] + fn get_block_verbosity_0() { + let bitcoind = $crate::bitcoind_no_wallet(); + let block_hash = best_block_hash(); + + let json = bitcoind.client.get_block_verbosity_zero(&block_hash).expect("getblock 0"); + json.into_model().unwrap(); } }; } /// Requires `Client` to be in scope and to implement `get_block`. #[macro_export] -macro_rules! impl_test_v17__getblock { +macro_rules! impl_test_v17__getblock_verbosity_1 { + () => { + #[test] + fn get_block_verbosity_1() { + let bitcoind = $crate::bitcoind_no_wallet(); + let block_hash = best_block_hash(); + + let json = bitcoind.client.get_block_verbosity_one(&block_hash).expect("getblock 1"); + json.into_model().unwrap(); + } + }; +} + +/// Requires `Client` to be in scope and to implement `get_block 2`. +#[macro_export] +macro_rules! impl_test_v17__getblock_verbosity_2 { () => { #[test] - fn get_block() { + fn get_block_verbosity_2() { let bitcoind = $crate::bitcoind_no_wallet(); let block_hash = best_block_hash(); - let _ = bitcoind.client.get_block_verbosity_zero(&block_hash).expect("getblock 0"); - let _ = bitcoind.client.get_block_verbosity_one(&block_hash).expect("getblock 1"); - // TODO: getblock 2 - // let json = client.get_block_verbosity_two(&block_hash).expect("getblock 2"); + let json = client.get_block_verbosity_two(&block_hash).expect("getblock 2"); + json.into_model().unwrap(); } }; } diff --git a/integration_test/src/v17/control.rs b/integration_test/src/v17/control.rs index 0dffa3c..afb496c 100644 --- a/integration_test/src/v17/control.rs +++ b/integration_test/src/v17/control.rs @@ -12,6 +12,7 @@ macro_rules! impl_test_v17__stop { #[test] fn stop() { let bitcoind = $crate::bitcoind_no_wallet(); + // There is no json object for `stop`, we just return a string. let _ = bitcoind.client.stop().expect("stop"); } }; diff --git a/integration_test/src/v17/generating.rs b/integration_test/src/v17/generating.rs index 19000c5..06848a3 100644 --- a/integration_test/src/v17/generating.rs +++ b/integration_test/src/v17/generating.rs @@ -13,7 +13,8 @@ macro_rules! impl_test_v17__generatetoaddress { fn generate_to_address() { let bitcoind = $crate::bitcoind_with_default_wallet(); let address = bitcoind.client.new_address().expect("failed to get new address"); - let _ = bitcoind.client.generate_to_address(1, &address).expect("generatetoaddress"); + let json = bitcoind.client.generate_to_address(1, &address).expect("generatetoaddress"); + json.into_model().unwrap(); } }; } diff --git a/integration_test/src/v17/network.rs b/integration_test/src/v17/network.rs index 0d6d548..965266b 100644 --- a/integration_test/src/v17/network.rs +++ b/integration_test/src/v17/network.rs @@ -12,7 +12,9 @@ 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"); + json.into_model().unwrap(); + bitcoind.client.check_expected_server_version().expect("unexpected version"); } }; diff --git a/integration_test/src/v17/wallet.rs b/integration_test/src/v17/wallet.rs index b1174c5..63f34d6 100644 --- a/integration_test/src/v17/wallet.rs +++ b/integration_test/src/v17/wallet.rs @@ -1,3 +1,10 @@ +// SPDX-License-Identifier: CC0-1.0 + +//! Macros for implementing test methods on a JSON-RPC client. +//! +//! Specifically this is methods found under the `== Wallet ==` section of the +//! API docs of `bitcoind v0.17.1`. + /// Requires `Client` to be in scope and to implement `createwallet`. #[macro_export] macro_rules! impl_test_v17__createwallet { @@ -31,7 +38,8 @@ macro_rules! impl_test_v17__unloadwallet { let bitcoind = $crate::bitcoind_no_wallet(); let wallet = format!("wallet-{}", rand::random::()).to_string(); bitcoind.client.create_wallet(&wallet).expect("failed to create wallet"); - let _ = bitcoind.client.unload_wallet(&wallet).expect("unloadwallet"); + let json = bitcoind.client.unload_wallet(&wallet).expect("unloadwallet"); + assert!(json.into_model().is_ok()) } }; } @@ -45,13 +53,26 @@ macro_rules! impl_test_v17__getnewaddress { use bitcoind::AddressType; let bitcoind = $crate::bitcoind_with_default_wallet(); - let _ = bitcoind.client.get_new_address().expect("getnewaddress"); - let addr = bitcoind + let json = bitcoind.client.get_new_address().expect("getnewaddress"); + assert!(json.into_model().is_ok()); + + // Test the helper as well just for good measure. + let _ = bitcoind.client.new_address().unwrap(); + + // Exhaustively test address types with helper. + let _ = bitcoind + .client + .new_address_with_type(AddressType::Legacy) + .unwrap(); + let _ = bitcoind + .client + .new_address_with_type(AddressType::P2shSegwit) + .unwrap(); + let _ = bitcoind .client .new_address_with_type(AddressType::Bech32) .unwrap(); - } }; } @@ -66,7 +87,7 @@ macro_rules! impl_test_v17__getbalance { let bitcoind = $crate::bitcoind_with_default_wallet(); let json = bitcoind.client.get_balance().expect("getbalance"); - let _: model::GetBalance = json.try_into().unwrap(); + assert!(json.into_model().is_ok()) } }; } @@ -85,10 +106,11 @@ macro_rules! impl_test_v17__sendtoaddress { let address = bitcoind.client.new_address().expect("failed to create new address"); let _ = bitcoind.client.generate_to_address(101, &address).expect("generatetoaddress"); - let _ = bitcoind + let json = bitcoind .client .send_to_address(&address, Amount::from_sat(10_000)) - .expect("sendtoaddress"); + .expect("sendtddress"); + json.into_model().unwrap(); } }; } @@ -112,10 +134,12 @@ macro_rules! impl_test_v17__gettransaction { let txid = bitcoind .client .send_to_address(&address, Amount::from_sat(10_000)) - .expect("sendtoaddress"); + .expect("sendtoaddress") + .txid() + .unwrap(); let json = bitcoind.client.get_transaction(txid).expect("gettransaction"); - let _: model::GetTransaction = json.try_into().unwrap(); + json.into_model().unwrap(); } }; } diff --git a/integration_test/src/v19/wallet.rs b/integration_test/src/v19/wallet.rs index a6129bc..2d8d511 100644 --- a/integration_test/src/v19/wallet.rs +++ b/integration_test/src/v19/wallet.rs @@ -7,7 +7,8 @@ macro_rules! impl_test_v19__getbalances { let bitcoind = $crate::bitcoind_with_default_wallet(); let address = bitcoind.client.new_address().expect("failed to get new address"); let _ = bitcoind.client.generate_to_address(101, &address).expect("generatetoaddress"); - let _ = bitcoind.client.get_balances().expect("getbalances"); + let json = bitcoind.client.get_balances().expect("getbalances"); + json.into_model().unwrap(); } }; } diff --git a/integration_test/src/v22/blockchain.rs b/integration_test/src/v22/blockchain.rs deleted file mode 100644 index b17e97a..0000000 --- a/integration_test/src/v22/blockchain.rs +++ /dev/null @@ -1,25 +0,0 @@ -/// Requires `Client` to be in scope and to implement `get_block`. -#[macro_export] -macro_rules! impl_test_v22__getblock { - () => { - #[test] - fn get_block() { - use client::json::model; - - let bitcoind = $crate::bitcoind_no_wallet(); - let block_hash = best_block_hash(); - - // Users who only want to use `json` module can do: - // let block_hash = json.best_block_hash.parse::()?; - - let json = bitcoind.client.get_block_verbosity_zero(&block_hash).expect("getblock 0"); - let _ = model::GetBlockVerbosityZero::try_from(json).unwrap(); - - let json = bitcoind.client.get_block_verbosity_one(&block_hash).expect("getblock 1"); - let _ = model::GetBlockVerbosityOne::try_from(json).unwrap(); - - // TODO: getblock 2 - // let json = client.get_block_verbosity_two(&block_hash).expect("getblock 2"); - } - }; -} diff --git a/integration_test/src/v22/mod.rs b/integration_test/src/v22/mod.rs index d3755b8..6c126be 100644 --- a/integration_test/src/v22/mod.rs +++ b/integration_test/src/v22/mod.rs @@ -2,5 +2,4 @@ //! Macros for implementing test methods on a JSON-RPC client for `bitcoind v22.1`. -pub mod blockchain; pub mod wallet; diff --git a/integration_test/src/v22/wallet.rs b/integration_test/src/v22/wallet.rs index dfc116a..fb39625 100644 --- a/integration_test/src/v22/wallet.rs +++ b/integration_test/src/v22/wallet.rs @@ -1,3 +1,10 @@ +// SPDX-License-Identifier: CC0-1.0 + +//! Macros for implementing test methods on a JSON-RPC client. +//! +//! Specifically this is methods found under the `== Wallet ==` section of the +//! API docs of `bitcoind v22.1`. + /// Requires `Client` to be in scope and to implement `unloadwallet`. #[macro_export] macro_rules! impl_test_v22__unloadwallet { diff --git a/integration_test/tests/v17_api.rs b/integration_test/tests/v17_api.rs index ea01ef8..0ae968b 100644 --- a/integration_test/tests/v17_api.rs +++ b/integration_test/tests/v17_api.rs @@ -10,8 +10,8 @@ mod blockchain { impl_test_v17__getblockchaininfo!(); impl_test_v17__getbestblockhash!(); - impl_test_v17__getblock!(); - // impl_test_v22__gettxout!(); + impl_test_v17__getblock_verbosity_0!(); + impl_test_v17__getblock_verbosity_1!(); } // == Control == diff --git a/integration_test/tests/v18_api.rs b/integration_test/tests/v18_api.rs index 88ab0fe..0e85c9c 100644 --- a/integration_test/tests/v18_api.rs +++ b/integration_test/tests/v18_api.rs @@ -10,7 +10,8 @@ mod blockchain { impl_test_v17__getblockchaininfo!(); impl_test_v17__getbestblockhash!(); - impl_test_v17__getblock!(); + impl_test_v17__getblock_verbosity_0!(); + impl_test_v17__getblock_verbosity_1!(); } // == Control == diff --git a/integration_test/tests/v19_api.rs b/integration_test/tests/v19_api.rs index 53ba1c1..dbc3b73 100644 --- a/integration_test/tests/v19_api.rs +++ b/integration_test/tests/v19_api.rs @@ -10,7 +10,8 @@ mod blockchain { impl_test_v17__getblockchaininfo!(); impl_test_v17__getbestblockhash!(); - impl_test_v17__getblock!(); + impl_test_v17__getblock_verbosity_0!(); + impl_test_v17__getblock_verbosity_1!(); } // == Control == diff --git a/integration_test/tests/v20_api.rs b/integration_test/tests/v20_api.rs index b261adf..926aa88 100644 --- a/integration_test/tests/v20_api.rs +++ b/integration_test/tests/v20_api.rs @@ -10,7 +10,8 @@ mod blockchain { impl_test_v17__getblockchaininfo!(); impl_test_v17__getbestblockhash!(); - impl_test_v17__getblock!(); + impl_test_v17__getblock_verbosity_0!(); + impl_test_v17__getblock_verbosity_1!(); } // == Control == diff --git a/integration_test/tests/v21_api.rs b/integration_test/tests/v21_api.rs index cece911..f71c07d 100644 --- a/integration_test/tests/v21_api.rs +++ b/integration_test/tests/v21_api.rs @@ -10,7 +10,8 @@ mod blockchain { impl_test_v17__getblockchaininfo!(); impl_test_v17__getbestblockhash!(); - impl_test_v17__getblock!(); + impl_test_v17__getblock_verbosity_0!(); + impl_test_v17__getblock_verbosity_1!(); } // == Control == diff --git a/integration_test/tests/v22_api.rs b/integration_test/tests/v22_api.rs index d367b3d..280a7c5 100644 --- a/integration_test/tests/v22_api.rs +++ b/integration_test/tests/v22_api.rs @@ -1,4 +1,4 @@ -//! Test the JSON-RPC API against `bitcoind v22.1`. +//! Test the JSON-RPC API against `bitcoind v22`. #![cfg(feature = "v22")] @@ -10,8 +10,8 @@ mod blockchain { impl_test_v17__getblockchaininfo!(); impl_test_v17__getbestblockhash!(); - impl_test_v22__getblock!(); - // impl_test_v22__gettxout!(); + impl_test_v17__getblock_verbosity_0!(); + impl_test_v17__getblock_verbosity_1!(); } // == Control == diff --git a/integration_test/tests/v23_api.rs b/integration_test/tests/v23_api.rs index 4657bdd..44c9218 100644 --- a/integration_test/tests/v23_api.rs +++ b/integration_test/tests/v23_api.rs @@ -1,4 +1,4 @@ -//! Test the JSON-RPC API against `bitcoind v23.2`. +//! Test the JSON-RPC API against `bitcoind v23`. #![cfg(feature = "v23")] @@ -10,7 +10,8 @@ mod blockchain { impl_test_v17__getblockchaininfo!(); impl_test_v17__getbestblockhash!(); - impl_test_v22__getblock!(); + impl_test_v17__getblock_verbosity_0!(); + impl_test_v17__getblock_verbosity_1!(); } // == Control == diff --git a/integration_test/tests/v24_api.rs b/integration_test/tests/v24_api.rs index 62f8b68..998f3f3 100644 --- a/integration_test/tests/v24_api.rs +++ b/integration_test/tests/v24_api.rs @@ -1,4 +1,4 @@ -//! Test the JSON-RPC API against `bitcoind v24.2`. +//! Test the JSON-RPC API against `bitcoind v24`. #![cfg(feature = "v24")] @@ -10,7 +10,8 @@ mod blockchain { impl_test_v17__getblockchaininfo!(); impl_test_v17__getbestblockhash!(); - impl_test_v22__getblock!(); + impl_test_v17__getblock_verbosity_0!(); + impl_test_v17__getblock_verbosity_1!(); } // == Control == diff --git a/integration_test/tests/v25_api.rs b/integration_test/tests/v25_api.rs index bcc7758..17ee034 100644 --- a/integration_test/tests/v25_api.rs +++ b/integration_test/tests/v25_api.rs @@ -1,4 +1,4 @@ -//! Test the JSON-RPC API against `bitcoind v25.2`. +//! Test the JSON-RPC API against `bitcoind v25`. #![cfg(feature = "v25")] @@ -10,7 +10,8 @@ mod blockchain { impl_test_v17__getblockchaininfo!(); impl_test_v17__getbestblockhash!(); - impl_test_v22__getblock!(); + impl_test_v17__getblock_verbosity_0!(); + impl_test_v17__getblock_verbosity_1!(); } // == Control == diff --git a/integration_test/tests/v26_api.rs b/integration_test/tests/v26_api.rs index ba344fa..7848b8a 100644 --- a/integration_test/tests/v26_api.rs +++ b/integration_test/tests/v26_api.rs @@ -10,7 +10,8 @@ mod blockchain { impl_test_v17__getblockchaininfo!(); impl_test_v17__getbestblockhash!(); - impl_test_v22__getblock!(); + impl_test_v17__getblock_verbosity_0!(); + impl_test_v17__getblock_verbosity_1!(); } // == Control == diff --git a/json/src/model/blockchain.rs b/json/src/model/blockchain.rs index fec8087..26e1918 100644 --- a/json/src/model/blockchain.rs +++ b/json/src/model/blockchain.rs @@ -8,9 +8,15 @@ use std::collections::BTreeMap; use bitcoin::address::NetworkUnchecked; -use bitcoin::{block, Address, Block, BlockHash, CompactTarget, Network, TxOut, Weight, Work}; +use bitcoin::{ + block, Address, Block, BlockHash, CompactTarget, Network, TxOut, Txid, Weight, Work, +}; use serde::{Deserialize, Serialize}; +/// Models the result of JSON-RPC method `getbestblockhash`. +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +pub struct GetBestBlockHash(pub BlockHash); + /// Models the result of JSON-RPC method `getblockchaininfo`. #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] pub struct GetBlockchainInfo { @@ -95,7 +101,6 @@ pub struct Bip9SoftforkInfo { /// BIP-9 softfork status: one of "defined", "started", "locked_in", "active", "failed". #[derive(Copy, Clone, PartialEq, Eq, Debug, Deserialize, Serialize)] -#[serde(rename_all = "snake_case")] pub enum Bip9SoftforkStatus { /// BIP-9 softfork status "defined". Defined, @@ -138,7 +143,7 @@ pub struct GetBlockVerbosityOne { /// The block size. pub size: usize, /// The block size excluding witness data. - pub strippedsize: Option, // Weight? + pub stripped_size: Option, // Weight? /// The block weight as defined in BIP-141. pub weight: Weight, /// The block height or index. @@ -147,22 +152,22 @@ pub struct GetBlockVerbosityOne { pub version: block::Version, /// The block version formatted in hexadecimal. pub version_hex: String, - /// The merkle root - pub merkleroot: String, - /// The transaction ids - pub tx: Vec, + /// The merkle root. + pub merkle_root: String, + /// The transaction ids. + pub tx: Vec, /// The block time expressed in UNIX epoch time. pub time: usize, /// The median block time expressed in UNIX epoch time. pub median_time: Option, - /// The nonce + /// The nonce. pub nonce: u32, /// The bits. pub bits: CompactTarget, /// The difficulty. - pub difficulty: f64, // u128? + pub difficulty: f64, /// Expected number of hashes required to produce the chain up to this block (in hex). - pub chain_work: String, + pub chain_work: Work, /// The number of transactions in the block. pub n_tx: u32, /// The hash of the previous block (if available). @@ -177,7 +182,7 @@ pub struct GetTxOut { /// The hash of the block at the tip of the chain. pub best_block: BlockHash, /// The number of confirmations. - pub confirmations: u64, + pub confirmations: u32, /// The returned `TxOut` (strongly typed). pub tx_out: TxOut, /// Address that `tx_out` spends to. @@ -185,7 +190,3 @@ pub struct GetTxOut { /// Coinbase or not. pub coinbase: bool, } - -/// Models the result of JSON-RPC method `getbestblockhash`. -#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] -pub struct GetBestBlockHash(pub BlockHash); diff --git a/json/src/v17/blockchain.rs b/json/src/v17/blockchain.rs new file mode 100644 index 0000000..b086597 --- /dev/null +++ b/json/src/v17/blockchain.rs @@ -0,0 +1,502 @@ +// SPDX-License-Identifier: CC0-1.0 + +//! The JSON-RPC API for Bitcoin Core v0.17.1 - blockchain. +//! +//! Types for methods found under the `== Blockchain ==` section of the API docs. + +use std::collections::BTreeMap; +use std::fmt; +use std::str::FromStr; + +use bitcoin::consensus::encode; +use bitcoin::error::UnprefixedHexError; +use bitcoin::{ + address, amount, block, hex, network, Address, Amount, Block, BlockHash, CompactTarget, + Network, ScriptBuf, TxOut, Txid, Weight, Work, +}; +use internals::write_err; +use serde::{Deserialize, Serialize}; + +use crate::model; + +/// Result of JSON-RPC method `getbestblockhash`. +/// +/// > getbestblockhash +/// > +/// > Returns the hash of the best (tip) block in the most-work fully-validated chain. +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +pub struct GetBestBlockHash(pub String); + +impl GetBestBlockHash { + /// Converts version specific type to a version in-specific, more strongly typed type. + pub fn into_model(self) -> Result { + let hash = self.0.parse::()?; + Ok(model::GetBestBlockHash(hash)) + } + + /// Converts json straight to a `bitcoin::BlockHash`. + pub fn block_hash(self) -> Result { Ok(self.into_model()?.0) } +} + +/// Result of JSON-RPC method `getblockchaininfo`. +/// +/// Method call: `getblockchaininfo` +/// +/// > Returns an object containing various state info regarding blockchain processing. +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +pub struct GetBlockchainInfo { + /// Current network name as defined in BIP70 (main, test, signet, regtest). + pub chain: String, + /// The current number of blocks processed in the server. + pub blocks: u64, + /// The current number of headers we have validated. + pub headers: u64, + /// The hash of the currently best block. + #[serde(rename = "bestblockhash")] + pub best_block_hash: String, + /// The current difficulty. + pub difficulty: f64, + /// Median time for the current best block. + #[serde(rename = "mediantime")] + pub median_time: u64, + /// Estimate of verification progress (between 0 and 1). + #[serde(rename = "verificationprogress")] + pub verification_progress: f64, + /// Estimate of whether this node is in Initial Block Download (IBD) mode. + #[serde(rename = "initialblockdownload")] + pub initial_block_download: bool, + /// Total amount of work in active chain, in hexadecimal. + #[serde(rename = "chainwork")] + pub chain_work: String, + /// The estimated size of the block and undo files on disk. + pub size_on_disk: u64, + /// If the blocks are subject to pruning. + pub pruned: bool, + /// Lowest-height complete block stored (only present if pruning is enabled). + #[serde(rename = "pruneheight")] + pub prune_height: Option, + /// Whether automatic pruning is enabled (only present if pruning is enabled). + pub automatic_pruning: Option, + /// The target size used by pruning (only present if automatic pruning is enabled). + pub prune_target_size: Option, + /// Status of softforks in progress. + pub softforks: Vec, + /// Status of BIP-9 softforks in progress, maps softfork name -> [`Softfork`]. + pub bip9_softforks: BTreeMap, + /// Any network and blockchain warnings. + pub warnings: String, +} + +/// Status of softfork. +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +pub struct Softfork { + /// Name of softfork. + id: String, + /// Block version. + version: usize, + /// Progress toward rejecting pre-softfork blocks. + reject: SoftforkReject, +} + +/// Progress toward rejecting pre-softfork blocks. +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +pub struct SoftforkReject { + /// `true` if threshold reached. + status: bool, +} + +/// Status of BIP-9 softforksin progress. +#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)] +pub struct Bip9Softfork { + /// One of "defined", "started", "locked_in", "active", "failed". + pub status: Bip9SoftforkStatus, + /// The bit (0-28) in the block version field used to signal this softfork (only for "started" status). + pub bit: Option, + /// The minimum median time past of a block at which the bit gains its meaning. + #[serde(rename = "startTime")] + pub start_time: i64, + /// The median time past of a block at which the deployment is considered failed if not yet locked in. + pub timeout: u64, + /// Height of the first block to which the status applies. + pub since: u32, +} + +/// BIP-9 softfork status: one of "defined", "started", "locked_in", "active", "failed". +#[derive(Copy, Clone, PartialEq, Eq, Debug, Deserialize, Serialize)] +#[serde(rename_all = "snake_case")] +pub enum Bip9SoftforkStatus { + /// BIP-9 softfork status "defined". + Defined, + /// BIP-9 softfork status "started". + Started, + /// BIP-9 softfork status "locked_in". + LockedIn, + /// BIP-9 softfork status "active". + Active, + /// BIP-9 softfork status "failed". + Failed, +} + +impl GetBlockchainInfo { + /// Converts version specific type to a version in-specific, more strongly typed type. + pub fn into_model(self) -> Result { + use GetBlockchainInfoError as E; + + let chain = Network::from_core_arg(&self.chain).map_err(E::Chain)?; + let best_block_hash = + self.best_block_hash.parse::().map_err(E::BestBlockHash)?; + // FIXME: Is unprefixed correct? + let chain_work = Work::from_unprefixed_hex(&self.chain_work).map_err(E::ChainWork)?; + + let softforks = BTreeMap::new(); // TODO: Handle softforks stuff. + + Ok(model::GetBlockchainInfo { + chain, + blocks: self.blocks, + headers: self.headers, + best_block_hash, + difficulty: self.difficulty, + median_time: self.median_time, + verification_progress: self.verification_progress, + initial_block_download: self.initial_block_download, + chain_work, + size_on_disk: self.size_on_disk, + pruned: self.pruned, + prune_height: self.prune_height, + automatic_pruning: self.automatic_pruning, + prune_target_size: self.prune_target_size, + softforks, + warnings: self.warnings, + }) + } +} + +// FIXME: Me mightn't need this. +impl Bip9SoftforkStatus { + /// Converts version specific type to a version in-specific, more strongly typed type. + pub fn into_model(self) -> model::Bip9SoftforkStatus { + use model::Bip9SoftforkStatus::*; + + match self { + Self::Defined => Defined, + Self::Started => Started, + Self::LockedIn => LockedIn, + Self::Active => Active, + Self::Failed => Failed, + } + } +} + +/// Error when converting a `GetBlockchainInfo` type into the model type. +#[derive(Debug)] +pub enum GetBlockchainInfoError { + Chain(network::ParseNetworkError), + BestBlockHash(hex::HexToArrayError), + ChainWork(UnprefixedHexError), +} + +impl fmt::Display for GetBlockchainInfoError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use GetBlockchainInfoError::*; + + match *self { + Chain(ref e) => write_err!(f, "conversion of the `chain` field failed"; e), + BestBlockHash(ref e) => + write_err!(f, "conversion of the `best_block_hash` field failed"; e), + ChainWork(ref e) => write_err!(f, "conversion of the `chain_work` field failed"; e), + } + } +} + +impl std::error::Error for GetBlockchainInfoError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + use GetBlockchainInfoError::*; + + match *self { + Chain(ref e) => Some(e), + BestBlockHash(ref e) => Some(e), + ChainWork(ref e) => Some(e), + } + } +} + +/// 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); + +impl GetBlockVerbosityZero { + /// Converts version specific type to a version in-specific, more strongly typed type. + pub fn into_model(self) -> Result { + let block = encode::deserialize_hex(&self.0)?; + Ok(model::GetBlockVerbosityZero(block)) + } + + /// Converts json straight to a `bitcoin::Block`. + pub fn block(self) -> Result { Ok(self.into_model()?.0) } +} + +/// Result of JSON-RPC method `getblock` with verbosity set to 1. +#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] +pub struct GetBlockVerbosityOne { + /// The block hash (same as provided) in RPC call. + pub hash: String, + /// The number of confirmations, or -1 if the block is not on the main chain. + pub confirmations: i32, + /// The block size. + pub size: usize, + /// The block size excluding witness data. + #[serde(rename = "strippedsize")] + pub stripped_size: Option, + /// The block weight as defined in BIP-141. + pub weight: u64, + /// The block height or index. + pub height: usize, + /// The block version. + pub version: i32, + /// The block version formatted in hexadecimal. + #[serde(rename = "versionHex")] + pub version_hex: String, + /// The merkle root + #[serde(rename = "merkleroot")] + pub merkle_root: String, + /// The transaction ids + pub tx: Vec, + /// The block time expressed in UNIX epoch time. + pub time: usize, + /// The median block time expressed in UNIX epoch time. + #[serde(rename = "mediantime")] + pub median_time: Option, + /// The nonce + pub nonce: u32, + /// The bits. + pub bits: String, + /// The difficulty. + pub difficulty: f64, + /// Expected number of hashes required to produce the chain up to this block (in hex). + #[serde(rename = "chainwork")] + pub chain_work: String, + /// The number of transactions in the block. + #[serde(rename = "nTx")] + pub n_tx: u32, + /// The hash of the previous block (if available). + #[serde(rename = "previousblockhash")] + pub previous_block_hash: Option, + /// The hash of the next block (if available). + #[serde(rename = "nextblockhash")] + pub next_block_hash: Option, +} + +impl GetBlockVerbosityOne { + /// Converts version specific type to a version in-specific, more strongly typed type. + pub fn into_model(self) -> Result { + use GetBlockVerbosityOneError as E; + + let hash = self.hash.parse::().map_err(E::Hash)?; + let weight = Weight::from_wu(self.weight); // TODO: Confirm this uses weight units. + let version = block::Version::from_consensus(self.version); + + // FIXME: Is there a better way to handle the error without type annotations on `collect`? + let tx = self + .tx + .iter() + .map(|t| encode::deserialize_hex::(t).map_err(E::Tx)) + .collect::, _>>()?; + + // FIXME: Is unprefixed correct? + let bits = CompactTarget::from_unprefixed_hex(&self.bits).map_err(E::Bits)?; + let chain_work = Work::from_unprefixed_hex(&self.chain_work).map_err(E::ChainWork)?; + + let previous_block_hash = match self.previous_block_hash { + Some(hash) => Some(hash.parse::().map_err(E::PreviousBlockHash)?), + None => None, + }; + let next_block_hash = match self.next_block_hash { + Some(hash) => Some(hash.parse::().map_err(E::NextBlockHash)?), + None => None, + }; + + Ok(model::GetBlockVerbosityOne { + hash, + confirmations: self.confirmations, + size: self.size, + stripped_size: self.stripped_size, + weight, + height: self.height, + version, + version_hex: self.version_hex, + merkle_root: self.merkle_root, // TODO: Use hash, which one depends on segwit or not + tx, + time: self.time, // TODO: Use stronger type. + median_time: self.median_time, + nonce: self.nonce, + bits, + difficulty: self.difficulty, + chain_work, + n_tx: self.n_tx, + previous_block_hash, + next_block_hash, + }) + } +} + +/// Error when converting a `GetBlockVerbasityOne` type into the model type. +#[derive(Debug)] +pub enum GetBlockVerbosityOneError { + /// Conversion of the transaction `hash` field failed. + Hash(hex::HexToArrayError), + /// Conversion of the transaction `hex` field failed. + Tx(encode::FromHexError), + /// Conversion of the transaction `bits` field failed. + Bits(UnprefixedHexError), + /// Conversion of the transaction `chain_work` field failed. + ChainWork(UnprefixedHexError), + /// Conversion of the transaction `previous_block_hash` field failed. + PreviousBlockHash(hex::HexToArrayError), + /// Conversion of the transaction `next_block_hash` field failed. + NextBlockHash(hex::HexToArrayError), +} + +impl fmt::Display for GetBlockVerbosityOneError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use GetBlockVerbosityOneError::*; + + match *self { + Hash(ref e) => write_err!(f, "conversion of the `hash` field failed"; e), + Tx(ref e) => write_err!(f, "conversion of the `tx` field failed"; e), + Bits(ref e) => write_err!(f, "conversion of the `bits` field failed"; e), + ChainWork(ref e) => write_err!(f, "conversion of the `chain_ork` field failed"; e), + PreviousBlockHash(ref e) => + write_err!(f, "conversion of the `previous_block_hash` field failed"; e), + NextBlockHash(ref e) => + write_err!(f, "conversion of the `next_block_hash` field failed"; e), + } + } +} + +impl std::error::Error for GetBlockVerbosityOneError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + use GetBlockVerbosityOneError::*; + + match *self { + Hash(ref e) => Some(e), + Tx(ref e) => Some(e), + Bits(ref e) => Some(e), + ChainWork(ref e) => Some(e), + PreviousBlockHash(ref e) => Some(e), + NextBlockHash(ref e) => Some(e), + } + } +} + +/// Result of JSON-RPC method `gettxout`. +/// +/// > gettxout "txid" n ( include_mempool ) +/// > +/// > Returns details about an unspent transaction output. +/// > +/// > Arguments: +/// > 1. txid (string, required) The transaction id +/// > 2. n (numeric, required) vout number +/// > 3. include_mempool (boolean, optional, default=true) Whether to include the mempool. Note that an unspent output that is spent in the mempool won't appear. +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +pub struct GetTxOut { + /// The hash of the block at the tip of the chain. + #[serde(rename = "bestblock")] + pub best_block: String, + /// The number of confirmations. + pub confirmations: u32, + /// The transaction value in BTC. + pub value: f64, + /// The script pubkey. + #[serde(rename = "scriptPubkey")] + pub script_pubkey: ScriptPubkey, + /// Coinbase or not. + pub coinbase: bool, +} + +/// A script pubkey. +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +pub struct ScriptPubkey { + /// Script assembly. + pub asm: String, + /// Script hex. + pub hex: String, + // TODO: Add support for deprecatedrpc=addresses + // #[serde(rename = "reqSigs")] + // pub req_sigs: u64, + /// The type, eg pubkeyhash + #[serde(rename = "type")] + pub type_: String, + /// bitcoin address + pub address: String, + // TODO: Add support for deprecatedrpc=addresses + // pub addressess: Vec, +} + +impl GetTxOut { + /// Converts version specific type to a version in-specific, more strongly typed type. + pub fn into_model(self) -> Result { + use GetTxOutError as E; + + let best_block = self.best_block.parse::().map_err(E::BestBlock)?; + + let tx_out = TxOut { + value: Amount::from_btc(self.value).map_err(E::Value)?, + script_pubkey: ScriptBuf::from_hex(&self.script_pubkey.hex).map_err(E::ScriptPubkey)?, + }; + + let address = Address::from_str(&self.script_pubkey.address).map_err(E::Address)?; + + Ok(model::GetTxOut { + best_block, + confirmations: self.confirmations, + tx_out, + address, + coinbase: self.coinbase, + }) + } +} + +/// Error when converting a `GetTxOut` type into the model type. +#[derive(Debug)] +pub enum GetTxOutError { + /// Conversion of the transaction `best_block` field failed. + BestBlock(hex::HexToArrayError), + /// Conversion of the transaction `value` field failed. + Value(amount::ParseAmountError), + /// Conversion of the transaction `script_pubkey` field failed. + ScriptPubkey(hex::HexToBytesError), + /// Conversion of the transaction `address` field failed. + Address(address::ParseError), +} + +impl fmt::Display for GetTxOutError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use GetTxOutError::*; + + match *self { + BestBlock(ref e) => write_err!(f, "conversion of the `best_block` field failed"; e), + Value(ref e) => write_err!(f, "conversion of the `value` field failed"; e), + ScriptPubkey(ref e) => + write_err!(f, "conversion of the `script_pubkey` field failed"; e), + Address(ref e) => write_err!(f, "conversion of the `address` field failed"; e), + } + } +} + +impl std::error::Error for GetTxOutError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + use GetTxOutError::*; + + match *self { + BestBlock(ref e) => Some(e), + Value(ref e) => Some(e), + ScriptPubkey(ref e) => Some(e), + Address(ref e) => Some(e), + } + } +} diff --git a/json/src/v17/blockchain/convert.rs b/json/src/v17/blockchain/convert.rs deleted file mode 100644 index 53c43f3..0000000 --- a/json/src/v17/blockchain/convert.rs +++ /dev/null @@ -1,198 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -//! Convert stdlib (version specific) types to concrete types. -//! -//! This module does the conversion for `v0.17.1` types to the general concrete types. - -use core::fmt; -use core::str::FromStr; - -use bitcoin::consensus::encode; -use bitcoin::{ - address, amount, block, hex, Address, Amount, BlockHash, CompactTarget, ScriptBuf, TxOut, - Weight, -}; -use internals::write_err; - -use crate::{model, v17}; - -impl TryFrom for model::GetBlockchainInfo { - type Error = (); - - fn try_from(_: v17::GetBlockchainInfo) -> Result { - todo!("softfork fields have changed considerably by v22") - } -} - -impl TryFrom for model::GetBlockVerbosityZero { - type Error = encode::FromHexError; - - fn try_from(json: v17::GetBlockVerbosityZero) -> Result { - let header = encode::deserialize_hex(&json.0)?; - Ok(Self(header)) - } -} - -impl TryFrom for model::GetBlockVerbosityOne { - type Error = GetBlockVerbosityOneError; - - fn try_from(json: v17::GetBlockVerbosityOne) -> Result { - use GetBlockVerbosityOneError as E; - - let hash = json.hash.parse().map_err(E::Hash)?; - let weight = Weight::from_wu(json.weight); // FIXME: Is this correct? - let version = block::Version::from_consensus(json.version); - let bits = CompactTarget::from_unprefixed_hex(&json.bits).map_err(E::Bits)?; - let previous_block_hash = match json.previous_block_hash { - Some(hex) => Some(hex.parse().map_err(E::PreviousBlockHash)?), - None => None, - }; - let next_block_hash = match json.next_block_hash { - Some(hex) => Some(hex.parse().map_err(E::NextBlockHash)?), - None => None, - }; - - Ok(Self { - hash, - confirmations: json.confirmations, - size: json.size, - strippedsize: json.strippedsize, - weight, - height: json.height, - version, - version_hex: json.version_hex, - merkleroot: json.merkleroot, - tx: json.tx, - time: json.time, - median_time: json.median_time, - nonce: json.nonce, - bits, - difficulty: json.difficulty, // u128? - chain_work: json.chain_work, - n_tx: json.n_tx, - previous_block_hash, - next_block_hash, - }) - } -} - -/// Error when converting to a `v17::GetBlock` type to a `concrete` type. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum GetBlockVerbosityOneError { - /// Conversion of the `hash` field failed. - Hash(hex::HexToArrayError), - /// Conversion of the `bits` field failed. - Bits(bitcoin::error::UnprefixedHexError), - /// Conversion of the `previous_block_hash` field failed. - PreviousBlockHash(hex::HexToArrayError), - /// Conversion of the `next_block_hash` field failed. - NextBlockHash(hex::HexToArrayError), -} - -impl fmt::Display for GetBlockVerbosityOneError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - use GetBlockVerbosityOneError::*; - - match *self { - Hash(ref e) => write_err!(f, "conversion of the `hash` field failed"; e), - Bits(ref e) => write_err!(f, "conversion of the `bits` field failed"; e), - PreviousBlockHash(ref e) => { - write_err!(f, "conversion of the `previous_block_hash` field failed"; e) - } - NextBlockHash(ref e) => { - write_err!(f, "conversion of the `next_block_hash` field failed"; e) - } - } - } -} - -impl std::error::Error for GetBlockVerbosityOneError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - use GetBlockVerbosityOneError::*; - - match *self { - Hash(ref e) => Some(e), - Bits(ref e) => Some(e), - PreviousBlockHash(ref e) => Some(e), - NextBlockHash(ref e) => Some(e), - } - } -} - -impl TryFrom for model::GetTxOut { - type Error = GetTxOutError; - - fn try_from(json: v17::GetTxOut) -> Result { - use GetTxOutError as E; - - let best_block = json.best_block.parse().map_err(E::BestBlock)?; - - let value = Amount::from_sat(json.value); - - // TODO: We could parse `asm` as well and sanity check it matches the hex? - let script_pubkey = ScriptBuf::from_hex(&json.script_pubkey.hex).map_err(E::Hex)?; - - // TODO: We could parse the `type_` as well and sanity check it matches the `Address::address_type()`? - let address = Address::from_str(&json.script_pubkey.address).map_err(E::Address)?; - - Ok(Self { - best_block, - confirmations: json.confirmations, - tx_out: TxOut { value, script_pubkey }, - address, - coinbase: json.coinbase, - }) - } -} - -/// Error when converting to a `v17::GetTxOut` type to a `concrete` type. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum GetTxOutError { - /// Conversion of the `best_block` field failed. - BestBlock(hex::HexToArrayError), - /// Conversion of the `value` field failed. - Value(amount::ParseAmountError), // FIXME: Unused I thing - /// Conversion of the `script_pubkey.hex` field failed. - Hex(hex::HexToBytesError), - /// Conversion of the `script_pubkey.address` field failed. - Address(address::ParseError), -} - -impl fmt::Display for GetTxOutError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - use GetTxOutError::*; - - match *self { - BestBlock(ref e) => write_err!(f, "conversion of the `best_block` field failed"; e), - Value(ref e) => write_err!(f, "conversion of the `value` field failed"; e), - Hex(ref e) => { - write_err!(f, "conversion of the `script_pubkey.hex` field failed"; e) - } - Address(ref e) => { - write_err!(f, "conversion of the `script_pubkey.address` field failed"; e) - } - } - } -} - -impl std::error::Error for GetTxOutError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - use GetTxOutError::*; - - match *self { - BestBlock(ref e) => Some(e), - Value(ref e) => Some(e), - Hex(ref e) => Some(e), - Address(ref e) => Some(e), - } - } -} - -impl TryFrom for model::GetBestBlockHash { - type Error = hex::HexToArrayError; - - fn try_from(json: v17::GetBestBlockHash) -> Result { - let block_hash = BlockHash::from_str(&json.0)?; - Ok(Self(block_hash)) - } -} diff --git a/json/src/v17/blockchain/mod.rs b/json/src/v17/blockchain/mod.rs deleted file mode 100644 index 28b3e2a..0000000 --- a/json/src/v17/blockchain/mod.rs +++ /dev/null @@ -1,220 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -//! The JSON-RPC API for Bitcoin Core v0.17.1 - blockchain. -//! -//! Types for methods found under the `== Blockchain ==` section of the API docs. - -mod convert; - -use std::collections::BTreeMap; - -use serde::{Deserialize, Serialize}; - -/// Result of JSON-RPC method `getbestblockhash`. -/// -/// > getbestblockhash -/// > -/// > Returns the hash of the best (tip) block in the most-work fully-validated chain. -#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] -pub struct GetBestBlockHash(pub String); - -/// Result of JSON-RPC method `getblockchaininfo`. -/// -/// Method call: `getblockchaininfo` -/// -/// > Returns an object containing various state info regarding blockchain processing. -#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] -pub struct GetBlockchainInfo { - /// Current network name as defined in BIP70 (main, test, signet, regtest). - pub chain: String, - /// The current number of blocks processed in the server. - pub blocks: u64, - /// The current number of headers we have validated. - pub headers: u64, - /// The hash of the currently best block. - #[serde(rename = "bestblockhash")] - pub best_block_hash: String, - /// The current difficulty. - pub difficulty: f64, - /// Median time for the current best block. - #[serde(rename = "mediantime")] - pub median_time: u64, - /// Estimate of verification progress (between 0 and 1). - #[serde(rename = "verificationprogress")] - pub verification_progress: f64, - /// Estimate of whether this node is in Initial Block Download (IBD) mode. - #[serde(rename = "initialblockdownload")] - pub initial_block_download: bool, - /// Total amount of work in active chain, in hexadecimal. - #[serde(rename = "chainwork")] - pub chain_work: String, - /// The estimated size of the block and undo files on disk. - pub size_on_disk: u64, - /// If the blocks are subject to pruning. - pub pruned: bool, - /// Lowest-height complete block stored (only present if pruning is enabled). - #[serde(rename = "pruneheight")] - pub prune_height: Option, - /// Whether automatic pruning is enabled (only present if pruning is enabled). - pub automatic_pruning: Option, - /// The target size used by pruning (only present if automatic pruning is enabled). - pub prune_target_size: Option, - /// Status of softforks in progress. - pub softforks: Vec, - /// Status of BIP-9 softforks in progress, maps softfork name -> [`Softfork`]. - pub bip9_softforks: BTreeMap, - /// Any network and blockchain warnings. - pub warnings: String, -} - -/// Status of softfork. -#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] -pub struct Softfork { - /// Name of softfork. - id: String, - /// Block version. - version: usize, - /// Progress toward rejecting pre-softfork blocks. - reject: SoftforkReject, -} - -/// Progress toward rejecting pre-softfork blocks. -#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] -pub struct SoftforkReject { - /// `true` if threshold reached. - status: bool, -} - -/// Status of BIP-9 softforksin progress. -#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)] -pub struct Bip9Softfork { - /// One of "defined", "started", "locked_in", "active", "failed". - pub status: Bip9SoftforkStatus, - /// The bit (0-28) in the block version field used to signal this softfork (only for "started" status). - pub bit: Option, - /// The minimum median time past of a block at which the bit gains its meaning. - #[serde(rename = "startTime")] - pub start_time: i64, - /// The median time past of a block at which the deployment is considered failed if not yet locked in. - pub timeout: u64, - /// Height of the first block to which the status applies. - pub since: u32, -} - -/// BIP-9 softfork status: one of "defined", "started", "locked_in", "active", "failed". -#[derive(Copy, Clone, PartialEq, Eq, Debug, Deserialize, Serialize)] -#[serde(rename_all = "snake_case")] -pub enum Bip9SoftforkStatus { - /// BIP-9 softfork status "defined". - Defined, - /// BIP-9 softfork status "started". - Started, - /// BIP-9 softfork status "locked_in". - LockedIn, - /// BIP-9 softfork status "active". - Active, - /// BIP-9 softfork status "failed". - Failed, -} - -/// 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); - -/// Result of JSON-RPC method `getblock` with verbosity set to 1. -#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] -pub struct GetBlockVerbosityOne { - /// The block hash (same as provided) in RPC call. - pub hash: String, - /// The number of confirmations, or -1 if the block is not on the main chain. - pub confirmations: i32, - /// The block size. - pub size: usize, - /// The block size excluding witness data. - pub strippedsize: Option, - /// The block weight as defined in BIP-141. - pub weight: u64, - /// The block height or index. - pub height: usize, - /// The block version. - pub version: i32, - /// The block version formatted in hexadecimal. - #[serde(rename = "versionHex")] - pub version_hex: String, - /// The merkle root - pub merkleroot: String, - /// The transaction ids - pub tx: Vec, - /// The block time expressed in UNIX epoch time. - pub time: usize, - /// The median block time expressed in UNIX epoch time. - #[serde(rename = "mediantime")] - pub median_time: Option, - /// The nonce - pub nonce: u32, - /// The bits. - pub bits: String, - /// The difficulty. - pub difficulty: f64, - /// Expected number of hashes required to produce the chain up to this block (in hex). - #[serde(rename = "chainwork")] - pub chain_work: String, - /// The number of transactions in the block. - #[serde(rename = "nTx")] - pub n_tx: u32, - /// The hash of the previous block (if available). - #[serde(rename = "previousblockhash")] - pub previous_block_hash: Option, - /// The hash of the next block (if available). - #[serde(rename = "nextblockhash")] - pub next_block_hash: Option, -} - -/// Result of JSON-RPC method `gettxout`. -/// -/// > gettxout "txid" n ( include_mempool ) -/// > -/// > Returns details about an unspent transaction output. -/// > -/// > Arguments: -/// > 1. txid (string, required) The transaction id -/// > 2. n (numeric, required) vout number -/// > 3. include_mempool (boolean, optional, default=true) Whether to include the mempool. Note that an unspent output that is spent in the mempool won't appear. -#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] -pub struct GetTxOut { - /// The hash of the block at the tip of the chain. - #[serde(rename = "bestblock")] - pub best_block: String, - /// The number of confirmations. - pub confirmations: u64, - /// The transaction value in BTC. - pub value: u64, - /// The script pubkey. - #[serde(rename = "scriptPubkey")] - pub script_pubkey: ScriptPubkey, - /// Coinbase or not. - pub coinbase: bool, -} - -/// A script pubkey. -#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] -pub struct ScriptPubkey { - /// Script assembyl. - pub asm: String, - /// Script hex. - pub hex: String, - // TODO: Add support for deprecatedrpc=addresses - // #[serde(rename = "reqSigs")] - // pub req_sigs: u64, - /// The type, eg pubkeyhash - #[serde(rename = "type")] - pub type_: String, - /// bitcoin address - pub address: String, - // TODO: Add support for deprecatedrpc=addresses - // pub addressess: Vec, -} diff --git a/json/src/v17/control/mod.rs b/json/src/v17/control.rs similarity index 100% rename from json/src/v17/control/mod.rs rename to json/src/v17/control.rs diff --git a/json/src/v17/generating/mod.rs b/json/src/v17/generating.rs similarity index 68% rename from json/src/v17/generating/mod.rs rename to json/src/v17/generating.rs index 9317b2e..28c24f9 100644 --- a/json/src/v17/generating/mod.rs +++ b/json/src/v17/generating.rs @@ -4,10 +4,11 @@ //! //! Types for methods found under the `== Generating ==` section of the API docs. -mod convert; - +use bitcoin::{hex, BlockHash}; use serde::{Deserialize, Serialize}; +use crate::model; + /// Result of JSON-RPC method `generatetoaddress`. /// > generatetoaddress nblocks "address" ( maxtries ) /// > @@ -22,3 +23,11 @@ pub struct GenerateToAddress( /// Hashes of blocks generated. pub Vec, ); + +impl GenerateToAddress { + /// Converts version specific type to a version in-specific, more strongly typed type. + pub fn into_model(self) -> Result { + let v = self.0.iter().map(|s| s.parse::()).collect::, _>>()?; + Ok(model::GenerateToAddress(v)) + } +} diff --git a/json/src/v17/generating/convert.rs b/json/src/v17/generating/convert.rs deleted file mode 100644 index 2324b7a..0000000 --- a/json/src/v17/generating/convert.rs +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -//! Convert stdlib (version specific) types to concrete types. -//! -//! This module does the conversion for `v0.17.1` types to the general concrete types. - -use bitcoin::{hex, BlockHash}; - -use crate::{model, v17}; - -impl TryFrom for model::GenerateToAddress { - type Error = hex::HexToArrayError; - - fn try_from(json: v17::GenerateToAddress) -> Result { - // FIXME: Use combinators. - let mut v = vec![]; - for s in json.0.iter() { - let hash = s.parse::()?; - v.push(hash); - } - Ok(Self(v)) - } -} diff --git a/json/src/v17/mining/mod.rs b/json/src/v17/mining.rs similarity index 100% rename from json/src/v17/mining/mod.rs rename to json/src/v17/mining.rs diff --git a/json/src/v17/mining/convert.rs b/json/src/v17/mining/convert.rs deleted file mode 100644 index e69de29..0000000 diff --git a/json/src/v17/network.rs b/json/src/v17/network.rs new file mode 100644 index 0000000..e06145e --- /dev/null +++ b/json/src/v17/network.rs @@ -0,0 +1,173 @@ +// SPDX-License-Identifier: CC0-1.0 + +//! The JSON-RPC API for Bitcoin Core v0.17.1 - network. +//! +//! Types for methods found under the `== Network ==` section of the API docs. + +use core::fmt; + +use bitcoin::{amount, Amount, FeeRate}; +use internals::write_err; +use serde::{Deserialize, Serialize}; + +use crate::model; + +/// Result of the JSON-RPC method `getnetworkinfo` +/// +/// > getnetworkinfo +/// +/// > Returns an object containing various state info regarding P2P networking. +#[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. + #[serde(rename = "protocolversion")] + pub protocol_version: usize, + /// The services we offer to the network (hex string). + #[serde(rename = "localservices")] + pub local_services: String, + /// `true` if transaction relay is requested from peers. + #[serde(rename = "localrelay")] + pub local_relay: bool, + /// The time offset. + #[serde(rename = "timeoffset")] + pub time_offset: isize, + /// The total number of connections. + pub connections: usize, + #[serde(rename = "networkactive")] + /// Whether p2p networking is enabled. + pub network_active: bool, + /// Information per network. + pub networks: Vec, + /// Minimum relay fee rate for transactions in BTC/kB. + #[serde(rename = "relayfee")] + pub relay_fee: f64, + /// Minimum fee rate increment for mempool limiting or replacement in BTC/kB. + #[serde(rename = "incrementalfee")] + pub incremental_fee: f64, + /// List of local addresses. + #[serde(rename = "localaddresses")] + 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, Eq, 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, +} + +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, + connections_in: 0, // FIXME: Can we do better than this? + connections_out: 0, // FIXME: Can we do better than this? + 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::*; + + 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), + } + } +} + +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), + } + } +} diff --git a/json/src/v17/network/convert.rs b/json/src/v17/network/convert.rs deleted file mode 100644 index fc424ac..0000000 --- a/json/src/v17/network/convert.rs +++ /dev/null @@ -1,103 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -//! Convert stdlib (version specific) types to concrete types. -//! -//! This module does the conversion for `v0.17.1` types to the general concrete types. - -use core::fmt; - -use bitcoin::amount::ParseAmountError; -use bitcoin::{Amount, FeeRate}; -use internals::write_err; - -use crate::{model, v17}; - -// 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 TryFrom for model::GetNetworkInfo { - type Error = GetNetworkInfoError; - - fn try_from(json: v17::GetNetworkInfo) -> Result { - use GetNetworkInfoError as E; - - let relay_fee = fee_rate_from_btc_per_kb(json.relay_fee).map_err(E::RelayFee)?; - let incremental_fee = - fee_rate_from_btc_per_kb(json.incremental_fee).map_err(E::IncrementalFee)?; - - Ok(Self { - version: json.version, - subversion: json.subversion, - protocol_version: json.protocol_version, - local_services: json.local_services, - local_services_names: vec![], // TODO: Manually create names? - local_relay: json.local_relay, - time_offset: json.time_offset, - connections: json.connections, - connections_in: 0, // FIXME: Can we do better than this? - connections_out: 0, // FIXME: Can we do better than this? - network_active: json.network_active, - networks: json.networks.into_iter().map(From::from).collect(), - relay_fee, - incremental_fee, - local_addresses: json.local_addresses.into_iter().map(From::from).collect(), - warnings: json.warnings, - }) - } -} - -impl From for model::GetNetworkInfoNetwork { - fn from(json: v17::GetNetworkInfoNetwork) -> Self { - Self { - name: json.name, - limited: json.limited, - reachable: json.reachable, - proxy: json.proxy, - proxy_randomize_credentials: json.proxy_randomize_credentials, - } - } -} - -impl From for model::GetNetworkInfoAddress { - fn from(json: v17::GetNetworkInfoAddress) -> Self { - Self { address: json.address, port: json.port, score: json.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(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::*; - - 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), - } - } -} - -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), - } - } -} diff --git a/json/src/v17/network/mod.rs b/json/src/v17/network/mod.rs deleted file mode 100644 index 2112832..0000000 --- a/json/src/v17/network/mod.rs +++ /dev/null @@ -1,78 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -//! The JSON-RPC API for Bitcoin Core v0.17.1 - network. -//! -//! Types for methods found under the `== Network ==` section of the API docs. - -mod convert; - -use serde::{Deserialize, Serialize}; - -/// Result of the JSON-RPC method `getnetworkinfo` -/// -/// > getnetworkinfo -/// -/// > Returns an object containing various state info regarding P2P networking. -#[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. - #[serde(rename = "protocolversion")] - pub protocol_version: usize, - /// The services we offer to the network (hex string). - #[serde(rename = "localservices")] - pub local_services: String, - /// `true` if transaction relay is requested from peers. - #[serde(rename = "localrelay")] - pub local_relay: bool, - /// The time offset. - #[serde(rename = "timeoffset")] - pub time_offset: isize, - /// The total number of connections. - pub connections: usize, - #[serde(rename = "networkactive")] - /// Whether p2p networking is enabled. - pub network_active: bool, - /// Information per network. - pub networks: Vec, - /// Minimum relay fee rate for transactions in BTC/kB. - #[serde(rename = "relayfee")] - pub relay_fee: f64, - /// Minimum fee rate increment for mempool limiting or replacement in BTC/kB. - #[serde(rename = "incrementalfee")] - pub incremental_fee: f64, - /// List of local addresses. - #[serde(rename = "localaddresses")] - 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, Eq, 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/raw_transactions/mod.rs b/json/src/v17/raw_transactions.rs similarity index 60% rename from json/src/v17/raw_transactions/mod.rs rename to json/src/v17/raw_transactions.rs index 85324b9..a50bcef 100644 --- a/json/src/v17/raw_transactions/mod.rs +++ b/json/src/v17/raw_transactions.rs @@ -4,10 +4,11 @@ //! //! Types for methods found under the `== Rawtransactions ==` section of the API docs. -mod convert; - +use bitcoin::{hex, Txid}; use serde::{Deserialize, Serialize}; +use crate::model; + /// Result of JSON-RPC method `sendrawtransaction`. /// /// > sendrawtransaction "hexstring" ( allowhighfees ) @@ -21,3 +22,17 @@ use serde::{Deserialize, Serialize}; /// > 2. allowhighfees (boolean, optional, default=false) Allow high fees #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] pub struct SendRawTransaction(pub String); // The hex encoded txid. + +impl SendRawTransaction { + /// Converts version specific type to a version in-specific, more strongly typed type. + pub fn into_model(self) -> Result { + let txid = self.0.parse::()?; + Ok(model::SendRawTransaction(txid)) + } + + /// Converts json straight to a `bitcoin::Txid`. + pub fn txid(self) -> Result { + let model = self.into_model()?; + Ok(model.0) + } +} diff --git a/json/src/v17/raw_transactions/convert.rs b/json/src/v17/raw_transactions/convert.rs deleted file mode 100644 index 2ed329a..0000000 --- a/json/src/v17/raw_transactions/convert.rs +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -//! Convert stdlib (version specific) types to concrete types. -//! -//! This module does the conversion for `v0.17.1` types to the general concrete types. - -use bitcoin::{hex, Txid}; - -use crate::{model, v17}; - -impl TryFrom for model::SendRawTransaction { - type Error = hex::HexToArrayError; - - fn try_from(json: v17::SendRawTransaction) -> Result { - let txid = json.0.parse::()?; - Ok(Self(txid)) - } -} diff --git a/json/src/v17/util/mod.rs b/json/src/v17/util.rs similarity index 100% rename from json/src/v17/util/mod.rs rename to json/src/v17/util.rs diff --git a/json/src/v17/util/convert.rs b/json/src/v17/util/convert.rs deleted file mode 100644 index e69de29..0000000 diff --git a/json/src/v17/wallet.rs b/json/src/v17/wallet.rs new file mode 100644 index 0000000..ebf144d --- /dev/null +++ b/json/src/v17/wallet.rs @@ -0,0 +1,363 @@ +// SPDX-License-Identifier: CC0-1.0 + +//! The JSON-RPC API for Bitcoin Core v0.17.1 - wallet. +//! +//! Types for methods found under the `== Wallet ==` section of the API docs. + +use std::fmt; +use std::str::FromStr; + +use bitcoin::address::NetworkUnchecked; +use bitcoin::amount::ParseAmountError; +use bitcoin::consensus::encode; +use bitcoin::{address, hex, Address, Amount, SignedAmount, Transaction, Txid}; +use internals::write_err; +use serde::{Deserialize, Serialize}; + +use crate::model; + +/// Result of the JSON-RPC method `createwallet`. +/// +/// > createwallet "wallet_name" ( disable_private_keys ) +/// > +/// > Creates and loads a new wallet. +/// > +/// > Arguments: +/// > 1. "wallet_name" (string, required) The name for the new wallet. If this is a path, the wallet will be created at the path location. +/// > 2. disable_private_keys (boolean, optional, default: false) Disable the possibility of private keys (only watchonlys are possible in this mode). +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +pub struct CreateWallet { + /// The wallet name if created successfully. + /// + /// If the wallet was created using a full path, the wallet_name will be the full path. + pub name: String, + /// Warning messages, if any, related to creating and loading the wallet. + pub warning: String, +} + +impl CreateWallet { + /// Converts version specific type to a version in-specific, more strongly typed type. + pub fn into_model(self) -> model::CreateWallet { + model::CreateWallet { name: self.name, warnings: vec![self.warning] } + } + + /// Returns the created wallet name. + pub fn name(self) -> String { self.into_model().name } +} + +/// Result of the JSON-RPC method `loadwallet`. +/// +/// > loadwallet "filename" +/// > +/// > Loads a wallet from a wallet file or directory. +/// > Note that all wallet command-line options used when starting bitcoind will be +/// > applied to the new wallet (eg -zapwallettxes, upgradewallet, rescan, etc). +/// > +/// > Arguments: +/// > 1. "filename" (string, required) The wallet directory or .dat file. +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +pub struct LoadWallet { + /// The wallet name if loaded successfully. + pub name: String, + /// Warning messages, if any, related to loading the wallet. + pub warning: String, +} + +impl LoadWallet { + /// Converts version specific type to a version in-specific, more strongly typed type. + pub fn into_model(self) -> model::LoadWallet { + model::LoadWallet { name: self.name, warnings: vec![self.warning] } + } + + /// Returns the loaded wallet name. + pub fn name(self) -> String { self.into_model().name } +} + +/// Result of the JSON-RPC method `getnewaddress`. +/// +/// > getnewaddress ( "label" "address_type" ) +/// > +/// > Returns a new Bitcoin address for receiving payments. +/// > If 'label' is specified, it is added to the address book +/// > so payments received with the address will be associated with 'label'. +/// > +/// > Arguments: +/// > 1. "label" (string, optional) The label name for the address to be linked to. If not provided, the default label "" is used. It can also be set to the empty string "" to represent the default label. The label does not need to exist, it will be created if there is no label by the given name. +/// > 2. "address_type" (string, optional) The address type to use. Options are "legacy", "p2sh-segwit", and "bech32". Default is set by -addresstype. +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +pub struct GetNewAddress(pub String); + +impl GetNewAddress { + /// Converts version specific type to a version in-specific, more strongly typed type. + pub fn into_model(self) -> Result { + let address = Address::from_str(&self.0)?; + Ok(model::GetNewAddress(address)) + } + + /// Converts json straight to a `bitcoin::Address`. + pub fn address(self) -> Result, address::ParseError> { + let model = self.into_model()?; + Ok(model.0) + } +} + +/// Result of the JSON-RPC method `getbalance`. +/// +/// > getbalance ( "(dummy)" minconf include_watchonly ) +/// > +/// > Returns the total available balance. +/// > The available balance is what the wallet considers currently spendable, and is +/// > thus affected by options which limit spendability such as -spendzeroconfchange. +/// > +/// > Arguments: +/// > 1. (dummy) (string, optional) Remains for backward compatibility. Must be excluded or set to "*". +/// > 2. minconf (numeric, optional, default=0) Only include transactions confirmed at least this many times. +/// > 3. include_watchonly (bool, optional, default=false) Also include balance in watch-only addresses (see 'importaddress') +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +pub struct GetBalance(pub f64); + +impl GetBalance { + /// Converts version specific type to a version in-specific, more strongly typed type. + pub fn into_model(self) -> Result { + let amount = Amount::from_btc(self.0)?; + Ok(model::GetBalance(amount)) + } + + /// Converts json straight to a `bitcoin::Amount`. + pub fn balance(self) -> Result { + let model = self.into_model()?; + Ok(model.0) + } +} + +/// Result of the JSON-RPC method `sendtoaddress`. +/// +/// > sendtoaddress "address" amount ( "comment" "comment_to" subtractfeefromamount replaceable conf_target "estimate_mode") +/// > +/// > Send an amount to a given address. +/// > +/// > Arguments: +/// > 1. "address" (string, required) The bitcoin address to send to. +/// > 2. "amount" (numeric or string, required) The amount in BTC to send. eg 0.1 +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +pub struct SendToAddress(String); + +impl SendToAddress { + /// Converts version specific type to a version in-specific, more strongly typed type. + pub fn into_model(self) -> Result { + let txid = self.0.parse::()?; + Ok(model::SendToAddress { + txid, + // FIXME: Is this acceptable? + fee_reason: "".to_string(), + }) + } + + /// Converts json straight to a `bitcoin::Txid`. + pub fn txid(self) -> Result { Ok(self.into_model()?.txid) } +} + +/// Result of the JSON-RPC method `gettransaction`. +/// +/// > gettransaction "txid" ( include_watchonly ) +/// > +/// > Get detailed information about in-wallet transaction `` +/// > +/// > Arguments: +/// > 1. txid (string, required) The transaction id +/// > 2. include_watchonly (boolean, optional, default=false) Whether to include watch-only addresses in balance calculation and details[] +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +pub struct GetTransaction { + pub amount: f64, + pub fee: Option, + pub confirmations: u32, + // FIXME: The docs say these two fields should be here but it is not returned. + // Is it worth patching Core for a version this old? + // + // #[serde(rename = "blockhash")] + // pub block_hash: String, + // #[serde(rename = "blockindex")] + // pub block_index: u64, + pub txid: String, + pub time: u64, + #[serde(rename = "timereceived")] + pub time_received: u64, + #[serde(rename = "bip125-replaceable")] + pub bip125_replaceable: String, + pub details: Vec, + pub hex: String, +} + +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +pub struct GetTransactionDetail { + pub address: String, + pub category: GetTransactionDetailCategory, + pub amount: f64, + pub label: Option, + pub vout: u32, + pub fee: Option, + pub abandoned: Option, +} + +/// Enum to represent the category of a transaction. +#[derive(Copy, Clone, PartialEq, Eq, Debug, Deserialize, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum GetTransactionDetailCategory { + Send, + Receive, + Generate, + Immature, + Orphan, +} + +impl GetTransaction { + /// Converts version specific type to a version in-specific, more strongly typed type. + pub fn into_model(self) -> Result { + use GetTransactionError as E; + + let amount = SignedAmount::from_btc(self.amount).map_err(E::Amount)?; + // FIMXE: Use combinators. + let fee = match self.fee { + None => None, + Some(f) => Some(SignedAmount::from_btc(f).map_err(E::Fee)?), + }; + let txid = self.txid.parse::().map_err(E::Txid)?; + + let tx = encode::deserialize_hex::(&self.hex).map_err(E::Tx)?; + let mut details = vec![]; + for detail in self.details { + let concrete = detail.into_model().map_err(E::Details)?; + details.push(concrete); + } + + Ok(model::GetTransaction { + amount, + fee, + confirmations: self.confirmations, + txid, + time: self.time, + time_received: self.time_received, + bip125_replaceable: self.bip125_replaceable, + details, + tx, + }) + } +} + +/// Error when converting a `GetTransaction` type into the model type. +#[derive(Debug)] +pub enum GetTransactionError { + /// Conversion of the `amount` field failed. + Amount(ParseAmountError), + /// Conversion of the `fee` field failed. + Fee(ParseAmountError), + /// Conversion of the `txid` field failed. + Txid(hex::HexToArrayError), + /// Conversion of the transaction `hex` field failed. + Tx(encode::FromHexError), + /// Conversion of the `details` field failed. + Details(GetTransactionDetailError), +} + +impl fmt::Display for GetTransactionError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use GetTransactionError as E; + + match *self { + E::Amount(ref e) => write_err!(f, "conversion of the `amount` field failed"; e), + E::Fee(ref e) => write_err!(f, "conversion of the `fee` field failed"; e), + E::Txid(ref e) => write_err!(f, "conversion of the `txid` field failed"; e), + E::Tx(ref e) => write_err!(f, "conversion of the `hex` field failed"; e), + E::Details(ref e) => write_err!(f, "conversion of the `details` field failed"; e), + } + } +} + +impl std::error::Error for GetTransactionError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + use GetTransactionError as E; + + match *self { + E::Amount(ref e) => Some(e), + E::Fee(ref e) => Some(e), + E::Txid(ref e) => Some(e), + E::Tx(ref e) => Some(e), + E::Details(ref e) => Some(e), + } + } +} + +impl GetTransactionDetail { + /// Converts version specific type to a version in-specific, more strongly typed type. + pub fn into_model(self) -> Result { + use GetTransactionDetailError as E; + + let address = Address::from_str(&self.address).map_err(E::Address)?; + let amount = SignedAmount::from_btc(self.amount).map_err(E::Amount)?; + // FIMXE: Use combinators. + let fee = match self.fee { + None => None, + Some(f) => Some(SignedAmount::from_btc(f).map_err(E::Fee)?), + }; + + Ok(model::GetTransactionDetail { + address, + category: self.category.into_model(), + amount, + label: self.label, + vout: self.vout, + fee, + abandoned: self.abandoned, + }) + } +} + +/// Error when converting to a `v22::GetTransactionDetail` type to a `concrete` type. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum GetTransactionDetailError { + /// Conversion of the `address` field failed. + Address(address::ParseError), + /// Conversion of the `fee` field failed. + Fee(ParseAmountError), + /// Conversion of the `amount` field failed. + Amount(ParseAmountError), +} + +impl fmt::Display for GetTransactionDetailError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use GetTransactionDetailError::*; + + match *self { + Address(ref e) => write_err!(f, "conversion of the `address` field failed"; e), + Fee(ref e) => write_err!(f, "conversion of the `fee` field failed"; e), + Amount(ref e) => write_err!(f, "conversion of the `amount` field failed"; e), + } + } +} + +impl std::error::Error for GetTransactionDetailError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + use GetTransactionDetailError as E; + + match *self { + E::Address(ref e) => Some(e), + E::Fee(ref e) => Some(e), + E::Amount(ref e) => Some(e), + } + } +} + +impl GetTransactionDetailCategory { + /// Converts version specific type to a version in-specific, more strongly typed type. + pub fn into_model(self) -> model::GetTransactionDetailCategory { + use GetTransactionDetailCategory::*; + + match self { + Send => model::GetTransactionDetailCategory::Send, + Receive => model::GetTransactionDetailCategory::Receive, + Generate => model::GetTransactionDetailCategory::Generate, + Immature => model::GetTransactionDetailCategory::Immature, + Orphan => model::GetTransactionDetailCategory::Orphan, + } + } +} diff --git a/json/src/v17/wallet/convert.rs b/json/src/v17/wallet/convert.rs deleted file mode 100644 index 75640f4..0000000 --- a/json/src/v17/wallet/convert.rs +++ /dev/null @@ -1,211 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -//! Convert stdlib (version specific) types to concrete types. -//! -//! This module does the conversion for `v0.17.1` types to the general concrete types. - -use std::fmt; -use std::str::FromStr; - -use bitcoin::amount::ParseAmountError; -use bitcoin::consensus::encode; -use bitcoin::{address, hex, Address, Amount, SignedAmount, Transaction, Txid}; -use internals::write_err; - -use crate::{model, v17}; - -impl From for model::CreateWallet { - fn from(json: v17::CreateWallet) -> Self { - Self { name: json.name, warnings: vec![json.warning] } - } -} - -impl From for model::LoadWallet { - fn from(json: v17::LoadWallet) -> Self { - Self { name: json.name, warnings: vec![json.warning] } - } -} - -impl TryFrom for model::SendToAddress { - type Error = hex::HexToArrayError; - - fn try_from(json: v17::SendToAddress) -> Result { - let txid = json.txid.parse::()?; - Ok(Self { - txid, - // FIXME: Is this acceptable? - fee_reason: "".to_string(), - }) - } -} - -impl TryFrom for model::GetNewAddress { - type Error = address::ParseError; - - fn try_from(json: v17::GetNewAddress) -> Result { - let address = Address::from_str(&json.0)?; - Ok(Self(address)) - } -} - -impl TryFrom for model::GetBalance { - type Error = ParseAmountError; - - fn try_from(json: v17::GetBalance) -> Result { - let amount = Amount::from_btc(json.0)?; - Ok(Self(amount)) - } -} - -impl TryFrom for model::GetTransaction { - type Error = GetTransactionError; - - fn try_from(json: v17::GetTransaction) -> Result { - use GetTransactionError as E; - - let amount = SignedAmount::from_btc(json.amount).map_err(E::Amount)?; - // FIMXE: Use combinators. - let fee = match json.fee { - None => None, - Some(f) => Some(SignedAmount::from_btc(f).map_err(E::Fee)?), - }; - let txid = json.txid.parse::().map_err(E::Txid)?; - - let tx = encode::deserialize_hex::(&json.hex).map_err(E::Tx)?; - let mut details = vec![]; - for detail in json.details { - let concrete = detail.try_into().map_err(E::Details)?; - details.push(concrete); - } - - Ok(Self { - amount, - fee, - confirmations: json.confirmations, - txid, - time: json.time, - time_received: json.time_received, - bip125_replaceable: json.bip125_replaceable, - details, - tx, - }) - } -} - -/// Error when converting to a `v22::GetBlockchainInfo` type to a `concrete` type. -#[derive(Debug)] -pub enum GetTransactionError { - /// Conversion of the `amount` field failed. - Amount(ParseAmountError), - /// Conversion of the `fee` field failed. - Fee(ParseAmountError), - /// Conversion of the `txid` field failed. - Txid(hex::HexToArrayError), - /// Conversion of the transaction `hex` field failed. - Tx(encode::FromHexError), - /// Conversion of the `details` field failed. - Details(GetTransactionDetailError), -} - -impl fmt::Display for GetTransactionError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - use GetTransactionError as E; - - match *self { - E::Amount(ref e) => write_err!(f, "conversion of the `amount` field failed"; e), - E::Fee(ref e) => write_err!(f, "conversion of the `fee` field failed"; e), - E::Txid(ref e) => write_err!(f, "conversion of the `txid` field failed"; e), - E::Tx(ref e) => write_err!(f, "conversion of the `hex` field failed"; e), - E::Details(ref e) => write_err!(f, "conversion of the `details` field failed"; e), - } - } -} - -impl std::error::Error for GetTransactionError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - use GetTransactionError as E; - - match *self { - E::Amount(ref e) => Some(e), - E::Fee(ref e) => Some(e), - E::Txid(ref e) => Some(e), - E::Tx(ref e) => Some(e), - E::Details(ref e) => Some(e), - } - } -} - -impl TryFrom for model::GetTransactionDetail { - type Error = GetTransactionDetailError; - - fn try_from(json: v17::GetTransactionDetail) -> Result { - use GetTransactionDetailError as E; - - let address = Address::from_str(&json.address).map_err(E::Address)?; - let amount = SignedAmount::from_btc(json.amount).map_err(E::Amount)?; - // FIMXE: Use combinators. - let fee = match json.fee { - None => None, - Some(f) => Some(SignedAmount::from_btc(f).map_err(E::Fee)?), - }; - - Ok(Self { - address, - category: json.category.into(), - amount, - label: json.label, - vout: json.vout, - fee, - abandoned: json.abandoned, - }) - } -} - -/// Error when converting to a `v22::GetTransactionDetail` type to a `concrete` type. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum GetTransactionDetailError { - /// Conversion of the `address` field failed. - Address(address::ParseError), - /// Conversion of the `fee` field failed. - Fee(ParseAmountError), - /// Conversion of the `amount` field failed. - Amount(ParseAmountError), -} - -impl fmt::Display for GetTransactionDetailError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - use GetTransactionDetailError::*; - - match *self { - Address(ref e) => write_err!(f, "conversion of the `address` field failed"; e), - Fee(ref e) => write_err!(f, "conversion of the `fee` field failed"; e), - Amount(ref e) => write_err!(f, "conversion of the `amount` field failed"; e), - } - } -} - -impl std::error::Error for GetTransactionDetailError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - use GetTransactionDetailError as E; - - match *self { - E::Address(ref e) => Some(e), - E::Fee(ref e) => Some(e), - E::Amount(ref e) => Some(e), - } - } -} - -impl From for model::GetTransactionDetailCategory { - fn from(json: v17::GetTransactionDetailCategory) -> Self { - use v17::GetTransactionDetailCategory::*; - - match json { - Send => model::GetTransactionDetailCategory::Send, - Receive => model::GetTransactionDetailCategory::Receive, - Generate => model::GetTransactionDetailCategory::Generate, - Immature => model::GetTransactionDetailCategory::Immature, - Orphan => model::GetTransactionDetailCategory::Orphan, - } - } -} diff --git a/json/src/v17/wallet/mod.rs b/json/src/v17/wallet/mod.rs deleted file mode 100644 index 1213de5..0000000 --- a/json/src/v17/wallet/mod.rs +++ /dev/null @@ -1,143 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -//! The JSON-RPC API for Bitcoin Core v0.17.1 - wallet. -//! -//! Types for methods found under the `== Wallet ==` section of the API docs. - -mod convert; - -use serde::{Deserialize, Serialize}; - -/// Result of the JSON-RPC method `createwallet`. -/// -/// > createwallet "wallet_name" ( disable_private_keys ) -/// > -/// > Creates and loads a new wallet. -/// > -/// > Arguments: -/// > 1. "wallet_name" (string, required) The name for the new wallet. If this is a path, the wallet will be created at the path location. -/// > 2. disable_private_keys (boolean, optional, default: false) Disable the possibility of private keys (only watchonlys are possible in this mode). -#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] -pub struct CreateWallet { - /// The wallet name if created successfully. - /// - /// If the wallet was created using a full path, the wallet_name will be the full path. - pub name: String, - /// Warning messages, if any, related to creating and loading the wallet. - pub warning: String, -} - -/// Result of the JSON-RPC method `loadwallet`. -/// -/// > loadwallet "filename" -/// > -/// > Loads a wallet from a wallet file or directory. -/// > Note that all wallet command-line options used when starting bitcoind will be -/// > applied to the new wallet (eg -zapwallettxes, upgradewallet, rescan, etc). -/// > -/// > Arguments: -/// > 1. "filename" (string, required) The wallet directory or .dat file. -#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] -pub struct LoadWallet { - /// The wallet name if loaded successfully. - pub name: String, - /// Warning messages, if any, related to loading the wallet. - pub warning: String, -} - -/// Result of the JSON-RPC method `getnewaddress`. -/// -/// > getnewaddress ( "label" "address_type" ) -/// > -/// > Returns a new Bitcoin address for receiving payments. -/// > If 'label' is specified, it is added to the address book -/// > so payments received with the address will be associated with 'label'. -/// > -/// > Arguments: -/// > 1. "label" (string, optional) The label name for the address to be linked to. If not provided, the default label "" is used. It can also be set to the empty string "" to represent the default label. The label does not need to exist, it will be created if there is no label by the given name. -/// > 2. "address_type" (string, optional) The address type to use. Options are "legacy", "p2sh-segwit", and "bech32". Default is set by -addresstype. -#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] -pub struct GetNewAddress(pub String); - -/// Result of the JSON-RPC method `getbalance`. -/// -/// > getbalance ( "(dummy)" minconf include_watchonly ) -/// > -/// > Returns the total available balance. -/// > The available balance is what the wallet considers currently spendable, and is -/// > thus affected by options which limit spendability such as -spendzeroconfchange. -/// > -/// > Arguments: -/// > 1. (dummy) (string, optional) Remains for backward compatibility. Must be excluded or set to "*". -/// > 2. minconf (numeric, optional, default=0) Only include transactions confirmed at least this many times. -/// > 3. include_watchonly (bool, optional, default=false) Also include balance in watch-only addresses (see 'importaddress') -#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] -pub struct GetBalance(pub f64); - -/// Result of the JSON-RPC method `sendtoaddress`. -/// -/// > sendtoaddress "address" amount ( "comment" "comment_to" subtractfeefromamount replaceable conf_target "estimate_mode") -/// > -/// > Send an amount to a given address. -/// > -/// > Arguments: -/// > 1. "address" (string, required) The bitcoin address to send to. -/// > 2. "amount" (numeric or string, required) The amount in BTC to send. eg 0.1 -#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] -pub struct SendToAddress { - /// The transaction id. - pub txid: String, -} - -/// Result of the JSON-RPC method `gettransaction`. -/// -/// > gettransaction "txid" ( include_watchonly ) -/// > -/// > Get detailed information about in-wallet transaction `` -/// > -/// > Arguments: -/// > 1. txid (string, required) The transaction id -/// > 2. include_watchonly (boolean, optional, default=false) Whether to include watch-only addresses in balance calculation and details[] -#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] -pub struct GetTransaction { - pub amount: f64, - pub fee: Option, - pub confirmations: u32, - // FIXME: The docs say these two fields should be here but it is not returned. - // Is it worth patching Core for a version this old? - // - // #[serde(rename = "blockhash")] - // pub block_hash: String, - // #[serde(rename = "blockindex")] - // pub block_index: u64, - pub txid: String, - pub time: u64, - #[serde(rename = "timereceived")] - pub time_received: u64, - #[serde(rename = "bip125-replaceable")] - pub bip125_replaceable: String, - pub details: Vec, - pub hex: String, -} - -#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] -pub struct GetTransactionDetail { - pub address: String, - pub category: GetTransactionDetailCategory, - pub amount: f64, - pub label: Option, - pub vout: u32, - pub fee: Option, - pub abandoned: Option, -} - -/// Enum to represent the category of a transaction. -#[derive(Copy, Clone, PartialEq, Eq, Debug, Deserialize, Serialize)] -#[serde(rename_all = "lowercase")] -pub enum GetTransactionDetailCategory { - Send, - Receive, - Generate, - Immature, - Orphan, -} diff --git a/json/src/v17/zmq/mod.rs b/json/src/v17/zmq.rs similarity index 100% rename from json/src/v17/zmq/mod.rs rename to json/src/v17/zmq.rs diff --git a/json/src/v19/blockchain/mod.rs b/json/src/v19/blockchain.rs similarity index 67% rename from json/src/v19/blockchain/mod.rs rename to json/src/v19/blockchain.rs index 2d536da..ea23bd8 100644 --- a/json/src/v19/blockchain/mod.rs +++ b/json/src/v19/blockchain.rs @@ -4,12 +4,16 @@ //! //! Types for methods found under the `== Blockchain ==` section of the API docs. -mod convert; - +use core::fmt; use std::collections::BTreeMap; +use bitcoin::error::UnprefixedHexError; +use bitcoin::{hex, network, BlockHash, Network, Work}; +use internals::write_err; use serde::{Deserialize, Serialize}; +use crate::model; + #[rustfmt::skip] // Keep public re-exports separate. /// Result of JSON-RPC method `getblockchaininfo`. @@ -135,3 +139,71 @@ pub struct Bip9SoftforkStatistics { /// `false` if there are not enough blocks left in this period to pass activation threshold. pub possible: Option, } + +impl GetBlockchainInfo { + /// Converts version specific type to a version in-specific, more strongly typed type. + pub fn into_model(self) -> Result { + use GetBlockchainInfoError as E; + + let chain = Network::from_core_arg(&self.chain).map_err(E::Chain)?; + let best_block_hash = + self.best_block_hash.parse::().map_err(E::BestBlockHash)?; + // FIXME: Is unprefixed correct? + let chain_work = Work::from_unprefixed_hex(&self.chain_work).map_err(E::ChainWork)?; + + let softforks = BTreeMap::new(); // TODO: Handle softforks stuff. + + Ok(model::GetBlockchainInfo { + chain, + blocks: self.blocks, + headers: self.headers, + best_block_hash, + difficulty: self.difficulty, + median_time: self.median_time, + verification_progress: self.verification_progress, + initial_block_download: self.initial_block_download, + chain_work, + size_on_disk: self.size_on_disk, + pruned: self.pruned, + prune_height: self.prune_height, + automatic_pruning: self.automatic_pruning, + prune_target_size: self.prune_target_size, + softforks, + warnings: self.warnings, + }) + } +} + +/// Error when converting a `GetBlockchainInfo` type into the model type. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum GetBlockchainInfoError { + Chain(network::ParseNetworkError), + BestBlockHash(hex::HexToArrayError), + ChainWork(UnprefixedHexError), +} + +impl fmt::Display for GetBlockchainInfoError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use GetBlockchainInfoError::*; + + match *self { + Chain(ref e) => write_err!(f, "conversion of the `chain` field failed"; e), + BestBlockHash(ref e) => { + write_err!(f, "conversion of the `best_block_hash` field failed"; e) + } + ChainWork(ref e) => write_err!(f, "conversion of the `chain_work` field failed"; e), + } + } +} + +impl std::error::Error for GetBlockchainInfoError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + use GetBlockchainInfoError::*; + + match *self { + Chain(ref e) => Some(e), + BestBlockHash(ref e) => Some(e), + ChainWork(ref e) => Some(e), + } + } +} diff --git a/json/src/v19/blockchain/convert.rs b/json/src/v19/blockchain/convert.rs deleted file mode 100644 index 6e8e2a9..0000000 --- a/json/src/v19/blockchain/convert.rs +++ /dev/null @@ -1,143 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -//! Convert stdlib (version specific) types to concrete types. -//! -//! This module does the conversion for `v0.19.1` types to the general concrete types. - -use core::fmt; -use std::collections::BTreeMap; - -use bitcoin::{hex, network, Network, Work}; -use internals::write_err; - -use crate::{model, v19}; - -impl TryFrom for model::GetBlockchainInfo { - type Error = GetBlockchainInfoError; - - fn try_from(json: v19::GetBlockchainInfo) -> Result { - use GetBlockchainInfoError as E; - - let chain = Network::from_core_arg(&json.chain).map_err(E::Chain)?; - let best_block_hash = json.best_block_hash.parse().map_err(E::BestBlockHash)?; - let chain_work = Work::from_unprefixed_hex(&json.chain_work).map_err(E::ChainWork)?; - - let mut softforks = BTreeMap::new(); - for (key, value) in json.softforks.into_iter() { - softforks.insert(key, value.into()); - } - - Ok(Self { - chain, - blocks: json.blocks, - headers: json.headers, - best_block_hash, - difficulty: json.difficulty, // FIXME: Should we convert to u128? - median_time: json.median_time, - verification_progress: json.verification_progress, - initial_block_download: json.initial_block_download, - chain_work, - size_on_disk: json.size_on_disk, - pruned: json.pruned, - prune_height: json.prune_height, - automatic_pruning: json.automatic_pruning, - prune_target_size: json.prune_target_size, - softforks, - warnings: json.warnings, - }) - } -} - -/// Error when converting to a `v17::GetBlockchainInfo` type to a `concrete` type. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum GetBlockchainInfoError { - /// Conversion of the `chain` field failed. - Chain(network::ParseNetworkError), - /// Conversion of the `best_block_hash` field failed. - BestBlockHash(hex::HexToArrayError), - /// Conversion of the `chain_work` field failed. - ChainWork(bitcoin::error::UnprefixedHexError), -} - -impl fmt::Display for GetBlockchainInfoError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - use GetBlockchainInfoError::*; - - match *self { - Chain(ref e) => write_err!(f, "conversion of the `chain` field failed"; e), - BestBlockHash(ref e) => { - write_err!(f, "conversion of the `best_block_hash` field failed"; e) - } - ChainWork(ref e) => write_err!(f, "conversion of the `chain_work` field failed"; e), - } - } -} - -impl std::error::Error for GetBlockchainInfoError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - use GetBlockchainInfoError::*; - - match *self { - Chain(ref e) => Some(e), - BestBlockHash(ref e) => Some(e), - ChainWork(ref e) => Some(e), - } - } -} - -impl From for model::Softfork { - fn from(json: v19::Softfork) -> Self { - Self { - type_: json.type_.into(), - bip9: json.bip9.map(Into::into), - height: json.height, - active: json.active, - } - } -} - -impl From for model::SoftforkType { - fn from(json: v19::SoftforkType) -> Self { - match json { - v19::SoftforkType::Buried => Self::Buried, - v19::SoftforkType::Bip9 => Self::Bip9, - } - } -} - -impl From for model::Bip9SoftforkInfo { - fn from(json: v19::Bip9SoftforkInfo) -> Self { - Self { - status: json.status.into(), - bit: json.bit, - start_time: json.start_time, - timeout: json.timeout, - since: json.since, - statistics: json.statistics.map(Into::into), - } - } -} - -impl From for model::Bip9SoftforkStatus { - fn from(json: v19::Bip9SoftforkStatus) -> Self { - match json { - v19::Bip9SoftforkStatus::Defined => Self::Defined, - v19::Bip9SoftforkStatus::Started => Self::Started, - v19::Bip9SoftforkStatus::LockedIn => Self::LockedIn, - v19::Bip9SoftforkStatus::Active => Self::Active, - v19::Bip9SoftforkStatus::Failed => Self::Failed, - } - } -} - -impl From for model::Bip9SoftforkStatistics { - fn from(json: v19::Bip9SoftforkStatistics) -> Self { - Self { - period: json.period, - threshold: json.threshold, - elapsed: json.elapsed, - count: json.count, - possible: json.possible, - } - } -} diff --git a/json/src/v19/wallet.rs b/json/src/v19/wallet.rs new file mode 100644 index 0000000..f1e35d7 --- /dev/null +++ b/json/src/v19/wallet.rs @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: CC0-1.0 + +//! The JSON-RPC API for Bitcoin Core v0.19.1 - wallet. +//! +//! Types for methods found under the `== Wallet ==` section of the API docs. + +use bitcoin::amount::ParseAmountError; +use bitcoin::Amount; +use serde::{Deserialize, Serialize}; + +use crate::model; + +/// Result of the JSON-RPC method `getbalances`. +/// +/// > getbalances +/// > +/// > Returns an object with all balances in BTC. +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +pub struct GetBalances { + /// Balances from outputs that the wallet can sign. + pub mine: GetBalancesMine, + #[serde(rename = "watchonly")] + pub watch_only: Option, +} + +/// Balances from outputs that the wallet can sign. +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +pub struct GetBalancesMine { + /// Trusted balance (outputs created by the wallet or confirmed outputs). + pub trusted: f64, + /// Untrusted pending balance (outputs created by others that are in the mempool). + pub untrusted_pending: f64, + /// Balance from immature coinbase outputs. + pub immature: f64, + /// Balance from coins sent to addresses that were previously spent from (potentially privacy violating). + /// + /// Only present if `avoid_reuse` is set. + pub used: Option, +} + +/// Hash and height of the block this information was generated on. +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +pub struct GetBalancesWatchOnly { + /// Trusted balance (outputs created by the wallet or confirmed outputs). + pub trusted: f64, + /// Untrusted pending balance (outputs created by others that are in the mempool). + pub untrusted_pending: f64, + /// Balance from immature coinbase outputs. + pub immature: f64, +} + +impl GetBalances { + /// Converts version specific type to a version in-specific, more strongly typed type. + pub fn into_model(self) -> Result { + let mine = self.mine.into_model()?; + // FIXME: Use combinators instead of matching like a noob. + let watch_only = match self.watch_only { + Some(watch_only) => Some(watch_only.into_model()?), + None => None, + }; + + Ok(model::GetBalances { mine, watch_only }) + } +} + +impl GetBalancesMine { + /// Converts version specific type to a version in-specific, more strongly typed type. + pub fn into_model(self) -> Result { + let trusted = Amount::from_btc(self.trusted)?; + let untrusted_pending = Amount::from_btc(self.untrusted_pending)?; + let immature = Amount::from_btc(self.immature)?; + // FIXME: Use combinators instead of matching like a noob. + let used = match self.used { + Some(used) => Some(Amount::from_btc(used)?), + None => None, + }; + + Ok(model::GetBalancesMine { trusted, untrusted_pending, immature, used }) + } +} + +impl GetBalancesWatchOnly { + /// Converts version specific type to a version in-specific, more strongly typed type. + pub fn into_model(self) -> Result { + let trusted = Amount::from_btc(self.trusted)?; + let untrusted_pending = Amount::from_btc(self.untrusted_pending)?; + let immature = Amount::from_btc(self.immature)?; + + Ok(model::GetBalancesWatchOnly { trusted, untrusted_pending, immature }) + } +} diff --git a/json/src/v19/wallet/convert.rs b/json/src/v19/wallet/convert.rs deleted file mode 100644 index 811b0ca..0000000 --- a/json/src/v19/wallet/convert.rs +++ /dev/null @@ -1,54 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -//! Convert stdlib (version specific) types to concrete types. -//! -//! This module does the conversion for `v0.19.1` types to the general concrete types. - -use bitcoin::amount::ParseAmountError; -use bitcoin::Amount; - -use crate::{model, v19}; - -impl TryFrom for model::GetBalances { - type Error = ParseAmountError; - - fn try_from(json: v19::GetBalances) -> Result { - let mine = json.mine.try_into()?; - // FIXME: Use combinators instead of matching like a noob. - let watch_only = match json.watch_only { - Some(watch_only) => Some(watch_only.try_into()?), - None => None, - }; - - Ok(Self { mine, watch_only }) - } -} - -impl TryFrom for model::GetBalancesMine { - type Error = ParseAmountError; - - fn try_from(json: v19::GetBalancesMine) -> Result { - let trusted = Amount::from_btc(json.trusted)?; - let untrusted_pending = Amount::from_btc(json.untrusted_pending)?; - let immature = Amount::from_btc(json.immature)?; - // FIXME: Use combinators instead of matching like a noob. - let used = match json.used { - Some(used) => Some(Amount::from_btc(used)?), - None => None, - }; - - Ok(Self { trusted, untrusted_pending, immature, used }) - } -} - -impl TryFrom for model::GetBalancesWatchOnly { - type Error = ParseAmountError; - - fn try_from(json: v19::GetBalancesWatchOnly) -> Result { - let trusted = Amount::from_btc(json.trusted)?; - let untrusted_pending = Amount::from_btc(json.untrusted_pending)?; - let immature = Amount::from_btc(json.immature)?; - - Ok(Self { trusted, untrusted_pending, immature }) - } -} diff --git a/json/src/v19/wallet/mod.rs b/json/src/v19/wallet/mod.rs deleted file mode 100644 index 64bfe90..0000000 --- a/json/src/v19/wallet/mod.rs +++ /dev/null @@ -1,48 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -//! The JSON-RPC API for Bitcoin Core v0.19.1 - wallet. -//! -//! Types for methods found under the `== Wallet ==` section of the API docs. - -mod convert; - -use serde::{Deserialize, Serialize}; - -/// Result of the JSON-RPC method `getbalances`. -/// -/// > getbalances -/// > -/// > Returns an object with all balances in BTC. -#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] -pub struct GetBalances { - /// Balances from outputs that the wallet can sign. - pub mine: GetBalancesMine, - #[serde(rename = "watchonly")] - pub watch_only: Option, -} - -/// Balances from outputs that the wallet can sign. -#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] -pub struct GetBalancesMine { - /// Trusted balance (outputs created by the wallet or confirmed outputs). - pub trusted: f64, - /// Untrusted pending balance (outputs created by others that are in the mempool). - pub untrusted_pending: f64, - /// Balance from immature coinbase outputs. - pub immature: f64, - /// Balance from coins sent to addresses that were previously spent from (potentially privacy violating). - /// - /// Only present if `avoid_reuse` is set. - pub used: Option, -} - -/// Hash and height of the block this information was generated on. -#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] -pub struct GetBalancesWatchOnly { - /// Trusted balance (outputs created by the wallet or confirmed outputs). - pub trusted: f64, - /// Untrusted pending balance (outputs created by others that are in the mempool). - pub untrusted_pending: f64, - /// Balance from immature coinbase outputs. - pub immature: f64, -} diff --git a/json/src/v22/mod.rs b/json/src/v22/mod.rs index eb149ff..837b87f 100644 --- a/json/src/v22/mod.rs +++ b/json/src/v22/mod.rs @@ -3,7 +3,7 @@ //! Structs with standard types. //! //! These structs model the types returned by the JSON-RPC API and use stdlib types (or custom -//! types) and are specific to a specific to Bitcoin Core `v22.1`. +//! types) and are specific to a specific to Bitcoin Core `v22`. //! //! A `x` marks methods that are implemented _and_ tested. //! @@ -171,14 +171,14 @@ mod wallet; #[doc(inline)] -pub use self::wallet::{SendToAddress, UnloadWallet}; +pub use self::wallet::UnloadWallet; #[doc(inline)] pub use crate::{ v17::{ CreateWallet, GenerateToAddress, GetBalance, GetBestBlockHash, GetBlockVerbosityOne, GetBlockVerbosityZero, GetNetworkInfo, GetNetworkInfoAddress, GetNetworkInfoNetwork, GetNewAddress, GetTransaction, GetTransactionDetail, GetTransactionDetailCategory, - GetTxOut, LoadWallet, SendRawTransaction, + GetTxOut, LoadWallet, SendRawTransaction, SendToAddress, }, v19::{ Bip9SoftforkInfo, Bip9SoftforkStatistics, Bip9SoftforkStatus, GetBalances, GetBalancesMine, diff --git a/json/src/v22/wallet/mod.rs b/json/src/v22/wallet.rs similarity index 55% rename from json/src/v22/wallet/mod.rs rename to json/src/v22/wallet.rs index beba8a1..4ae5257 100644 --- a/json/src/v22/wallet/mod.rs +++ b/json/src/v22/wallet.rs @@ -1,13 +1,13 @@ // SPDX-License-Identifier: CC0-1.0 -//! The JSON-RPC API for Bitcoin Core v22.1 - wallet. +//! The JSON-RPC API for Bitcoin Core v22 - wallet. //! //! Types for methods found under the `== Wallet ==` section of the API docs. -mod convert; - use serde::{Deserialize, Serialize}; +use crate::model; + /// Result of the JSON-RPC method `unloadwallet`. /// /// > unloadwallet ( "wallet_name" load_on_startup ) @@ -24,20 +24,9 @@ pub struct UnloadWallet { pub warning: String, } -/// Result of the JSON-RPC method `sendtoaddress`. -/// -/// > sendtoaddress "address" amount ( "comment" "comment_to" subtractfeefromamount replaceable conf_target "estimate_mode" avoid_reuse fee_rate verbose ) -/// > -/// > Send an amount to a given address. -/// > Requires wallet passphrase to be set with walletpassphrase call if wallet is encrypted. -/// > -/// > Arguments: -/// > 1. address (string, required) The bitcoin address to send to. -/// > 2. amount (numeric or string, required) The amount in BTC to send. eg 0.1 -#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] -pub struct SendToAddress { - /// The transaction id. - pub txid: String, - /// The transaction fee reason. - pub fee_reason: String, +impl UnloadWallet { + /// Converts version specific type to a version in-specific, more strongly typed type. + pub fn into_model(self) -> model::UnloadWallet { + model::UnloadWallet { warnings: vec![self.warning] } + } } diff --git a/json/src/v22/wallet/convert.rs b/json/src/v22/wallet/convert.rs deleted file mode 100644 index fd449c7..0000000 --- a/json/src/v22/wallet/convert.rs +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -//! Convert stdlib (version specific) types to concrete types. -//! -//! This module does the conversion for `v22.1` types to the general concrete types. - -use bitcoin::{hex, Txid}; - -use crate::{model, v22}; - -impl From for model::UnloadWallet { - fn from(json: v22::UnloadWallet) -> Self { Self { warnings: vec![json.warning] } } -} - -impl TryFrom for model::SendToAddress { - type Error = hex::HexToArrayError; - - fn try_from(json: v22::SendToAddress) -> Result { - let txid = json.txid.parse::()?; - Ok(Self { txid, fee_reason: json.fee_reason }) - } -} diff --git a/json/src/v23/mod.rs b/json/src/v23/mod.rs index 30bc871..a88428c 100644 --- a/json/src/v23/mod.rs +++ b/json/src/v23/mod.rs @@ -3,7 +3,7 @@ //! Structs with standard types. //! //! These structs model the types returned by the JSON-RPC API and use stdlib types (or custom -//! types) and are specific to a specific to Bitcoin Core `v23.2`. +//! types) and are specific to a specific to Bitcoin Core `v23`. //! //! **== Blockchain ==** //! - [x] `getbestblockhash` diff --git a/json/src/v24/mod.rs b/json/src/v24/mod.rs index 2eb5db4..97518ed 100644 --- a/json/src/v24/mod.rs +++ b/json/src/v24/mod.rs @@ -3,7 +3,7 @@ //! Structs with standard types. //! //! These structs model the types returned by the JSON-RPC API and use stdlib types (or custom -//! types) and are specific to a specific to Bitcoin Core `v24.2`. +//! types) and are specific to a specific to Bitcoin Core `v24`. //! //! **== Blockchain ==** //! - [x] `getbestblockhash` diff --git a/json/src/v25/mod.rs b/json/src/v25/mod.rs index 59c4d4b..76dff2a 100644 --- a/json/src/v25/mod.rs +++ b/json/src/v25/mod.rs @@ -3,7 +3,7 @@ //! Structs with standard types. //! //! These structs model the types returned by the JSON-RPC API and use stdlib types (or custom -//! types) and are specific to a specific to Bitcoin Core `v25.2`. +//! types) and are specific to a specific to Bitcoin Core `v25`. //! //! **== Blockchain ==** //! - [x] `getbestblockhash` diff --git a/json/src/v25/wallet/mod.rs b/json/src/v25/wallet.rs similarity index 79% rename from json/src/v25/wallet/mod.rs rename to json/src/v25/wallet.rs index d4a3060..ed25c18 100644 --- a/json/src/v25/wallet/mod.rs +++ b/json/src/v25/wallet.rs @@ -1,11 +1,13 @@ // SPDX-License-Identifier: CC0-1.0 -//! The JSON-RPC API for Bitcoin Core v25.2 - wallet. +//! The JSON-RPC API for Bitcoin Core v25 - wallet. //! //! Types for methods found under the `== Wallet ==` section of the API docs. use serde::{Deserialize, Serialize}; +use crate::model; + /// Result of the JSON-RPC method `createwallet`. /// /// > createwallet "wallet_name" ( disable_private_keys blank "passphrase" avoid_reuse descriptors load_on_startup external_signer ) @@ -31,6 +33,16 @@ pub struct CreateWallet { pub warnings: Option>, } +impl CreateWallet { + /// Converts version specific type to a version in-specific, more strongly typed type. + pub fn into_model(self) -> model::CreateWallet { + model::CreateWallet { name: self.name, warnings: self.warnings.unwrap_or_default() } + } + + /// Returns the created wallet name. + pub fn name(self) -> String { self.into_model().name } +} + /// Result of the JSON-RPC method `loadwallet`. /// /// > loadwallet "filename" ( load_on_startup ) @@ -49,3 +61,13 @@ pub struct LoadWallet { /// Warning messages, if any, related to loading the wallet. pub warnings: Option>, } + +impl LoadWallet { + /// Converts version specific type to a version in-specific, more strongly typed type. + pub fn into_model(self) -> model::LoadWallet { + model::LoadWallet { name: self.name, warnings: self.warnings.unwrap_or_default() } + } + + /// Returns the loaded wallet name. + pub fn name(self) -> String { self.into_model().name } +} diff --git a/json/src/v26/mod.rs b/json/src/v26/mod.rs index 8fb2cc6..ba21ed1 100644 --- a/json/src/v26/mod.rs +++ b/json/src/v26/mod.rs @@ -3,7 +3,7 @@ //! Structs with standard types. //! //! These structs model the types returned by the JSON-RPC API and use stdlib types (or custom -//! types) and are specific to a specific to Bitcoin Core `v26.0`. +//! types) and are specific to a specific to Bitcoin Core `v26`. //! //! **== Blockchain ==** //! - [ ] `dumptxoutset "path"` diff --git a/regtest/src/lib.rs b/regtest/src/lib.rs index e54a469..2b1f9ad 100644 --- a/regtest/src/lib.rs +++ b/regtest/src/lib.rs @@ -397,9 +397,16 @@ impl BitcoinD { let url = match &conf.wallet { Some(wallet) => { debug!("trying to create/load wallet: {}", wallet); - if let Err(e) = client_base.create_wallet(wallet) { - debug!("initial create_wallet unsuccessful, try loading instead: {:?}", e); - client_base.load_wallet(wallet)?; + // Debugging logic here implicitly tests `into_model` for create/load wallet. + match client_base.create_wallet(wallet) { + Ok(json) => { + debug!("created wallet: {}", json.name()); + }, + Err(e) => { + debug!("initial create_wallet unsuccessful, try loading instead: {:?}", e); + let wallet = client_base.load_wallet(wallet)?.name(); + debug!("loaded wallet: {}", wallet); + } } format!("{}/wallet/{}", rpc_url, wallet) }