Skip to content

Commit 04006e5

Browse files
committed
implement hidden RPC getorphantxs
1 parent b453c71 commit 04006e5

File tree

14 files changed

+732
-2
lines changed

14 files changed

+732
-2
lines changed
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// SPDX-License-Identifier: CC0-1.0
2+
3+
//! Macros for implementing JSON-RPC methods on a client.
4+
//!
5+
//! Specifically this is `== Hidden ==` methods that are not listed in the
6+
//! API docs of Bitcoin Core `v29`.
7+
//!
8+
//! All macros require `Client` to be in scope.
9+
//!
10+
//! See, or use the `define_jsonrpc_bitreq_client!` macro to define a `Client`.
11+
12+
/// Implements Bitcoin Core JSON-RPC API method `getorphantxs` verbosity level 0.
13+
#[macro_export]
14+
macro_rules! impl_client_v29__get_orphan_txs {
15+
() => {
16+
impl Client {
17+
pub fn get_orphan_txs(&self) -> Result<GetOrphanTxs> { self.call("getorphantxs", &[]) }
18+
}
19+
};
20+
}
21+
22+
/// Implements Bitcoin Core JSON-RPC API method `getorphantxs` verbosity level 1.
23+
#[macro_export]
24+
macro_rules! impl_client_v29__get_orphan_txs_verbosity_1 {
25+
() => {
26+
impl Client {
27+
pub fn get_orphan_txs_verbosity_1(&self) -> Result<GetOrphanTxsVerboseOne> {
28+
self.call("getorphantxs", &[into_json(1)?])
29+
}
30+
}
31+
};
32+
}
33+
34+
/// Implements Bitcoin Core JSON-RPC API method `getorphantxs` verbosity level 2.
35+
#[macro_export]
36+
macro_rules! impl_client_v29__get_orphan_txs_verbosity_2 {
37+
() => {
38+
impl Client {
39+
pub fn get_orphan_txs_verbosity_2(&self) -> Result<GetOrphanTxsVerboseTwo> {
40+
self.call("getorphantxs", &[into_json(2)?])
41+
}
42+
}
43+
};
44+
}

client/src/client_sync/v29/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
//! We ignore option arguments unless they effect the shape of the returned JSON data.
66
77
pub mod blockchain;
8+
pub mod hidden;
89
pub mod util;
910

1011
use std::collections::BTreeMap;
@@ -80,6 +81,9 @@ crate::impl_client_v17__invalidate_block!();
8081
crate::impl_client_v27__add_connection!();
8182
crate::impl_client_v21__add_peer_address!();
8283
crate::impl_client_v17__estimate_raw_fee!();
84+
crate::impl_client_v29__get_orphan_txs!();
85+
crate::impl_client_v29__get_orphan_txs_verbosity_1!();
86+
crate::impl_client_v29__get_orphan_txs_verbosity_2!();
8387
crate::impl_client_v17__wait_for_block!();
8488
crate::impl_client_v17__wait_for_block_height!();
8589
crate::impl_client_v17__wait_for_new_block!();

client/src/client_sync/v30/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@ crate::impl_client_v17__invalidate_block!();
8080
// == Hidden ==
8181
crate::impl_client_v27__add_connection!();
8282
crate::impl_client_v21__add_peer_address!();
83+
crate::impl_client_v29__get_orphan_txs_verbosity_2!();
84+
crate::impl_client_v29__get_orphan_txs_verbosity_1!();
85+
crate::impl_client_v29__get_orphan_txs!();
8386

8487
// == Mining ==
8588
crate::impl_client_v17__get_block_template!();

integration_test/tests/hidden.rs

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,20 @@
44
55
#![allow(non_snake_case)] // Test names intentionally use double underscore.
66

