Skip to content

Commit d045796

Browse files
committed
Align hot grid to freezer when migrating archive nodes
1 parent 444c1cd commit d045796

File tree

2 files changed

+78
-7
lines changed

2 files changed

+78
-7
lines changed

beacon_node/beacon_chain/src/schema_change/migration_schema_v24.rs

Lines changed: 55 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -166,17 +166,67 @@ pub fn upgrade_to_v24<T: BeaconChainTypes>(
166166
//
167167
// If the node has been running for a while the `anchor_slot` might be less than the finalized
168168
// checkpoint. This upgrade constructs a grid only with unfinalized states, rooted in the
169-
// current finalize state. So we set the `anchor_slot` to `split.slot` to root the grid in the
169+
// current finalized state. So we set the `anchor_slot` to `split.slot` to root the grid in the
170170
// current finalized state. Each migration sets the split to
171171
// ```
172172
// Split { slot: finalized_state.slot(), state_root: finalized_state_root }
173173
// ```
174174
{
175175
let anchor_info = db.get_anchor_info();
176-
let mut new_anchor_info = anchor_info.clone();
177-
new_anchor_info.anchor_slot = hot_hdiff_start_slot;
178-
// Update the anchor in disk atomically if migration is successful
179-
migrate_ops.push(db.compare_and_set_anchor_info(anchor_info, new_anchor_info)?);
176+
177+
// If the node is already an archive node, we can set the anchor slot to 0 and copy
178+
// snapshots and diffs from the freezer DB to the hot DB in order to establish an initial
179+
// hot grid that is aligned/"perfect" (no `start_slot`/`anchor_slot` to worry about).
180+
let dummy_start_slot = Slot::new(0);
181+
let closest_layer_points = db
182+
.hierarchy
183+
.closest_layer_points(split.slot, dummy_start_slot);
184+
185+
let previous_snapshot_slot =
186+
closest_layer_points
187+
.iter()
188+
.copied()
189+
.min()
190+
.ok_or(Error::MigrationError(
191+
"closest_layer_points must not be empty".to_string(),
192+
))?;
193+
194+
// If we have the previous snapshot stored in the freezer DB, then we can use this
195+
// optimisation.
196+
if previous_snapshot_slot >= anchor_info.state_upper_limit {
197+
info!(
198+
%previous_snapshot_slot,
199+
split_slot = %split.slot,
200+
"Aligning hot diff grid to freezer"
201+
);
202+
203+
// Set anchor slot to 0 in case it was set to something else by a previous checkpoint
204+
// sync.
205+
let mut new_anchor_info = anchor_info.clone();
206+
new_anchor_info.anchor_slot = Slot::new(0);
207+
208+
// Update the anchor on disk atomically if migration is successful
209+
migrate_ops.push(db.compare_and_set_anchor_info(anchor_info, new_anchor_info)?);
210+
211+
// Copy each of the freezer layers to the hot DB in slot ascending order.
212+
for layer_slot in closest_layer_points.into_iter().rev() {
213+
let mut freezer_state = db.load_cold_state_by_slot(layer_slot)?;
214+
215+
let state_root = freezer_state.canonical_root()?;
216+
217+
let mut state_ops = vec![];
218+
db.store_hot_state(&state_root, &freezer_state, &mut state_ops)?;
219+
db.hot_db.do_atomically(state_ops)?;
220+
}
221+
} else {
222+
// Otherwise for non-archive nodes, set the anchor slot for the hot grid to the current
223+
// split slot (the oldest slot available).
224+
let mut new_anchor_info = anchor_info.clone();
225+
new_anchor_info.anchor_slot = hot_hdiff_start_slot;
226+
227+
// Update the anchor in disk atomically if migration is successful
228+
migrate_ops.push(db.compare_and_set_anchor_info(anchor_info, new_anchor_info)?);
229+
}
180230
}
181231

182232
let state_summaries_dag = new_dag::<T>(&db)?;

beacon_node/store/src/hdiff.rs

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -733,14 +733,22 @@ impl HierarchyModuli {
733733

734734
/// For each layer, returns the closest diff less than or equal to `slot`.
735735
pub fn closest_layer_points(&self, slot: Slot, start_slot: Slot) -> Vec<Slot> {
736-
self.moduli
736+
let mut layers = self
737+
.moduli
737738
.iter()
738739
.map(|&n| {
739740
let from = slot / n * n;
740741
// Or from start point
741742
std::cmp::max(from, start_slot)
742743
})
743-
.collect()
744+
.collect::<Vec<_>>();
745+
746+
// Remove duplication caused by the capping at `start_slot` (multiple
747+
// layers may have the same slot equal to `start_slot`), or shared multiples (a slot that is
748+
// a multiple of 2**n will also be a multiple of 2**m for all m < n).
749+
layers.dedup();
750+
751+
layers
744752
}
745753
}
746754

@@ -1067,4 +1075,17 @@ mod tests {
10671075
test_slots_retained_invariant(hierarchy, start_slot, epoch_jump);
10681076
}
10691077
}
1078+
1079+
#[test]
1080+
fn closest_layer_points_unique() {
1081+
let hierarchy = HierarchyConfig::default().to_moduli().unwrap();
1082+
1083+
let start_slot = Slot::new(0);
1084+
let end_slot = hierarchy.next_snapshot_slot(Slot::new(1)).unwrap();
1085+
1086+
for slot in (0..end_slot.as_u64()).map(Slot::new) {
1087+
let closest_layer_points = hierarchy.closest_layer_points(slot, start_slot);
1088+
assert!(closest_layer_points.is_sorted_by(|a, b| a > b));
1089+
}
1090+
}
10701091
}

0 commit comments

Comments
 (0)