Skip to content

Commit 80e95ce

Browse files
committed
chore(dash-spv): integrations tests cleanup
1 parent fc3d41c commit 80e95ce

3 files changed

Lines changed: 159 additions & 391 deletions

File tree

dash-spv/tests/dashd_sync/main.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,3 @@ mod tests_mempool;
1010
mod tests_multi_wallet;
1111
mod tests_restart;
1212
mod tests_transaction;
13-
mod tests_transaction_builder;

dash-spv/tests/dashd_sync/tests_transaction.rs

Lines changed: 159 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,23 @@
11
use dash_spv::sync::ProgressPercentage;
2-
use dashcore::Amount;
3-
4-
use super::helpers::wait_for_sync;
5-
use super::setup::TestContext;
2+
use dashcore::{Address, Amount, Network};
3+
use std::sync::Arc;
4+
use std::time::Duration;
5+
use tokio::sync::RwLock;
6+
7+
use super::helpers::{
8+
wait_for_mempool_tx, wait_for_sync, wait_for_wallet_synced, EMPTY_MNEMONIC, SECONDARY_MNEMONIC,
9+
};
10+
use super::setup::{create_and_start_client, create_test_wallet, TestContext};
611
use dash_spv::test_utils::TestChain;
12+
use dashcore::address::NetworkUnchecked;
13+
use key_wallet::account::ManagedAccountTrait;
14+
use key_wallet::wallet::managed_wallet_info::transaction_builder::{
15+
BuilderError, TransactionBuilder,
16+
};
17+
use key_wallet::wallet::managed_wallet_info::wallet_info_interface::WalletInfoInterface;
18+
use key_wallet::wallet::ManagedWalletInfo;
19+
use key_wallet::ManagedAccountType;
20+
use key_wallet_manager::{WalletId, WalletManager};
721

