Skip to content

Commit 4ca8b52

Browse files
shumkovclaude
andcommitted
fix: restore base branch E2E harness flow (register after SPV start)
- Reverted to base branch SPV initialization ordering - wait_for_spv_running replaces wait_for_spv_syncing_or_running - Removed birth_height setting and filter_committed_height reset - Per-commit workdir instead of stable workdir - 30s balance timeout (SPV already synced) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 5412d9e commit 4ca8b52

2 files changed

Lines changed: 68 additions & 114 deletions

File tree

tests/backend-e2e/framework/harness.rs

Lines changed: 62 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -90,17 +90,16 @@ impl BackendTestContext {
9090
tracing::debug!(".env not loaded ({e}), relying on environment");
9191
}
9292

93-
// Persistent workdir — stable across commits so SPV header/filter
94-
// cache is reused. Wallet state (UTXOs, balances) is NOT persisted
95-
// yet (apply() TODO), so each run must rescan filters from birth_height.
96-
// We clear the SPV state directory to reset filter_committed_height,
97-
// but keep headers/filters cached for fast re-download.
98-
let workdir = std::env::temp_dir().join("dash-evo-e2e-testnet");
99-
// TODO: Once apply() restores wallet state from persistence, remove
100-
// this filter_committed_height reset. Currently wallet UTXOs/balances
101-
// are lost on restart, so we must force a filter rescan from
102-
// birth_height each run. We do NOT delete the SPV cache — headers
103-
// and filters take ~90 min to re-download from scratch.
93+
// Persistent workdir keyed by git revision
94+
let git_hash = std::process::Command::new("git")
95+
.args(["rev-parse", "--short", "HEAD"])
96+
.output()
97+
.ok()
98+
.and_then(|o| String::from_utf8(o.stdout).ok())
99+
.map(|s| s.trim().to_string())
100+
.unwrap_or_else(|| "unknown".to_string());
101+
102+
let workdir = std::env::temp_dir().join(format!("dash-evo-e2e-testnet-{}", git_hash));
104103
std::fs::create_dir_all(&workdir).expect("Failed to create workdir");
105104
tracing::info!("E2E workdir: {}", workdir.display());
106105

@@ -129,82 +128,8 @@ impl BackendTestContext {
129128
)
130129
.expect("Failed to create AppContext for testnet");
131130

