1- use lazy_static:: lazy_static;
2-
31use beacon_chain:: test_utils:: {
42 AttestationStrategy , BeaconChainHarness , BlockStrategy , EphemeralHarnessType ,
53} ;
64use beacon_chain:: validator_monitor:: { ValidatorMonitorConfig , MISSED_BLOCK_LAG_SLOTS } ;
5+ use lazy_static:: lazy_static;
6+ use logging:: test_logger;
77use types:: { Epoch , EthSpec , Keypair , MainnetEthSpec , PublicKeyBytes , Slot } ;
88
99// Should ideally be divisible by 3.
@@ -23,6 +23,7 @@ fn get_harness(
2323 let harness = BeaconChainHarness :: builder ( MainnetEthSpec )
2424 . default_spec ( )
2525 . keypairs ( KEYPAIRS [ 0 ..validator_count] . to_vec ( ) )
26+ . logger ( test_logger ( ) )
2627 . fresh_ephemeral_store ( )
2728 . mock_execution_layer ( )
2829 . validator_monitor_config ( ValidatorMonitorConfig {
@@ -39,6 +40,83 @@ fn get_harness(
3940 harness
4041}
4142
43+ // Regression test for off-by-one caching issue in missed block detection.
44+ #[ tokio:: test]
45+ async fn missed_blocks_across_epochs ( ) {
46+ let slots_per_epoch = E :: slots_per_epoch ( ) ;
47+ let all_validators = ( 0 ..VALIDATOR_COUNT ) . collect :: < Vec < _ > > ( ) ;
48+
49+ let harness = get_harness ( VALIDATOR_COUNT , vec ! [ ] ) ;
50+ let validator_monitor = & harness. chain . validator_monitor ;
51+ let mut genesis_state = harness. get_current_state ( ) ;
52+ let genesis_state_root = genesis_state. update_tree_hash_cache ( ) . unwrap ( ) ;
53+ let genesis_block_root = harness. head_block_root ( ) ;
54+
55+ // Skip a slot in the first epoch (to prime the cache inside the missed block function) and then
56+ // at a different offset in the 2nd epoch. The missed block in the 2nd epoch MUST NOT reuse
57+ // the cache from the first epoch.
58+ let first_skip_offset = 3 ;
59+ let second_skip_offset = slots_per_epoch / 2 ;
60+ assert_ne ! ( first_skip_offset, second_skip_offset) ;
61+ let first_skip_slot = Slot :: new ( first_skip_offset) ;
62+ let second_skip_slot = Slot :: new ( slots_per_epoch + second_skip_offset) ;
63+ let slots = ( 1 ..2 * slots_per_epoch)
64+ . map ( Slot :: new)
65+ . filter ( |slot| * slot != first_skip_slot && * slot != second_skip_slot)
66+ . collect :: < Vec < _ > > ( ) ;
67+
68+ let ( block_roots_by_slot, state_roots_by_slot, _, head_state) = harness
69+ . add_attested_blocks_at_slots ( genesis_state, genesis_state_root, & slots, & all_validators)
70+ . await ;
71+
72+ // Prime the proposer shuffling cache.
73+ let mut proposer_shuffling_cache = harness. chain . beacon_proposer_cache . lock ( ) ;
74+ for epoch in [ 0 , 1 ] . into_iter ( ) . map ( Epoch :: new) {
75+ let start_slot = epoch. start_slot ( slots_per_epoch) + 1 ;
76+ let state = harness
77+ . get_hot_state ( state_roots_by_slot[ & start_slot] )
78+ . unwrap ( ) ;
79+ let decision_root = state
80+ . proposer_shuffling_decision_root ( genesis_block_root)
81+ . unwrap ( ) ;
82+ proposer_shuffling_cache
83+ . insert (
84+ epoch,
85+ decision_root,
86+ state
87+ . get_beacon_proposer_indices ( & harness. chain . spec )
88+ . unwrap ( ) ,
89+ state. fork ( ) ,
90+ )
91+ . unwrap ( ) ;
92+ }
93+ drop ( proposer_shuffling_cache) ;
94+
95+ // Monitor the validator that proposed the block at the same offset in the 0th epoch as the skip
96+ // in the 1st epoch.
97+ let innocent_proposer_slot = Slot :: new ( second_skip_offset) ;
98+ let innocent_proposer = harness
99+ . get_block ( block_roots_by_slot[ & innocent_proposer_slot] )
100+ . unwrap ( )
101+ . message ( )
102+ . proposer_index ( ) ;
103+
104+ let mut vm_write = validator_monitor. write ( ) ;
105+
106+ // Call `process_` once to update validator indices.
107+ vm_write. process_valid_state ( head_state. current_epoch ( ) , & head_state, & harness. chain . spec ) ;
108+ // Start monitoring the innocent validator.
109+ vm_write. add_validator_pubkey ( KEYPAIRS [ innocent_proposer as usize ] . pk . compress ( ) ) ;
110+ // Check for missed blocks.
111+ vm_write. process_valid_state ( head_state. current_epoch ( ) , & head_state, & harness. chain . spec ) ;
112+
113+ // My client is innocent, your honour!
114+ assert_eq ! (
115+ vm_write. get_monitored_validator_missed_block_count( innocent_proposer) ,
116+ 0
117+ ) ;
118+ }
119+
42120#[ tokio:: test]
43121async fn produces_missed_blocks ( ) {
44122 let validator_count = 16 ;
0 commit comments