822
/// Verify incremental sync works by generating blocks after initial sync.
923
///
@@ -230,3 +244,144 @@ async fn test_multiple_transactions_across_blocks() {
230244
fees_paid
231245
);
232246
}
247+
248+
const MEMPOOL_TIMEOUT: Duration = Duration::from_secs(30);
249+
250+
async fn reserve_first_address(mnemonic: &str) -> Address {
251+
let (temp_mgr, temp_id) = create_test_wallet(mnemonic, Network::Regtest);
252+
253+
let reader = temp_mgr.read().await;
254+
let info = reader.get_wallet_info(&temp_id).expect("wallet info");
255+
let account = info.accounts().standard_bip44_accounts.get(&0).expect("BIP44 account 0");
256+
257+
let ManagedAccountType::Standard {
258+
external_addresses,
259+
..
260+
} = &account.managed_account_type()
261+
else {
262+
panic!("not a Standard account");
263+
};
264+
265+
external_addresses.unused_addresses().into_iter().next().expect("unused address")
266+
}
267+
268+
async fn build_and_sign(
269+
wallet: &Arc<RwLock<WalletManager<ManagedWalletInfo>>>,
270+
wallet_id: &WalletId,
271+
destination: &Address,
272+
amount: u64,
273+
) -> Result<(dashcore::Transaction, u64), BuilderError> {
274+
let dest_unchecked: Address<NetworkUnchecked> =
275+
destination.to_string().parse().expect("destination address");
276+
277+
let mut wallet_lock = wallet.write().await;
278+
let (w, info) = wallet_lock.get_wallet_and_info_mut(wallet_id).expect("wallet present");
279+
280+
let height = info.last_processed_height();
281+
let network = w.network;
282+
let account = w.get_bip44_account(0).expect("account 0").clone();
283+
let funds_account = info.accounts.standard_bip44_accounts.get_mut(&0).expect("account 0");
284+
let dest = dest_unchecked.require_network(network).expect("destination network");
285+
286+
TransactionBuilder::new()
287+
.set_current_height(height)
288+
.set_funding(funds_account, &account)
289+
.add_output(&dest, amount)
290+
.build_signed(w, |a| funds_account.address_derivation_path(&a))
291+
.await
292+
}
293+
294+
/// Build, sign and broadcast a tx via `TransactionBuilder`, then re-spend
295+
/// the resulting mempool change UTXO before its parent confirms.
296+
#[tokio::test]
297+
async fn test_spend_change_balance() {
298+
let Some(ctx) = TestContext::new(TestChain::Minimal).await else {
299+
return;
300+
};
301+
if !ctx.dashd.supports_mining {
302+
eprintln!("Skipping test (dashd RPC miner not available)");
303+
return;
304+
}
305+
306+
let (wallet, wallet_id) = create_test_wallet(EMPTY_MNEMONIC, Network::Regtest);
307+
let mut client_handle = create_and_start_client(&ctx.client_config, Arc::clone(&wallet)).await;
308+
wait_for_sync(&mut client_handle.progress_receiver, ctx.dashd.initial_height).await;
309+
310+
let receive_address = reserve_first_address(EMPTY_MNEMONIC).await;
311+
let funding_amount = Amount::from_sat(500_000_000);
312+
ctx.dashd.node.send_to_address(&receive_address, funding_amount);
313+
314+
let miner_address = ctx.dashd.node.get_new_address_from_wallet("default");
315+
ctx.dashd.node.generate_blocks(1, &miner_address);
316+
let funded_height = ctx.dashd.initial_height + 1;
317+
wait_for_sync(&mut client_handle.progress_receiver, funded_height).await;
318+
wait_for_wallet_synced(&wallet, &wallet_id, funded_height).await;
319+
320+
let dest_a = Address::dummy(Network::Regtest, 1);
321+
let (tx_a, _) =
322+
build_and_sign(&wallet, &wallet_id, &dest_a, 100_000_000).await.expect("build tx_a");
323+
324+
client_handle.client.broadcast_transaction(&tx_a).await.expect("broadcast tx_a");
325+
wait_for_mempool_tx(&mut client_handle.wallet_event_receiver, MEMPOOL_TIMEOUT)
326+
.await
327+
.expect("detect tx_a");
328+
329+
// The wallet's only UTXO now is the mempool change from tx_a, so a
330+
// successful build proves coin selection used it.
331+
let dest_b = Address::dummy(Network::Regtest, 2);
332+
let (tx_b, _) = build_and_sign(&wallet, &wallet_id, &dest_b, 50_000_000)
333+
.await
334+
.expect("spend mempool change");
335+
assert!(
336+
tx_b.input.iter().any(|i| i.previous_output.txid == tx_a.txid()),
337+
"tx_b must spend tx_a's mempool change UTXO",
338+
);
339+
340+
client_handle.client.broadcast_transaction(&tx_b).await.expect("broadcast tx_b");
341+
wait_for_mempool_tx(&mut client_handle.wallet_event_receiver, MEMPOOL_TIMEOUT)
342+
.await
343+
.expect("detect tx_b");
344+
345+
client_handle.stop().await;
346+
}
347+
348+
/// Spend an incoming mempool UTXO (we own the output, none of the inputs)
349+
/// before it confirms.
350+
#[tokio::test]
351+
async fn test_spend_incoming_balance() {
352+
let Some(ctx) = TestContext::new(TestChain::Minimal).await else {
353+
return;
354+
};
355+
if !ctx.dashd.supports_mining {
356+
eprintln!("Skipping test (dashd RPC miner not available)");
357+
return;
358+
}
359+
360+
let (wallet, wallet_id) = create_test_wallet(SECONDARY_MNEMONIC, Network::Regtest);
361+
let mut client_handle = create_and_start_client(&ctx.client_config, Arc::clone(&wallet)).await;
362+
wait_for_sync(&mut client_handle.progress_receiver, ctx.dashd.initial_height).await;
363+
364+
let receive_address = reserve_first_address(SECONDARY_MNEMONIC).await;
365+
let incoming_amount = Amount::from_sat(300_000_000);
366+
let incoming_txid = ctx.dashd.node.send_to_address(&receive_address, incoming_amount);
367+
368+
wait_for_mempool_tx(&mut client_handle.wallet_event_receiver, MEMPOOL_TIMEOUT)
369+
.await
370+
.expect("detect incoming");
371+
372+
let dest = Address::dummy(Network::Regtest, 3);
373+
let (tx, _) = build_and_sign(&wallet, &wallet_id, &dest, 150_000_000)
374+
.await
375+
.expect("spend unconfirmed incoming");
376+
assert!(
377+
tx.input.iter().any(|i| i.previous_output.txid == incoming_txid),
378+
"spend must reference the unconfirmed incoming txid",
379+
);
380+
381+
client_handle.client.broadcast_transaction(&tx).await.expect("broadcast spend");
382+
wait_for_mempool_tx(&mut client_handle.wallet_event_receiver, MEMPOOL_TIMEOUT)
383+
.await
384+
.expect("detect spend");
385+
386+
client_handle.stop().await;
387+
}

0 commit comments

Comments
 (0)