Skip to content

Commit 0be5338

Browse files
committed
chore: extract TestEnv into separate crate
`TestEnv` is extracted into its own crate to serve as a framework for testing other block explorer APIs.
1 parent 2e4bc3c commit 0be5338

File tree

5 files changed

+174
-149
lines changed

5 files changed

+174
-149
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ members = [
77
"crates/electrum",
88
"crates/esplora",
99
"crates/bitcoind_rpc",
10+
"crates/testenv",
1011
"example-crates/example_cli",
1112
"example-crates/example_electrum",
1213
"example-crates/example_esplora",

crates/bitcoind_rpc/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ bitcoincore-rpc = { version = "0.17" }
1212
bdk_chain = { path = "../chain", version = "0.6", default-features = false }
1313

1414
[dev-dependencies]
15+
testenv = { path = "../testenv", version = "0.1.0", default_features = false }
1516
bitcoind = { version = "0.33", features = ["25_0"] }
1617
anyhow = { version = "1" }
1718

crates/bitcoind_rpc/tests/test_emitter.rs

Lines changed: 3 additions & 149 deletions
Original file line numberDiff line numberDiff line change
@@ -7,155 +7,9 @@ use bdk_chain::{
77
local_chain::{self, CheckPoint, LocalChain},
88
Append, BlockId, IndexedTxGraph, SpkTxOutIndex,
99
};
10-
use bitcoin::{
11-
address::NetworkChecked, block::Header, hash_types::TxMerkleNode, hashes::Hash,
12-
secp256k1::rand::random, Block, CompactTarget, OutPoint, ScriptBuf, ScriptHash, Transaction,
13-
TxIn, TxOut, WScriptHash,
14-
};
15-
use bitcoincore_rpc::{
16-
bitcoincore_rpc_json::{GetBlockTemplateModes, GetBlockTemplateRules},
17-
RpcApi,
18-
};
19-
20-
struct TestEnv {
21-
#[allow(dead_code)]
22-
daemon: bitcoind::BitcoinD,
23-
client: bitcoincore_rpc::Client,
24-
}
25-
26-
impl TestEnv {
27-
fn new() -> anyhow::Result<Self> {
28-
let daemon = match std::env::var_os("TEST_BITCOIND") {
29-
Some(bitcoind_path) => bitcoind::BitcoinD::new(bitcoind_path),
30-
None => bitcoind::BitcoinD::from_downloaded(),
31-
}?;
32-
let client = bitcoincore_rpc::Client::new(
33-
&daemon.rpc_url(),
34-
bitcoincore_rpc::Auth::CookieFile(daemon.params.cookie_file.clone()),
35-
)?;
36-
Ok(Self { daemon, client })
37-
}
38-
39-
fn mine_blocks(
40-
&self,
41-
count: usize,
42-
address: Option<Address>,
43-
) -> anyhow::Result<Vec<BlockHash>> {
44-
let coinbase_address = match address {
45-
Some(address) => address,
46-
None => self.client.get_new_address(None, None)?.assume_checked(),
47-
};
48-
let block_hashes = self
49-
.client
50-
.generate_to_address(count as _, &coinbase_address)?;
51-
Ok(block_hashes)
52-
}
53-
54-
fn mine_empty_block(&self) -> anyhow::Result<(usize, BlockHash)> {
55-
let bt = self.client.get_block_template(
56-
GetBlockTemplateModes::Template,
57-
&[GetBlockTemplateRules::SegWit],
58-
&[],
59-
)?;
60-
61-
let txdata = vec![Transaction {
62-
version: 1,
63-
lock_time: bitcoin::absolute::LockTime::from_height(0)?,
64-
input: vec![TxIn {
65-
previous_output: bitcoin::OutPoint::default(),
66-
script_sig: ScriptBuf::builder()
67-
.push_int(bt.height as _)
68-
// randomn number so that re-mining creates unique block
69-
.push_int(random())
70-
.into_script(),
71-
sequence: bitcoin::Sequence::default(),
72-
witness: bitcoin::Witness::new(),
73-
}],
74-
output: vec![TxOut {
75-
value: 0,
76-
script_pubkey: ScriptBuf::new_p2sh(&ScriptHash::all_zeros()),
77-
}],
78-
}];
79-
80-
let bits: [u8; 4] = bt
81-
.bits
82-
.clone()
83-
.try_into()
84-
.expect("rpc provided us with invalid bits");
85-
86-
let mut block = Block {
87-
header: Header {
88-
version: bitcoin::block::Version::default(),
89-
prev_blockhash: bt.previous_block_hash,
90-
merkle_root: TxMerkleNode::all_zeros(),
91-
time: Ord::max(bt.min_time, std::time::UNIX_EPOCH.elapsed()?.as_secs()) as u32,
92-
bits: CompactTarget::from_consensus(u32::from_be_bytes(bits)),
93-
nonce: 0,
94-
},
95-
txdata,
96-
};
97-
98-
block.header.merkle_root = block.compute_merkle_root().expect("must compute");
99-
100-
for nonce in 0..=u32::MAX {
101-
block.header.nonce = nonce;
102-
if block.header.target().is_met_by(block.block_hash()) {
103-
break;
104-
}
105-
}
106-
107-
self.client.submit_block(&block)?;
108-
Ok((bt.height as usize, block.block_hash()))
109-
}
110-
111-
fn invalidate_blocks(&self, count: usize) -> anyhow::Result<()> {
112-
let mut hash = self.client.get_best_block_hash()?;
113-
for _ in 0..count {
114-
let prev_hash = self.client.get_block_info(&hash)?.previousblockhash;
115-
self.client.invalidate_block(&hash)?;
116-
match prev_hash {
117-
Some(prev_hash) => hash = prev_hash,
118-
None => break,
119-
}
120-
}
121-
Ok(())
122-
}
123-
124-
fn reorg(&self, count: usize) -> anyhow::Result<Vec<BlockHash>> {
125-
let start_height = self.client.get_block_count()?;
126-
self.invalidate_blocks(count)?;
127-
128-
let res = self.mine_blocks(count, None);
129-
assert_eq!(
130-
self.client.get_block_count()?,
131-
start_height,
132-
"reorg should not result in height change"
133-
);
134-
res
135-
}
136-
137-
fn reorg_empty_blocks(&self, count: usize) -> anyhow::Result<Vec<(usize, BlockHash)>> {
138-
let start_height = self.client.get_block_count()?;
139-
self.invalidate_blocks(count)?;
140-
141-
let res = (0..count)
142-
.map(|_| self.mine_empty_block())
143-
.collect::<Result<Vec<_>, _>>()?;
144-
assert_eq!(
145-
self.client.get_block_count()?,
146-
start_height,
147-
"reorg should not result in height change"
148-
);
149-
Ok(res)
150-
}
151-
152-
fn send(&self, address: &Address<NetworkChecked>, amount: Amount) -> anyhow::Result<Txid> {
153-
let txid = self
154-
.client
155-
.send_to_address(address, amount, None, None, None, None, None, None)?;
156-
Ok(txid)
157-
}
158-
}
10+
use bitcoin::{hashes::Hash, Block, OutPoint, ScriptBuf, WScriptHash};
11+
use bitcoincore_rpc::RpcApi;
12+
use testenv::TestEnv;
15913

16014
fn block_to_chain_update(block: &bitcoin::Block, height: u32) -> local_chain::Update {
16115
let this_id = BlockId {

crates/testenv/Cargo.toml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
[package]
2+
name = "testenv"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7+
8+
[dependencies]
9+
# For no-std, remember to enable the bitcoin/no-std feature
10+
bitcoin = { version = "0.30", default-features = false }
11+
bitcoincore-rpc = { version = "0.17" }
12+
bdk_chain = { path = "../chain", version = "0.6", default-features = false }
13+
bdk_bitcoind_rpc = { path = "../bitcoind_rpc", version = "0.1.0", default-features = false }
14+
bitcoind = { version = "0.33", features = ["25_0"] }
15+
anyhow = { version = "1" }
16+
17+
[features]
18+
default = ["std"]
19+
std = ["bitcoin/std", "bdk_chain/std"]
20+
serde = ["bitcoin/serde", "bdk_chain/serde"]

crates/testenv/src/lib.rs

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
use bdk_chain::bitcoin::{Address, Amount, BlockHash, Txid};
2+
use bitcoin::{
3+
address::NetworkChecked, block::Header, hash_types::TxMerkleNode, hashes::Hash,
4+
secp256k1::rand::random, Block, CompactTarget, ScriptBuf, ScriptHash, Transaction, TxIn, TxOut,
5+
};
6+
use bitcoincore_rpc::{
7+
bitcoincore_rpc_json::{GetBlockTemplateModes, GetBlockTemplateRules},
8+
RpcApi,
9+
};
10+
11+
pub struct TestEnv {
12+
#[allow(dead_code)]
13+
pub daemon: bitcoind::BitcoinD,
14+
pub client: bitcoincore_rpc::Client,
15+
}
16+
17+
impl TestEnv {
18+
pub fn new() -> anyhow::Result<Self> {
19+
let daemon = match std::env::var_os("TEST_BITCOIND") {
20+
Some(bitcoind_path) => bitcoind::BitcoinD::new(bitcoind_path),
21+
None => bitcoind::BitcoinD::from_downloaded(),
22+
}?;
23+
let client = bitcoincore_rpc::Client::new(
24+
&daemon.rpc_url(),
25+
bitcoincore_rpc::Auth::CookieFile(daemon.params.cookie_file.clone()),
26+
)?;
27+
Ok(Self { daemon, client })
28+
}
29+
30+
pub fn mine_blocks(
31+
&self,
32+
count: usize,
33+
address: Option<Address>,
34+
) -> anyhow::Result<Vec<BlockHash>> {
35+
let coinbase_address = match address {
36+
Some(address) => address,
37+
None => self.client.get_new_address(None, None)?.assume_checked(),
38+
};
39+
let block_hashes = self
40+
.client
41+
.generate_to_address(count as _, &coinbase_address)?;
42+
Ok(block_hashes)
43+
}
44+
45+
pub fn mine_empty_block(&self) -> anyhow::Result<(usize, BlockHash)> {
46+
let bt = self.client.get_block_template(
47+
GetBlockTemplateModes::Template,
48+
&[GetBlockTemplateRules::SegWit],
49+
&[],
50+
)?;
51+
52+
let txdata = vec![Transaction {
53+
version: 1,
54+
lock_time: bitcoin::absolute::LockTime::from_height(0)?,
55+
input: vec![TxIn {
56+
previous_output: bitcoin::OutPoint::default(),
57+
script_sig: ScriptBuf::builder()
58+
.push_int(bt.height as _)
59+
// randomn number so that re-mining creates unique block
60+
.push_int(random())
61+
.into_script(),
62+
sequence: bitcoin::Sequence::default(),
63+
witness: bitcoin::Witness::new(),
64+
}],
65+
output: vec![TxOut {
66+
value: 0,
67+
script_pubkey: ScriptBuf::new_p2sh(&ScriptHash::all_zeros()),
68+
}],
69+
}];
70+
71+
let bits: [u8; 4] = bt
72+
.bits
73+
.clone()
74+
.try_into()
75+
.expect("rpc provided us with invalid bits");
76+
77+
let mut block = Block {
78+
header: Header {
79+
version: bitcoin::block::Version::default(),
80+
prev_blockhash: bt.previous_block_hash,
81+
merkle_root: TxMerkleNode::all_zeros(),
82+
time: Ord::max(bt.min_time, std::time::UNIX_EPOCH.elapsed()?.as_secs()) as u32,
83+
bits: CompactTarget::from_consensus(u32::from_be_bytes(bits)),
84+
nonce: 0,
85+
},
86+
txdata,
87+
};
88+
89+
block.header.merkle_root = block.compute_merkle_root().expect("must compute");
90+
91+
for nonce in 0..=u32::MAX {
92+
block.header.nonce = nonce;
93+
if block.header.target().is_met_by(block.block_hash()) {
94+
break;
95+
}
96+
}
97+
98+
self.client.submit_block(&block)?;
99+
Ok((bt.height as usize, block.block_hash()))
100+
}
101+
102+
pub fn invalidate_blocks(&self, count: usize) -> anyhow::Result<()> {
103+
let mut hash = self.client.get_best_block_hash()?;
104+
for _ in 0..count {
105+
let prev_hash = self.client.get_block_info(&hash)?.previousblockhash;
106+
self.client.invalidate_block(&hash)?;
107+
match prev_hash {
108+
Some(prev_hash) => hash = prev_hash,
109+
None => break,
110+
}
111+
}
112+
Ok(())
113+
}
114+
115+
pub fn reorg(&self, count: usize) -> anyhow::Result<Vec<BlockHash>> {
116+
let start_height = self.client.get_block_count()?;
117+
self.invalidate_blocks(count)?;
118+
119+
let res = self.mine_blocks(count, None);
120+
assert_eq!(
121+
self.client.get_block_count()?,
122+
start_height,
123+
"reorg should not result in height change"
124+
);
125+
res
126+
}
127+
128+
pub fn reorg_empty_blocks(&self, count: usize) -> anyhow::Result<Vec<(usize, BlockHash)>> {
129+
let start_height = self.client.get_block_count()?;
130+
self.invalidate_blocks(count)?;
131+
132+
let res = (0..count)
133+
.map(|_| self.mine_empty_block())
134+
.collect::<Result<Vec<_>, _>>()?;
135+
assert_eq!(
136+
self.client.get_block_count()?,
137+
start_height,
138+
"reorg should not result in height change"
139+
);
140+
Ok(res)
141+
}
142+
143+
pub fn send(&self, address: &Address<NetworkChecked>, amount: Amount) -> anyhow::Result<Txid> {
144+
let txid = self
145+
.client
146+
.send_to_address(address, amount, None, None, None, None, None, None)?;
147+
Ok(txid)
148+
}
149+
}

0 commit comments

Comments
 (0)