Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions client/src/client_sync/v29/hidden.rs
Original file line number Diff line number Diff line change
@@ -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<GetOrphanTxs> { 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<GetOrphanTxsVerboseOne> {
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<GetOrphanTxsVerboseTwo> {
self.call("getorphantxs", &[into_json(2)?])
}
}
};
}
4 changes: 4 additions & 0 deletions client/src/client_sync/v29/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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!();
Expand Down
3 changes: 3 additions & 0 deletions client/src/client_sync/v30/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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!();

Expand Down
113 changes: 113 additions & 0 deletions integration_test/tests/hidden.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand All @@ -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<Transaction> = (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::<HashMap<String, String>>(
"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());
}
}
}
}
62 changes: 61 additions & 1 deletion types/src/model/hidden.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down Expand Up @@ -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<Txid>);

/// Models the result of JSON-RPC method `getorphantxs` with verbosity level 1.
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub struct GetOrphanTxsVerboseOne(pub Vec<GetOrphanTxsVerboseOneEntry>);

/// 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<u32>,
/// The orphan expiration time expressed in UNIX epoch time
/// Only present in v29.
pub expiration_time: Option<u32>,
/// List of peer ids that we store this transaction for.
pub from: Vec<u64>,
}

/// Models the result of JSON-RPC method `getorphantxs` with verbosity level 2.
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub struct GetOrphanTxsVerboseTwo(pub Vec<GetOrphanTxsVerboseTwoEntry>);

/// 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<u32>,
/// The orphan expiration time expressed in UNIX epoch time
/// Only present in v29.
pub expiration_time: Option<u32>,
/// List of peer ids that we store this transaction for.
pub from: Vec<u64>,
/// The orphan transaction.
pub transaction: Transaction,
}
5 changes: 4 additions & 1 deletion types/src/model/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
73 changes: 73 additions & 0 deletions types/src/v29/hidden/error.rs
Original file line number Diff line number Diff line change
@@ -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),
}
}
}
Loading