Skip to content

Commit 502b296

Browse files
authored
fix(epoch-oracle): activate validators whose epoch was skipped on startup (#1996)
## Description The configured epoch oracle was not activating validators whose `registration_epoch + 1` had already passed when the oracle started. When using the hybrid oracle, if the base layer jumped ahead (e.g., from stored epoch 0 to epoch 7321), validators queued at intermediate epochs (e.g., 7320) were silently skipped. ## Motivation Validators that registered on the base layer prior to the oracle starting could fail to be added to the active set, causing them to miss participation in consensus entirely. ## Changes - `initialize()`: validators whose activation epoch has already passed are now queued at `Epoch(1)` as a low sentinel, so they are picked up on the first epoch processed instead of being dropped - `prepare_next_epoch()`: changed from removing only the exact current epoch key to draining all entries `<= current epoch` using `BTreeMap::split_off`, correctly handling any skipped epochs - Changed `queued_validators` from `HashMap` to `BTreeMap` to enable the ordered `split_off` operation
1 parent 9e7f7bf commit 502b296

File tree

1 file changed

+21
-14
lines changed

1 file changed

+21
-14
lines changed

crates/epoch_oracles/src/configured/oracle.rs

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// SPDX-License-Identifier: BSD-3-Clause
33

44
use std::{
5-
collections::{HashMap, VecDeque},
5+
collections::{BTreeMap, VecDeque},
66
future::poll_fn,
77
task::{Context, Poll},
88
};
@@ -25,7 +25,7 @@ pub struct ConfiguredEpochOracle<TStore, TTicker> {
2525
pending_events: VecDeque<EpochEvent>,
2626
store: TStore,
2727
ticker: TTicker,
28-
queued_validators: HashMap<Epoch, Vec<Validator>>,
28+
queued_validators: BTreeMap<Epoch, Vec<Validator>>,
2929
is_initialized: bool,
3030
is_done: bool,
3131
}
@@ -46,7 +46,7 @@ impl<TStore: EpochOracleStore + Send> ConfiguredEpochOracle<TStore, RealTimeEpoc
4646
store,
4747
ticker,
4848
pending_events: VecDeque::new(),
49-
queued_validators: HashMap::new(),
49+
queued_validators: BTreeMap::new(),
5050
is_initialized: false,
5151
is_done: false,
5252
})
@@ -60,7 +60,7 @@ impl<TStore: EpochOracleStore + Send, TTicker: EpochTicker> ConfiguredEpochOracl
6060
store,
6161
ticker,
6262
pending_events: VecDeque::new(),
63-
queued_validators: HashMap::new(),
63+
queued_validators: BTreeMap::new(),
6464
is_initialized: false,
6565
is_done: false,
6666
}
@@ -73,14 +73,16 @@ impl<TStore: EpochOracleStore + Send, TTicker: EpochTicker> ConfiguredEpochOracl
7373
.unwrap_or_else(Epoch::zero);
7474

7575
for vn in &self.config.validators {
76-
if vn.registration_epoch <= epoch {
77-
continue;
78-
}
79-
80-
let vns = self
81-
.queued_validators
82-
.entry(vn.registration_epoch + Epoch(1))
83-
.or_default();
76+
let activation_epoch = vn.registration_epoch + Epoch(1);
77+
// If the activation epoch has already passed, activate on the next epoch we process.
78+
// Use Epoch(1) as a sentinel so they are picked up by prepare_next_epoch's <= check.
79+
let queue_epoch = if activation_epoch <= epoch {
80+
Epoch(1)
81+
} else {
82+
activation_epoch
83+
};
84+
85+
let vns = self.queued_validators.entry(queue_epoch).or_default();
8486
vns.push(vn.clone());
8587
}
8688

@@ -132,10 +134,15 @@ impl<TStore: EpochOracleStore + Send, TTicker: EpochTicker> ConfiguredEpochOracl
132134
}))
133135
}
134136

135-
if let Some(vns) = self.queued_validators.remove(&next_epoch) {
137+
// Activate all validators queued at or before next_epoch (handles skipped epochs).
138+
// split_off returns everything > next_epoch, leaving everything <= next_epoch in place.
139+
let remaining = self.queued_validators.split_off(&(next_epoch + Epoch(1)));
140+
let due = std::mem::replace(&mut self.queued_validators, remaining);
141+
142+
for (epoch, vns) in due {
136143
debug!(
137144
target: LOG_TARGET,
138-
"☘️ {} VNS activated for epoch {next_epoch}",
145+
"☘️ {} VNS activated for epoch {epoch} (current: {next_epoch})",
139146
vns.len()
140147
);
141148
self.pending_events

0 commit comments

Comments
 (0)