@@ -499,8 +499,8 @@ async fn validate_block_number_mismatch() {
499499 ) ;
500500}
501501
502- /// A block subscription replays the backed-up blocks from the requested height and then streams
503- /// newly signed blocks as they arrive .
502+ /// A block subscription replays the finalized backed-up blocks from the requested height and then
503+ /// streams blocks live as they are finalized (i.e. once a child block is signed on top of them) .
504504#[ tokio:: test]
505505async fn block_subscription_replays_then_follows ( ) {
506506 use std:: time:: Duration ;
@@ -511,11 +511,14 @@ async fn block_subscription_replays_then_follows() {
511511
512512 let mut tv = TestValidator :: new ( ) . await ;
513513
514- // Sign blocks 1 and 2 so the validator backs them up to its block store.
514+ // Sign blocks 1, 2 and 3. The signed tip is 3, so blocks 1 and 2 are finalized while block 3
515+ // remains the provisional, replaceable tip and is withheld from subscribers.
516+ tv. apply_empty_block ( ) . await ;
515517 tv. apply_empty_block ( ) . await ;
516518 tv. apply_empty_block ( ) . await ;
517519
518- // Subscribe from the first signed block and confirm the backed-up blocks are replayed in order.
520+ // Subscribe from the first signed block and confirm only the finalized blocks (1 and 2) are
521+ // replayed; the provisional tip (block 3) is withheld.
519522 let mut stream = tv. call_block_subscription ( 1 ) . await ;
520523 for expected in 1 ..=2 {
521524 let response = tokio:: time:: timeout ( Duration :: from_secs ( 5 ) , stream. next ( ) )
@@ -528,7 +531,7 @@ async fn block_subscription_replays_then_follows() {
528531 assert_eq ! ( response. committed_chain_tip, 2 ) ;
529532 }
530533
531- // Sign a new block and confirm it is streamed live to the existing subscriber .
534+ // Sign a new block (4), finalizing block 3, and confirm block 3 is now streamed live.
532535 tv. apply_empty_block ( ) . await ;
533536 let response = tokio:: time:: timeout ( Duration :: from_secs ( 5 ) , stream. next ( ) )
534537 . await
@@ -539,3 +542,36 @@ async fn block_subscription_replays_then_follows() {
539542 assert_eq ! ( block. header( ) . block_num( ) . as_u32( ) , 3 ) ;
540543 assert_eq ! ( response. committed_chain_tip, 3 ) ;
541544}
545+
546+ /// The provisional chain tip (the most recently signed block) must be withheld from subscribers
547+ /// until a child block is signed on top of it, since it can still be replaced.
548+ #[ tokio:: test]
549+ async fn provisional_tip_is_withheld_from_subscribers ( ) {
550+ use std:: time:: Duration ;
551+
552+ use miden_protocol:: block:: SignedBlock ;
553+ use miden_tx:: utils:: serde:: Deserializable ;
554+ use tokio_stream:: StreamExt ;
555+
556+ let mut tv = TestValidator :: new ( ) . await ;
557+
558+ // Sign blocks 1 and 2. Block 1 is finalized (it has a child); block 2 is the provisional tip.
559+ tv. apply_empty_block ( ) . await ;
560+ tv. apply_empty_block ( ) . await ;
561+
562+ let mut stream = tv. call_block_subscription ( 1 ) . await ;
563+
564+ // Block 1 is finalized and streamed.
565+ let response = tokio:: time:: timeout ( Duration :: from_secs ( 5 ) , stream. next ( ) )
566+ . await
567+ . expect ( "finalized block should arrive promptly" )
568+ . expect ( "stream should not end" )
569+ . expect ( "stream item should not be an error" ) ;
570+ let block = SignedBlock :: read_from_bytes ( & response. block ) . expect ( "valid signed block" ) ;
571+ assert_eq ! ( block. header( ) . block_num( ) . as_u32( ) , 1 ) ;
572+ assert_eq ! ( response. committed_chain_tip, 1 ) ;
573+
574+ // Block 2 is the provisional tip and must not be streamed while it remains replaceable.
575+ let next = tokio:: time:: timeout ( Duration :: from_millis ( 500 ) , stream. next ( ) ) . await ;
576+ assert ! ( next. is_err( ) , "provisional tip (block 2) should be withheld from subscribers" ) ;
577+ }
0 commit comments