Skip to content

Commit

Permalink
feat: added electrsd support for TestEnv
Browse files Browse the repository at this point in the history
Added `electrsd` support so that `TestEnv` can serve `esplora` and
`electrum`.
  • Loading branch information
LagginTimes committed Oct 17, 2023
1 parent 0be5338 commit 50c5ccd
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 54 deletions.
83 changes: 56 additions & 27 deletions crates/bitcoind_rpc/tests/test_emitter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,23 @@ fn block_to_chain_update(block: &bitcoin::Block, height: u32) -> local_chain::Up
pub fn test_sync_local_chain() -> anyhow::Result<()> {
let env = TestEnv::new()?;
let mut local_chain = LocalChain::default();
let mut emitter = Emitter::from_height(&env.client, 0);
let tip = env.rpc_client().get_block_count()?;
let mut emitter = Emitter::from_height(env.rpc_client(), tip as u32);

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

(0..tip).for_each(|height| {
let changeset = BTreeMap::from([(height as u32, Some(exp_hashes[height as usize]))]);
local_chain.apply_changeset(&changeset);
});

// see if the emitter outputs the right blocks
println!("first sync:");
while let Some((height, block)) = emitter.next_block()? {
Expand Down Expand Up @@ -141,9 +149,18 @@ fn test_into_tx_graph() -> anyhow::Result<()> {
let env = TestEnv::new()?;

println!("getting new addresses!");
let addr_0 = env.client.get_new_address(None, None)?.assume_checked();
let addr_1 = env.client.get_new_address(None, None)?.assume_checked();
let addr_2 = env.client.get_new_address(None, None)?.assume_checked();
let addr_0 = env
.rpc_client()
.get_new_address(None, None)?
.assume_checked();
let addr_1 = env
.rpc_client()
.get_new_address(None, None)?
.assume_checked();
let addr_2 = env
.rpc_client()
.get_new_address(None, None)?
.assume_checked();
println!("got new addresses!");

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

let emitter = &mut Emitter::from_height(&env.client, 0);
let emitter = &mut Emitter::from_height(env.rpc_client(), 0);

while let Some((height, block)) = emitter.next_block()? {
let _ = chain.apply_update(block_to_chain_update(&block, height))?;
Expand All @@ -171,7 +188,7 @@ fn test_into_tx_graph() -> anyhow::Result<()> {
let exp_txids = {
let mut txids = BTreeSet::new();
for _ in 0..3 {
txids.insert(env.client.send_to_address(
txids.insert(env.rpc_client().send_to_address(
&addr_0,
Amount::from_sat(10_000),
None,
Expand Down Expand Up @@ -207,7 +224,7 @@ fn test_into_tx_graph() -> anyhow::Result<()> {

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

let env = TestEnv::new()?;
let mut emitter = Emitter::from_height(&env.client, EMITTER_START_HEIGHT as _);
let mut emitter = Emitter::from_height(env.rpc_client(), EMITTER_START_HEIGHT as _);

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

let env = TestEnv::new()?;
let mut emitter = Emitter::from_height(&env.client, 0);
let mut emitter = Emitter::from_height(env.rpc_client(), 0);

// setup addresses
let addr_to_mine = env.client.get_new_address(None, None)?.assume_checked();
let addr_to_mine = env
.rpc_client()
.get_new_address(None, None)?
.assume_checked();
let spk_to_track = ScriptBuf::new_v0_p2wsh(&WScriptHash::all_zeros());
let addr_to_track = Address::from_script(&spk_to_track, bitcoin::Network::Regtest)?;

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

// lock outputs that send to `addr_to_track`
let outpoints_to_lock = env
.client
.rpc_client()
.get_transaction(&txid, None)?
.transaction()?
.output
Expand All @@ -348,7 +368,7 @@ fn tx_can_become_unconfirmed_after_reorg() -> anyhow::Result<()> {
.filter(|(_, txo)| txo.script_pubkey == spk_to_track)
.map(|(vout, _)| OutPoint::new(txid, vout as _))
.collect::<Vec<_>>();
env.client.lock_unspent(&outpoints_to_lock)?;
env.rpc_client().lock_unspent(&outpoints_to_lock)?;

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

let env = TestEnv::new()?;
let mut emitter = Emitter::from_height(&env.client, 0);
let mut emitter = Emitter::from_height(env.rpc_client(), 0);

// mine blocks and sync up emitter
let addr = env.client.get_new_address(None, None)?.assume_checked();
let addr = env
.rpc_client()
.get_new_address(None, None)?
.assume_checked();
env.mine_blocks(BLOCKS_TO_MINE, Some(addr.clone()))?;
while emitter.next_header()?.is_some() {}

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

let env = TestEnv::new()?;
let mut emitter = Emitter::from_height(&env.client, 0);
let mut emitter = Emitter::from_height(env.rpc_client(), 0);

// mine blocks to get initial balance, sync emitter up to tip
let addr = env.client.get_new_address(None, None)?.assume_checked();
let addr = env
.rpc_client()
.get_new_address(None, None)?
.assume_checked();
env.mine_blocks(PREMINE_COUNT, Some(addr.clone()))?;
while emitter.next_header()?.is_some() {}

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

let env = TestEnv::new()?;
let mut emitter = Emitter::from_height(&env.client, 0);
let mut emitter = Emitter::from_height(env.rpc_client(), 0);

// mine blocks to get initial balance
let addr = env.client.get_new_address(None, None)?.assume_checked();
let addr = env
.rpc_client()
.get_new_address(None, None)?
.assume_checked();
env.mine_blocks(PREMINE_COUNT, Some(addr.clone()))?;

// introduce mempool tx at each block extension
Expand All @@ -549,7 +578,7 @@ fn mempool_during_reorg() -> anyhow::Result<()> {
.into_iter()
.map(|(tx, _)| tx.txid())
.collect::<BTreeSet<_>>(),
env.client
env.rpc_client()
.get_raw_mempool()?
.into_iter()
.collect::<BTreeSet<_>>(),
Expand All @@ -568,7 +597,7 @@ fn mempool_during_reorg() -> anyhow::Result<()> {
// emission.
// TODO: How can have have reorg logic in `TestEnv` NOT blacklast old blocks first?
let tx_introductions = dbg!(env
.client
.rpc_client()
.get_raw_mempool_verbose()?
.into_iter()
.map(|(txid, entry)| (txid, entry.height as usize))
Expand Down Expand Up @@ -643,7 +672,7 @@ fn no_agreement_point() -> anyhow::Result<()> {
let env = TestEnv::new()?;

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

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

// get hash for block 101a
let block_hash_101a = env.client.get_block_hash(101)?;
let block_hash_101a = env.rpc_client().get_block_hash(101)?;

// invalidate blocks 99a, 100a, 101a
env.client.invalidate_block(&block_hash_99a)?;
env.client.invalidate_block(&block_hash_100a)?;
env.client.invalidate_block(&block_hash_101a)?;
env.rpc_client().invalidate_block(&block_hash_99a)?;
env.rpc_client().invalidate_block(&block_hash_100a)?;
env.rpc_client().invalidate_block(&block_hash_101a)?;

// mine new blocks 99b, 100b, 101b
env.mine_blocks(3, None)?;
Expand Down
3 changes: 1 addition & 2 deletions crates/testenv/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ edition = "2021"
bitcoin = { version = "0.30", default-features = false }
bitcoincore-rpc = { version = "0.17" }
bdk_chain = { path = "../chain", version = "0.6", default-features = false }
bdk_bitcoind_rpc = { path = "../bitcoind_rpc", version = "0.1.0", default-features = false }
bitcoind = { version = "0.33", features = ["25_0"] }
electrsd = { version= "0.25.0", features = ["bitcoind_25_0", "esplora_a33e97e1", "legacy"] }
anyhow = { version = "1" }

[features]
Expand Down
76 changes: 51 additions & 25 deletions crates/testenv/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,43 +7,64 @@ use bitcoincore_rpc::{
bitcoincore_rpc_json::{GetBlockTemplateModes, GetBlockTemplateRules},
RpcApi,
};
use electrsd::electrum_client::ElectrumApi;

pub struct TestEnv {
#[allow(dead_code)]
pub daemon: bitcoind::BitcoinD,
pub client: bitcoincore_rpc::Client,
pub bitcoind: electrsd::bitcoind::BitcoinD,
pub electrsd: electrsd::ElectrsD,
}

impl TestEnv {
pub fn new() -> anyhow::Result<Self> {
let daemon = match std::env::var_os("TEST_BITCOIND") {
Some(bitcoind_path) => bitcoind::BitcoinD::new(bitcoind_path),
None => bitcoind::BitcoinD::from_downloaded(),
let bitcoind = match std::env::var_os("TEST_BITCOIND") {
Some(bitcoind_path) => electrsd::bitcoind::BitcoinD::new(bitcoind_path),
None => electrsd::bitcoind::BitcoinD::from_downloaded(),
}?;
let client = bitcoincore_rpc::Client::new(
&daemon.rpc_url(),
bitcoincore_rpc::Auth::CookieFile(daemon.params.cookie_file.clone()),
)?;
Ok(Self { daemon, client })

let electrsd = match std::env::var_os("ELECTRS_EXE") {
Some(env_electrs_exe) => electrsd::ElectrsD::new(env_electrs_exe, &bitcoind),
None => {
let mut electrs_conf = electrsd::Conf::default();
electrs_conf.http_enabled = true;
let electrs_exe = electrsd::downloaded_exe_path()
.expect("electrs version feature must be enabled");
electrsd::ElectrsD::with_conf(electrs_exe, &bitcoind, &electrs_conf)
}
}?;

Ok(Self { bitcoind, electrsd })
}

pub fn electrum_client(&self) -> &impl ElectrumApi {
&self.electrsd.client
}

pub fn rpc_client(&self) -> &impl RpcApi {
&self.bitcoind.client
}

pub fn mine_blocks(
&self,
count: usize,
address: Option<Address>,
bitcoind: &electrsd::bitcoind::BitcoinD,
) -> anyhow::Result<Vec<BlockHash>> {
let coinbase_address = match address {
Some(address) => address,
None => self.client.get_new_address(None, None)?.assume_checked(),
None => bitcoind
.client
.get_new_address(None, None)?
.assume_checked(),
};
let block_hashes = self
let block_hashes = bitcoind
.client
.generate_to_address(count as _, &coinbase_address)?;
Ok(block_hashes)
}

pub fn mine_empty_block(&self) -> anyhow::Result<(usize, BlockHash)> {
let bt = self.client.get_block_template(
let bt = self.bitcoind.client.get_block_template(
GetBlockTemplateModes::Template,
&[GetBlockTemplateRules::SegWit],
&[],
Expand Down Expand Up @@ -95,15 +116,19 @@ impl TestEnv {
}
}

self.client.submit_block(&block)?;
self.bitcoind.client.submit_block(&block)?;
Ok((bt.height as usize, block.block_hash()))
}

pub fn invalidate_blocks(&self, count: usize) -> anyhow::Result<()> {
let mut hash = self.client.get_best_block_hash()?;
pub fn invalidate_blocks(
&self,
count: usize,
bitcoind: &electrsd::bitcoind::BitcoinD,
) -> anyhow::Result<()> {
let mut hash = bitcoind.client.get_best_block_hash()?;
for _ in 0..count {
let prev_hash = self.client.get_block_info(&hash)?.previousblockhash;
self.client.invalidate_block(&hash)?;
let prev_hash = bitcoind.client.get_block_info(&hash)?.previousblockhash;
bitcoind.client.invalidate_block(&hash)?;
match prev_hash {
Some(prev_hash) => hash = prev_hash,
None => break,
Expand All @@ -113,27 +138,27 @@ impl TestEnv {
}

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

let res = self.mine_blocks(count, None);
let res = self.mine_blocks(count, None, &self.bitcoind);
assert_eq!(
self.client.get_block_count()?,
self.bitcoind.client.get_block_count()?,
start_height,
"reorg should not result in height change"
);
res
}

pub fn reorg_empty_blocks(&self, count: usize) -> anyhow::Result<Vec<(usize, BlockHash)>> {
let start_height = self.client.get_block_count()?;
self.invalidate_blocks(count)?;
let start_height = self.bitcoind.client.get_block_count()?;
self.invalidate_blocks(count, &self.bitcoind)?;

let res = (0..count)
.map(|_| self.mine_empty_block())
.collect::<Result<Vec<_>, _>>()?;
assert_eq!(
self.client.get_block_count()?,
self.bitcoind.client.get_block_count()?,
start_height,
"reorg should not result in height change"
);
Expand All @@ -142,6 +167,7 @@ impl TestEnv {

pub fn send(&self, address: &Address<NetworkChecked>, amount: Amount) -> anyhow::Result<Txid> {
let txid = self
.bitcoind
.client
.send_to_address(address, amount, None, None, None, None, None, None)?;
Ok(txid)
Expand Down

0 comments on commit 50c5ccd

Please sign in to comment.