22aa534d7648e5808414ea3adfcfb702572bd6c9 feat: use `Amount` on `TxBuilder::add_recipient` (Leonardo Lima) d5c0e7200cba0c3b4d3e3fbea168cd07ee6c1d2c feat: use `Amount` on `spk_txout_index` and related (Leonardo Lima) 8a33d98db977a07e130ad57fa9c658a5c90d4a4b feat: update `wallet::Balance` to use `bitcoin::Amount` (Leonardo Lima) Pull request description: fixes #823 <!-- You can erase any parts of this template not applicable to your Pull Request. --> ### Description It's being used on `Balance`, and throughout the code, an `u64` represents the amount, which relies on the user to infer its sats, not millisats, or any other representation. It updates the usage of `u64` on `Balance`, and other APIs: - `TxParams::add_recipient` - `KeyChainTxOutIndex::sent_and_received`, `KeyChainTxOutIndex::net_value` - `SpkTxOutIndex::sent_and_received`, `SpkTxOutIndex::net_value` <!-- Describe the purpose of this PR, what's being adding and/or fixed --> ### Notes to the reviewers <!-- In this section you can include notes directed to the reviewers, like explaining why some parts of the PR were done in a specific way --> It updates some of the APIs to expect the `bitcoin::Amount`, but it does not update internal usage of u64, such as `TxParams` still expects and uses `u64`, please see the PR comments for related discussion. ### Changelog notice <!-- Notice the release manager should include in the release tag message changelog --> <!-- See https://keepachangelog.com/en/1.0.0/ for examples --> - Changed the `keychain::Balance` struct fields to use `Amount` instead of `u64`. - Changed the `add_recipient` method on `TxBuilder` implementation to expect `bitcoin::Amount`. - Changed the `sent_and_received`, and `net_value` methods on `KeyChainTxOutIndex` to expect `bitcoin::Amount`. - Changed the `sent_and_received`, and `net_value` methods on `SpkTxOutIndex` to expect `bitcoin::Amount`. ### Checklists #### All Submissions: * [x] I've signed all my commits * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md) * [x] I ran `cargo fmt` and `cargo clippy` before committing #### New Features: * [x] I've added tests for the new feature * [x] I've added docs for the new feature #### Bugfixes: * [x] This pull request breaks the existing API * [ ] I've added tests to reproduce the issue which are now passing * [x] I'm linking the issue being fixed by this PR ACKs for top commit: evanlinjin: ACK 22aa534d7648e5808414ea3adfcfb702572bd6c9 Tree-SHA512: c4e8198d96c0d66cc3d2e4149e8a56bb7565b9cd49ff42113eaebd24b1d7bfeecd7124db0b06524b78b8891ee1bde1546705b80afad408f48495cf3c02446d02
190 lines
6.4 KiB
Rust
190 lines
6.4 KiB
Rust
use bdk_chain::{
|
|
bitcoin::{hashes::Hash, Address, Amount, ScriptBuf, WScriptHash},
|
|
keychain::Balance,
|
|
local_chain::LocalChain,
|
|
ConfirmationTimeHeightAnchor, IndexedTxGraph, SpkTxOutIndex,
|
|
};
|
|
use bdk_electrum::{ElectrumExt, ElectrumUpdate};
|
|
use bdk_testenv::{anyhow, anyhow::Result, bitcoincore_rpc::RpcApi, TestEnv};
|
|
|
|
fn get_balance(
|
|
recv_chain: &LocalChain,
|
|
recv_graph: &IndexedTxGraph<ConfirmationTimeHeightAnchor, SpkTxOutIndex<()>>,
|
|
) -> Result<Balance> {
|
|
let chain_tip = recv_chain.tip().block_id();
|
|
let outpoints = recv_graph.index.outpoints().clone();
|
|
let balance = recv_graph
|
|
.graph()
|
|
.balance(recv_chain, chain_tip, outpoints, |_, _| true);
|
|
Ok(balance)
|
|
}
|
|
|
|
/// Ensure that [`ElectrumExt`] can sync properly.
|
|
///
|
|
/// 1. Mine 101 blocks.
|
|
/// 2. Send a tx.
|
|
/// 3. Mine extra block to confirm sent tx.
|
|
/// 4. Check [`Balance`] to ensure tx is confirmed.
|
|
#[test]
|
|
fn scan_detects_confirmed_tx() -> Result<()> {
|
|
const SEND_AMOUNT: Amount = Amount::from_sat(10_000);
|
|
|
|
let env = TestEnv::new()?;
|
|
let client = electrum_client::Client::new(env.electrsd.electrum_url.as_str())?;
|
|
|
|
// Setup addresses.
|
|
let addr_to_mine = env
|
|
.bitcoind
|
|
.client
|
|
.get_new_address(None, None)?
|
|
.assume_checked();
|
|
let spk_to_track = ScriptBuf::new_p2wsh(&WScriptHash::all_zeros());
|
|
let addr_to_track = Address::from_script(&spk_to_track, bdk_chain::bitcoin::Network::Regtest)?;
|
|
|
|
// Setup receiver.
|
|
let (mut recv_chain, _) = LocalChain::from_genesis_hash(env.bitcoind.client.get_block_hash(0)?);
|
|
let mut recv_graph = IndexedTxGraph::<ConfirmationTimeHeightAnchor, _>::new({
|
|
let mut recv_index = SpkTxOutIndex::default();
|
|
recv_index.insert_spk((), spk_to_track.clone());
|
|
recv_index
|
|
});
|
|
|
|
// Mine some blocks.
|
|
env.mine_blocks(101, Some(addr_to_mine))?;
|
|
|
|
// Create transaction that is tracked by our receiver.
|
|
env.send(&addr_to_track, SEND_AMOUNT)?;
|
|
|
|
// Mine a block to confirm sent tx.
|
|
env.mine_blocks(1, None)?;
|
|
|
|
// Sync up to tip.
|
|
env.wait_until_electrum_sees_block()?;
|
|
let ElectrumUpdate {
|
|
chain_update,
|
|
relevant_txids,
|
|
} = client.sync(recv_chain.tip(), [spk_to_track], None, None, 5)?;
|
|
|
|
let missing = relevant_txids.missing_full_txs(recv_graph.graph());
|
|
let graph_update = relevant_txids.into_confirmation_time_tx_graph(&client, missing)?;
|
|
let _ = recv_chain
|
|
.apply_update(chain_update)
|
|
.map_err(|err| anyhow::anyhow!("LocalChain update error: {:?}", err))?;
|
|
let _ = recv_graph.apply_update(graph_update);
|
|
|
|
// Check to see if tx is confirmed.
|
|
assert_eq!(
|
|
get_balance(&recv_chain, &recv_graph)?,
|
|
Balance {
|
|
confirmed: SEND_AMOUNT,
|
|
..Balance::default()
|
|
},
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Ensure that confirmed txs that are reorged become unconfirmed.
|
|
///
|
|
/// 1. Mine 101 blocks.
|
|
/// 2. Mine 8 blocks with a confirmed tx in each.
|
|
/// 3. Perform 8 separate reorgs on each block with a confirmed tx.
|
|
/// 4. Check [`Balance`] after each reorg to ensure unconfirmed amount is correct.
|
|
#[test]
|
|
fn tx_can_become_unconfirmed_after_reorg() -> Result<()> {
|
|
const REORG_COUNT: usize = 8;
|
|
const SEND_AMOUNT: Amount = Amount::from_sat(10_000);
|
|
|
|
let env = TestEnv::new()?;
|
|
let client = electrum_client::Client::new(env.electrsd.electrum_url.as_str())?;
|
|
|
|
// Setup addresses.
|
|
let addr_to_mine = env
|
|
.bitcoind
|
|
.client
|
|
.get_new_address(None, None)?
|
|
.assume_checked();
|
|
let spk_to_track = ScriptBuf::new_p2wsh(&WScriptHash::all_zeros());
|
|
let addr_to_track = Address::from_script(&spk_to_track, bdk_chain::bitcoin::Network::Regtest)?;
|
|
|
|
// Setup receiver.
|
|
let (mut recv_chain, _) = LocalChain::from_genesis_hash(env.bitcoind.client.get_block_hash(0)?);
|
|
let mut recv_graph = IndexedTxGraph::<ConfirmationTimeHeightAnchor, _>::new({
|
|
let mut recv_index = SpkTxOutIndex::default();
|
|
recv_index.insert_spk((), spk_to_track.clone());
|
|
recv_index
|
|
});
|
|
|
|
// Mine some blocks.
|
|
env.mine_blocks(101, Some(addr_to_mine))?;
|
|
|
|
// Create transactions that are tracked by our receiver.
|
|
for _ in 0..REORG_COUNT {
|
|
env.send(&addr_to_track, SEND_AMOUNT)?;
|
|
env.mine_blocks(1, None)?;
|
|
}
|
|
|
|
// Sync up to tip.
|
|
env.wait_until_electrum_sees_block()?;
|
|
let ElectrumUpdate {
|
|
chain_update,
|
|
relevant_txids,
|
|
} = client.sync(recv_chain.tip(), [spk_to_track.clone()], None, None, 5)?;
|
|
|
|
let missing = relevant_txids.missing_full_txs(recv_graph.graph());
|
|
let graph_update = relevant_txids.into_confirmation_time_tx_graph(&client, missing)?;
|
|
let _ = recv_chain
|
|
.apply_update(chain_update)
|
|
.map_err(|err| anyhow::anyhow!("LocalChain update error: {:?}", err))?;
|
|
let _ = recv_graph.apply_update(graph_update.clone());
|
|
|
|
// Retain a snapshot of all anchors before reorg process.
|
|
let initial_anchors = graph_update.all_anchors();
|
|
|
|
// Check if initial balance is correct.
|
|
assert_eq!(
|
|
get_balance(&recv_chain, &recv_graph)?,
|
|
Balance {
|
|
confirmed: SEND_AMOUNT * REORG_COUNT as u64,
|
|
..Balance::default()
|
|
},
|
|
"initial balance must be correct",
|
|
);
|
|
|
|
// Perform reorgs with different depths.
|
|
for depth in 1..=REORG_COUNT {
|
|
env.reorg_empty_blocks(depth)?;
|
|
|
|
env.wait_until_electrum_sees_block()?;
|
|
let ElectrumUpdate {
|
|
chain_update,
|
|
relevant_txids,
|
|
} = client.sync(recv_chain.tip(), [spk_to_track.clone()], None, None, 5)?;
|
|
|
|
let missing = relevant_txids.missing_full_txs(recv_graph.graph());
|
|
let graph_update = relevant_txids.into_confirmation_time_tx_graph(&client, missing)?;
|
|
let _ = recv_chain
|
|
.apply_update(chain_update)
|
|
.map_err(|err| anyhow::anyhow!("LocalChain update error: {:?}", err))?;
|
|
|
|
// Check to see if a new anchor is added during current reorg.
|
|
if !initial_anchors.is_superset(graph_update.all_anchors()) {
|
|
println!("New anchor added at reorg depth {}", depth);
|
|
}
|
|
let _ = recv_graph.apply_update(graph_update);
|
|
|
|
assert_eq!(
|
|
get_balance(&recv_chain, &recv_graph)?,
|
|
Balance {
|
|
confirmed: SEND_AMOUNT * (REORG_COUNT - depth) as u64,
|
|
trusted_pending: SEND_AMOUNT * depth as u64,
|
|
..Balance::default()
|
|
},
|
|
"reorg_count: {}",
|
|
depth,
|
|
);
|
|
}
|
|
|
|
Ok(())
|
|
}
|