132-
// E2E_WALLET_MNEMONIC is required
133-
let mnemonic_phrase = std::env::var("E2E_WALLET_MNEMONIC").unwrap_or_else(|_| {
134-
panic!(
135-
"E2E_WALLET_MNEMONIC is not set.\n\
136-
This environment variable is required for backend E2E tests.\n\
137-
Set it to a BIP-39 mnemonic of a pre-funded testnet wallet.\n\
138-
Example: E2E_WALLET_MNEMONIC=\"word1 word2 word3 ... word12\"\n\
139-
You can also add it to the project root .env file."
140-
);
141-
});
142-
143-
tracing::info!("Restoring framework wallet from E2E_WALLET_MNEMONIC");
144-
let mnemonic = Mnemonic::parse_in(Language::English, &mnemonic_phrase)
145-
.expect("Invalid E2E_WALLET_MNEMONIC");
146-
147-
let seed = mnemonic.to_seed("");
148-
let wallet = dash_evo_tool::model::wallet::Wallet::new_from_seed(
149-
seed,
150-
Network::Testnet,
151-
Some("E2E Framework Wallet".to_string()),
152-
None,
153-
)
154-
.expect("Failed to create framework wallet");
155-
156-
let framework_wallet_hash = wallet.seed_hash();
157-
158-
// Register wallet BEFORE starting SPV so the wallet's addresses
159-
// are in the bloom filter from the start. This is important because
160-
// the SPV filter scan checks monitored_addresses() once at startup.
161-
match app_context.register_wallet(wallet) {
162-
Ok((hash, _)) => {
163-
tracing::info!("Registered framework wallet (seed_hash: {:?})", &hash[..4]);
164-
}
165-
Err(TaskError::WalletAlreadyImported) => {
166-
tracing::info!("Framework wallet already registered (reusing from persistent DB)");
167-
}
168-
Err(e) => panic!("Failed to register framework wallet: {}", e),
169-
}
170-
171-
// Set birth_height on the framework wallet so SPV filter scanning
172-
// starts from a recent block instead of genesis. Without this, a
173-
// fresh testnet scan (~1.4M blocks) takes >90 minutes and exceeds
174-
// the test timeout. E2E_WALLET_BIRTH_HEIGHT can override the default.
175-
{
176-
use dash_sdk::dpp::key_wallet::wallet::managed_wallet_info::wallet_info_interface::WalletInfoInterface;
177-
178-
let birth_height: u32 = std::env::var("E2E_WALLET_BIRTH_HEIGHT")
179-
.ok()
180-
.and_then(|s| s.parse().ok())
181-
.unwrap_or(1_400_000);
182-
183-
// Access the PlatformWallet through the old Wallet model.
184-
let wallets = app_context.wallets().read().expect("wallets lock");
185-
if let Some(wallet_arc) = wallets.get(&framework_wallet_hash) {
186-
let wallet_guard = wallet_arc.read().expect("wallet lock");
187-
if let Some(pw) = &wallet_guard.platform_wallet {
188-
if let Some(mut wi) = pw.try_state_mut() {
189-
if wi.managed_state.wallet_info().birth_height() == 0 {
190-
wi.managed_state.wallet_info_mut().set_birth_height(birth_height);
191-
tracing::info!("Set framework wallet birth_height to {}", birth_height);
192-
}
193-
}
194-
}
195-
}
196-
}
197-
198-
// Switch to SPV mode and start (wallet already registered above)
131+
// Switch to SPV mode and start
199132
app_context.set_core_backend_mode(CoreBackendMode::Spv);
200-
// Reset filter_committed_height so the filter scan restarts from
201-
// birth_height. Without this, cached committed height from a previous
202-
// run causes the scan to skip historical blocks, and since wallet
203-
// state isn't persisted yet (apply() TODO), the balance stays 0.
204-
tokio::task::block_in_place(|| {
205-
tokio::runtime::Handle::current()
206-
.block_on(app_context.reset_spv_filter_committed_height());
207-
});
208133
app_context.start_spv().expect("Failed to start SPV");
209134

