From 426e7b309e239ee3159580a416a8ddadcf095ffa Mon Sep 17 00:00:00 2001 From: 0xb10c Date: Thu, 18 Dec 2025 17:09:29 +0100 Subject: [PATCH] implement hidden RPC `getorphantxs` --- client/src/client_sync/v29/hidden.rs | 44 +++++++++++ client/src/client_sync/v29/mod.rs | 4 + client/src/client_sync/v30/mod.rs | 3 + integration_test/tests/hidden.rs | 113 +++++++++++++++++++++++++++ types/src/model/hidden.rs | 62 ++++++++++++++- types/src/model/mod.rs | 5 +- types/src/v29/hidden/error.rs | 73 +++++++++++++++++ types/src/v29/hidden/into.rs | 88 +++++++++++++++++++++ types/src/v29/hidden/mod.rs | 92 ++++++++++++++++++++++ types/src/v29/mod.rs | 6 ++ types/src/v30/hidden/error.rs | 73 +++++++++++++++++ types/src/v30/hidden/into.rs | 88 +++++++++++++++++++++ types/src/v30/hidden/mod.rs | 90 +++++++++++++++++++++ types/src/v30/mod.rs | 6 ++ 14 files changed, 745 insertions(+), 2 deletions(-) create mode 100644 client/src/client_sync/v29/hidden.rs create mode 100644 types/src/v29/hidden/error.rs create mode 100644 types/src/v29/hidden/into.rs create mode 100644 types/src/v29/hidden/mod.rs create mode 100644 types/src/v30/hidden/error.rs create mode 100644 types/src/v30/hidden/into.rs create mode 100644 types/src/v30/hidden/mod.rs diff --git a/client/src/client_sync/v29/hidden.rs b/client/src/client_sync/v29/hidden.rs new file mode 100644 index 00000000..f2d0c6f0 --- /dev/null +++ b/client/src/client_sync/v29/hidden.rs @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: CC0-1.0 + +//! Macros for implementing JSON-RPC methods on a client. +//! +//! Specifically this is `== Hidden ==` methods that are not listed in the +//! API docs of Bitcoin Core `v29`. +//! +//! All macros require `Client` to be in scope. +//! +//! See, or use the `define_jsonrpc_bitreq_client!` macro to define a `Client`. + +/// Implements Bitcoin Core JSON-RPC API method `getorphantxs` verbosity level 0. +#[macro_export] +macro_rules! impl_client_v29__get_orphan_txs { + () => { + impl Client { + pub fn get_orphan_txs(&self) -> Result { self.call("getorphantxs", &[]) } + } + }; +} + +/// Implements Bitcoin Core JSON-RPC API method `getorphantxs` verbosity level 1. +#[macro_export] +macro_rules! impl_client_v29__get_orphan_txs_verbosity_1 { + () => { + impl Client { + pub fn get_orphan_txs_verbosity_1(&self) -> Result { + self.call("getorphantxs", &[into_json(1)?]) + } + } + }; +} + +/// Implements Bitcoin Core JSON-RPC API method `getorphantxs` verbosity level 2. +#[macro_export] +macro_rules! impl_client_v29__get_orphan_txs_verbosity_2 { + () => { + impl Client { + pub fn get_orphan_txs_verbosity_2(&self) -> Result { + self.call("getorphantxs", &[into_json(2)?]) + } + } + }; +} diff --git a/client/src/client_sync/v29/mod.rs b/client/src/client_sync/v29/mod.rs index 9fe5d04e..851c2c2a 100644 --- a/client/src/client_sync/v29/mod.rs +++ b/client/src/client_sync/v29/mod.rs @@ -5,6 +5,7 @@ //! We ignore option arguments unless they effect the shape of the returned JSON data. pub mod blockchain; +pub mod hidden; pub mod util; use std::collections::BTreeMap; @@ -77,6 +78,9 @@ crate::impl_client_v20__generate_to_descriptor!(); crate::impl_client_v17__invalidate_block!(); // == Hidden == +crate::impl_client_v29__get_orphan_txs_verbosity_2!(); +crate::impl_client_v29__get_orphan_txs_verbosity_1!(); +crate::impl_client_v29__get_orphan_txs!(); crate::impl_client_v27__add_connection!(); crate::impl_client_v21__add_peer_address!(); crate::impl_client_v17__estimate_raw_fee!(); diff --git a/client/src/client_sync/v30/mod.rs b/client/src/client_sync/v30/mod.rs index d22722ac..7acb8cbd 100644 --- a/client/src/client_sync/v30/mod.rs +++ b/client/src/client_sync/v30/mod.rs @@ -78,6 +78,9 @@ crate::impl_client_v20__generate_to_descriptor!(); crate::impl_client_v17__invalidate_block!(); // == Hidden == +crate::impl_client_v29__get_orphan_txs_verbosity_2!(); +crate::impl_client_v29__get_orphan_txs_verbosity_1!(); +crate::impl_client_v29__get_orphan_txs!(); crate::impl_client_v27__add_connection!(); crate::impl_client_v21__add_peer_address!(); diff --git a/integration_test/tests/hidden.rs b/integration_test/tests/hidden.rs index 5a7cfcb6..77ce3609 100644 --- a/integration_test/tests/hidden.rs +++ b/integration_test/tests/hidden.rs @@ -4,6 +4,20 @@ #![allow(non_snake_case)] // Test names intentionally use double underscore. +#[cfg(not(feature = "v28_and_below"))] +use std::collections::HashMap; + +#[cfg(not(feature = "v28_and_below"))] +use bitcoin::{ + absolute, transaction, consensus, Amount, OutPoint, ScriptBuf, Sequence, Transaction, + TxIn, TxOut, Txid, Witness, +}; + +#[cfg(not(feature = "v28_and_below"))] +use bitcoin::hex::DisplayHex; +#[cfg(not(feature = "v28_and_below"))] +use bitcoin::hashes::Hash; + use integration_test::{Node, NodeExt as _, Wallet}; use node::mtype; use node::vtype::*; // All the version specific types. @@ -43,6 +57,7 @@ fn hidden__add_connection() { #[test] fn hidden__estimate_raw_fee__modelled() { let node = Node::with_wallet(Wallet::Default, &[]); + node.fund_wallet(); // Give the fee estimator some confirmation history. @@ -60,3 +75,101 @@ fn hidden__estimate_raw_fee__modelled() { assert!(estimate.long.scale > 0); } + +#[test] +#[cfg(not(feature = "v28_and_below"))] +fn hidden__get_orphan_txs__modelled() { + // We use node1 to send node2 orphan transactions via a P2P `tx` message. + let (node1, node2, _node3) = integration_test::three_node_network(); + + // Generate a couple of orphan transactions by spending from non-existing UTXOs. + const NUM_ORPHANS: u8 = 3; + let address = node1.client.new_address().expect("failed to get new address"); + let orphans: Vec = (0..NUM_ORPHANS).map(|i| { + Transaction { + version: transaction::Version::ONE, + lock_time: absolute::LockTime::ZERO, + input: vec![TxIn { + previous_output: OutPoint { + txid: Txid::from_raw_hash(Txid::from_byte_array([i; 32]).into()), + vout: 0, + }, + script_sig: ScriptBuf::new(), + sequence: Sequence::MAX, + witness: Witness::new(), + }], + output: vec![TxOut { + value: Amount::from_sat(100_000), + script_pubkey: address.script_pubkey(), + }], + } + }).collect(); + + // The receiving node needs to be out of IBD to start accepting transactions. + node2.mine_a_block(); + + // node2 is peer=0 of node1 + const PEER_ID: u64 = 0; + for orphan in orphans.iter() { + let tx_bytes = consensus::encode::serialize(orphan); + let tx_hex: String = tx_bytes.as_hex().to_string(); + // HACK: We should use sendmsgtopeer directly but it's not implemented yet. + node1.client + .call::>( + "sendmsgtopeer", + &[PEER_ID.into(), "tx".into(), tx_hex.into()], + ).unwrap(); + } + + let json_v0: GetOrphanTxs = node2.client.get_orphan_txs().expect("getorphantxs"); + let json_v1: GetOrphanTxsVerboseOne = node2.client.get_orphan_txs_verbosity_1().expect("getorphantxs 1"); + let json_v2: GetOrphanTxsVerboseTwo = node2.client.get_orphan_txs_verbosity_2().expect("getorphantxs 2"); + + let model_v0: mtype::GetOrphanTxs = json_v0.into_model(); + let model_v1: mtype::GetOrphanTxsVerboseOne = json_v1.into_model().unwrap(); + let model_v2: mtype::GetOrphanTxsVerboseTwo = json_v2.into_model().unwrap(); + + + assert_eq!(model_v0.0.len(), NUM_ORPHANS as usize); + assert_eq!(model_v1.0.len(), NUM_ORPHANS as usize); + assert_eq!(model_v2.0.len(), NUM_ORPHANS as usize); + + for orphan in orphans.iter() { + assert!(model_v0.0.contains(&orphan.compute_txid())); + + match model_v1.0 + .iter() + .filter(|e| e.txid == orphan.compute_txid()) + .next_back() { + Some(e) => { + assert_eq!(e.wtxid, orphan.compute_wtxid()); + assert_eq!(e.bytes as usize, orphan.total_size()); + assert_eq!(e.vsize as usize, orphan.vsize()); + assert_eq!(e.weight, orphan.weight().to_wu()); + // node2 received all orphans from node1, which is node2's peer=0 + assert_eq!(e.from, vec![0]); + }, + None => { + panic!("Orphan with txid={} not found in `getorphantxs 1` response", orphan.compute_txid()); + } + } + + match model_v2.0 + .iter() + .filter(|e| e.txid == orphan.compute_txid()) + .next_back() { + Some(e) => { + assert_eq!(e.wtxid, orphan.compute_wtxid()); + assert_eq!(e.bytes as usize, orphan.total_size()); + assert_eq!(e.vsize as usize, orphan.vsize()); + assert_eq!(e.weight, orphan.weight().to_wu()); + // node2 received all orphans from node1, which is node2's peer=0 + assert_eq!(e.from, vec![0]); + assert_eq!(e.transaction, *orphan); + }, + None => { + panic!("Orphan with txid={} not found in `getorphantxs 2` response", orphan.compute_txid()); + } + } + } +} diff --git a/types/src/model/hidden.rs b/types/src/model/hidden.rs index 3665d626..405f95c2 100644 --- a/types/src/model/hidden.rs +++ b/types/src/model/hidden.rs @@ -5,7 +5,7 @@ //! These structs model the types returned by the JSON-RPC API but have concrete types //! and are not specific to a specific version of Bitcoin Core. -use bitcoin::FeeRate; +use bitcoin::{FeeRate, Transaction, Txid, Wtxid}; use serde::{Deserialize, Serialize}; /// Models the result of JSON-RPC method `estimaterawfee`. @@ -52,3 +52,63 @@ pub struct RawFeeRange { /// Number of txs over history horizon in the feerate range that left mempool unconfirmed after target. pub left_mempool: f64, } + +/// Models the result of JSON-RPC method `getorphantxs` with verbosity level 0. +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +pub struct GetOrphanTxs(pub Vec); + +/// Models the result of JSON-RPC method `getorphantxs` with verbosity level 1. +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +pub struct GetOrphanTxsVerboseOne(pub Vec); + +/// Models an entry of the result list of JSON-RPC method `getorphantxs` with verbosity level 1. +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +pub struct GetOrphanTxsVerboseOneEntry { + /// The transaction hash in hex + pub txid: Txid, + /// The transaction witness hash in hex + pub wtxid: Wtxid, + /// The serialized transaction size in bytes + pub bytes: u64, + /// The virtual transaction size as defined in BIP 141. This is different from actual serialized size for witness transactions as witness data is discounted. + pub vsize: u64, + /// The transaction weight as defined in BIP 141. + pub weight: u64, + /// The entry time into the orphanage expressed in UNIX epoch time + /// Only present in v29. + pub entry_time: Option, + /// The orphan expiration time expressed in UNIX epoch time + /// Only present in v29. + pub expiration_time: Option, + /// List of peer ids that we store this transaction for. + pub from: Vec, +} + +/// Models the result of JSON-RPC method `getorphantxs` with verbosity level 2. +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +pub struct GetOrphanTxsVerboseTwo(pub Vec); + +/// Models an entry of the result list of JSON-RPC method `getorphantxs` with verbosity level 2. +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +pub struct GetOrphanTxsVerboseTwoEntry { + /// The transaction hash in hex + pub txid: Txid, + /// The transaction witness hash in hex + pub wtxid: Wtxid, + /// The serialized transaction size in bytes + pub bytes: u64, + /// The virtual transaction size as defined in BIP 141. This is different from actual serialized size for witness transactions as witness data is discounted. + pub vsize: u64, + /// The transaction weight as defined in BIP 141. + pub weight: u64, + /// The entry time into the orphanage expressed in UNIX epoch time + /// Only present in v29. + pub entry_time: Option, + /// The orphan expiration time expressed in UNIX epoch time + /// Only present in v29. + pub expiration_time: Option, + /// List of peer ids that we store this transaction for. + pub from: Vec, + /// The orphan transaction. + pub transaction: Transaction, +} diff --git a/types/src/model/mod.rs b/types/src/model/mod.rs index 1b0b326f..70c8ddfd 100644 --- a/types/src/model/mod.rs +++ b/types/src/model/mod.rs @@ -39,7 +39,10 @@ pub use self::{ WaitForBlockHeight, WaitForNewBlock, }, generating::{Generate, GenerateBlock, GenerateToAddress, GenerateToDescriptor}, - hidden::{EstimateRawFee, RawFeeDetail, RawFeeRange}, + hidden::{ + EstimateRawFee, GetOrphanTxs, GetOrphanTxsVerboseOne, GetOrphanTxsVerboseOneEntry, + GetOrphanTxsVerboseTwo, GetOrphanTxsVerboseTwoEntry, RawFeeDetail, RawFeeRange, + }, mining::{ BlockTemplateTransaction, GetBlockTemplate, GetMiningInfo, GetPrioritisedTransactions, NextBlockInfo, PrioritisedTransaction, diff --git a/types/src/v29/hidden/error.rs b/types/src/v29/hidden/error.rs new file mode 100644 index 00000000..93a62dd3 --- /dev/null +++ b/types/src/v29/hidden/error.rs @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: CC0-1.0 + +use core::fmt; + +use bitcoin::consensus::encode; +use bitcoin::hex; + +use crate::error::write_err; + +/// Error when converting a `GetOrphanTxsVerboseOneEntry` type into the model type. +#[derive(Debug)] +pub enum GetOrphanTxsVerboseOneEntryError { + /// Conversion of the transaction `txid` field failed. + Txid(hex::HexToArrayError), + /// Conversion of the transaction `wtxid` field failed. + Wtxid(hex::HexToArrayError), +} + +impl fmt::Display for GetOrphanTxsVerboseOneEntryError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Self::Txid(ref e) => write_err!(f, "conversion of the `txid` field failed"; e), + Self::Wtxid(ref e) => write_err!(f, "conversion of the `wtxid` field failed"; e), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for GetOrphanTxsVerboseOneEntryError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match *self { + Self::Txid(ref e) => Some(e), + Self::Wtxid(ref e) => Some(e), + } + } +} + +/// Error when converting a `GetOrphanTxsVerboseTwoEntry` type into the model type. +#[derive(Debug)] +pub enum GetOrphanTxsVerboseTwoEntryError { + /// Conversion of the transaction `txid` field failed. + Txid(hex::HexToArrayError), + /// Conversion of the transaction `wtxid` field failed. + Wtxid(hex::HexToArrayError), + /// Conversion of hex data to bytes failed. + Hex(hex::HexToBytesError), + /// Consensus decoding of `hex` to transaction failed. + Consensus(encode::Error), +} + +impl fmt::Display for GetOrphanTxsVerboseTwoEntryError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Self::Txid(ref e) => write_err!(f, "conversion of the `txid` field failed"; e), + Self::Wtxid(ref e) => write_err!(f, "conversion of the `wtxid` field failed"; e), + Self::Hex(ref e) => write_err!(f, "conversion of hex data to bytes failed"; e), + Self::Consensus(ref e) => + write_err!(f, "consensus decoding of `hex` to transaction failed"; e), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for GetOrphanTxsVerboseTwoEntryError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match *self { + Self::Txid(ref e) => Some(e), + Self::Wtxid(ref e) => Some(e), + Self::Hex(ref e) => Some(e), + Self::Consensus(ref e) => Some(e), + } + } +} diff --git a/types/src/v29/hidden/into.rs b/types/src/v29/hidden/into.rs new file mode 100644 index 00000000..6f62dcaa --- /dev/null +++ b/types/src/v29/hidden/into.rs @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: CC0-1.0 + +use bitcoin::consensus::encode; +use bitcoin::hashes::hex::FromHex; +use bitcoin::{Transaction, Txid, Wtxid}; + +use super::{ + GetOrphanTxs, GetOrphanTxsVerboseOne, GetOrphanTxsVerboseOneEntry, + GetOrphanTxsVerboseOneEntryError, GetOrphanTxsVerboseTwo, GetOrphanTxsVerboseTwoEntry, + GetOrphanTxsVerboseTwoEntryError, +}; +use crate::model; + +impl GetOrphanTxs { + /// Converts version specific type to a version nonspecific, more strongly typed type. + pub fn into_model(self) -> model::GetOrphanTxs { model::GetOrphanTxs(self.0) } +} + +impl GetOrphanTxsVerboseOneEntry { + /// Converts version specific type to a version nonspecific, more strongly typed type. + pub fn into_model( + self, + ) -> Result { + use GetOrphanTxsVerboseOneEntryError as E; + + let txid = self.txid.parse::().map_err(E::Txid)?; + let wtxid = self.wtxid.parse::().map_err(E::Wtxid)?; + + Ok(model::GetOrphanTxsVerboseOneEntry { + txid, + wtxid, + bytes: self.bytes, + vsize: self.vsize, + weight: self.weight, + from: self.from, + entry_time: Some(self.entry_time), + expiration_time: Some(self.expiration_time), + }) + } +} + +impl GetOrphanTxsVerboseTwoEntry { + /// Converts version specific type to a version nonspecific, more strongly typed type. + pub fn into_model( + self, + ) -> Result { + use GetOrphanTxsVerboseTwoEntryError as E; + + let txid = self.txid.parse::().map_err(E::Txid)?; + let wtxid = self.wtxid.parse::().map_err(E::Wtxid)?; + let v = Vec::from_hex(&self.hex).map_err(E::Hex)?; + let transaction = encode::deserialize::(&v).map_err(E::Consensus)?; + + Ok(model::GetOrphanTxsVerboseTwoEntry { + txid, + wtxid, + bytes: self.bytes, + vsize: self.vsize, + weight: self.weight, + from: self.from, + entry_time: Some(self.entry_time), + expiration_time: Some(self.expiration_time), + transaction, + }) + } +} + +impl GetOrphanTxsVerboseOne { + /// Converts version specific type to a version nonspecific, more strongly typed type. + pub fn into_model( + self, + ) -> Result { + let v = self.0.into_iter().map(|e| e.into_model()).collect::, _>>()?; + + Ok(model::GetOrphanTxsVerboseOne(v)) + } +} + +impl GetOrphanTxsVerboseTwo { + /// Converts version specific type to a version nonspecific, more strongly typed type. + pub fn into_model( + self, + ) -> Result { + let v = self.0.into_iter().map(|e| e.into_model()).collect::, _>>()?; + + Ok(model::GetOrphanTxsVerboseTwo(v)) + } +} diff --git a/types/src/v29/hidden/mod.rs b/types/src/v29/hidden/mod.rs new file mode 100644 index 00000000..366a01b4 --- /dev/null +++ b/types/src/v29/hidden/mod.rs @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: CC0-1.0 + +//! The JSON-RPC API for Bitcoin Core `v29` - hidden. +//! +//! Types for methods that are excluded from the API docs by default. + +mod error; +mod into; + +use bitcoin::Txid; +use serde::{Deserialize, Serialize}; + +pub use self::error::{GetOrphanTxsVerboseOneEntryError, GetOrphanTxsVerboseTwoEntryError}; + +/// Result of JSON-RPC method `getorphantxs` verbosity 0. +/// +/// > getorphantxs ( verbosity ) +/// > +/// > Shows transactions in the tx orphanage. +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +#[cfg_attr(feature = "serde-deny-unknown-fields", serde(deny_unknown_fields))] +pub struct GetOrphanTxs(pub Vec); + +/// Result of JSON-RPC method `getorphantxs` verbosity 1. +/// +/// > getorphantxs ( verbosity ) +/// > +/// > Shows transactions in the tx orphanage. +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +#[cfg_attr(feature = "serde-deny-unknown-fields", serde(deny_unknown_fields))] +pub struct GetOrphanTxsVerboseOne(pub Vec); + +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +#[cfg_attr(feature = "serde-deny-unknown-fields", serde(deny_unknown_fields))] +pub struct GetOrphanTxsVerboseOneEntry { + /// The transaction hash in hex + pub txid: String, + /// The transaction witness hash in hex + pub wtxid: String, + /// The serialized transaction size in bytes + pub bytes: u64, + /// The virtual transaction size as defined in BIP 141. This is different from actual serialized size for witness transactions as witness data is discounted. + pub vsize: u64, + /// The transaction weight as defined in BIP 141. + pub weight: u64, + /// The entry time into the orphanage expressed in UNIX epoch time + /// Only present in v29. + #[serde(rename = "entry")] + pub entry_time: u32, + /// The orphan expiration time expressed in UNIX epoch time + /// Only present in v29. + #[serde(rename = "expiration")] + pub expiration_time: u32, + /// List of peer ids that we store this transaction for. + pub from: Vec, +} + +/// Result of JSON-RPC method `getorphantxs` verbosity 2. +/// +/// > getorphantxs ( verbosity ) +/// > +/// > Shows transactions in the tx orphanage. +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +#[cfg_attr(feature = "serde-deny-unknown-fields", serde(deny_unknown_fields))] +pub struct GetOrphanTxsVerboseTwo(pub Vec); + +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +#[cfg_attr(feature = "serde-deny-unknown-fields", serde(deny_unknown_fields))] +pub struct GetOrphanTxsVerboseTwoEntry { + /// The transaction hash in hex + pub txid: String, + /// The transaction witness hash in hex + pub wtxid: String, + /// The serialized transaction size in bytes + pub bytes: u64, + /// The virtual transaction size as defined in BIP 141. This is different from actual serialized size for witness transactions as witness data is discounted. + pub vsize: u64, + /// The transaction weight as defined in BIP 141. + pub weight: u64, + /// List of peer ids that we store this transaction for. + pub from: Vec, + /// The entry time into the orphanage expressed in UNIX epoch time + /// Only present in v29. + #[serde(rename = "entry")] + pub entry_time: u32, + /// The orphan expiration time expressed in UNIX epoch time + /// Only present in v29. + #[serde(rename = "expiration")] + pub expiration_time: u32, + /// The serialized, hex-encoded transaction data. + pub hex: String, +} diff --git a/types/src/v29/mod.rs b/types/src/v29/mod.rs index 024d01bc..9912fda1 100644 --- a/types/src/v29/mod.rs +++ b/types/src/v29/mod.rs @@ -252,6 +252,7 @@ //! mod blockchain; +mod hidden; mod mining; mod raw_transactions; mod util; @@ -264,6 +265,11 @@ pub use self::{ GetBlockchainInfoError, GetChainStates, GetChainStatesError, GetDescriptorActivity, GetDescriptorActivityError, ReceiveActivity, SpendActivity, }, + hidden::{ + GetOrphanTxs, GetOrphanTxsVerboseOne, GetOrphanTxsVerboseOneEntry, + GetOrphanTxsVerboseOneEntryError, GetOrphanTxsVerboseTwo, GetOrphanTxsVerboseTwoEntry, + GetOrphanTxsVerboseTwoEntryError, + }, mining::{GetMiningInfo, GetMiningInfoError, NextBlockInfo, NextBlockInfoError}, raw_transactions::{MempoolAcceptance, MempoolAcceptanceFees, TestMempoolAccept}, util::{DeriveAddressesMultipath, GetDescriptorInfo}, diff --git a/types/src/v30/hidden/error.rs b/types/src/v30/hidden/error.rs new file mode 100644 index 00000000..93a62dd3 --- /dev/null +++ b/types/src/v30/hidden/error.rs @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: CC0-1.0 + +use core::fmt; + +use bitcoin::consensus::encode; +use bitcoin::hex; + +use crate::error::write_err; + +/// Error when converting a `GetOrphanTxsVerboseOneEntry` type into the model type. +#[derive(Debug)] +pub enum GetOrphanTxsVerboseOneEntryError { + /// Conversion of the transaction `txid` field failed. + Txid(hex::HexToArrayError), + /// Conversion of the transaction `wtxid` field failed. + Wtxid(hex::HexToArrayError), +} + +impl fmt::Display for GetOrphanTxsVerboseOneEntryError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Self::Txid(ref e) => write_err!(f, "conversion of the `txid` field failed"; e), + Self::Wtxid(ref e) => write_err!(f, "conversion of the `wtxid` field failed"; e), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for GetOrphanTxsVerboseOneEntryError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match *self { + Self::Txid(ref e) => Some(e), + Self::Wtxid(ref e) => Some(e), + } + } +} + +/// Error when converting a `GetOrphanTxsVerboseTwoEntry` type into the model type. +#[derive(Debug)] +pub enum GetOrphanTxsVerboseTwoEntryError { + /// Conversion of the transaction `txid` field failed. + Txid(hex::HexToArrayError), + /// Conversion of the transaction `wtxid` field failed. + Wtxid(hex::HexToArrayError), + /// Conversion of hex data to bytes failed. + Hex(hex::HexToBytesError), + /// Consensus decoding of `hex` to transaction failed. + Consensus(encode::Error), +} + +impl fmt::Display for GetOrphanTxsVerboseTwoEntryError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Self::Txid(ref e) => write_err!(f, "conversion of the `txid` field failed"; e), + Self::Wtxid(ref e) => write_err!(f, "conversion of the `wtxid` field failed"; e), + Self::Hex(ref e) => write_err!(f, "conversion of hex data to bytes failed"; e), + Self::Consensus(ref e) => + write_err!(f, "consensus decoding of `hex` to transaction failed"; e), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for GetOrphanTxsVerboseTwoEntryError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match *self { + Self::Txid(ref e) => Some(e), + Self::Wtxid(ref e) => Some(e), + Self::Hex(ref e) => Some(e), + Self::Consensus(ref e) => Some(e), + } + } +} diff --git a/types/src/v30/hidden/into.rs b/types/src/v30/hidden/into.rs new file mode 100644 index 00000000..e4ab66bc --- /dev/null +++ b/types/src/v30/hidden/into.rs @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: CC0-1.0 + +use bitcoin::consensus::encode; +use bitcoin::hashes::hex::FromHex; +use bitcoin::{Transaction, Txid, Wtxid}; + +use super::{ + GetOrphanTxs, GetOrphanTxsVerboseOne, GetOrphanTxsVerboseOneEntry, + GetOrphanTxsVerboseOneEntryError, GetOrphanTxsVerboseTwo, GetOrphanTxsVerboseTwoEntry, + GetOrphanTxsVerboseTwoEntryError, +}; +use crate::model; + +impl GetOrphanTxs { + /// Converts version specific type to a version nonspecific, more strongly typed type. + pub fn into_model(self) -> model::GetOrphanTxs { model::GetOrphanTxs(self.0) } +} + +impl GetOrphanTxsVerboseOneEntry { + /// Converts version specific type to a version nonspecific, more strongly typed type. + pub fn into_model( + self, + ) -> Result { + use GetOrphanTxsVerboseOneEntryError as E; + + let txid = self.txid.parse::().map_err(E::Txid)?; + let wtxid = self.wtxid.parse::().map_err(E::Wtxid)?; + + Ok(model::GetOrphanTxsVerboseOneEntry { + txid, + wtxid, + bytes: self.bytes, + vsize: self.vsize, + weight: self.weight, + from: self.from, + entry_time: None, + expiration_time: None, + }) + } +} + +impl GetOrphanTxsVerboseTwoEntry { + /// Converts version specific type to a version nonspecific, more strongly typed type. + pub fn into_model( + self, + ) -> Result { + use GetOrphanTxsVerboseTwoEntryError as E; + + let txid = self.txid.parse::().map_err(E::Txid)?; + let wtxid = self.wtxid.parse::().map_err(E::Wtxid)?; + let v = Vec::from_hex(&self.hex).map_err(E::Hex)?; + let transaction = encode::deserialize::(&v).map_err(E::Consensus)?; + + Ok(model::GetOrphanTxsVerboseTwoEntry { + txid, + wtxid, + bytes: self.bytes, + vsize: self.vsize, + weight: self.weight, + from: self.from, + entry_time: None, + expiration_time: None, + transaction, + }) + } +} + +impl GetOrphanTxsVerboseOne { + /// Converts version specific type to a version nonspecific, more strongly typed type. + pub fn into_model( + self, + ) -> Result { + let v = self.0.into_iter().map(|e| e.into_model()).collect::, _>>()?; + + Ok(model::GetOrphanTxsVerboseOne(v)) + } +} + +impl GetOrphanTxsVerboseTwo { + /// Converts version specific type to a version nonspecific, more strongly typed type. + pub fn into_model( + self, + ) -> Result { + let v = self.0.into_iter().map(|e| e.into_model()).collect::, _>>()?; + + Ok(model::GetOrphanTxsVerboseTwo(v)) + } +} diff --git a/types/src/v30/hidden/mod.rs b/types/src/v30/hidden/mod.rs new file mode 100644 index 00000000..72f88620 --- /dev/null +++ b/types/src/v30/hidden/mod.rs @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: CC0-1.0 + +//! The JSON-RPC API for Bitcoin Core `v29` - hidden. +//! +//! Types for methods that are excluded from the API docs by default. + +mod error; +mod into; + +use bitcoin::Txid; +use serde::{Deserialize, Serialize}; + +pub use self::error::{GetOrphanTxsVerboseOneEntryError, GetOrphanTxsVerboseTwoEntryError}; + +/// Result of JSON-RPC method `getorphantxs` verbosity 0. +/// +/// > getorphantxs ( verbosity ) +/// > +/// > Shows transactions in the tx orphanage. +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +#[cfg_attr(feature = "serde-deny-unknown-fields", serde(deny_unknown_fields))] +pub struct GetOrphanTxs(pub Vec); + +/// Result of JSON-RPC method `getorphantxs` verbosity 1. +/// +/// > getorphantxs ( verbosity ) +/// > +/// > Shows transactions in the tx orphanage. +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +#[cfg_attr(feature = "serde-deny-unknown-fields", serde(deny_unknown_fields))] +pub struct GetOrphanTxsVerboseOne(pub Vec); + +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +#[cfg_attr(feature = "serde-deny-unknown-fields", serde(deny_unknown_fields))] +pub struct GetOrphanTxsVerboseOneEntry { + /// The transaction hash in hex + pub txid: String, + /// The transaction witness hash in hex + pub wtxid: String, + /// The serialized transaction size in bytes + pub bytes: u64, + /// The virtual transaction size as defined in BIP 141. This is different from actual serialized size for witness transactions as witness data is discounted. + pub vsize: u64, + /// The transaction weight as defined in BIP 141. + pub weight: u64, + /// The entry time into the orphanage expressed in UNIX epoch time + /// Only present in v29. + #[serde(rename = "entry")] + pub entry_time: Option, + /// The orphan expiration time expressed in UNIX epoch time + /// Only present in v29. + #[serde(rename = "expiration")] + pub expiration_time: Option, + /// List of peer ids that we store this transaction for. + pub from: Vec, +} + +/// Result of JSON-RPC method `getorphantxs` verbosity 2. +/// +/// > getorphantxs ( verbosity ) +/// > +/// > Shows transactions in the tx orphanage. +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +#[cfg_attr(feature = "serde-deny-unknown-fields", serde(deny_unknown_fields))] +pub struct GetOrphanTxsVerboseTwo(pub Vec); + +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +#[cfg_attr(feature = "serde-deny-unknown-fields", serde(deny_unknown_fields))] +pub struct GetOrphanTxsVerboseTwoEntry { + /// The transaction hash in hex + pub txid: String, + /// The transaction witness hash in hex + pub wtxid: String, + /// The serialized transaction size in bytes + pub bytes: u64, + /// The virtual transaction size as defined in BIP 141. This is different from actual serialized size for witness transactions as witness data is discounted. + pub vsize: u64, + /// The transaction weight as defined in BIP 141. + pub weight: u64, + /// List of peer ids that we store this transaction for. + pub from: Vec, + /// The entry time into the orphanage expressed in UNIX epoch time + /// Only present in v29. + pub entry_time: Option, + /// The orphan expiration time expressed in UNIX epoch time + /// Only present in v29. + pub expiration_time: Option, + /// The serialized, hex-encoded transaction data. + pub hex: String, +} diff --git a/types/src/v30/mod.rs b/types/src/v30/mod.rs index 786e5085..70103202 100644 --- a/types/src/v30/mod.rs +++ b/types/src/v30/mod.rs @@ -243,6 +243,7 @@ //! mod blockchain; +mod hidden; mod mining; mod raw_transactions; mod wallet; @@ -250,6 +251,11 @@ mod wallet; #[doc(inline)] pub use self::{ blockchain::GetMempoolInfo, + hidden::{ + GetOrphanTxs, GetOrphanTxsVerboseOne, GetOrphanTxsVerboseOneEntry, + GetOrphanTxsVerboseOneEntryError, GetOrphanTxsVerboseTwo, GetOrphanTxsVerboseTwoEntry, + GetOrphanTxsVerboseTwoEntryError, + }, mining::{GetMiningInfo, GetMiningInfoError}, raw_transactions::{ ControlBlocksError, DecodePsbt, DecodePsbtError, GlobalXpub, GlobalXpubError,