Skip to content

Commit 1b0e6c9

Browse files
committed
Regression test
1 parent 366f0d7 commit 1b0e6c9

File tree

2 files changed

+81
-3
lines changed

2 files changed

+81
-3
lines changed

beacon_node/beacon_chain/src/validator_monitor.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -442,7 +442,7 @@ impl<T: EthSpec> ValidatorMonitor<T> {
442442
}
443443

444444
/// Add some validators to `self` for additional monitoring.
445-
fn add_validator_pubkey(&mut self, pubkey: PublicKeyBytes) {
445+
pub fn add_validator_pubkey(&mut self, pubkey: PublicKeyBytes) {
446446
let index_opt = self
447447
.indices
448448
.iter()

beacon_node/beacon_chain/tests/validator_monitor.rs

Lines changed: 80 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
use lazy_static::lazy_static;
2-
31
use beacon_chain::test_utils::{
42
AttestationStrategy, BeaconChainHarness, BlockStrategy, EphemeralHarnessType,
53
};
64
use beacon_chain::validator_monitor::{ValidatorMonitorConfig, MISSED_BLOCK_LAG_SLOTS};
5+
use lazy_static::lazy_static;
6+
use logging::test_logger;
77
use 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]
43121
async fn produces_missed_blocks() {
44122
let validator_count = 16;

0 commit comments

Comments
 (0)