210135
// Stash the cancellation token so the panic hook can stop SPV if
@@ -243,25 +168,66 @@ impl BackendTestContext {
243168
.expect("SPV failed to connect to any peers within 60s");
244169
tracing::info!("SPV connected to peers");
245170

171+
// E2E_WALLET_MNEMONIC is required
172+
let mnemonic_phrase = std::env::var("E2E_WALLET_MNEMONIC").unwrap_or_else(|_| {
173+
panic!(
174+
"E2E_WALLET_MNEMONIC is not set.\n\
175+
This environment variable is required for backend E2E tests.\n\
176+
Set it to a BIP-39 mnemonic of a pre-funded testnet wallet.\n\
177+
Example: E2E_WALLET_MNEMONIC=\"word1 word2 word3 ... word12\"\n\
178+
You can also add it to the project root .env file."
179+
);
180+
});
181+
182+
tracing::info!("Restoring framework wallet from E2E_WALLET_MNEMONIC");
183+
let mnemonic = Mnemonic::parse_in(Language::English, &mnemonic_phrase)
184+
.expect("Invalid E2E_WALLET_MNEMONIC");
185+
186+
let seed = mnemonic.to_seed("");
187+
let wallet = dash_evo_tool::model::wallet::Wallet::new_from_seed(
188+
seed,
189+
Network::Testnet,
190+
Some("E2E Framework Wallet".to_string()),
191+
None,
192+
)
193+
.expect("Failed to create framework wallet");
194+
195+
let framework_wallet_hash = wallet.seed_hash();
196+
197+
// Try to register; if the wallet already exists (persistent DB), just look it up.
198+
match app_context.register_wallet(wallet) {
199+
Ok((hash, _)) => {
200+
tracing::info!("Registered framework wallet (seed_hash: {:?})", &hash[..4]);
201+
}
202+
Err(TaskError::WalletAlreadyImported) => {
203+
tracing::info!("Framework wallet already registered (reusing from persistent DB)");
204+
}
205+
Err(e) => panic!("Failed to register framework wallet: {}", e),
206+
}
207+
246208
// Wait for wallet to appear in SPV
247209
wait::wait_for_wallet_in_spv(&app_context, framework_wallet_hash, Duration::from_secs(30))
248210
.await
249211
.expect("Framework wallet not picked up by SPV");
250212

251-
// Wait for SPV to sync and funds to become spendable
252-
// First run with a fresh SPV cache requires downloading ~54K filter
253-
// headers + filters (from birth_height). This takes 2-5 minutes.
254-
// Subsequent runs use cached data and complete in ~8 seconds.
255-
let balance_timeout = std::env::var("E2E_BALANCE_TIMEOUT_SECS")
256-
.ok()
257-
.and_then(|s| s.parse().ok())
258-
.unwrap_or(300);
259-
tracing::info!("Waiting for SPV to sync framework wallet spendable balance (timeout: {}s)...", balance_timeout);
213+
// Wait for SPV to fully sync (including masternodes) so MempoolManager
214+
// is active and bloom filter is built before any test broadcasts.
215+
// This must come BEFORE the spendable balance check — wallet balances
216+
// are only available after compact filter sync completes.
217+
tracing::info!("Waiting for SPV to complete full sync (masternodes + mempool)...");
218+
wait::wait_for_spv_running(&app_context, Duration::from_secs(300))
219+
.await
220+
.expect("SPV did not reach Running state within 300s");
221+
tracing::info!("SPV fully synced — mempool bloom filter active");
222+
223+
// Now check framework wallet balance — SPV has synced, so balances
224+
// should be available immediately (no need for a long timeout).
225+
tracing::info!("Waiting for SPV to sync framework wallet spendable balance...");
260226
match wait::wait_for_spendable_balance(
261227
&app_context,
262228
framework_wallet_hash,
263229
1, // at least 1 duff spendable
264-
Duration::from_secs(balance_timeout),
230+
Duration::from_secs(30),
265231
)
266232
.await
267233
{
@@ -303,16 +269,6 @@ impl BackendTestContext {
303269
}
304270
}
305271

306-
// Wait for SPV filters to sync so mempool bloom filter is active
307-
// before any test broadcasts. We don't require masternodes to sync
308-
// because testnet quorum rotation data can fail (QRInfo errors).
309-
// The wallet is fully functional for transactions without masternodes.
310-
tracing::info!("Waiting for SPV filters to sync...");
311-
wait::wait_for_spv_syncing_or_running(&app_context, Duration::from_secs(120))
312-
.await
313-
.expect("SPV did not start syncing within 120s");
314-
tracing::info!("SPV filters synced — ready for transactions");
315-
316272
// Verify balance is above minimum threshold
317273
funding::verify_framework_funded(&app_context, framework_wallet_hash).await;
318274

tests/backend-e2e/framework/wait.rs

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -162,20 +162,18 @@ pub async fn wait_for_wallet_in_spv(
162162
.map_err(|_| "Timed out waiting for wallet in SPV".to_string())
163163
}
164164

165-
/// Wait for SPV to reach at least Syncing state (filters active).
165+
/// Wait for SPV to complete initial sync (all managers including masternodes).
166166
///
167-
/// Accepts both `Syncing` and `Running`. Does NOT require masternode
168-
/// sync which can fail on testnet (QRInfo errors). The wallet is fully
169-
/// functional for transactions once filters are synced.
170-
pub async fn wait_for_spv_syncing_or_running(
167+
/// `SpvStatus::Running` is set after `SyncComplete` fires, which means
168+
/// MempoolManager is activated and bloom filter is built.
169+
pub async fn wait_for_spv_running(
171170
app_context: &Arc<AppContext>,
172171
wait_timeout: Duration,
173172
) -> Result<(), String> {
174173
use dash_evo_tool::spv::SpvStatus;
175174
timeout(wait_timeout, async {
176175
loop {
177-
let status = app_context.connection_status().spv_status();
178-
if matches!(status, SpvStatus::Syncing | SpvStatus::Running) {
176+
if app_context.connection_status().spv_status() == SpvStatus::Running {
179177
return;
180178
}
181179
tokio::time::sleep(Duration::from_millis(500)).await;
@@ -184,7 +182,7 @@ pub async fn wait_for_spv_syncing_or_running(
184182
.await
185183
.map_err(|_| {
186184
format!(
187-
"Timed out after {:?} waiting for SPV to reach Syncing/Running state",
185+
"Timed out after {:?} waiting for SPV to reach Running state",
188186
wait_timeout
189187
)
190188
})

0 commit comments

Comments
 (0)