/home/darosior/projects/bdk/crates/testenv/src/lib.rs
Line | Count | Source (jump to first uncovered line) |
1 | | use bdk_chain::{ |
2 | | bitcoin::{ |
3 | | address::NetworkChecked, block::Header, hash_types::TxMerkleNode, hashes::Hash, |
4 | | secp256k1::rand::random, transaction, Address, Amount, Block, BlockHash, CompactTarget, |
5 | | ScriptBuf, ScriptHash, Transaction, TxIn, TxOut, Txid, |
6 | | }, |
7 | | local_chain::CheckPoint, |
8 | | BlockId, |
9 | | }; |
10 | | use bitcoincore_rpc::{ |
11 | | bitcoincore_rpc_json::{GetBlockTemplateModes, GetBlockTemplateRules}, |
12 | | RpcApi, |
13 | | }; |
14 | | use electrsd::bitcoind::anyhow::Context; |
15 | | |
16 | | pub use electrsd; |
17 | | pub use electrsd::bitcoind; |
18 | | pub use electrsd::bitcoind::anyhow; |
19 | | pub use electrsd::bitcoind::bitcoincore_rpc; |
20 | | pub use electrsd::electrum_client; |
21 | | use electrsd::electrum_client::ElectrumApi; |
22 | | use std::time::Duration; |
23 | | |
24 | | /// Struct for running a regtest environment with a single `bitcoind` node with an `electrs` |
25 | | /// instance connected to it. |
26 | | pub struct TestEnv { |
27 | | pub bitcoind: electrsd::bitcoind::BitcoinD, |
28 | | pub electrsd: electrsd::ElectrsD, |
29 | | } |
30 | | |
31 | | /// Configuration parameters. |
32 | | #[derive(Debug)] |
33 | | pub struct Config<'a> { |
34 | | /// [`bitcoind::Conf`] |
35 | | pub bitcoind: bitcoind::Conf<'a>, |
36 | | /// [`electrsd::Conf`] |
37 | | pub electrsd: electrsd::Conf<'a>, |
38 | | } |
39 | | |
40 | | impl<'a> Default for Config<'a> { |
41 | | /// Use the default configuration plus set `http_enabled = true` for [`electrsd::Conf`] |
42 | | /// which is required for testing `bdk_esplora`. |
43 | 71 | fn default() -> Self { |
44 | 71 | Self { |
45 | 71 | bitcoind: bitcoind::Conf::default(), |
46 | 71 | electrsd: { |
47 | 71 | let mut conf = electrsd::Conf::default(); |
48 | 71 | conf.http_enabled = true; |
49 | 71 | conf |
50 | 71 | }, |
51 | 71 | } |
52 | 71 | } <bdk_testenv::Config as core::default::Default>::default Line | Count | Source | 43 | 69 | fn default() -> Self { | 44 | 69 | Self { | 45 | 69 | bitcoind: bitcoind::Conf::default(), | 46 | 69 | electrsd: { | 47 | 69 | let mut conf = electrsd::Conf::default(); | 48 | 69 | conf.http_enabled = true; | 49 | 69 | conf | 50 | 69 | }, | 51 | 69 | } | 52 | 69 | } |
<bdk_testenv::Config as core::default::Default>::default Line | Count | Source | 43 | 2 | fn default() -> Self { | 44 | 2 | Self { | 45 | 2 | bitcoind: bitcoind::Conf::default(), | 46 | 2 | electrsd: { | 47 | 2 | let mut conf = electrsd::Conf::default(); | 48 | 2 | conf.http_enabled = true; | 49 | 2 | conf | 50 | 2 | }, | 51 | 2 | } | 52 | 2 | } |
|
53 | | } |
54 | | |
55 | | impl TestEnv { |
56 | | /// Construct a new [`TestEnv`] instance with the default configuration used by BDK. |
57 | 71 | pub fn new() -> anyhow::Result<Self> { |
58 | 71 | TestEnv::new_with_config(Config::default()) |
59 | 71 | } <bdk_testenv::TestEnv>::new Line | Count | Source | 57 | 69 | pub fn new() -> anyhow::Result<Self> { | 58 | 69 | TestEnv::new_with_config(Config::default()) | 59 | 69 | } |
<bdk_testenv::TestEnv>::new Line | Count | Source | 57 | 2 | pub fn new() -> anyhow::Result<Self> { | 58 | 2 | TestEnv::new_with_config(Config::default()) | 59 | 2 | } |
|
60 | | |
61 | | /// Construct a new [`TestEnv`] instance with the provided [`Config`]. |
62 | 71 | pub fn new_with_config(config: Config) -> anyhow::Result<Self> { |
63 | 71 | let bitcoind_exe = match std::env::var("BITCOIND_EXE") { |
64 | 0 | Ok(path) => path, |
65 | 71 | Err(_) => bitcoind::downloaded_exe_path().context( |
66 | 71 | "you need to provide an env var BITCOIND_EXE or specify a bitcoind version feature", |
67 | 71 | )?0 , |
68 | | }; |
69 | 71 | let bitcoind = bitcoind::BitcoinD::with_conf(bitcoind_exe, &config.bitcoind)?0 ; |
70 | | |
71 | 71 | let electrs_exe = match std::env::var("ELECTRS_EXE") { |
72 | 0 | Ok(path) => path, |
73 | 71 | Err(_) => electrsd::downloaded_exe_path() |
74 | 71 | .context("electrs version feature must be enabled")?0 , |
75 | | }; |
76 | 71 | let electrsd = electrsd::ElectrsD::with_conf(electrs_exe, &bitcoind, &config.electrsd)?0 ; |
77 | | |
78 | 71 | Ok(Self { bitcoind, electrsd }) |
79 | 71 | } <bdk_testenv::TestEnv>::new_with_config Line | Count | Source | 62 | 69 | pub fn new_with_config(config: Config) -> anyhow::Result<Self> { | 63 | 69 | let bitcoind_exe = match std::env::var("BITCOIND_EXE") { | 64 | 0 | Ok(path) => path, | 65 | 69 | Err(_) => bitcoind::downloaded_exe_path().context( | 66 | 69 | "you need to provide an env var BITCOIND_EXE or specify a bitcoind version feature", | 67 | 69 | )?0 , | 68 | | }; | 69 | 69 | let bitcoind = bitcoind::BitcoinD::with_conf(bitcoind_exe, &config.bitcoind)?0 ; | 70 | | | 71 | 69 | let electrs_exe = match std::env::var("ELECTRS_EXE") { | 72 | 0 | Ok(path) => path, | 73 | 69 | Err(_) => electrsd::downloaded_exe_path() | 74 | 69 | .context("electrs version feature must be enabled")?0 , | 75 | | }; | 76 | 69 | let electrsd = electrsd::ElectrsD::with_conf(electrs_exe, &bitcoind, &config.electrsd)?0 ; | 77 | | | 78 | 69 | Ok(Self { bitcoind, electrsd }) | 79 | 69 | } |
<bdk_testenv::TestEnv>::new_with_config Line | Count | Source | 62 | 2 | pub fn new_with_config(config: Config) -> anyhow::Result<Self> { | 63 | 2 | let bitcoind_exe = match std::env::var("BITCOIND_EXE") { | 64 | 0 | Ok(path) => path, | 65 | 2 | Err(_) => bitcoind::downloaded_exe_path().context( | 66 | 2 | "you need to provide an env var BITCOIND_EXE or specify a bitcoind version feature", | 67 | 2 | )?0 , | 68 | | }; | 69 | 2 | let bitcoind = bitcoind::BitcoinD::with_conf(bitcoind_exe, &config.bitcoind)?0 ; | 70 | | | 71 | 2 | let electrs_exe = match std::env::var("ELECTRS_EXE") { | 72 | 0 | Ok(path) => path, | 73 | 2 | Err(_) => electrsd::downloaded_exe_path() | 74 | 2 | .context("electrs version feature must be enabled")?0 , | 75 | | }; | 76 | 2 | let electrsd = electrsd::ElectrsD::with_conf(electrs_exe, &bitcoind, &config.electrsd)?0 ; | 77 | | | 78 | 2 | Ok(Self { bitcoind, electrsd }) | 79 | 2 | } |
|
80 | | |
81 | | /// Exposes the [`ElectrumApi`] calls from the Electrum client. |
82 | 0 | pub fn electrum_client(&self) -> &impl ElectrumApi { |
83 | 0 | &self.electrsd.client |
84 | 0 | } Unexecuted instantiation: <bdk_testenv::TestEnv>::electrum_client Unexecuted instantiation: <bdk_testenv::TestEnv>::electrum_client |
85 | | |
86 | | /// Exposes the [`RpcApi`] calls from [`bitcoincore_rpc`]. |
87 | 201 | pub fn rpc_client(&self) -> &impl RpcApi { |
88 | 201 | &self.bitcoind.client |
89 | 201 | } <bdk_testenv::TestEnv>::rpc_client Line | Count | Source | 87 | 201 | pub fn rpc_client(&self) -> &impl RpcApi { | 88 | 201 | &self.bitcoind.client | 89 | 201 | } |
Unexecuted instantiation: <bdk_testenv::TestEnv>::rpc_client |
90 | | |
91 | | // Reset `electrsd` so that new blocks can be seen. |
92 | 3 | pub fn reset_electrsd(mut self) -> anyhow::Result<Self> { |
93 | 3 | let mut electrsd_conf = electrsd::Conf::default(); |
94 | 3 | electrsd_conf.http_enabled = true; |
95 | 3 | let electrsd = match std::env::var_os("ELECTRS_EXE") { |
96 | 0 | Some(env_electrs_exe) => { |
97 | 0 | electrsd::ElectrsD::with_conf(env_electrs_exe, &self.bitcoind, &electrsd_conf) |
98 | | } |
99 | | None => { |
100 | 3 | let electrs_exe = electrsd::downloaded_exe_path() |
101 | 3 | .expect("electrs version feature must be enabled"); |
102 | 3 | electrsd::ElectrsD::with_conf(electrs_exe, &self.bitcoind, &electrsd_conf) |
103 | | } |
104 | 0 | }?; |
105 | 3 | self.electrsd = electrsd; |
106 | 3 | Ok(self) |
107 | 3 | } <bdk_testenv::TestEnv>::reset_electrsd Line | Count | Source | 92 | 3 | pub fn reset_electrsd(mut self) -> anyhow::Result<Self> { | 93 | 3 | let mut electrsd_conf = electrsd::Conf::default(); | 94 | 3 | electrsd_conf.http_enabled = true; | 95 | 3 | let electrsd = match std::env::var_os("ELECTRS_EXE") { | 96 | 0 | Some(env_electrs_exe) => { | 97 | 0 | electrsd::ElectrsD::with_conf(env_electrs_exe, &self.bitcoind, &electrsd_conf) | 98 | | } | 99 | | None => { | 100 | 3 | let electrs_exe = electrsd::downloaded_exe_path() | 101 | 3 | .expect("electrs version feature must be enabled"); | 102 | 3 | electrsd::ElectrsD::with_conf(electrs_exe, &self.bitcoind, &electrsd_conf) | 103 | | } | 104 | 0 | }?; | 105 | 3 | self.electrsd = electrsd; | 106 | 3 | Ok(self) | 107 | 3 | } |
Unexecuted instantiation: <bdk_testenv::TestEnv>::reset_electrsd |
108 | | |
109 | | /// Mine a number of blocks of a given size `count`, which may be specified to a given coinbase |
110 | | /// `address`. |
111 | 190 | pub fn mine_blocks( |
112 | 190 | &self, |
113 | 190 | count: usize, |
114 | 190 | address: Option<Address>, |
115 | 190 | ) -> anyhow::Result<Vec<BlockHash>> { |
116 | 190 | let coinbase_address = match address { |
117 | 18 | Some(address) => address, |
118 | 172 | None => self |
119 | 172 | .bitcoind |
120 | 172 | .client |
121 | 172 | .get_new_address(None, None)?0 |
122 | 172 | .assume_checked(), |
123 | | }; |
124 | 190 | let block_hashes = self |
125 | 190 | .bitcoind |
126 | 190 | .client |
127 | 190 | .generate_to_address(count as _, &coinbase_address)?0 ; |
128 | 190 | Ok(block_hashes) |
129 | 190 | } <bdk_testenv::TestEnv>::mine_blocks Line | Count | Source | 111 | 186 | pub fn mine_blocks( | 112 | 186 | &self, | 113 | 186 | count: usize, | 114 | 186 | address: Option<Address>, | 115 | 186 | ) -> anyhow::Result<Vec<BlockHash>> { | 116 | 186 | let coinbase_address = match address { | 117 | 18 | Some(address) => address, | 118 | 168 | None => self | 119 | 168 | .bitcoind | 120 | 168 | .client | 121 | 168 | .get_new_address(None, None)?0 | 122 | 168 | .assume_checked(), | 123 | | }; | 124 | 186 | let block_hashes = self | 125 | 186 | .bitcoind | 126 | 186 | .client | 127 | 186 | .generate_to_address(count as _, &coinbase_address)?0 ; | 128 | 186 | Ok(block_hashes) | 129 | 186 | } |
<bdk_testenv::TestEnv>::mine_blocks Line | Count | Source | 111 | 4 | pub fn mine_blocks( | 112 | 4 | &self, | 113 | 4 | count: usize, | 114 | 4 | address: Option<Address>, | 115 | 4 | ) -> anyhow::Result<Vec<BlockHash>> { | 116 | 4 | let coinbase_address = match address { | 117 | 0 | Some(address) => address, | 118 | 4 | None => self | 119 | 4 | .bitcoind | 120 | 4 | .client | 121 | 4 | .get_new_address(None, None)?0 | 122 | 4 | .assume_checked(), | 123 | | }; | 124 | 4 | let block_hashes = self | 125 | 4 | .bitcoind | 126 | 4 | .client | 127 | 4 | .generate_to_address(count as _, &coinbase_address)?0 ; | 128 | 4 | Ok(block_hashes) | 129 | 4 | } |
|
130 | | |
131 | | /// Mine a block that is guaranteed to be empty even with transactions in the mempool. |
132 | 1.00k | pub fn mine_empty_block(&self) -> anyhow::Result<(usize, BlockHash)> { |
133 | 1.00k | let bt = self.bitcoind.client.get_block_template( |
134 | 1.00k | GetBlockTemplateModes::Template, |
135 | 1.00k | &[GetBlockTemplateRules::SegWit], |
136 | 1.00k | &[], |
137 | 1.00k | )?0 ; |
138 | | |
139 | 1.00k | let txdata = vec![Transaction { |
140 | 1.00k | version: transaction::Version::ONE, |
141 | 1.00k | lock_time: bdk_chain::bitcoin::absolute::LockTime::from_height(0)?0 , |
142 | 1.00k | input: vec![TxIn { |
143 | 1.00k | previous_output: bdk_chain::bitcoin::OutPoint::default(), |
144 | 1.00k | script_sig: ScriptBuf::builder() |
145 | 1.00k | .push_int(bt.height as _) |
146 | 1.00k | // randomn number so that re-mining creates unique block |
147 | 1.00k | .push_int(random()) |
148 | 1.00k | .into_script(), |
149 | 1.00k | sequence: bdk_chain::bitcoin::Sequence::default(), |
150 | 1.00k | witness: bdk_chain::bitcoin::Witness::new(), |
151 | 1.00k | }], |
152 | 1.00k | output: vec![TxOut { |
153 | 1.00k | value: Amount::ZERO, |
154 | 1.00k | script_pubkey: ScriptBuf::new_p2sh(&ScriptHash::all_zeros()), |
155 | 1.00k | }], |
156 | 1.00k | }]; |
157 | 1.00k | |
158 | 1.00k | let bits: [u8; 4] = bt |
159 | 1.00k | .bits |
160 | 1.00k | .clone() |
161 | 1.00k | .try_into() |
162 | 1.00k | .expect("rpc provided us with invalid bits"); |
163 | | |
164 | 1.00k | let mut block = Block { |
165 | | header: Header { |
166 | 1.00k | version: bdk_chain::bitcoin::block::Version::default(), |
167 | 1.00k | prev_blockhash: bt.previous_block_hash, |
168 | 1.00k | merkle_root: TxMerkleNode::all_zeros(), |
169 | 1.00k | time: Ord::max(bt.min_time, std::time::UNIX_EPOCH.elapsed()?0 .as_secs()) as u32, |
170 | 1.00k | bits: CompactTarget::from_consensus(u32::from_be_bytes(bits)), |
171 | 1.00k | nonce: 0, |
172 | 1.00k | }, |
173 | 1.00k | txdata, |
174 | 1.00k | }; |
175 | 1.00k | |
176 | 1.00k | block.header.merkle_root = block.compute_merkle_root().expect("must compute"); |
177 | | |
178 | 2.03k | for nonce in 0..=u32::MAX { |
179 | 2.03k | block.header.nonce = nonce; |
180 | 2.03k | if block.header.target().is_met_by(block.block_hash()) { |
181 | 1.00k | break; |
182 | 1.03k | } |
183 | | } |
184 | | |
185 | 1.00k | self.bitcoind.client.submit_block(&block)?0 ; |
186 | 1.00k | Ok((bt.height as usize, block.block_hash())) |
187 | 1.00k | } <bdk_testenv::TestEnv>::mine_empty_block Line | Count | Source | 132 | 1.00k | pub fn mine_empty_block(&self) -> anyhow::Result<(usize, BlockHash)> { | 133 | 1.00k | let bt = self.bitcoind.client.get_block_template( | 134 | 1.00k | GetBlockTemplateModes::Template, | 135 | 1.00k | &[GetBlockTemplateRules::SegWit], | 136 | 1.00k | &[], | 137 | 1.00k | )?0 ; | 138 | | | 139 | 1.00k | let txdata = vec![Transaction { | 140 | 1.00k | version: transaction::Version::ONE, | 141 | 1.00k | lock_time: bdk_chain::bitcoin::absolute::LockTime::from_height(0)?0 , | 142 | 1.00k | input: vec![TxIn { | 143 | 1.00k | previous_output: bdk_chain::bitcoin::OutPoint::default(), | 144 | 1.00k | script_sig: ScriptBuf::builder() | 145 | 1.00k | .push_int(bt.height as _) | 146 | 1.00k | // randomn number so that re-mining creates unique block | 147 | 1.00k | .push_int(random()) | 148 | 1.00k | .into_script(), | 149 | 1.00k | sequence: bdk_chain::bitcoin::Sequence::default(), | 150 | 1.00k | witness: bdk_chain::bitcoin::Witness::new(), | 151 | 1.00k | }], | 152 | 1.00k | output: vec![TxOut { | 153 | 1.00k | value: Amount::ZERO, | 154 | 1.00k | script_pubkey: ScriptBuf::new_p2sh(&ScriptHash::all_zeros()), | 155 | 1.00k | }], | 156 | 1.00k | }]; | 157 | 1.00k | | 158 | 1.00k | let bits: [u8; 4] = bt | 159 | 1.00k | .bits | 160 | 1.00k | .clone() | 161 | 1.00k | .try_into() | 162 | 1.00k | .expect("rpc provided us with invalid bits"); | 163 | | | 164 | 1.00k | let mut block = Block { | 165 | | header: Header { | 166 | 1.00k | version: bdk_chain::bitcoin::block::Version::default(), | 167 | 1.00k | prev_blockhash: bt.previous_block_hash, | 168 | 1.00k | merkle_root: TxMerkleNode::all_zeros(), | 169 | 1.00k | time: Ord::max(bt.min_time, std::time::UNIX_EPOCH.elapsed()?0 .as_secs()) as u32, | 170 | 1.00k | bits: CompactTarget::from_consensus(u32::from_be_bytes(bits)), | 171 | 1.00k | nonce: 0, | 172 | 1.00k | }, | 173 | 1.00k | txdata, | 174 | 1.00k | }; | 175 | 1.00k | | 176 | 1.00k | block.header.merkle_root = block.compute_merkle_root().expect("must compute"); | 177 | | | 178 | 2.03k | for nonce in 0..=u32::MAX { | 179 | 2.03k | block.header.nonce = nonce; | 180 | 2.03k | if block.header.target().is_met_by(block.block_hash()) { | 181 | 1.00k | break; | 182 | 1.03k | } | 183 | | } | 184 | | | 185 | 1.00k | self.bitcoind.client.submit_block(&block)?0 ; | 186 | 1.00k | Ok((bt.height as usize, block.block_hash())) | 187 | 1.00k | } |
Unexecuted instantiation: <bdk_testenv::TestEnv>::mine_empty_block |
188 | | |
189 | | /// This method waits for the Electrum notification indicating that a new block has been mined. |
190 | | /// `timeout` is the maximum [`Duration`] we want to wait for a response from Electrsd. |
191 | 52 | pub fn wait_until_electrum_sees_block(&self, timeout: Duration) -> anyhow::Result<()> { |
192 | 52 | self.electrsd.client.block_headers_subscribe()?0 ; |
193 | 52 | let delay = Duration::from_millis(200); |
194 | 52 | let start = std::time::Instant::now(); |
195 | | |
196 | 104 | while start.elapsed() < timeout { |
197 | 104 | self.electrsd.trigger()?0 ; |
198 | 104 | self.electrsd.client.ping()?0 ; |
199 | 104 | if self.electrsd.client.block_headers_pop()?0 .is_some() { |
200 | 52 | return Ok(()); |
201 | 52 | } |
202 | 52 | |
203 | 52 | std::thread::sleep(delay); |
204 | | } |
205 | | |
206 | 0 | Err(anyhow::Error::msg( |
207 | 0 | "Timed out waiting for Electrsd to get block header", |
208 | 0 | )) |
209 | 52 | } <bdk_testenv::TestEnv>::wait_until_electrum_sees_block Line | Count | Source | 191 | 48 | pub fn wait_until_electrum_sees_block(&self, timeout: Duration) -> anyhow::Result<()> { | 192 | 48 | self.electrsd.client.block_headers_subscribe()?0 ; | 193 | 48 | let delay = Duration::from_millis(200); | 194 | 48 | let start = std::time::Instant::now(); | 195 | | | 196 | 96 | while start.elapsed() < timeout { | 197 | 96 | self.electrsd.trigger()?0 ; | 198 | 96 | self.electrsd.client.ping()?0 ; | 199 | 96 | if self.electrsd.client.block_headers_pop()?0 .is_some() { | 200 | 48 | return Ok(()); | 201 | 48 | } | 202 | 48 | | 203 | 48 | std::thread::sleep(delay); | 204 | | } | 205 | | | 206 | 0 | Err(anyhow::Error::msg( | 207 | 0 | "Timed out waiting for Electrsd to get block header", | 208 | 0 | )) | 209 | 48 | } |
<bdk_testenv::TestEnv>::wait_until_electrum_sees_block Line | Count | Source | 191 | 4 | pub fn wait_until_electrum_sees_block(&self, timeout: Duration) -> anyhow::Result<()> { | 192 | 4 | self.electrsd.client.block_headers_subscribe()?0 ; | 193 | 4 | let delay = Duration::from_millis(200); | 194 | 4 | let start = std::time::Instant::now(); | 195 | | | 196 | 8 | while start.elapsed() < timeout { | 197 | 8 | self.electrsd.trigger()?0 ; | 198 | 8 | self.electrsd.client.ping()?0 ; | 199 | 8 | if self.electrsd.client.block_headers_pop()?0 .is_some() { | 200 | 4 | return Ok(()); | 201 | 4 | } | 202 | 4 | | 203 | 4 | std::thread::sleep(delay); | 204 | | } | 205 | | | 206 | 0 | Err(anyhow::Error::msg( | 207 | 0 | "Timed out waiting for Electrsd to get block header", | 208 | 0 | )) | 209 | 4 | } |
|
210 | | |
211 | | /// This method waits for Electrsd to see a transaction with given `txid`. `timeout` is the |
212 | | /// maximum [`Duration`] we want to wait for a response from Electrsd. |
213 | 3 | pub fn wait_until_electrum_sees_txid( |
214 | 3 | &self, |
215 | 3 | txid: Txid, |
216 | 3 | timeout: Duration, |
217 | 3 | ) -> anyhow::Result<()> { |
218 | 3 | let delay = Duration::from_millis(200); |
219 | 3 | let start = std::time::Instant::now(); |
220 | | |
221 | 78 | while start.elapsed() < timeout { |
222 | 78 | if self.electrsd.client.transaction_get(&txid).is_ok() { |
223 | 3 | return Ok(()); |
224 | 75 | } |
225 | 75 | |
226 | 75 | std::thread::sleep(delay); |
227 | | } |
228 | | |
229 | 0 | Err(anyhow::Error::msg( |
230 | 0 | "Timed out waiting for Electrsd to get transaction", |
231 | 0 | )) |
232 | 3 | } <bdk_testenv::TestEnv>::wait_until_electrum_sees_txid Line | Count | Source | 213 | 3 | pub fn wait_until_electrum_sees_txid( | 214 | 3 | &self, | 215 | 3 | txid: Txid, | 216 | 3 | timeout: Duration, | 217 | 3 | ) -> anyhow::Result<()> { | 218 | 3 | let delay = Duration::from_millis(200); | 219 | 3 | let start = std::time::Instant::now(); | 220 | | | 221 | 78 | while start.elapsed() < timeout { | 222 | 78 | if self.electrsd.client.transaction_get(&txid).is_ok() { | 223 | 3 | return Ok(()); | 224 | 75 | } | 225 | 75 | | 226 | 75 | std::thread::sleep(delay); | 227 | | } | 228 | | | 229 | 0 | Err(anyhow::Error::msg( | 230 | 0 | "Timed out waiting for Electrsd to get transaction", | 231 | 0 | )) | 232 | 3 | } |
Unexecuted instantiation: <bdk_testenv::TestEnv>::wait_until_electrum_sees_txid |
233 | | |
234 | | /// Invalidate a number of blocks of a given size `count`. |
235 | 122 | pub fn invalidate_blocks(&self, count: usize) -> anyhow::Result<()> { |
236 | 122 | let mut hash = self.bitcoind.client.get_best_block_hash()?0 ; |
237 | 122 | for _ in 0..count { |
238 | 639 | let prev_hash = self |
239 | 639 | .bitcoind |
240 | 639 | .client |
241 | 639 | .get_block_info(&hash)?0 |
242 | | .previousblockhash; |
243 | 639 | self.bitcoind.client.invalidate_block(&hash)?0 ; |
244 | 639 | match prev_hash { |
245 | 639 | Some(prev_hash) => hash = prev_hash, |
246 | 0 | None => break, |
247 | | } |
248 | | } |
249 | 122 | Ok(()) |
250 | 122 | } <bdk_testenv::TestEnv>::invalidate_blocks Line | Count | Source | 235 | 120 | pub fn invalidate_blocks(&self, count: usize) -> anyhow::Result<()> { | 236 | 120 | let mut hash = self.bitcoind.client.get_best_block_hash()?0 ; | 237 | 120 | for _ in 0..count { | 238 | 627 | let prev_hash = self | 239 | 627 | .bitcoind | 240 | 627 | .client | 241 | 627 | .get_block_info(&hash)?0 | 242 | | .previousblockhash; | 243 | 627 | self.bitcoind.client.invalidate_block(&hash)?0 ; | 244 | 627 | match prev_hash { | 245 | 627 | Some(prev_hash) => hash = prev_hash, | 246 | 0 | None => break, | 247 | | } | 248 | | } | 249 | 120 | Ok(()) | 250 | 120 | } |
<bdk_testenv::TestEnv>::invalidate_blocks Line | Count | Source | 235 | 2 | pub fn invalidate_blocks(&self, count: usize) -> anyhow::Result<()> { | 236 | 2 | let mut hash = self.bitcoind.client.get_best_block_hash()?0 ; | 237 | 2 | for _ in 0..count { | 238 | 12 | let prev_hash = self | 239 | 12 | .bitcoind | 240 | 12 | .client | 241 | 12 | .get_block_info(&hash)?0 | 242 | | .previousblockhash; | 243 | 12 | self.bitcoind.client.invalidate_block(&hash)?0 ; | 244 | 12 | match prev_hash { | 245 | 12 | Some(prev_hash) => hash = prev_hash, | 246 | 0 | None => break, | 247 | | } | 248 | | } | 249 | 2 | Ok(()) | 250 | 2 | } |
|
251 | | |
252 | | /// Reorg a number of blocks of a given size `count`. |
253 | | /// Refer to [`TestEnv::mine_empty_block`] for more information. |
254 | 5 | pub fn reorg(&self, count: usize) -> anyhow::Result<Vec<BlockHash>> { |
255 | 5 | let start_height = self.bitcoind.client.get_block_count()?0 ; |
256 | 5 | self.invalidate_blocks(count)?0 ; |
257 | | |
258 | 5 | let res = self.mine_blocks(count, None); |
259 | 5 | assert_eq!( |
260 | 5 | self.bitcoind.client.get_block_count()?0 , |
261 | | start_height, |
262 | 0 | "reorg should not result in height change" |
263 | | ); |
264 | 5 | res |
265 | 5 | } <bdk_testenv::TestEnv>::reorg Line | Count | Source | 254 | 3 | pub fn reorg(&self, count: usize) -> anyhow::Result<Vec<BlockHash>> { | 255 | 3 | let start_height = self.bitcoind.client.get_block_count()?0 ; | 256 | 3 | self.invalidate_blocks(count)?0 ; | 257 | | | 258 | 3 | let res = self.mine_blocks(count, None); | 259 | 3 | assert_eq!( | 260 | 3 | self.bitcoind.client.get_block_count()?0 , | 261 | | start_height, | 262 | 0 | "reorg should not result in height change" | 263 | | ); | 264 | 3 | res | 265 | 3 | } |
<bdk_testenv::TestEnv>::reorg Line | Count | Source | 254 | 2 | pub fn reorg(&self, count: usize) -> anyhow::Result<Vec<BlockHash>> { | 255 | 2 | let start_height = self.bitcoind.client.get_block_count()?0 ; | 256 | 2 | self.invalidate_blocks(count)?0 ; | 257 | | | 258 | 2 | let res = self.mine_blocks(count, None); | 259 | 2 | assert_eq!( | 260 | 2 | self.bitcoind.client.get_block_count()?0 , | 261 | | start_height, | 262 | 0 | "reorg should not result in height change" | 263 | | ); | 264 | 2 | res | 265 | 2 | } |
|
266 | | |
267 | | /// Reorg with a number of empty blocks of a given size `count`. |
268 | 117 | pub fn reorg_empty_blocks(&self, count: usize) -> anyhow::Result<Vec<(usize, BlockHash)>> { |
269 | 117 | let start_height = self.bitcoind.client.get_block_count()?0 ; |
270 | 117 | self.invalidate_blocks(count)?0 ; |
271 | | |
272 | 117 | let res = (0..count) |
273 | 609 | .map(|_| self.mine_empty_block()) <bdk_testenv::TestEnv>::reorg_empty_blocks::{closure#0} Line | Count | Source | 273 | 609 | .map(|_| self.mine_empty_block()) |
Unexecuted instantiation: <bdk_testenv::TestEnv>::reorg_empty_blocks::{closure#0} |
274 | 117 | .collect::<Result<Vec<_>, _>>()?0 ; |
275 | 117 | assert_eq!( |
276 | 117 | self.bitcoind.client.get_block_count()?0 , |
277 | | start_height, |
278 | 0 | "reorg should not result in height change" |
279 | | ); |
280 | 117 | Ok(res) |
281 | 117 | } <bdk_testenv::TestEnv>::reorg_empty_blocks Line | Count | Source | 268 | 117 | pub fn reorg_empty_blocks(&self, count: usize) -> anyhow::Result<Vec<(usize, BlockHash)>> { | 269 | 117 | let start_height = self.bitcoind.client.get_block_count()?0 ; | 270 | 117 | self.invalidate_blocks(count)?0 ; | 271 | | | 272 | 117 | let res = (0..count) | 273 | 117 | .map(|_| self.mine_empty_block()) | 274 | 117 | .collect::<Result<Vec<_>, _>>()?0 ; | 275 | 117 | assert_eq!( | 276 | 117 | self.bitcoind.client.get_block_count()?0 , | 277 | | start_height, | 278 | 0 | "reorg should not result in height change" | 279 | | ); | 280 | 117 | Ok(res) | 281 | 117 | } |
Unexecuted instantiation: <bdk_testenv::TestEnv>::reorg_empty_blocks |
282 | | |
283 | | /// Send a tx of a given `amount` to a given `address`. |
284 | 159 | pub fn send(&self, address: &Address<NetworkChecked>, amount: Amount) -> anyhow::Result<Txid> { |
285 | 159 | let txid = self |
286 | 159 | .bitcoind |
287 | 159 | .client |
288 | 159 | .send_to_address(address, amount, None, None, None, None, None, None)?0 ; |
289 | 159 | Ok(txid) |
290 | 159 | } <bdk_testenv::TestEnv>::send Line | Count | Source | 284 | 159 | pub fn send(&self, address: &Address<NetworkChecked>, amount: Amount) -> anyhow::Result<Txid> { | 285 | 159 | let txid = self | 286 | 159 | .bitcoind | 287 | 159 | .client | 288 | 159 | .send_to_address(address, amount, None, None, None, None, None, None)?0 ; | 289 | 159 | Ok(txid) | 290 | 159 | } |
Unexecuted instantiation: <bdk_testenv::TestEnv>::send |
291 | | |
292 | | /// Create a checkpoint linked list of all the blocks in the chain. |
293 | 54 | pub fn make_checkpoint_tip(&self) -> CheckPoint { |
294 | 2.94k | CheckPoint::from_block_ids((0_u32..).map_while(|height| { |
295 | 2.94k | self.bitcoind |
296 | 2.94k | .client |
297 | 2.94k | .get_block_hash(height as u64) |
298 | 2.94k | .ok() |
299 | 2.94k | .map(|hash| BlockId { height, hash }2.88k ) <bdk_testenv::TestEnv>::make_checkpoint_tip::{closure#0}::{closure#0} Line | Count | Source | 299 | 2.88k | .map(|hash| BlockId { height, hash }) |
Unexecuted instantiation: <bdk_testenv::TestEnv>::make_checkpoint_tip::{closure#0}::{closure#0} |
300 | 2.94k | })) <bdk_testenv::TestEnv>::make_checkpoint_tip::{closure#0} Line | Count | Source | 294 | 2.94k | CheckPoint::from_block_ids((0_u32..).map_while(|height| { | 295 | 2.94k | self.bitcoind | 296 | 2.94k | .client | 297 | 2.94k | .get_block_hash(height as u64) | 298 | 2.94k | .ok() | 299 | 2.94k | .map(|hash| BlockId { height, hash }) | 300 | 2.94k | })) |
Unexecuted instantiation: <bdk_testenv::TestEnv>::make_checkpoint_tip::{closure#0} |
301 | 54 | .expect("must craft tip") |
302 | 54 | } <bdk_testenv::TestEnv>::make_checkpoint_tip Line | Count | Source | 293 | 54 | pub fn make_checkpoint_tip(&self) -> CheckPoint { | 294 | 54 | CheckPoint::from_block_ids((0_u32..).map_while(|height| { | 295 | | self.bitcoind | 296 | | .client | 297 | | .get_block_hash(height as u64) | 298 | | .ok() | 299 | | .map(|hash| BlockId { height, hash }) | 300 | 54 | })) | 301 | 54 | .expect("must craft tip") | 302 | 54 | } |
Unexecuted instantiation: <bdk_testenv::TestEnv>::make_checkpoint_tip |
303 | | |
304 | | /// Get the genesis hash of the blockchain. |
305 | 18 | pub fn genesis_hash(&self) -> anyhow::Result<BlockHash> { |
306 | 18 | let hash = self.bitcoind.client.get_block_hash(0)?0 ; |
307 | 18 | Ok(hash) |
308 | 18 | } <bdk_testenv::TestEnv>::genesis_hash Line | Count | Source | 305 | 18 | pub fn genesis_hash(&self) -> anyhow::Result<BlockHash> { | 306 | 18 | let hash = self.bitcoind.client.get_block_hash(0)?0 ; | 307 | 18 | Ok(hash) | 308 | 18 | } |
Unexecuted instantiation: <bdk_testenv::TestEnv>::genesis_hash |
309 | | } |
310 | | |
311 | | #[cfg(test)] |
312 | | mod test { |
313 | | use crate::TestEnv; |
314 | | use core::time::Duration; |
315 | | use electrsd::bitcoind::{anyhow::Result, bitcoincore_rpc::RpcApi}; |
316 | | |
317 | | /// This checks that reorgs initiated by `bitcoind` is detected by our `electrsd` instance. |
318 | | #[test] |
319 | 2 | fn test_reorg_is_detected_in_electrsd() -> Result<()> { |
320 | 2 | let env = TestEnv::new()?0 ; |
321 | | |
322 | | // Mine some blocks. |
323 | 2 | env.mine_blocks(101, None)?0 ; |
324 | 2 | env.wait_until_electrum_sees_block(Duration::from_secs(6))?0 ; |
325 | 2 | let height = env.bitcoind.client.get_block_count()?0 ; |
326 | 2 | let blocks = (0..=height) |
327 | 206 | .map(|i| env.bitcoind.client.get_block_hash(i)) |
328 | 2 | .collect::<Result<Vec<_>, _>>()?0 ; |
329 | | |
330 | | // Perform reorg on six blocks. |
331 | 2 | env.reorg(6)?0 ; |
332 | 2 | env.wait_until_electrum_sees_block(Duration::from_secs(6))?0 ; |
333 | 2 | let reorged_height = env.bitcoind.client.get_block_count()?0 ; |
334 | 2 | let reorged_blocks = (0..=height) |
335 | 206 | .map(|i| env.bitcoind.client.get_block_hash(i)) |
336 | 2 | .collect::<Result<Vec<_>, _>>()?0 ; |
337 | | |
338 | 2 | assert_eq!(height, reorged_height); |
339 | | |
340 | | // Block hashes should not be equal on the six reorged blocks. |
341 | 206 | for (i, (block, reorged_block)) in blocks.iter().zip(reorged_blocks.iter()).enumerate()2 { |
342 | 206 | match i <= height as usize - 6 { |
343 | 194 | true => assert_eq!(block, reorged_block), |
344 | 12 | false => assert_ne!(block, reorged_block), |
345 | | } |
346 | | } |
347 | | |
348 | 2 | Ok(()) |
349 | 2 | } |
350 | | } |