@@ -248,6 +248,11 @@ def test_snapshot_not_on_most_work_chain(self, dump_output_path):
248248 node1 .submitheader (main_block1 )
249249 node1 .submitheader (main_block2 )
250250
251+ def assert_only_network_limited_service (self , node ):
252+ node_services = node .getnetworkinfo ()['localservicesnames' ]
253+ assert 'NETWORK' not in node_services
254+ assert 'NETWORK_LIMITED' in node_services
255+
251256 def run_test (self ):
252257 """
253258 Bring up two (disconnected) nodes, mine some new blocks on the first,
@@ -381,13 +386,20 @@ def check_dump_output(output):
381386 self .test_snapshot_block_invalidated (dump_output ['path' ])
382387 self .test_snapshot_not_on_most_work_chain (dump_output ['path' ])
383388
389+ # Prune-node sanity check
390+ assert 'NETWORK' not in n1 .getnetworkinfo ()['localservicesnames' ]
391+
384392 self .log .info (f"Loading snapshot into second node from { dump_output ['path' ]} " )
385393 # This node's tip is on an ancestor block of the snapshot, which should
386394 # be the normal case
387395 loaded = n1 .loadtxoutset (dump_output ['path' ])
388396 assert_equal (loaded ['coins_loaded' ], SNAPSHOT_BASE_HEIGHT )
389397 assert_equal (loaded ['base_height' ], SNAPSHOT_BASE_HEIGHT )
390398
399+ self .log .info ("Confirm that local services remain unchanged" )
400+ # Since n1 is a pruned node, the 'NETWORK' service flag must always be unset.
401+ self .assert_only_network_limited_service (n1 )
402+
391403 self .log .info ("Check that UTXO-querying RPCs operate on snapshot chainstate" )
392404 snapshot_hash = loaded ['tip_hash' ]
393405 snapshot_num_coins = loaded ['coins_loaded' ]
@@ -491,6 +503,9 @@ def check_tx_counts(final: bool) -> None:
491503 self .restart_node (1 , extra_args = [
492504 f"-stopatheight={ PAUSE_HEIGHT } " , * self .extra_args [1 ]])
493505
506+ # Upon restart during snapshot tip sync, the node must remain in 'limited' mode.
507+ self .assert_only_network_limited_service (n1 )
508+
494509 # Finally connect the nodes and let them sync.
495510 #
496511 # Set `wait_for_connect=False` to avoid a race between performing connection
@@ -507,6 +522,9 @@ def check_tx_counts(final: bool) -> None:
507522 self .log .info ("Restarted node before snapshot validation completed, reloading..." )
508523 self .restart_node (1 , extra_args = self .extra_args [1 ])
509524
525+ # Upon restart, the node must remain in 'limited' mode
526+ self .assert_only_network_limited_service (n1 )
527+
510528 # Send snapshot block to n1 out of order. This makes the test less
511529 # realistic because normally the snapshot block is one of the last
512530 # blocks downloaded, but its useful to test because it triggers more
@@ -525,6 +543,10 @@ def check_tx_counts(final: bool) -> None:
525543 self .log .info ("Ensuring background validation completes" )
526544 self .wait_until (lambda : len (n1 .getchainstates ()['chainstates' ]) == 1 )
527545
546+ # Since n1 is a pruned node, it will not signal NODE_NETWORK after
547+ # completing the background sync.
548+ self .assert_only_network_limited_service (n1 )
549+
528550 # Ensure indexes have synced.
529551 completed_idx_state = {
530552 'basic block filter index' : COMPLETE_IDX ,
@@ -555,12 +577,18 @@ def check_tx_counts(final: bool) -> None:
555577
556578 self .log .info ("-- Testing all indexes + reindex" )
557579 assert_equal (n2 .getblockcount (), START_HEIGHT )
580+ assert 'NETWORK' in n2 .getnetworkinfo ()['localservicesnames' ] # sanity check
558581
559582 self .log .info (f"Loading snapshot into third node from { dump_output ['path' ]} " )
560583 loaded = n2 .loadtxoutset (dump_output ['path' ])
561584 assert_equal (loaded ['coins_loaded' ], SNAPSHOT_BASE_HEIGHT )
562585 assert_equal (loaded ['base_height' ], SNAPSHOT_BASE_HEIGHT )
563586
587+ # Even though n2 is a full node, it will unset the 'NETWORK' service flag during snapshot loading.
588+ # This indicates other peers that the node will temporarily not provide historical blocks.
589+ self .log .info ("Check node2 updated the local services during snapshot load" )
590+ self .assert_only_network_limited_service (n2 )
591+
564592 for reindex_arg in ['-reindex=1' , '-reindex-chainstate=1' ]:
565593 self .log .info (f"Check that restarting with { reindex_arg } will delete the snapshot chainstate" )
566594 self .restart_node (2 , extra_args = [reindex_arg , * self .extra_args [2 ]])
@@ -584,13 +612,21 @@ def check_tx_counts(final: bool) -> None:
584612 msg = "Unable to load UTXO snapshot: Can't activate a snapshot-based chainstate more than once"
585613 assert_raises_rpc_error (- 32603 , msg , n2 .loadtxoutset , dump_output ['path' ])
586614
615+ # Upon restart, the node must stay in 'limited' mode until the background
616+ # chain sync completes.
617+ self .restart_node (2 , extra_args = self .extra_args [2 ])
618+ self .assert_only_network_limited_service (n2 )
619+
587620 self .connect_nodes (0 , 2 )
588621 self .wait_until (lambda : n2 .getchainstates ()['chainstates' ][- 1 ]['blocks' ] == FINAL_HEIGHT )
589622 self .sync_blocks (nodes = (n0 , n2 ))
590623
591624 self .log .info ("Ensuring background validation completes" )
592625 self .wait_until (lambda : len (n2 .getchainstates ()['chainstates' ]) == 1 )
593626
627+ # Once background chain sync completes, the full node must start offering historical blocks again.
628+ assert {'NETWORK' , 'NETWORK_LIMITED' }.issubset (n2 .getnetworkinfo ()['localservicesnames' ])
629+
594630 completed_idx_state = {
595631 'basic block filter index' : COMPLETE_IDX ,
596632 'coinstatsindex' : COMPLETE_IDX ,
0 commit comments