7+
#[cfg(not(feature = "v28_and_below"))]
8+
use std::collections::HashMap;
9+
10+
#[cfg(not(feature = "v28_and_below"))]
11+
use bitcoin::{
12+
absolute, transaction, consensus, Amount, OutPoint, ScriptBuf, Sequence, Transaction,
13+
TxIn, TxOut, Txid, Witness,
14+
};
15+
16+
#[cfg(not(feature = "v28_and_below"))]
17+
use bitcoin::hex::DisplayHex;
18+
#[cfg(not(feature = "v28_and_below"))]
19+
use bitcoin::hashes::Hash;
20+
721
use integration_test::{Node, NodeExt as _, Wallet};
822
use node::mtype;
923
use node::vtype::*; // All the version specific types.
@@ -45,6 +59,7 @@ fn hidden__add_connection__modelled() {
4559
#[test]
4660
fn hidden__estimate_raw_fee__modelled() {
4761
let node = Node::with_wallet(Wallet::Default, &[]);
62+
4863
node.fund_wallet();
4964

5065
// Give the fee estimator some confirmation history.
@@ -62,3 +77,101 @@ fn hidden__estimate_raw_fee__modelled() {
6277

6378
assert!(estimate.long.scale > 0);
6479
}
80+
81+
#[test]
82+
#[cfg(not(feature = "v28_and_below"))]
83+
fn hidden__get_orphan_txs__modelled() {
84+
// We use node1 to send node2 orphan transactions via a P2P `tx` message.
85+
let (node1, node2, _node3) = integration_test::three_node_network();
86+
87+
// Generate a few orphan transactions by spending from non-existing UTXOs.
88+
const NUM_ORPHANS: u8 = 3;
89+
let address = node1.client.new_address().expect("failed to get new address");
90+
let orphans: Vec<Transaction> = (0..NUM_ORPHANS).map(|i| {
91+
Transaction {
92+
version: transaction::Version::ONE,
93+
lock_time: absolute::LockTime::ZERO,
94+
input: vec![TxIn {
95+
previous_output: OutPoint {
96+
txid: Txid::from_raw_hash(Txid::from_byte_array([i; 32]).into()),
97+
vout: 0,
98+
},
99+
script_sig: ScriptBuf::new(),
100+
sequence: Sequence::MAX,
101+
witness: Witness::new(),
102+
}],
103+
output: vec![TxOut {
104+
value: Amount::from_sat(100_000),
105+
script_pubkey: address.script_pubkey(),
106+
}],
107+
}
108+
}).collect();
109+
110+
// The receiving node needs to be out of IBD to start accepting transactions.
111+
node2.mine_a_block();
112+
113+
// node2 is peer=0 of node1
114+
const PEER_ID: u64 = 0;
115+
for orphan in orphans.iter() {
116+
let tx_bytes = consensus::encode::serialize(orphan);
117+
let tx_hex: String = tx_bytes.as_hex().to_string();
118+
// HACK: We should use sendmsgtopeer directly but it's not implemented yet.
119+
node1.client
120+
.call::<HashMap<String, String>>(
121+
"sendmsgtopeer",
122+
&[PEER_ID.into(), "tx".into(), tx_hex.into()],
123+
).unwrap();
124+
}
125+
126+
let json_v0: GetOrphanTxs = node2.client.get_orphan_txs().expect("getorphantxs");
127+
let json_v1: GetOrphanTxsVerboseOne = node2.client.get_orphan_txs_verbosity_1().expect("getorphantxs 1");
128+
let json_v2: GetOrphanTxsVerboseTwo = node2.client.get_orphan_txs_verbosity_2().expect("getorphantxs 2");
129+
130+
let model_v0: mtype::GetOrphanTxs = json_v0.into_model();
131+
let model_v1: mtype::GetOrphanTxsVerboseOne = json_v1.into_model().unwrap();
132+
let model_v2: mtype::GetOrphanTxsVerboseTwo = json_v2.into_model().unwrap();
133+
134+
135+
assert_eq!(model_v0.0.len(), NUM_ORPHANS as usize);
136+
assert_eq!(model_v1.0.len(), NUM_ORPHANS as usize);
137+
assert_eq!(model_v2.0.len(), NUM_ORPHANS as usize);
138+
139+
for orphan in orphans.iter() {
140+
assert!(model_v0.0.contains(&orphan.compute_txid()));
141+
142+
match model_v1.0
143+
.iter()
144+
.filter(|e| e.txid == orphan.compute_txid())
145+
.next_back() {
146+
Some(e) => {
147+
assert_eq!(e.wtxid, orphan.compute_wtxid());
148+
assert_eq!(e.bytes as usize, orphan.total_size());
149+
assert_eq!(e.vsize as usize, orphan.vsize());
150+
assert_eq!(e.weight, orphan.weight().to_wu());
151+
// node2 received all orphans from node1, which is node2's peer=0
152+
assert_eq!(e.from, vec![0]);
153+
},
154+
None => {
155+
panic!("Orphan with txid={} not found in `getorphantxs 1` response", orphan.compute_txid());
156+
}
157+
}
158+
159+
match model_v2.0
160+
.iter()
161+
.filter(|e| e.txid == orphan.compute_txid())
162+
.next_back() {
163+
Some(e) => {
164+
assert_eq!(e.wtxid, orphan.compute_wtxid());
165+
assert_eq!(e.bytes as usize, orphan.total_size());
166+
assert_eq!(e.vsize as usize, orphan.vsize());
167+
assert_eq!(e.weight, orphan.weight().to_wu());
168+
// node2 received all orphans from node1, which is node2's peer=0
169+
assert_eq!(e.from, vec![0]);
170+
assert_eq!(e.transaction, *orphan);
171+
},
172+
None => {
173+
panic!("Orphan with txid={} not found in `getorphantxs 2` response", orphan.compute_txid());
174+
}
175+
}
176+
}
177+
}

types/src/model/hidden.rs

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
//! These structs model the types returned by the JSON-RPC API but have concrete types
66
//! and are not specific to a specific version of Bitcoin Core.
77
8-
use bitcoin::FeeRate;
8+
use bitcoin::{FeeRate, Transaction, Txid, Wtxid};
99
use serde::{Deserialize, Serialize};
1010

1111
/// Models the result of JSON-RPC method `addconnection`.
@@ -61,3 +61,63 @@ pub struct RawFeeRange {
6161
/// Number of txs over history horizon in the feerate range that left mempool unconfirmed after target.
6262
pub left_mempool: f64,
6363
}
64+
65+
/// Models the result of JSON-RPC method `getorphantxs` with verbosity level 0.
66+
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
67+
pub struct GetOrphanTxs(pub Vec<Txid>);
68+
69+
/// Models the result of JSON-RPC method `getorphantxs` with verbosity level 1.
70+
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
71+
pub struct GetOrphanTxsVerboseOne(pub Vec<GetOrphanTxsVerboseOneEntry>);
72+
73+
/// Models an entry of the result list of JSON-RPC method `getorphantxs` with verbosity level 1.
74+
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
75+
pub struct GetOrphanTxsVerboseOneEntry {
76+
/// The transaction hash in hex
77+
pub txid: Txid,
78+
/// The transaction witness hash in hex
79+
pub wtxid: Wtxid,
80+
/// The serialized transaction size in bytes
81+
pub bytes: u64,
82+
/// The virtual transaction size as defined in BIP 141. This is different from actual serialized size for witness transactions as witness data is discounted.
83+
pub vsize: u64,
84+
/// The transaction weight as defined in BIP 141.
85+
pub weight: u64,
86+
/// The entry time into the orphanage expressed in UNIX epoch time
87+
/// Only present in v29.
88+
pub entry_time: Option<u32>,
89+
/// The orphan expiration time expressed in UNIX epoch time
90+
/// Only present in v29.
91+
pub expiration_time: Option<u32>,
92+
/// List of peer ids that we store this transaction for.
93+
pub from: Vec<u64>,
94+
}
95+
96+
/// Models the result of JSON-RPC method `getorphantxs` with verbosity level 2.
97+
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
98+
pub struct GetOrphanTxsVerboseTwo(pub Vec<GetOrphanTxsVerboseTwoEntry>);
99+
100+
/// Models an entry of the result list of JSON-RPC method `getorphantxs` with verbosity level 2.
101+
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
102+
pub struct GetOrphanTxsVerboseTwoEntry {
103+
/// The transaction hash in hex
104+
pub txid: Txid,
105+
/// The transaction witness hash in hex
106+
pub wtxid: Wtxid,
107+
/// The serialized transaction size in bytes
108+
pub bytes: u64,
109+
/// The virtual transaction size as defined in BIP 141. This is different from actual serialized size for witness transactions as witness data is discounted.
110+
pub vsize: u64,
111+
/// The transaction weight as defined in BIP 141.
112+
pub weight: u64,
113+
/// The entry time into the orphanage expressed in UNIX epoch time
114+
/// Only present in v29.
115+
pub entry_time: Option<u32>,
116+
/// The orphan expiration time expressed in UNIX epoch time
117+
/// Only present in v29.
118+
pub expiration_time: Option<u32>,
119+
/// List of peer ids that we store this transaction for.
120+
pub from: Vec<u64>,
121+
/// The orphan transaction.
122+
pub transaction: Transaction,
123+
}

types/src/model/mod.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,11 @@ pub use self::{
3939
WaitForBlockHeight, WaitForNewBlock,
4040
},
4141
generating::{Generate, GenerateBlock, GenerateToAddress, GenerateToDescriptor},
42-
hidden::{AddConnection, EstimateRawFee, RawFeeDetail, RawFeeRange},
42+
hidden::{
43+
AddConnection, EstimateRawFee, GetOrphanTxs, GetOrphanTxsVerboseOne,
44+
GetOrphanTxsVerboseOneEntry, GetOrphanTxsVerboseTwo, GetOrphanTxsVerboseTwoEntry,
45+
RawFeeDetail, RawFeeRange,
46+
},
4347
mining::{
4448
BlockTemplateTransaction, GetBlockTemplate, GetMiningInfo, GetPrioritisedTransactions,
4549
NextBlockInfo, PrioritisedTransaction,

types/src/v29/hidden/error.rs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// SPDX-License-Identifier: CC0-1.0
2+
3+
use core::fmt;
4+
5+
use bitcoin::consensus::encode;
6+
use bitcoin::hex;
7+
8+
use crate::error::write_err;
9+
10+
/// Error when converting a `GetOrphanTxsVerboseOneEntry` type into the model type.
11+
#[derive(Debug)]
12+
pub enum GetOrphanTxsVerboseOneEntryError {
13+
/// Conversion of the transaction `txid` field failed.
14+
Txid(hex::HexToArrayError),
15+
/// Conversion of the transaction `wtxid` field failed.
16+
Wtxid(hex::HexToArrayError),
17+
}
18+
19+
impl fmt::Display for GetOrphanTxsVerboseOneEntryError {
20+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
21+
match *self {
22+
Self::Txid(ref e) => write_err!(f, "conversion of the `txid` field failed"; e),
23+
Self::Wtxid(ref e) => write_err!(f, "conversion of the `wtxid` field failed"; e),
24+
}
25+
}
26+
}
27+
28+
#[cfg(feature = "std")]
29+
impl std::error::Error for GetOrphanTxsVerboseOneEntryError {
30+
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
31+
match *self {
32+
Self::Txid(ref e) => Some(e),
33+
Self::Wtxid(ref e) => Some(e),
34+
}
35+
}
36+
}
37+
38+
/// Error when converting a `GetOrphanTxsVerboseTwoEntry` type into the model type.
39+
#[derive(Debug)]
40+
pub enum GetOrphanTxsVerboseTwoEntryError {
41+
/// Conversion of the transaction `txid` field failed.
42+
Txid(hex::HexToArrayError),
43+
/// Conversion of the transaction `wtxid` field failed.
44+
Wtxid(hex::HexToArrayError),
45+
/// Conversion of hex data to bytes failed.
46+
Hex(hex::HexToBytesError),
47+
/// Consensus decoding of `hex` to transaction failed.
48+
Consensus(encode::Error),
49+
}
50+
51+
impl fmt::Display for GetOrphanTxsVerboseTwoEntryError {
52+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
53+
match *self {
54+
Self::Txid(ref e) => write_err!(f, "conversion of the `txid` field failed"; e),
55+
Self::Wtxid(ref e) => write_err!(f, "conversion of the `wtxid` field failed"; e),
56+
Self::Hex(ref e) => write_err!(f, "conversion of hex data to bytes failed"; e),
57+
Self::Consensus(ref e) =>
58+
write_err!(f, "consensus decoding of `hex` to transaction failed"; e),
59+
}
60+
}
61+
}
62+
63+
#[cfg(feature = "std")]
64+
impl std::error::Error for GetOrphanTxsVerboseTwoEntryError {
65+
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
66+
match *self {
67+
Self::Txid(ref e) => Some(e),
68+
Self::Wtxid(ref e) => Some(e),
69+
Self::Hex(ref e) => Some(e),
70+
Self::Consensus(ref e) => Some(e),
71+
}
72+
}
73+
}

0 commit comments

Comments
 (0)