@@ -113,8 +113,9 @@ const NUM_WALLET_UTXOS: u32 = 50;
113113// boundaries. Mining commands are capped in `safe_mine_block_count` if
114114// unresolved HTLCs are near expiry.
115115const MINE_BLOCK_COUNTS : [ u32 ; 8 ] = [ 1 , 2 , 3 , 6 , 12 , 24 , 48 , 144 ] ;
116- // Finish-time relay/mining rounds are capped so cleanup cannot spin forever.
117- const MAX_FINISH_RELAY_MINE_ROUNDS : usize = 32 ;
116+ // Progress loops are capped so cleanup can drive realistic asynchronous
117+ // transaction work without letting a malformed state spin forever.
118+ const QUIESCENCE_ROUNDS : usize = 32 ;
118119
119120struct FuzzEstimator {
120121 ret_val : atomic:: AtomicU32 ,
@@ -402,10 +403,24 @@ impl ChainState {
402403 self . pending_txs . push ( ( txid, tx) ) ;
403404 }
404405
405- fn relay_transactions ( & mut self , txs : Vec < Transaction > ) {
406+ // Feeds broadcast transactions through modeled mempool admission. We need
407+ // this on ChainState so propagation and confirmation share one owner for
408+ // duplicate, locktime, input, and RBF rules. The return value reports
409+ // whether any broadcasts were drained, even if admission later ignores a
410+ // duplicate or invalid transaction.
411+ fn relay_transactions ( & mut self , txs : Vec < Transaction > ) -> bool {
412+ let found = !txs. is_empty ( ) ;
406413 for tx in txs {
407414 self . admit_tx_to_mempool ( tx) ;
408415 }
416+ found
417+ }
418+
419+ // Reports whether the modeled mempool is non-empty. Fuzz mining bytes and
420+ // cleanup loops use this to decide whether another mining pass can make
421+ // progress.
422+ fn has_pending_txs ( & self ) -> bool {
423+ !self . pending_txs . is_empty ( )
409424 }
410425
411426 // Mines `count` blocks, confirming the current mempool in the first block.
@@ -934,12 +949,17 @@ type ChanMan<'a> = ChannelManager<
934949 Arc < dyn Logger + MaybeSend + MaybeSync > ,
935950> ;
936951
952+ #[ inline]
953+ fn is_quiescent_disconnect_warning ( msg : & msgs:: WarningMessage ) -> bool {
954+ msg. data . contains ( "already sent splice_locked, cannot RBF" )
955+ }
956+
937957#[ inline]
938958fn assert_disconnect_action ( action : & msgs:: ErrorAction ) -> ( & msgs:: WarningMessage , bool ) {
939959 // Since sending/receiving messages may be delayed, `timer_tick_occurred` may cause a node to
940960 // disconnect their counterparty if they're expecting a timely response.
941961 if let msgs:: ErrorAction :: DisconnectPeerWithWarning { ref msg } = action {
942- let is_quiescent_msg = msg . data . contains ( "already sent splice_locked, cannot RBF" ) ;
962+ let is_quiescent_msg = is_quiescent_disconnect_warning ( msg ) ;
943963 if !msg. data . contains ( "Disconnecting due to timeout awaiting response" ) && !is_quiescent_msg
944964 {
945965 panic ! ( "Unexpected disconnect case: {}" , msg. data) ;
@@ -2611,30 +2631,7 @@ impl<'a, Out: Output + MaybeSend + MaybeSync> Harness<'a, Out> {
26112631 // Final invariants should not depend on the input ending with explicit relay
26122632 // and mining bytes.
26132633 fn finish ( & mut self ) {
2614- for _ in 0 ..MAX_FINISH_RELAY_MINE_ROUNDS {
2615- let mut txs = Vec :: new ( ) ;
2616- for node in & self . nodes {
2617- txs. extend ( node. broadcaster . txn_broadcasted . borrow_mut ( ) . drain ( ..) ) ;
2618- }
2619- self . chain_state . relay_transactions ( txs) ;
2620- if self . chain_state . pending_txs . is_empty ( ) {
2621- assert_test_invariants ( & self . nodes ) ;
2622- return ;
2623- }
2624- if self . mine_blocks ( ANTI_REORG_DELAY ) == 0 {
2625- // The input ended with pending mempool transactions but no safe
2626- // block left before an HTLC fail-back window. Leave them
2627- // unconfirmed rather than forcing finish cleanup to advance
2628- // the chain past that boundary.
2629- assert_test_invariants ( & self . nodes ) ;
2630- return ;
2631- }
2632- }
2633- assert ! (
2634- !self . nodes. iter( ) . any( |node| !node. broadcaster. txn_broadcasted. borrow( ) . is_empty( ) )
2635- && self . chain_state. pending_txs. is_empty( ) ,
2636- "finish tx mining loop failed to quiesce" ,
2637- ) ;
2634+ self . mine_relayed_txs_until_quiet ( "finish" ) ;
26382635 assert_test_invariants ( & self . nodes ) ;
26392636 }
26402637
@@ -3358,6 +3355,18 @@ impl<'a, Out: Output + MaybeSend + MaybeSync> Harness<'a, Out> {
33583355 self . chain_state . relay_transactions ( txs) ;
33593356 }
33603357
3358+ // Relays every node's pending broadcasts into the modeled mempool. Cleanup
3359+ // uses this when it should not depend on which peer fuzz bytes propagate.
3360+ // The bool reports whether any broadcasts were drained, not whether they
3361+ // were all admitted.
3362+ fn relay_all_broadcasts ( & mut self ) -> bool {
3363+ let mut txs = Vec :: new ( ) ;
3364+ for node in & self . nodes {
3365+ txs. extend ( node. broadcaster . txn_broadcasted . borrow_mut ( ) . drain ( ..) ) ;
3366+ }
3367+ self . chain_state . relay_transactions ( txs)
3368+ }
3369+
33613370 fn earliest_pending_htlc_expiry ( & self ) -> Option < u32 > {
33623371 let mut earliest_expiry: Option < u32 > = None ;
33633372 for node in & self . nodes {
@@ -3441,6 +3450,27 @@ impl<'a, Out: Output + MaybeSend + MaybeSync> Harness<'a, Out> {
34413450 }
34423451 count
34433452 }
3453+
3454+ // Repeatedly relays broadcasts and mines pending transactions to depth. We
3455+ // need this for finish paths where confirmed transactions may broadcast
3456+ // child transactions that also need confirmation.
3457+ fn mine_relayed_txs_until_quiet ( & mut self , context : & str ) {
3458+ for _ in 0 ..QUIESCENCE_ROUNDS {
3459+ self . relay_all_broadcasts ( ) ;
3460+ if !self . chain_state . has_pending_txs ( ) {
3461+ return ;
3462+ }
3463+ assert ! (
3464+ self . mine_blocks( ANTI_REORG_DELAY ) > 0 ,
3465+ "{context} cannot mine pending mempool transactions without crossing an unresolved HTLC timeout deadline"
3466+ ) ;
3467+ }
3468+ assert ! (
3469+ !self . nodes. iter( ) . any( |node| !node. broadcaster. txn_broadcasted. borrow( ) . is_empty( ) )
3470+ && !self . chain_state. has_pending_txs( ) ,
3471+ "{context} tx mining loop failed to quiesce" ,
3472+ ) ;
3473+ }
34443474}
34453475
34463476#[ inline]
0 commit comments