From 1fb762dac95358cc5a3d5fa310059685127908c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= <bkchr@users.noreply.github.com> Date: Wed, 13 Oct 2021 13:31:36 +0200 Subject: [PATCH] Ensure we don't create gap epochs when importing multiple genesis epochs (#10019) The babe `authoring_blocks` test was flaky because it could happen that we imported the first block multiple times. This lead to import the genesis epoch multiples times. `EpochChanges` was assuming that importing a genesis epoch while there was already an imported epoch means that there is a "gap". However, this wasn't true as we just imported 2 genesis epochs. The bug is solved by checking that the already imported epochs are not all genesis epochs. --- substrate/client/consensus/epochs/src/lib.rs | 81 +++++++++++++++++++- 1 file changed, 79 insertions(+), 2 deletions(-) diff --git a/substrate/client/consensus/epochs/src/lib.rs b/substrate/client/consensus/epochs/src/lib.rs index 661cb900ae6..c1d0bd1d040 100644 --- a/substrate/client/consensus/epochs/src/lib.rs +++ b/substrate/client/consensus/epochs/src/lib.rs @@ -229,13 +229,20 @@ impl<Hash, Number, E: Epoch> ViableEpochDescriptor<Hash, Number, E> { /// Persisted epoch stored in EpochChanges. #[derive(Clone, Encode, Decode, Debug)] -pub enum PersistedEpoch<E: Epoch> { +pub enum PersistedEpoch<E> { /// Genesis persisted epoch data. epoch_0, epoch_1. Genesis(E, E), /// Regular persisted epoch data. epoch_n. Regular(E), } +impl<E> PersistedEpoch<E> { + /// Returns if this is a genesis epoch. + pub fn is_genesis(&self) -> bool { + matches!(self, Self::Genesis(_, _)) + } +} + impl<'a, E: Epoch> From<&'a PersistedEpoch<E>> for PersistedEpochHeader<E> { fn from(epoch: &'a PersistedEpoch<E>) -> Self { match epoch { @@ -748,7 +755,7 @@ where Err(e) => PersistedEpoch::Regular(e), } } - } else if !self.epochs.is_empty() && matches!(epoch, PersistedEpoch::Genesis(_, _)) { + } else if epoch.is_genesis() && !self.epochs.values().all(|e| e.is_genesis()) { // There's a genesis epoch imported when we already have an active epoch. // This happens after the warp sync as the ancient blocks download start. // We need to start tracking gap epochs here. @@ -1073,6 +1080,76 @@ mod tests { } } + /// Test that ensures that the gap is not enabled when we import multiple genesis blocks. + #[test] + fn gap_is_not_enabled_when_multiple_genesis_epochs_are_imported() { + // X + // / + // 0 - A + // + let is_descendent_of = |base: &Hash, block: &Hash| -> Result<bool, TestError> { + match (base, *block) { + (b"0", _) => Ok(true), + _ => Ok(false), + } + }; + + let duration = 100; + + let make_genesis = |slot| Epoch { start_slot: slot, duration }; + + let mut epoch_changes = EpochChanges::new(); + let next_descriptor = (); + + // insert genesis epoch for A + { + let genesis_epoch_a_descriptor = epoch_changes + .epoch_descriptor_for_child_of(&is_descendent_of, b"0", 0, 100) + .unwrap() + .unwrap(); + + let incremented_epoch = epoch_changes + .viable_epoch(&genesis_epoch_a_descriptor, &make_genesis) + .unwrap() + .increment(next_descriptor.clone()); + + epoch_changes + .import(&is_descendent_of, *b"A", 1, *b"0", incremented_epoch) + .unwrap(); + } + + // insert genesis epoch for X + { + let genesis_epoch_x_descriptor = epoch_changes + .epoch_descriptor_for_child_of(&is_descendent_of, b"0", 0, 1000) + .unwrap() + .unwrap(); + + let incremented_epoch = epoch_changes + .viable_epoch(&genesis_epoch_x_descriptor, &make_genesis) + .unwrap() + .increment(next_descriptor.clone()); + + epoch_changes + .import(&is_descendent_of, *b"X", 1, *b"0", incremented_epoch) + .unwrap(); + } + + // Clearing the gap should be a no-op. + epoch_changes.clear_gap(); + + // Check that both epochs are available. + epoch_changes + .epoch_data_for_child_of(&is_descendent_of, b"A", 1, 101, &make_genesis) + .unwrap() + .unwrap(); + + epoch_changes + .epoch_data_for_child_of(&is_descendent_of, b"X", 1, 1001, &make_genesis) + .unwrap() + .unwrap(); + } + #[test] fn gap_epochs_advance() { // 0 - 1 - 2 - 3 - .... 42 - 43 -- GitLab