Skip to content

Commit 50c5ccd

Browse files
committed
feat: added electrsd support for TestEnv
Added `electrsd` support so that `TestEnv` can serve `esplora` and `electrum`.
1 parent 0be5338 commit 50c5ccd

File tree

3 files changed

+108
-54
lines changed

3 files changed

+108
-54
lines changed

crates/bitcoind_rpc/tests/test_emitter.rs

Lines changed: 56 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -43,15 +43,23 @@ fn block_to_chain_update(block: &bitcoin::Block, height: u32) -> local_chain::Up
4343
pub fn test_sync_local_chain() -> anyhow::Result<()> {
4444
let env = TestEnv::new()?;
4545
let mut local_chain = LocalChain::default();
46-
let mut emitter = Emitter::from_height(&env.client, 0);
46+
let tip = env.rpc_client().get_block_count()?;
47+
let mut emitter = Emitter::from_height(env.rpc_client(), tip as u32);
4748

4849
// mine some blocks and returned the actual block hashes
4950
let exp_hashes = {
50-
let mut hashes = vec![env.client.get_block_hash(0)?]; // include genesis block
51-
hashes.extend(env.mine_blocks(101, None)?);
51+
let mut hashes = (0..=tip)
52+
.map(|height| env.rpc_client().get_block_hash(height))
53+
.collect::<Result<Vec<_>, _>>()?; // includes genesis block
54+
hashes.extend(env.mine_blocks(101 - tip as usize, None)?);
5255
hashes
5356
};
5457

58+
(0..tip).for_each(|height| {
59+
let changeset = BTreeMap::from([(height as u32, Some(exp_hashes[height as usize]))]);
60+
local_chain.apply_changeset(&changeset);
61+
});
62+
5563
// see if the emitter outputs the right blocks
5664
println!("first sync:");
5765
while let Some((height, block)) = emitter.next_block()? {
@@ -141,9 +149,18 @@ fn test_into_tx_graph() -> anyhow::Result<()> {
141149
let env = TestEnv::new()?;
142150

143151
println!("getting new addresses!");
144-
let addr_0 = env.client.get_new_address(None, None)?.assume_checked();
145-
let addr_1 = env.client.get_new_address(None, None)?.assume_checked();
146-
let addr_2 = env.client.get_new_address(None, None)?.assume_checked();
152+
let addr_0 = env
153+
.rpc_client()
154+
.get_new_address(None, None)?
155+
.assume_checked();
156+
let addr_1 = env
157+
.rpc_client()
158+
.get_new_address(None, None)?
159+
.assume_checked();
160+
let addr_2 = env
161+
.rpc_client()
162+
.get_new_address(None, None)?
163+
.assume_checked();
147164
println!("got new addresses!");
148165

149166
println!("mining block!");
@@ -159,7 +176,7 @@ fn test_into_tx_graph() -> anyhow::Result<()> {
159176
index
160177
});
161178

162-
let emitter = &mut Emitter::from_height(&env.client, 0);
179+
let emitter = &mut Emitter::from_height(env.rpc_client(), 0);
163180

164181
while let Some((height, block)) = emitter.next_block()? {
165182
let _ = chain.apply_update(block_to_chain_update(&block, height))?;
@@ -171,7 +188,7 @@ fn test_into_tx_graph() -> anyhow::Result<()> {
171188
let exp_txids = {
172189
let mut txids = BTreeSet::new();
173190
for _ in 0..3 {
174-
txids.insert(env.client.send_to_address(
191+
txids.insert(env.rpc_client().send_to_address(
175192
&addr_0,
176193
Amount::from_sat(10_000),
177194
None,
@@ -207,7 +224,7 @@ fn test_into_tx_graph() -> anyhow::Result<()> {
207224

208225
// mine a block that confirms the 3 txs
209226
let exp_block_hash = env.mine_blocks(1, None)?[0];
210-
let exp_block_height = env.client.get_block_info(&exp_block_hash)?.height as u32;
227+
let exp_block_height = env.rpc_client().get_block_info(&exp_block_hash)?.height as u32;
211228
let exp_anchors = exp_txids
212229
.iter()
213230
.map({
@@ -247,7 +264,7 @@ fn ensure_block_emitted_after_reorg_is_at_reorg_height() -> anyhow::Result<()> {
247264
const CHAIN_TIP_HEIGHT: usize = 110;
248265

249266
let env = TestEnv::new()?;
250-
let mut emitter = Emitter::from_height(&env.client, EMITTER_START_HEIGHT as _);
267+
let mut emitter = Emitter::from_height(env.rpc_client(), EMITTER_START_HEIGHT as _);
251268

252269
env.mine_blocks(CHAIN_TIP_HEIGHT, None)?;
253270
while emitter.next_header()?.is_some() {}
@@ -315,10 +332,13 @@ fn tx_can_become_unconfirmed_after_reorg() -> anyhow::Result<()> {
315332
const SEND_AMOUNT: Amount = Amount::from_sat(10_000);
316333

317334
let env = TestEnv::new()?;
318-
let mut emitter = Emitter::from_height(&env.client, 0);
335+
let mut emitter = Emitter::from_height(env.rpc_client(), 0);
319336

320337
// setup addresses
321-
let addr_to_mine = env.client.get_new_address(None, None)?.assume_checked();
338+
let addr_to_mine = env
339+
.rpc_client()
340+
.get_new_address(None, None)?
341+
.assume_checked();
322342
let spk_to_track = ScriptBuf::new_v0_p2wsh(&WScriptHash::all_zeros());
323343
let addr_to_track = Address::from_script(&spk_to_track, bitcoin::Network::Regtest)?;
324344

@@ -339,7 +359,7 @@ fn tx_can_become_unconfirmed_after_reorg() -> anyhow::Result<()> {
339359

340360
// lock outputs that send to `addr_to_track`
341361
let outpoints_to_lock = env
342-
.client
362+
.rpc_client()
343363
.get_transaction(&txid, None)?
344364
.transaction()?
345365
.output
@@ -348,7 +368,7 @@ fn tx_can_become_unconfirmed_after_reorg() -> anyhow::Result<()> {
348368
.filter(|(_, txo)| txo.script_pubkey == spk_to_track)
349369
.map(|(vout, _)| OutPoint::new(txid, vout as _))
350370
.collect::<Vec<_>>();
351-
env.client.lock_unspent(&outpoints_to_lock)?;
371+
env.rpc_client().lock_unspent(&outpoints_to_lock)?;
352372

353373
let _ = env.mine_blocks(1, None)?;
354374
}
@@ -396,10 +416,13 @@ fn mempool_avoids_re_emission() -> anyhow::Result<()> {
396416
const MEMPOOL_TX_COUNT: usize = 2;
397417

398418
let env = TestEnv::new()?;
399-
let mut emitter = Emitter::from_height(&env.client, 0);
419+
let mut emitter = Emitter::from_height(env.rpc_client(), 0);
400420

401421
// mine blocks and sync up emitter
402-
let addr = env.client.get_new_address(None, None)?.assume_checked();
422+
let addr = env
423+
.rpc_client()
424+
.get_new_address(None, None)?
425+
.assume_checked();
403426
env.mine_blocks(BLOCKS_TO_MINE, Some(addr.clone()))?;
404427
while emitter.next_header()?.is_some() {}
405428

@@ -451,10 +474,13 @@ fn mempool_re_emits_if_tx_introduction_height_not_reached() -> anyhow::Result<()
451474
const MEMPOOL_TX_COUNT: usize = 21;
452475

453476
let env = TestEnv::new()?;
454-
let mut emitter = Emitter::from_height(&env.client, 0);
477+
let mut emitter = Emitter::from_height(env.rpc_client(), 0);
455478

456479
// mine blocks to get initial balance, sync emitter up to tip
457-
let addr = env.client.get_new_address(None, None)?.assume_checked();
480+
let addr = env
481+
.rpc_client()
482+
.get_new_address(None, None)?
483+
.assume_checked();
458484
env.mine_blocks(PREMINE_COUNT, Some(addr.clone()))?;
459485
while emitter.next_header()?.is_some() {}
460486

@@ -528,10 +554,13 @@ fn mempool_during_reorg() -> anyhow::Result<()> {
528554
const PREMINE_COUNT: usize = 101;
529555

530556
let env = TestEnv::new()?;
531-
let mut emitter = Emitter::from_height(&env.client, 0);
557+
let mut emitter = Emitter::from_height(env.rpc_client(), 0);
532558

533559
// mine blocks to get initial balance
534-
let addr = env.client.get_new_address(None, None)?.assume_checked();
560+
let addr = env
561+
.rpc_client()
562+
.get_new_address(None, None)?
563+
.assume_checked();
535564
env.mine_blocks(PREMINE_COUNT, Some(addr.clone()))?;
536565

537566
// introduce mempool tx at each block extension
@@ -549,7 +578,7 @@ fn mempool_during_reorg() -> anyhow::Result<()> {
549578
.into_iter()
550579
.map(|(tx, _)| tx.txid())
551580
.collect::<BTreeSet<_>>(),
552-
env.client
581+
env.rpc_client()
553582
.get_raw_mempool()?
554583
.into_iter()
555584
.collect::<BTreeSet<_>>(),
@@ -568,7 +597,7 @@ fn mempool_during_reorg() -> anyhow::Result<()> {
568597
// emission.
569598
// TODO: How can have have reorg logic in `TestEnv` NOT blacklast old blocks first?
570599
let tx_introductions = dbg!(env
571-
.client
600+
.rpc_client()
572601
.get_raw_mempool_verbose()?
573602
.into_iter()
574603
.map(|(txid, entry)| (txid, entry.height as usize))
@@ -643,7 +672,7 @@ fn no_agreement_point() -> anyhow::Result<()> {
643672
let env = TestEnv::new()?;
644673

645674
// start height is 99
646-
let mut emitter = Emitter::from_height(&env.client, (PREMINE_COUNT - 2) as u32);
675+
let mut emitter = Emitter::from_height(env.rpc_client(), (PREMINE_COUNT - 2) as u32);
647676

648677
// mine 101 blocks
649678
env.mine_blocks(PREMINE_COUNT, None)?;
@@ -658,12 +687,12 @@ fn no_agreement_point() -> anyhow::Result<()> {
658687
let block_hash_100a = block_header_100a.block_hash();
659688

660689
// get hash for block 101a
661-
let block_hash_101a = env.client.get_block_hash(101)?;
690+
let block_hash_101a = env.rpc_client().get_block_hash(101)?;
662691

663692
// invalidate blocks 99a, 100a, 101a
664-
env.client.invalidate_block(&block_hash_99a)?;
665-
env.client.invalidate_block(&block_hash_100a)?;
666-
env.client.invalidate_block(&block_hash_101a)?;
693+
env.rpc_client().invalidate_block(&block_hash_99a)?;
694+
env.rpc_client().invalidate_block(&block_hash_100a)?;
695+
env.rpc_client().invalidate_block(&block_hash_101a)?;
667696

668697
// mine new blocks 99b, 100b, 101b
669698
env.mine_blocks(3, None)?;

crates/testenv/Cargo.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@ edition = "2021"
1010
bitcoin = { version = "0.30", default-features = false }
1111
bitcoincore-rpc = { version = "0.17" }
1212
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"] }
13+
electrsd = { version= "0.25.0", features = ["bitcoind_25_0", "esplora_a33e97e1", "legacy"] }
1514
anyhow = { version = "1" }
1615

1716
[features]

crates/testenv/src/lib.rs

Lines changed: 51 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -7,43 +7,64 @@ use bitcoincore_rpc::{
77
bitcoincore_rpc_json::{GetBlockTemplateModes, GetBlockTemplateRules},
88
RpcApi,
99
};
10+
use electrsd::electrum_client::ElectrumApi;
1011

1112
pub struct TestEnv {
1213
#[allow(dead_code)]
13-
pub daemon: bitcoind::BitcoinD,
14-
pub client: bitcoincore_rpc::Client,
14+
pub bitcoind: electrsd::bitcoind::BitcoinD,
15+
pub electrsd: electrsd::ElectrsD,
1516
}
1617

1718
impl TestEnv {
1819
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(),
20+
let bitcoind = match std::env::var_os("TEST_BITCOIND") {
21+
Some(bitcoind_path) => electrsd::bitcoind::BitcoinD::new(bitcoind_path),
22+
None => electrsd::bitcoind::BitcoinD::from_downloaded(),
2223
}?;
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 })
24+
25+
let electrsd = match std::env::var_os("ELECTRS_EXE") {
26+
Some(env_electrs_exe) => electrsd::ElectrsD::new(env_electrs_exe, &bitcoind),
27+
None => {
28+
let mut electrs_conf = electrsd::Conf::default();
29+
electrs_conf.http_enabled = true;
30+
let electrs_exe = electrsd::downloaded_exe_path()
31+
.expect("electrs version feature must be enabled");
32+
electrsd::ElectrsD::with_conf(electrs_exe, &bitcoind, &electrs_conf)
33+
}
34+
}?;
35+
36+
Ok(Self { bitcoind, electrsd })
37+
}
38+
39+
pub fn electrum_client(&self) -> &impl ElectrumApi {
40+
&self.electrsd.client
41+
}
42+
43+
pub fn rpc_client(&self) -> &impl RpcApi {
44+
&self.bitcoind.client
2845
}
2946

3047
pub fn mine_blocks(
3148
&self,
3249
count: usize,
3350
address: Option<Address>,
51+
bitcoind: &electrsd::bitcoind::BitcoinD,
3452
) -> anyhow::Result<Vec<BlockHash>> {
3553
let coinbase_address = match address {
3654
Some(address) => address,
37-
None => self.client.get_new_address(None, None)?.assume_checked(),
55+
None => bitcoind
56+
.client
57+
.get_new_address(None, None)?
58+
.assume_checked(),
3859
};
39-
let block_hashes = self
60+
let block_hashes = bitcoind
4061
.client
4162
.generate_to_address(count as _, &coinbase_address)?;
4263
Ok(block_hashes)
4364
}
4465

4566
pub fn mine_empty_block(&self) -> anyhow::Result<(usize, BlockHash)> {
46-
let bt = self.client.get_block_template(
67+
let bt = self.bitcoind.client.get_block_template(
4768
GetBlockTemplateModes::Template,
4869
&[GetBlockTemplateRules::SegWit],
4970
&[],
@@ -95,15 +116,19 @@ impl TestEnv {
95116
}
96117
}
97118

98-
self.client.submit_block(&block)?;
119+
self.bitcoind.client.submit_block(&block)?;
99120
Ok((bt.height as usize, block.block_hash()))
100121
}
101122

102-
pub fn invalidate_blocks(&self, count: usize) -> anyhow::Result<()> {
103-
let mut hash = self.client.get_best_block_hash()?;
123+
pub fn invalidate_blocks(
124+
&self,
125+
count: usize,
126+
bitcoind: &electrsd::bitcoind::BitcoinD,
127+
) -> anyhow::Result<()> {
128+
let mut hash = bitcoind.client.get_best_block_hash()?;
104129
for _ in 0..count {
105-
let prev_hash = self.client.get_block_info(&hash)?.previousblockhash;
106-
self.client.invalidate_block(&hash)?;
130+
let prev_hash = bitcoind.client.get_block_info(&hash)?.previousblockhash;
131+
bitcoind.client.invalidate_block(&hash)?;
107132
match prev_hash {
108133
Some(prev_hash) => hash = prev_hash,
109134
None => break,
@@ -113,27 +138,27 @@ impl TestEnv {
113138
}
114139

115140
pub fn reorg(&self, count: usize) -> anyhow::Result<Vec<BlockHash>> {
116-
let start_height = self.client.get_block_count()?;
117-
self.invalidate_blocks(count)?;
141+
let start_height = self.bitcoind.client.get_block_count()?;
142+
self.invalidate_blocks(count, &self.bitcoind)?;
118143

119-
let res = self.mine_blocks(count, None);
144+
let res = self.mine_blocks(count, None, &self.bitcoind);
120145
assert_eq!(
121-
self.client.get_block_count()?,
146+
self.bitcoind.client.get_block_count()?,
122147
start_height,
123148
"reorg should not result in height change"
124149
);
125150
res
126151
}
127152

128153
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)?;
154+
let start_height = self.bitcoind.client.get_block_count()?;
155+
self.invalidate_blocks(count, &self.bitcoind)?;
131156

132157
let res = (0..count)
133158
.map(|_| self.mine_empty_block())
134159
.collect::<Result<Vec<_>, _>>()?;
135160
assert_eq!(
136-
self.client.get_block_count()?,
161+
self.bitcoind.client.get_block_count()?,
137162
start_height,
138163
"reorg should not result in height change"
139164
);
@@ -142,6 +167,7 @@ impl TestEnv {
142167

143168
pub fn send(&self, address: &Address<NetworkChecked>, amount: Amount) -> anyhow::Result<Txid> {
144169
let txid = self
170+
.bitcoind
145171
.client
146172
.send_to_address(address, amount, None, None, None, None, None, None)?;
147173
Ok(txid)

0 commit comments

Comments
 (0)