@@ -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
0 commit comments