From e2a6da427416889fc378e907c784fd04d0aefa14 Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Thu, 5 Jan 2023 18:28:49 +0100 Subject: [PATCH 01/64] Boiler plate code for blobs pruning --- beacon_node/store/src/hot_cold_store.rs | 140 ++++++++++++++++++++++++ beacon_node/store/src/lib.rs | 1 + 2 files changed, 141 insertions(+) diff --git a/beacon_node/store/src/hot_cold_store.rs b/beacon_node/store/src/hot_cold_store.rs index 82a65883ae4..345d06841e0 100644 --- a/beacon_node/store/src/hot_cold_store.rs +++ b/beacon_node/store/src/hot_cold_store.rs @@ -477,6 +477,12 @@ impl, Cold: ItemStore> HotColdDB .map(|payload| payload.is_some()) } + /// Check if the blobs sidecar for a block exists on disk. + pub fn blobs_sidecar_exists(&self, block_root: &Hash256) -> Result { + self.get_item::(block_root) + .map(|blobs| blobs.is_some()) + } + /// Determine whether a block exists in the database. pub fn block_exists(&self, block_root: &Hash256) -> Result { self.hot_db @@ -777,6 +783,11 @@ impl, Cold: ItemStore> HotColdDB key_value_batch.push(KeyValueStoreOp::DeleteKey(key)); } + StoreOp::DeleteBlobs(block_root) => { + let key = get_key_for_col(DBColumn::BeaconBlob.into(), block_root.as_bytes()); + key_value_batch.push(KeyValueStoreOp::DeleteKey(key)); + } + StoreOp::DeleteState(state_root, slot) => { let state_summary_key = get_key_for_col(DBColumn::BeaconStateSummary.into(), state_root.as_bytes()); @@ -826,6 +837,10 @@ impl, Cold: ItemStore> HotColdDB guard.pop(block_root); } + StoreOp::DeleteBlobs(block_root) => { + guard_blob.pop(block_root); + } + StoreOp::DeleteState(_, _) => (), StoreOp::DeleteExecutionPayload(_) => (), @@ -835,6 +850,7 @@ impl, Cold: ItemStore> HotColdDB self.hot_db .do_atomically(self.convert_to_kv_batch(batch)?)?; drop(guard); + drop(guard_blob); Ok(()) } @@ -1669,6 +1685,130 @@ impl, Cold: ItemStore> HotColdDB ); Ok(()) } + + pub fn try_prune_blobs(&self, force: bool) -> Result<(), Error> { + let split = self.get_split_info(); + + if split.slot == 0 { + return Ok(()); + } + + let eip4844_fork_slot = if let Some(epoch) = self.spec.eip4844_fork_epoch { + epoch.start_slot(E::slots_per_epoch()) + } else { + return Ok(()); + }; + + // Load the split state so we can backtrack to find blobs sidecars. + // todo(emhane): MIN_EPOCHS_FOR_BLOBS_SIDECARS_REQUESTS + let split_state = self.get_state(&split.state_root, Some(split.slot))?.ok_or( + HotColdDBError::MissingSplitState(split.state_root, split.slot), + )?; + + // The finalized block may or may not have its blobs sidecar stored, depending on + // whether it was at a skipped slot. However for a fully pruned database its parent + // should *always* have been pruned. In case of a long split (no parent found) we + // continue as if the payloads are pruned, as the node probably has other things to worry + // about. + let split_block_root = split_state.get_latest_block_root(split.state_root); + + let already_pruned = + process_results(split_state.rev_iter_block_roots(&self.spec), |mut iter| { + iter.find(|(_, block_root)| { + move || -> bool { + if *block_root != split_block_root { + if let Ok(Some(split_parent_block)) = + self.get_blinded_block(&block_root) + { + if let Ok(expected_kzg_commitments) = + split_parent_block.message().body().blob_kzg_commitments() + { + if expected_kzg_commitments.len() > 0 { + return true; + } + } + } + } + false + }() + }) + .map_or(Ok(true), |(_, split_parent_root)| { + self.blobs_sidecar_exists(&split_parent_root) + .map(|exists| !exists) + }) + })??; + + if already_pruned && !force { + info!(self.log, "Blobs sidecars are pruned"); + return Ok(()); + } + + // Iterate block roots backwards to the Eip48444 fork or the latest blob slot, whichever + // comes first. + warn!( + self.log, + "Pruning finalized blobs sidecars"; + "info" => "you may notice degraded I/O performance while this runs" + ); + let latest_blob_slot = self.get_blob_info().map(|info| info.latest_blob_slot); + + let mut ops = vec![]; + let mut last_pruned_block_root = None; + + for res in std::iter::once(Ok((split_block_root, split.slot))) + .chain(BlockRootsIterator::new(self, &split_state)) + { + let (block_root, slot) = match res { + Ok(tuple) => tuple, + Err(e) => { + warn!( + self.log, + "Stopping blobs sidecar pruning early"; + "error" => ?e, + ); + break; + } + }; + + if slot < eip4844_fork_slot { + info!( + self.log, + "Blobs sidecar pruning reached Eip4844 boundary"; + ); + break; + } + + if Some(block_root) != last_pruned_block_root + && self.blobs_sidecar_exists(&block_root)? + { + debug!( + self.log, + "Pruning blobs sidecar"; + "slot" => slot, + "block_root" => ?block_root, + ); + last_pruned_block_root = Some(block_root); + ops.push(StoreOp::DeleteBlobs(block_root)); + } + + if Some(slot) == latest_blob_slot { + info!( + self.log, + "Blobs sidecar pruning reached anchor state"; + "slot" => slot + ); + break; + } + } + let blobs_sidecars_pruned = ops.len(); + self.do_atomically(ops)?; + info!( + self.log, + "Blobs sidecar pruning complete"; + "blobs_sidecars_pruned" => blobs_sidecars_pruned, + ); + Ok(()) + } } /// Advance the split point of the store, moving new finalized states to the freezer. diff --git a/beacon_node/store/src/lib.rs b/beacon_node/store/src/lib.rs index dfdeab941be..8998d56baa4 100644 --- a/beacon_node/store/src/lib.rs +++ b/beacon_node/store/src/lib.rs @@ -161,6 +161,7 @@ pub enum StoreOp<'a, E: EthSpec> { PutStateTemporaryFlag(Hash256), DeleteStateTemporaryFlag(Hash256), DeleteBlock(Hash256), + DeleteBlobs(Hash256), DeleteState(Hash256, Option), DeleteExecutionPayload(Hash256), } From 7bf88c2336fc6d425430d7d05652240a99ade84b Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Fri, 6 Jan 2023 18:29:42 +0100 Subject: [PATCH 02/64] Prune blobs before data availability breakpoint --- beacon_node/beacon_chain/src/beacon_chain.rs | 16 ++++ beacon_node/store/src/hot_cold_store.rs | 83 +++++++++++--------- beacon_node/store/src/lib.rs | 1 + beacon_node/store/src/metadata.rs | 6 +- 4 files changed, 66 insertions(+), 40 deletions(-) diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 221d380a86e..11b0d6af2b3 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -3021,6 +3021,22 @@ impl BeaconChain { info!(self.log, "Writing blobs to store"; "block_root" => ?block_root); ops.push(StoreOp::PutBlobs(block_root, blobs)); } + + // Update db's metadata for blobs pruning. + if current_slot == current_epoch.start_slot(T::EthSpec::slots_per_epoch()) { + if let Some(mut blob_info) = self.store.get_blob_info() { + let next_epoch_to_prune = + blob_info.last_pruned_epoch + *MIN_EPOCHS_FOR_BLOBS_SIDECARS_REQUESTS; + + if current_epoch > next_epoch_to_prune { + blob_info.availability_breakpoint = block_root; + self.store.compare_and_set_blob_info_with_write( + self.store.get_blob_info(), + Some(blob_info), + )?; + } + } + } }; let txn_lock = self.store.hot_db.begin_rw_transaction(); diff --git a/beacon_node/store/src/hot_cold_store.rs b/beacon_node/store/src/hot_cold_store.rs index 345d06841e0..bbb74ef5cdd 100644 --- a/beacon_node/store/src/hot_cold_store.rs +++ b/beacon_node/store/src/hot_cold_store.rs @@ -94,6 +94,7 @@ pub enum HotColdDBError { MissingHotStateSummary(Hash256), MissingEpochBoundaryState(Hash256), MissingSplitState(Hash256, Slot), + MissingStateToPruneBlobs(Hash256, Slot), MissingExecutionPayload(Hash256), MissingFullBlockExecutionPayloadPruned(Hash256, Slot), MissingAnchorInfo, @@ -1687,41 +1688,50 @@ impl, Cold: ItemStore> HotColdDB } pub fn try_prune_blobs(&self, force: bool) -> Result<(), Error> { - let split = self.get_split_info(); + let mut blob_info: BlobInfo; - if split.slot == 0 { + if let Some(old_blob_info) = self.get_blob_info() { + blob_info = old_blob_info; + } else { return Ok(()); } - let eip4844_fork_slot = if let Some(epoch) = self.spec.eip4844_fork_epoch { - epoch.start_slot(E::slots_per_epoch()) - } else { + if blob_info.availability_breakpoint == blob_info.oldest_blob_parent { return Ok(()); - }; + } - // Load the split state so we can backtrack to find blobs sidecars. - // todo(emhane): MIN_EPOCHS_FOR_BLOBS_SIDECARS_REQUESTS - let split_state = self.get_state(&split.state_root, Some(split.slot))?.ok_or( - HotColdDBError::MissingSplitState(split.state_root, split.slot), - )?; + // Load the state from which to prune blobs so we can backtrack. + let erase_state = self + .get_state( + &blob_info.availability_breakpoint, + Some(blob_info.last_pruned_epoch.end_slot(E::slots_per_epoch())), + )? + .ok_or(HotColdDBError::MissingStateToPruneBlobs( + blob_info.availability_breakpoint, + blob_info.oldest_blob_slot, + ))?; + + // The data availability breakpoint is set at the start of an epoch indicating the epoch + // before can be pruned. + let erase_epoch = erase_state.current_epoch() - 1; + let erase_slot = erase_epoch.end_slot(E::slots_per_epoch()); // The finalized block may or may not have its blobs sidecar stored, depending on // whether it was at a skipped slot. However for a fully pruned database its parent - // should *always* have been pruned. In case of a long split (no parent found) we - // continue as if the payloads are pruned, as the node probably has other things to worry - // about. - let split_block_root = split_state.get_latest_block_root(split.state_root); + // should *always* have been pruned. In the case of blobs sidecars we look at the next + // parent block with at least one kzg commitment. - let already_pruned = - process_results(split_state.rev_iter_block_roots(&self.spec), |mut iter| { - iter.find(|(_, block_root)| { + let already_pruned = process_results( + BlockRootsIter::new(&erase_state, blob_info.oldest_blob_slot), + |mut iter| { + iter.find(|(slot, block_root)| { move || -> bool { - if *block_root != split_block_root { - if let Ok(Some(split_parent_block)) = + if *slot <= erase_slot { + if let Ok(Some(erase_parent_block)) = self.get_blinded_block(&block_root) { if let Ok(expected_kzg_commitments) = - split_parent_block.message().body().blob_kzg_commitments() + erase_parent_block.message().body().blob_kzg_commitments() { if expected_kzg_commitments.len() > 0 { return true; @@ -1736,28 +1746,25 @@ impl, Cold: ItemStore> HotColdDB self.blobs_sidecar_exists(&split_parent_root) .map(|exists| !exists) }) - })??; + }, + )??; if already_pruned && !force { info!(self.log, "Blobs sidecars are pruned"); return Ok(()); } - // Iterate block roots backwards to the Eip48444 fork or the latest blob slot, whichever - // comes first. + // Iterate block roots backwards to oldest blob slot. warn!( self.log, - "Pruning finalized blobs sidecars"; + "Pruning blobs sidecars stored longer than data availability boundary"; "info" => "you may notice degraded I/O performance while this runs" ); - let latest_blob_slot = self.get_blob_info().map(|info| info.latest_blob_slot); let mut ops = vec![]; let mut last_pruned_block_root = None; - for res in std::iter::once(Ok((split_block_root, split.slot))) - .chain(BlockRootsIterator::new(self, &split_state)) - { + for res in BlockRootsIterator::new(self, &erase_state) { let (block_root, slot) = match res { Ok(tuple) => tuple, Err(e) => { @@ -1770,14 +1777,6 @@ impl, Cold: ItemStore> HotColdDB } }; - if slot < eip4844_fork_slot { - info!( - self.log, - "Blobs sidecar pruning reached Eip4844 boundary"; - ); - break; - } - if Some(block_root) != last_pruned_block_root && self.blobs_sidecar_exists(&block_root)? { @@ -1791,15 +1790,16 @@ impl, Cold: ItemStore> HotColdDB ops.push(StoreOp::DeleteBlobs(block_root)); } - if Some(slot) == latest_blob_slot { + if slot <= erase_slot { info!( self.log, - "Blobs sidecar pruning reached anchor state"; + "Blobs sidecar pruning reached earliest available blob state"; "slot" => slot ); break; } } + let blobs_sidecars_pruned = ops.len(); self.do_atomically(ops)?; info!( @@ -1807,6 +1807,11 @@ impl, Cold: ItemStore> HotColdDB "Blobs sidecar pruning complete"; "blobs_sidecars_pruned" => blobs_sidecars_pruned, ); + + blob_info.last_pruned_epoch = erase_epoch; + blob_info.oldest_blob_parent = blob_info.availability_breakpoint; + self.compare_and_set_blob_info_with_write(self.get_blob_info(), Some(blob_info))?; + Ok(()) } } diff --git a/beacon_node/store/src/lib.rs b/beacon_node/store/src/lib.rs index 8998d56baa4..e1b2c948a9e 100644 --- a/beacon_node/store/src/lib.rs +++ b/beacon_node/store/src/lib.rs @@ -35,6 +35,7 @@ pub use self::hot_cold_store::{HotColdDB, HotStateSummary, Split}; pub use self::leveldb_store::LevelDB; pub use self::memory_store::MemoryStore; pub use self::partial_beacon_state::PartialBeaconState; +pub use crate::metadata::BlobInfo; pub use errors::Error; pub use impls::beacon_state::StorageContainer as BeaconStateStorageContainer; pub use metadata::AnchorInfo; diff --git a/beacon_node/store/src/metadata.rs b/beacon_node/store/src/metadata.rs index 15bcaf1bb0b..c2e72fac35d 100644 --- a/beacon_node/store/src/metadata.rs +++ b/beacon_node/store/src/metadata.rs @@ -2,7 +2,7 @@ use crate::{DBColumn, Error, StoreItem}; use serde_derive::{Deserialize, Serialize}; use ssz::{Decode, Encode}; use ssz_derive::{Decode, Encode}; -use types::{Checkpoint, Hash256, Slot}; +use types::{Checkpoint, Epoch, Hash256, Slot}; pub const CURRENT_SCHEMA_VERSION: SchemaVersion = SchemaVersion(15); @@ -122,6 +122,10 @@ impl StoreItem for AnchorInfo { /// Database parameters relevant to blob sync. #[derive(Debug, PartialEq, Eq, Clone, Encode, Decode, Serialize, Deserialize)] pub struct BlobInfo { + /// The latest epoch that blobs were pruned. + pub last_pruned_epoch: Epoch, + /// The block root of the next blobs to prune from. + pub availability_breakpoint: Hash256, /// The block root of the next blob that needs to be added to fill in the history. pub oldest_blob_parent: Hash256, /// The slot before which blobs are available. From fe0c9114027faf9e2b63954eb14ff036866497d4 Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Sun, 8 Jan 2023 16:22:11 +0100 Subject: [PATCH 03/64] Plug in pruning of blobs into app --- beacon_node/beacon_chain/src/builder.rs | 14 +++++++++++ beacon_node/store/src/config.rs | 3 +++ database_manager/src/lib.rs | 31 +++++++++++++++++++++++++ 3 files changed, 48 insertions(+) diff --git a/beacon_node/beacon_chain/src/builder.rs b/beacon_node/beacon_chain/src/builder.rs index 164ed8a9353..fe62f8094a5 100644 --- a/beacon_node/beacon_chain/src/builder.rs +++ b/beacon_node/beacon_chain/src/builder.rs @@ -914,6 +914,20 @@ where ); } + // Prune blobs sidecars older than the blob data availability boundary in the background. + if beacon_chain.store.get_config().prune_blobs { + let store = beacon_chain.store.clone(); + let log = log.clone(); + beacon_chain.task_executor.spawn_blocking( + move || { + if let Err(e) = store.try_prune_blobs(false) { + error!(log, "Error pruning blobs in background"; "error" => ?e); + } + }, + "prune_blobs_background", + ); + } + Ok(beacon_chain) } } diff --git a/beacon_node/store/src/config.rs b/beacon_node/store/src/config.rs index 53d99f75ebf..13ac674dfff 100644 --- a/beacon_node/store/src/config.rs +++ b/beacon_node/store/src/config.rs @@ -26,6 +26,8 @@ pub struct StoreConfig { pub compact_on_prune: bool, /// Whether to prune payloads on initialization and finalization. pub prune_payloads: bool, + /// Whether to prune blobs older than the blob data availability boundary. + pub prune_blobs: bool, } /// Variant of `StoreConfig` that gets written to disk. Contains immutable configuration params. @@ -50,6 +52,7 @@ impl Default for StoreConfig { compact_on_init: false, compact_on_prune: true, prune_payloads: true, + prune_blobs: true, } } } diff --git a/database_manager/src/lib.rs b/database_manager/src/lib.rs index 33accfc0579..7222c19f352 100644 --- a/database_manager/src/lib.rs +++ b/database_manager/src/lib.rs @@ -65,6 +65,12 @@ pub fn prune_payloads_app<'a, 'b>() -> App<'a, 'b> { .about("Prune finalized execution payloads") } +pub fn prune_blobs_app<'a, 'b>() -> App<'a, 'b> { + App::new("prune_blobs") + .setting(clap::AppSettings::ColoredHelp) + .about("Prune blobs older than data availability boundary") +} + pub fn cli_app<'a, 'b>() -> App<'a, 'b> { App::new(CMD) .visible_aliases(&["db"]) @@ -92,6 +98,7 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> { .subcommand(version_cli_app()) .subcommand(inspect_cli_app()) .subcommand(prune_payloads_app()) + .subcommand(prune_blobs_app()) } fn parse_client_config( @@ -287,6 +294,29 @@ pub fn prune_payloads( db.try_prune_execution_payloads(force) } +pub fn prune_blobs( + client_config: ClientConfig, + runtime_context: &RuntimeContext, + log: Logger, +) -> Result<(), Error> { + let spec = &runtime_context.eth2_config.spec; + let hot_path = client_config.get_db_path(); + let cold_path = client_config.get_freezer_db_path(); + + let db = HotColdDB::, LevelDB>::open( + &hot_path, + &cold_path, + |_, _, _| Ok(()), + client_config.store, + spec.clone(), + log, + )?; + + // If we're trigging a prune manually then ignore the check on the split's parent that bails + // out early by passing true to the force parameter. + db.try_prune_blobs(true) +} + /// Run the database manager, returning an error string if the operation did not succeed. pub fn run(cli_args: &ArgMatches<'_>, env: Environment) -> Result<(), String> { let client_config = parse_client_config(cli_args, &env)?; @@ -304,6 +334,7 @@ pub fn run(cli_args: &ArgMatches<'_>, env: Environment) -> Result inspect_db(inspect_config, client_config, &context, log) } ("prune_payloads", Some(_)) => prune_payloads(client_config, &context, log), + ("prune_blobs", Some(_)) => prune_blobs(client_config, &context, log), _ => { return Err("Unknown subcommand, for help `lighthouse database_manager --help`".into()) } From 2a41f25d68b3ee58fa66c7dd9c27d7e691428b8b Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Sun, 8 Jan 2023 17:45:08 +0100 Subject: [PATCH 04/64] fixup! Prune blobs before data availability breakpoint --- beacon_node/beacon_chain/src/beacon_chain.rs | 2 +- beacon_node/store/src/hot_cold_store.rs | 29 ++++++++------------ beacon_node/store/src/metadata.rs | 2 +- 3 files changed, 14 insertions(+), 19 deletions(-) diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 11b0d6af2b3..59d29298a77 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -3029,7 +3029,7 @@ impl BeaconChain { blob_info.last_pruned_epoch + *MIN_EPOCHS_FOR_BLOBS_SIDECARS_REQUESTS; if current_epoch > next_epoch_to_prune { - blob_info.availability_breakpoint = block_root; + blob_info.data_availability_breakpoint = block_root; self.store.compare_and_set_blob_info_with_write( self.store.get_blob_info(), Some(blob_info), diff --git a/beacon_node/store/src/hot_cold_store.rs b/beacon_node/store/src/hot_cold_store.rs index bbb74ef5cdd..54e4132a409 100644 --- a/beacon_node/store/src/hot_cold_store.rs +++ b/beacon_node/store/src/hot_cold_store.rs @@ -1696,25 +1696,24 @@ impl, Cold: ItemStore> HotColdDB return Ok(()); } - if blob_info.availability_breakpoint == blob_info.oldest_blob_parent { + if blob_info.data_availability_breakpoint == blob_info.oldest_blob_parent { return Ok(()); } // Load the state from which to prune blobs so we can backtrack. let erase_state = self .get_state( - &blob_info.availability_breakpoint, + &blob_info.data_availability_breakpoint, Some(blob_info.last_pruned_epoch.end_slot(E::slots_per_epoch())), )? .ok_or(HotColdDBError::MissingStateToPruneBlobs( - blob_info.availability_breakpoint, + blob_info.data_availability_breakpoint, blob_info.oldest_blob_slot, ))?; // The data availability breakpoint is set at the start of an epoch indicating the epoch // before can be pruned. let erase_epoch = erase_state.current_epoch() - 1; - let erase_slot = erase_epoch.end_slot(E::slots_per_epoch()); // The finalized block may or may not have its blobs sidecar stored, depending on // whether it was at a skipped slot. However for a fully pruned database its parent @@ -1724,18 +1723,14 @@ impl, Cold: ItemStore> HotColdDB let already_pruned = process_results( BlockRootsIter::new(&erase_state, blob_info.oldest_blob_slot), |mut iter| { - iter.find(|(slot, block_root)| { + iter.find(|(_, block_root)| { move || -> bool { - if *slot <= erase_slot { - if let Ok(Some(erase_parent_block)) = - self.get_blinded_block(&block_root) + if let Ok(Some(erase_parent_block)) = self.get_blinded_block(&block_root) { + if let Ok(expected_kzg_commitments) = + erase_parent_block.message().body().blob_kzg_commitments() { - if let Ok(expected_kzg_commitments) = - erase_parent_block.message().body().blob_kzg_commitments() - { - if expected_kzg_commitments.len() > 0 { - return true; - } + if expected_kzg_commitments.len() > 0 { + return true; } } } @@ -1790,10 +1785,10 @@ impl, Cold: ItemStore> HotColdDB ops.push(StoreOp::DeleteBlobs(block_root)); } - if slot <= erase_slot { + if block_root == blob_info.oldest_blob_parent { info!( self.log, - "Blobs sidecar pruning reached earliest available blob state"; + "Blobs sidecar pruning reached earliest available blobs sidecar"; "slot" => slot ); break; @@ -1809,7 +1804,7 @@ impl, Cold: ItemStore> HotColdDB ); blob_info.last_pruned_epoch = erase_epoch; - blob_info.oldest_blob_parent = blob_info.availability_breakpoint; + blob_info.oldest_blob_parent = blob_info.data_availability_breakpoint; self.compare_and_set_blob_info_with_write(self.get_blob_info(), Some(blob_info))?; Ok(()) diff --git a/beacon_node/store/src/metadata.rs b/beacon_node/store/src/metadata.rs index c2e72fac35d..803b75b9838 100644 --- a/beacon_node/store/src/metadata.rs +++ b/beacon_node/store/src/metadata.rs @@ -125,7 +125,7 @@ pub struct BlobInfo { /// The latest epoch that blobs were pruned. pub last_pruned_epoch: Epoch, /// The block root of the next blobs to prune from. - pub availability_breakpoint: Hash256, + pub data_availability_breakpoint: Hash256, /// The block root of the next blob that needs to be added to fill in the history. pub oldest_blob_parent: Hash256, /// The slot before which blobs are available. From b88d8881459d8e7cdd273f67a6c6f16d17ab489e Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Sun, 8 Jan 2023 20:34:50 +0100 Subject: [PATCH 05/64] fixup! Plug in pruning of blobs into app --- beacon_node/store/src/hot_cold_store.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/beacon_node/store/src/hot_cold_store.rs b/beacon_node/store/src/hot_cold_store.rs index 54e4132a409..5106505798d 100644 --- a/beacon_node/store/src/hot_cold_store.rs +++ b/beacon_node/store/src/hot_cold_store.rs @@ -212,6 +212,10 @@ impl HotColdDB, LevelDB> { ); } + if db.spec.eip4844_fork_epoch.is_some() { + *db.blob_info.write() = db.load_blob_info()?; + } + // Ensure that the schema version of the on-disk database matches the software. // If the version is mismatched, an automatic migration will be attempted. let db = Arc::new(db); From d21c66ddf4e197d94d2355a0a9d791ffd524ca38 Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Sun, 8 Jan 2023 20:51:40 +0100 Subject: [PATCH 06/64] fixup! Plug in pruning of blobs into app --- beacon_node/beacon_chain/src/beacon_chain.rs | 2 +- beacon_node/store/src/hot_cold_store.rs | 17 ++++++++++++----- beacon_node/store/src/metadata.rs | 4 ++-- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 59d29298a77..ab0bcb74147 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -3029,7 +3029,7 @@ impl BeaconChain { blob_info.last_pruned_epoch + *MIN_EPOCHS_FOR_BLOBS_SIDECARS_REQUESTS; if current_epoch > next_epoch_to_prune { - blob_info.data_availability_breakpoint = block_root; + blob_info.data_availability_breakpoint = Some(block_root); self.store.compare_and_set_blob_info_with_write( self.store.get_blob_info(), Some(blob_info), diff --git a/beacon_node/store/src/hot_cold_store.rs b/beacon_node/store/src/hot_cold_store.rs index 5106505798d..ad4aa3233c0 100644 --- a/beacon_node/store/src/hot_cold_store.rs +++ b/beacon_node/store/src/hot_cold_store.rs @@ -213,7 +213,7 @@ impl HotColdDB, LevelDB> { } if db.spec.eip4844_fork_epoch.is_some() { - *db.blob_info.write() = db.load_blob_info()?; + *db.blob_info.write() = db.load_blob_info()?.or(Some(BlobInfo::default())); } // Ensure that the schema version of the on-disk database matches the software. @@ -1700,18 +1700,25 @@ impl, Cold: ItemStore> HotColdDB return Ok(()); } - if blob_info.data_availability_breakpoint == blob_info.oldest_blob_parent { + let data_availability_breakpoint: Hash256; + + if let Some(breakpoint) = blob_info.data_availability_breakpoint { + if breakpoint == blob_info.oldest_blob_parent { + return Ok(()); + } + data_availability_breakpoint = breakpoint; + } else { return Ok(()); } // Load the state from which to prune blobs so we can backtrack. let erase_state = self .get_state( - &blob_info.data_availability_breakpoint, + &data_availability_breakpoint, Some(blob_info.last_pruned_epoch.end_slot(E::slots_per_epoch())), )? .ok_or(HotColdDBError::MissingStateToPruneBlobs( - blob_info.data_availability_breakpoint, + data_availability_breakpoint, blob_info.oldest_blob_slot, ))?; @@ -1808,7 +1815,7 @@ impl, Cold: ItemStore> HotColdDB ); blob_info.last_pruned_epoch = erase_epoch; - blob_info.oldest_blob_parent = blob_info.data_availability_breakpoint; + blob_info.oldest_blob_parent = data_availability_breakpoint; self.compare_and_set_blob_info_with_write(self.get_blob_info(), Some(blob_info))?; Ok(()) diff --git a/beacon_node/store/src/metadata.rs b/beacon_node/store/src/metadata.rs index 803b75b9838..3757940c21f 100644 --- a/beacon_node/store/src/metadata.rs +++ b/beacon_node/store/src/metadata.rs @@ -120,12 +120,12 @@ impl StoreItem for AnchorInfo { } /// Database parameters relevant to blob sync. -#[derive(Debug, PartialEq, Eq, Clone, Encode, Decode, Serialize, Deserialize)] +#[derive(Debug, PartialEq, Eq, Clone, Encode, Decode, Serialize, Deserialize, Default)] pub struct BlobInfo { /// The latest epoch that blobs were pruned. pub last_pruned_epoch: Epoch, /// The block root of the next blobs to prune from. - pub data_availability_breakpoint: Hash256, + pub data_availability_breakpoint: Option, /// The block root of the next blob that needs to be added to fill in the history. pub oldest_blob_parent: Hash256, /// The slot before which blobs are available. From 934f3ab5875cbf6d9b8c0e37edac21809daba029 Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Sun, 8 Jan 2023 21:07:28 +0100 Subject: [PATCH 07/64] Remove inaccurate guess for db index --- beacon_node/store/src/hot_cold_store.rs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/beacon_node/store/src/hot_cold_store.rs b/beacon_node/store/src/hot_cold_store.rs index ad4aa3233c0..806a5c104a2 100644 --- a/beacon_node/store/src/hot_cold_store.rs +++ b/beacon_node/store/src/hot_cold_store.rs @@ -94,7 +94,7 @@ pub enum HotColdDBError { MissingHotStateSummary(Hash256), MissingEpochBoundaryState(Hash256), MissingSplitState(Hash256, Slot), - MissingStateToPruneBlobs(Hash256, Slot), + MissingStateToPruneBlobs(Hash256), MissingExecutionPayload(Hash256), MissingFullBlockExecutionPayloadPruned(Hash256, Slot), MissingAnchorInfo, @@ -1712,15 +1712,9 @@ impl, Cold: ItemStore> HotColdDB } // Load the state from which to prune blobs so we can backtrack. - let erase_state = self - .get_state( - &data_availability_breakpoint, - Some(blob_info.last_pruned_epoch.end_slot(E::slots_per_epoch())), - )? - .ok_or(HotColdDBError::MissingStateToPruneBlobs( - data_availability_breakpoint, - blob_info.oldest_blob_slot, - ))?; + let erase_state = self.get_state(&data_availability_breakpoint, None)?.ok_or( + HotColdDBError::MissingStateToPruneBlobs(data_availability_breakpoint), + )?; // The data availability breakpoint is set at the start of an epoch indicating the epoch // before can be pruned. From d3b94d8617648cb7c8c6f98b157b5a17839185fe Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Mon, 9 Jan 2023 11:11:36 +0100 Subject: [PATCH 08/64] fixup! Prune blobs before data availability breakpoint --- beacon_node/store/src/hot_cold_store.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/beacon_node/store/src/hot_cold_store.rs b/beacon_node/store/src/hot_cold_store.rs index 806a5c104a2..c020fe0bd3f 100644 --- a/beacon_node/store/src/hot_cold_store.rs +++ b/beacon_node/store/src/hot_cold_store.rs @@ -1712,13 +1712,13 @@ impl, Cold: ItemStore> HotColdDB } // Load the state from which to prune blobs so we can backtrack. - let erase_state = self.get_state(&data_availability_breakpoint, None)?.ok_or( + let prune_state = self.get_state(&data_availability_breakpoint, None)?.ok_or( HotColdDBError::MissingStateToPruneBlobs(data_availability_breakpoint), )?; - // The data availability breakpoint is set at the start of an epoch indicating the epoch + // The data_availability_breakpoint is set at the start of an epoch indicating the epoch // before can be pruned. - let erase_epoch = erase_state.current_epoch() - 1; + let prune_epoch = prune_state.current_epoch() - 1; // The finalized block may or may not have its blobs sidecar stored, depending on // whether it was at a skipped slot. However for a fully pruned database its parent @@ -1726,7 +1726,7 @@ impl, Cold: ItemStore> HotColdDB // parent block with at least one kzg commitment. let already_pruned = process_results( - BlockRootsIter::new(&erase_state, blob_info.oldest_blob_slot), + BlockRootsIter::new(&prune_state, blob_info.oldest_blob_slot), |mut iter| { iter.find(|(_, block_root)| { move || -> bool { @@ -1764,7 +1764,7 @@ impl, Cold: ItemStore> HotColdDB let mut ops = vec![]; let mut last_pruned_block_root = None; - for res in BlockRootsIterator::new(self, &erase_state) { + for res in BlockRootsIterator::new(self, &prune_state) { let (block_root, slot) = match res { Ok(tuple) => tuple, Err(e) => { @@ -1796,6 +1796,9 @@ impl, Cold: ItemStore> HotColdDB "Blobs sidecar pruning reached earliest available blobs sidecar"; "slot" => slot ); + blob_info.oldest_blob_slot = slot; + blob_info.last_pruned_epoch = prune_epoch; + blob_info.oldest_blob_parent = data_availability_breakpoint; break; } } @@ -1808,8 +1811,6 @@ impl, Cold: ItemStore> HotColdDB "blobs_sidecars_pruned" => blobs_sidecars_pruned, ); - blob_info.last_pruned_epoch = erase_epoch; - blob_info.oldest_blob_parent = data_availability_breakpoint; self.compare_and_set_blob_info_with_write(self.get_blob_info(), Some(blob_info))?; Ok(()) From 28e1e635c31f8cbd0565292a6a239281e3c019c4 Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Mon, 6 Feb 2023 10:58:39 +0100 Subject: [PATCH 09/64] Fix rebase conflict --- beacon_node/store/src/errors.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/beacon_node/store/src/errors.rs b/beacon_node/store/src/errors.rs index 8a0b44197aa..6a22c888a7d 100644 --- a/beacon_node/store/src/errors.rs +++ b/beacon_node/store/src/errors.rs @@ -19,6 +19,8 @@ pub enum Error { }, RlpError(String), BlockNotFound(Hash256), + /// The blobs sidecar mapping to this block root is older than the data availability boundary. + BlobsTooOld(Hash256), NoContinuationData, SplitPointModified(Slot, Slot), ConfigError(StoreConfigError), From a211e6afeebcefe725517685451ad45988c7a182 Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Mon, 6 Feb 2023 10:58:58 +0100 Subject: [PATCH 10/64] Fix rebase conflict --- beacon_node/beacon_chain/src/beacon_chain.rs | 12 ++++++++++ beacon_node/store/src/hot_cold_store.rs | 24 ++++++++++++-------- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index ab0bcb74147..5768fd85bc8 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -1074,12 +1074,24 @@ impl BeaconChain { ) -> Result>, Error> { match self.store.get_blobs(block_root)? { Some(blobs) => Ok(Some(blobs)), +<<<<<<< HEAD None => { // Check for the corresponding block to understand whether we *should* have blobs. self.get_blinded_block(block_root)? .map(|block| { // If there are no KZG commitments in the block, we know the sidecar should // be empty. +======= + None => match self.get_blinded_block(block_root)? { + Some(block) => { + let current_slot = self.slot()?; + let current_epoch = current_slot.epoch(T::EthSpec::slots_per_epoch()); + + if block.slot().epoch(T::EthSpec::slots_per_epoch()) + + *MIN_EPOCHS_FOR_BLOBS_SIDECARS_REQUESTS + >= current_epoch + { +>>>>>>> 292426505 (Improve syntax) let expected_kzg_commitments = match block.message().body().blob_kzg_commitments() { Ok(kzg_commitments) => kzg_commitments, diff --git a/beacon_node/store/src/hot_cold_store.rs b/beacon_node/store/src/hot_cold_store.rs index c020fe0bd3f..6ed0d129d4c 100644 --- a/beacon_node/store/src/hot_cold_store.rs +++ b/beacon_node/store/src/hot_cold_store.rs @@ -1694,21 +1694,27 @@ impl, Cold: ItemStore> HotColdDB pub fn try_prune_blobs(&self, force: bool) -> Result<(), Error> { let mut blob_info: BlobInfo; - if let Some(old_blob_info) = self.get_blob_info() { - blob_info = old_blob_info; - } else { - return Ok(()); + match self.get_blob_info() { + Some(old_blob_info) => { + blob_info = old_blob_info; + } + None => { + return Ok(()); + } } let data_availability_breakpoint: Hash256; - if let Some(breakpoint) = blob_info.data_availability_breakpoint { - if breakpoint == blob_info.oldest_blob_parent { + match blob_info.data_availability_breakpoint { + Some(breakpoint) => { + if breakpoint == blob_info.oldest_blob_parent { + return Ok(()); + } + data_availability_breakpoint = breakpoint; + } + None => { return Ok(()); } - data_availability_breakpoint = breakpoint; - } else { - return Ok(()); } // Load the state from which to prune blobs so we can backtrack. From ce2db355de1b258f64dc640754f84efdd1776d29 Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Mon, 6 Feb 2023 11:01:09 +0100 Subject: [PATCH 11/64] Fix rebase conflict --- beacon_node/beacon_chain/src/beacon_chain.rs | 50 ++++++++++---------- beacon_node/src/cli.rs | 8 ++++ beacon_node/src/config.rs | 4 ++ beacon_node/store/src/errors.rs | 2 +- beacon_node/store/src/hot_cold_store.rs | 2 +- beacon_node/store/src/metadata.rs | 2 +- lighthouse/tests/beacon_node.rs | 13 +++++ 7 files changed, 54 insertions(+), 27 deletions(-) diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 5768fd85bc8..97c21097e7c 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -1074,24 +1074,12 @@ impl BeaconChain { ) -> Result>, Error> { match self.store.get_blobs(block_root)? { Some(blobs) => Ok(Some(blobs)), -<<<<<<< HEAD None => { // Check for the corresponding block to understand whether we *should* have blobs. self.get_blinded_block(block_root)? .map(|block| { // If there are no KZG commitments in the block, we know the sidecar should // be empty. -======= - None => match self.get_blinded_block(block_root)? { - Some(block) => { - let current_slot = self.slot()?; - let current_epoch = current_slot.epoch(T::EthSpec::slots_per_epoch()); - - if block.slot().epoch(T::EthSpec::slots_per_epoch()) - + *MIN_EPOCHS_FOR_BLOBS_SIDECARS_REQUESTS - >= current_epoch - { ->>>>>>> 292426505 (Improve syntax) let expected_kzg_commitments = match block.message().body().blob_kzg_commitments() { Ok(kzg_commitments) => kzg_commitments, @@ -3027,21 +3015,34 @@ impl BeaconChain { ops.push(StoreOp::PutBlock(block_root, signed_block.clone())); ops.push(StoreOp::PutState(block.state_root(), &state)); - if let Some(blobs) = blobs { - if blobs.blobs.len() > 0 { - //FIXME(sean) using this for debugging for now - info!(self.log, "Writing blobs to store"; "block_root" => ?block_root); - ops.push(StoreOp::PutBlobs(block_root, blobs)); + let block_epoch = block.slot().epoch(T::EthSpec::slots_per_epoch()); + + // Only store blobs that haven't passed the data availability boundary. + if Some(block_epoch) >= self.data_availability_boundary() { + if let Some(blobs) = blobs? { + if blobs.blobs.len() > 0 { + //FIXME(sean) using this for debugging for now + info!(self.log, "Writing blobs to store"; "block_root" => ?block_root); + ops.push(StoreOp::PutBlobs(block_root, blobs)); + } } + } + + if Some(current_epoch) + >= self.spec.eip4844_fork_epoch.map(|eip4844_fork_epoch| { + eip4844_fork_epoch + *MIN_EPOCHS_FOR_BLOBS_SIDECARS_REQUESTS + }) + { + let current_epoch_start_slot = current_epoch.start_slot(T::EthSpec::slots_per_epoch()); // Update db's metadata for blobs pruning. - if current_slot == current_epoch.start_slot(T::EthSpec::slots_per_epoch()) { + if current_slot == current_epoch_start_slot { if let Some(mut blob_info) = self.store.get_blob_info() { - let next_epoch_to_prune = - blob_info.last_pruned_epoch + *MIN_EPOCHS_FOR_BLOBS_SIDECARS_REQUESTS; - - if current_epoch > next_epoch_to_prune { - blob_info.data_availability_breakpoint = Some(block_root); + // Pruning enabled until data availability boundary. + if let Some(data_availability_boundary) = self.data_availability_boundary() { + blob_info.data_availability_boundary = self.state_root_at_slot( + data_availability_boundary.start_slot(T::EthSpec::slots_per_epoch()), + )?; self.store.compare_and_set_blob_info_with_write( self.store.get_blob_info(), Some(blob_info), @@ -3049,7 +3050,8 @@ impl BeaconChain { } } } - }; + } + let txn_lock = self.store.hot_db.begin_rw_transaction(); kv_store_ops.extend(self.store.convert_to_kv_batch(ops)?); diff --git a/beacon_node/src/cli.rs b/beacon_node/src/cli.rs index c5aef78aaa8..8932b503f60 100644 --- a/beacon_node/src/cli.rs +++ b/beacon_node/src/cli.rs @@ -551,6 +551,14 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> { .takes_value(true) .default_value("true") ) + .arg( + Arg::with_name("prune-blobs") + .long("prune-blobs") + .help("Prune blobs from Lighthouse's database when they are older than the data \ + data availability boundary relative to the current head.") + .takes_value(true) + .default_value("true") + ) /* * Misc. diff --git a/beacon_node/src/config.rs b/beacon_node/src/config.rs index 1ce0f5f77f0..a435a42fd3f 100644 --- a/beacon_node/src/config.rs +++ b/beacon_node/src/config.rs @@ -411,6 +411,10 @@ pub fn get_config( client_config.store.prune_payloads = prune_payloads; } + if let Some(prune_blobs) = clap_utils::parse_optional(cli_args, "prune-blobs")? { + client_config.store.prune_blobs = prune_blobs; + } + /* * Zero-ports * diff --git a/beacon_node/store/src/errors.rs b/beacon_node/store/src/errors.rs index 6a22c888a7d..ac50cc6aa30 100644 --- a/beacon_node/store/src/errors.rs +++ b/beacon_node/store/src/errors.rs @@ -20,7 +20,7 @@ pub enum Error { RlpError(String), BlockNotFound(Hash256), /// The blobs sidecar mapping to this block root is older than the data availability boundary. - BlobsTooOld(Hash256), + BlobsTooOld(Hash256, Slot), NoContinuationData, SplitPointModified(Slot, Slot), ConfigError(StoreConfigError), diff --git a/beacon_node/store/src/hot_cold_store.rs b/beacon_node/store/src/hot_cold_store.rs index 6ed0d129d4c..83047b053fa 100644 --- a/beacon_node/store/src/hot_cold_store.rs +++ b/beacon_node/store/src/hot_cold_store.rs @@ -1705,7 +1705,7 @@ impl, Cold: ItemStore> HotColdDB let data_availability_breakpoint: Hash256; - match blob_info.data_availability_breakpoint { + match blob_info.data_availability_boundary { Some(breakpoint) => { if breakpoint == blob_info.oldest_blob_parent { return Ok(()); diff --git a/beacon_node/store/src/metadata.rs b/beacon_node/store/src/metadata.rs index 3757940c21f..89cf8097604 100644 --- a/beacon_node/store/src/metadata.rs +++ b/beacon_node/store/src/metadata.rs @@ -125,7 +125,7 @@ pub struct BlobInfo { /// The latest epoch that blobs were pruned. pub last_pruned_epoch: Epoch, /// The block root of the next blobs to prune from. - pub data_availability_breakpoint: Option, + pub data_availability_boundary: Option, /// The block root of the next blob that needs to be added to fill in the history. pub oldest_blob_parent: Hash256, /// The slot before which blobs are available. diff --git a/lighthouse/tests/beacon_node.rs b/lighthouse/tests/beacon_node.rs index 7e581ee6152..59f03064a1b 100644 --- a/lighthouse/tests/beacon_node.rs +++ b/lighthouse/tests/beacon_node.rs @@ -1341,6 +1341,19 @@ fn prune_payloads_on_startup_false() { .with_config(|config| assert!(!config.store.prune_payloads)); } #[test] +fn prune_blobs_default() { + CommandLineTest::new() + .run_with_zero_port() + .with_config(|config| assert!(config.store.prune_blobs)); +} +#[test] +fn prune_blobs_on_startup_false() { + CommandLineTest::new() + .flag("prune-blobs", Some("false")) + .run_with_zero_port() + .with_config(|config| assert!(!config.store.prune_blobs)); +} +#[test] fn reconstruct_historic_states_flag() { CommandLineTest::new() .flag("reconstruct-historic-states", None) From 82ffec378a5545b6dc14b90a16eb6c2c5b03f64d Mon Sep 17 00:00:00 2001 From: Emilia Hane <58548332+emhane@users.noreply.github.com> Date: Thu, 12 Jan 2023 16:49:40 +0100 Subject: [PATCH 12/64] Fix typo Co-authored-by: realbigsean --- database_manager/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/database_manager/src/lib.rs b/database_manager/src/lib.rs index 7222c19f352..1cd6a3e08a0 100644 --- a/database_manager/src/lib.rs +++ b/database_manager/src/lib.rs @@ -312,7 +312,7 @@ pub fn prune_blobs( log, )?; - // If we're trigging a prune manually then ignore the check on the split's parent that bails + // If we're triggering a prune manually then ignore the check on the split's parent that bails // out early by passing true to the force parameter. db.try_prune_blobs(true) } From d67468d737a5fedc7a2ece48ba018158ee363ec3 Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Fri, 13 Jan 2023 09:55:58 +0100 Subject: [PATCH 13/64] Prune blobs on migration in addition to start-up --- beacon_node/store/src/hot_cold_store.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/beacon_node/store/src/hot_cold_store.rs b/beacon_node/store/src/hot_cold_store.rs index 83047b053fa..5e5c8ab4d47 100644 --- a/beacon_node/store/src/hot_cold_store.rs +++ b/beacon_node/store/src/hot_cold_store.rs @@ -1691,6 +1691,7 @@ impl, Cold: ItemStore> HotColdDB Ok(()) } + /// Try to prune blobs older than the data availability boundary. pub fn try_prune_blobs(&self, force: bool) -> Result<(), Error> { let mut blob_info: BlobInfo; @@ -1857,11 +1858,15 @@ pub fn migrate_database, Cold: ItemStore>( return Err(HotColdDBError::FreezeSlotUnaligned(frozen_head.slot()).into()); } + // Prune blobs before migration. + store.try_prune_blobs(false)?; + let mut hot_db_ops: Vec> = Vec::new(); // 1. Copy all of the states between the head and the split slot, from the hot DB // to the cold DB. Delete the execution payloads of these now-finalized blocks. let state_root_iter = RootsIterator::new(&store, frozen_head); + for maybe_tuple in state_root_iter.take_while(|result| match result { Ok((_, _, slot)) => { slot >= ¤t_split_slot @@ -1903,7 +1908,7 @@ pub fn migrate_database, Cold: ItemStore>( } // Warning: Critical section. We have to take care not to put any of the two databases in an - // inconsistent state if the OS process dies at any point during the freezeing + // inconsistent state if the OS process dies at any point during the freezing // procedure. // // Since it is pretty much impossible to be atomic across more than one database, we trade @@ -1919,7 +1924,7 @@ pub fn migrate_database, Cold: ItemStore>( let mut split_guard = store.split.write(); let latest_split_slot = split_guard.slot; - // Detect a sitation where the split point is (erroneously) changed from more than one + // Detect a situation where the split point is (erroneously) changed from more than one // place in code. if latest_split_slot != current_split_slot { error!( From 667cca5cf2ca8bc77ed7cad0f21215da7da71d21 Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Fri, 13 Jan 2023 16:04:29 +0100 Subject: [PATCH 14/64] Fix try_prune_blobs to use state root --- beacon_node/beacon_chain/src/beacon_chain.rs | 22 +++--- beacon_node/store/src/hot_cold_store.rs | 81 ++++++-------------- beacon_node/store/src/metadata.rs | 12 ++- 3 files changed, 41 insertions(+), 74 deletions(-) diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 97c21097e7c..52390f36a6d 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -50,6 +50,7 @@ use crate::persisted_fork_choice::PersistedForkChoice; use crate::pre_finalization_cache::PreFinalizationBlockCache; use crate::shuffling_cache::{BlockShufflingIds, ShufflingCache}; use crate::snapshot_cache::{BlockProductionPreState, SnapshotCache}; +use crate::store::Split; use crate::sync_committee_verification::{ Error as SyncCommitteeError, VerifiedSyncCommitteeMessage, VerifiedSyncContribution, }; @@ -3029,7 +3030,7 @@ impl BeaconChain { } if Some(current_epoch) - >= self.spec.eip4844_fork_epoch.map(|eip4844_fork_epoch| { + > self.spec.eip4844_fork_epoch.map(|eip4844_fork_epoch| { eip4844_fork_epoch + *MIN_EPOCHS_FOR_BLOBS_SIDECARS_REQUESTS }) { @@ -3038,15 +3039,18 @@ impl BeaconChain { // Update db's metadata for blobs pruning. if current_slot == current_epoch_start_slot { if let Some(mut blob_info) = self.store.get_blob_info() { - // Pruning enabled until data availability boundary. if let Some(data_availability_boundary) = self.data_availability_boundary() { - blob_info.data_availability_boundary = self.state_root_at_slot( - data_availability_boundary.start_slot(T::EthSpec::slots_per_epoch()), - )?; - self.store.compare_and_set_blob_info_with_write( - self.store.get_blob_info(), - Some(blob_info), - )?; + let dab_slot = + data_availability_boundary.end_slot(T::EthSpec::slots_per_epoch()); + if let Some(dab_state_root) = self.state_root_at_slot(dab_slot)? { + blob_info.data_availability_boundary = + Split::new(dab_slot, dab_state_root); + + self.store.compare_and_set_blob_info_with_write( + self.store.get_blob_info(), + Some(blob_info), + )?; + } } } } diff --git a/beacon_node/store/src/hot_cold_store.rs b/beacon_node/store/src/hot_cold_store.rs index 5e5c8ab4d47..c54381b73c3 100644 --- a/beacon_node/store/src/hot_cold_store.rs +++ b/beacon_node/store/src/hot_cold_store.rs @@ -1704,62 +1704,20 @@ impl, Cold: ItemStore> HotColdDB } } - let data_availability_breakpoint: Hash256; - - match blob_info.data_availability_boundary { - Some(breakpoint) => { - if breakpoint == blob_info.oldest_blob_parent { - return Ok(()); - } - data_availability_breakpoint = breakpoint; - } - None => { - return Ok(()); - } + if blob_info.last_pruned_epoch == blob_info.next_epoch_to_prune && !force { + info!(self.log, "Blobs sidecars are pruned"); + return Ok(()); } - // Load the state from which to prune blobs so we can backtrack. - let prune_state = self.get_state(&data_availability_breakpoint, None)?.ok_or( - HotColdDBError::MissingStateToPruneBlobs(data_availability_breakpoint), - )?; - - // The data_availability_breakpoint is set at the start of an epoch indicating the epoch - // before can be pruned. - let prune_epoch = prune_state.current_epoch() - 1; + let dab_state_root = blob_info.data_availability_boundary.state_root; - // The finalized block may or may not have its blobs sidecar stored, depending on - // whether it was at a skipped slot. However for a fully pruned database its parent - // should *always* have been pruned. In the case of blobs sidecars we look at the next - // parent block with at least one kzg commitment. - - let already_pruned = process_results( - BlockRootsIter::new(&prune_state, blob_info.oldest_blob_slot), - |mut iter| { - iter.find(|(_, block_root)| { - move || -> bool { - if let Ok(Some(erase_parent_block)) = self.get_blinded_block(&block_root) { - if let Ok(expected_kzg_commitments) = - erase_parent_block.message().body().blob_kzg_commitments() - { - if expected_kzg_commitments.len() > 0 { - return true; - } - } - } - false - }() - }) - .map_or(Ok(true), |(_, split_parent_root)| { - self.blobs_sidecar_exists(&split_parent_root) - .map(|exists| !exists) - }) - }, - )??; + // Load the state from which to prune blobs so we can backtrack. + let dab_state = self + .get_state(&dab_state_root, None)? + .ok_or(HotColdDBError::MissingStateToPruneBlobs(dab_state_root))?; - if already_pruned && !force { - info!(self.log, "Blobs sidecars are pruned"); - return Ok(()); - } + let dab_block_root = dab_state.get_latest_block_root(dab_state_root); + let dab_slot = dab_state.slot(); // Iterate block roots backwards to oldest blob slot. warn!( @@ -1771,7 +1729,9 @@ impl, Cold: ItemStore> HotColdDB let mut ops = vec![]; let mut last_pruned_block_root = None; - for res in BlockRootsIterator::new(self, &prune_state) { + for res in std::iter::once(Ok((dab_block_root, dab_slot))) + .chain(BlockRootsIterator::new(self, &dab_state)) + { let (block_root, slot) = match res { Ok(tuple) => tuple, Err(e) => { @@ -1797,15 +1757,13 @@ impl, Cold: ItemStore> HotColdDB ops.push(StoreOp::DeleteBlobs(block_root)); } - if block_root == blob_info.oldest_blob_parent { + if slot <= blob_info.oldest_blob_slot { info!( self.log, "Blobs sidecar pruning reached earliest available blobs sidecar"; "slot" => slot ); - blob_info.oldest_blob_slot = slot; - blob_info.last_pruned_epoch = prune_epoch; - blob_info.oldest_blob_parent = data_availability_breakpoint; + blob_info.oldest_blob_slot = dab_slot + 1; break; } } @@ -1818,6 +1776,7 @@ impl, Cold: ItemStore> HotColdDB "blobs_sidecars_pruned" => blobs_sidecars_pruned, ); + blob_info.last_pruned_epoch = dab_state.current_epoch(); self.compare_and_set_blob_info_with_write(self.get_blob_info(), Some(blob_info))?; Ok(()) @@ -1968,7 +1927,7 @@ pub fn migrate_database, Cold: ItemStore>( } /// Struct for storing the split slot and state root in the database. -#[derive(Debug, Clone, Copy, PartialEq, Default, Encode, Decode, Deserialize, Serialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode, Deserialize, Serialize)] pub struct Split { pub(crate) slot: Slot, pub(crate) state_root: Hash256, @@ -1988,6 +1947,12 @@ impl StoreItem for Split { } } +impl Split { + pub fn new(slot: Slot, state_root: Hash256) -> Self { + Split { slot, state_root } + } +} + /// Type hint. fn no_state_root_iter() -> Option>> { None diff --git a/beacon_node/store/src/metadata.rs b/beacon_node/store/src/metadata.rs index 89cf8097604..37d2e56459d 100644 --- a/beacon_node/store/src/metadata.rs +++ b/beacon_node/store/src/metadata.rs @@ -1,4 +1,4 @@ -use crate::{DBColumn, Error, StoreItem}; +use crate::{DBColumn, Error, Split, StoreItem}; use serde_derive::{Deserialize, Serialize}; use ssz::{Decode, Encode}; use ssz_derive::{Decode, Encode}; @@ -124,14 +124,12 @@ impl StoreItem for AnchorInfo { pub struct BlobInfo { /// The latest epoch that blobs were pruned. pub last_pruned_epoch: Epoch, - /// The block root of the next blobs to prune from. - pub data_availability_boundary: Option, - /// The block root of the next blob that needs to be added to fill in the history. - pub oldest_blob_parent: Hash256, + /// The next epoch to prune blobs from. + pub next_epoch_to_prune: Epoch, + /// The state root and slot of the next blobs to prune from. + pub data_availability_boundary: Split, /// The slot before which blobs are available. pub oldest_blob_slot: Slot, - /// The slot from which blobs are available. - pub latest_blob_slot: Slot, } impl StoreItem for BlobInfo { From 6f5ca02ac979401388b257df773205b0dd55b123 Mon Sep 17 00:00:00 2001 From: Emilia Hane <58548332+emhane@users.noreply.github.com> Date: Fri, 13 Jan 2023 21:18:14 +0100 Subject: [PATCH 15/64] Improve syntax Co-authored-by: Michael Sproul --- beacon_node/store/src/hot_cold_store.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/beacon_node/store/src/hot_cold_store.rs b/beacon_node/store/src/hot_cold_store.rs index c54381b73c3..4d00b836a5a 100644 --- a/beacon_node/store/src/hot_cold_store.rs +++ b/beacon_node/store/src/hot_cold_store.rs @@ -1693,16 +1693,14 @@ impl, Cold: ItemStore> HotColdDB /// Try to prune blobs older than the data availability boundary. pub fn try_prune_blobs(&self, force: bool) -> Result<(), Error> { - let mut blob_info: BlobInfo; - - match self.get_blob_info() { + let blob_info = match self.get_blob_info() { Some(old_blob_info) => { - blob_info = old_blob_info; + old_blob_info } None => { return Ok(()); } - } + }; if blob_info.last_pruned_epoch == blob_info.next_epoch_to_prune && !force { info!(self.log, "Blobs sidecars are pruned"); From a2b8c6ee69f157101e262f75e2742522ebba1442 Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Fri, 13 Jan 2023 22:09:15 +0100 Subject: [PATCH 16/64] Save fetching state for blobs pruning --- beacon_node/beacon_chain/src/beacon_chain.rs | 8 ++-- beacon_node/store/src/forwards_iter.rs | 6 +-- beacon_node/store/src/hot_cold_store.rs | 47 +++++++++++--------- 3 files changed, 32 insertions(+), 29 deletions(-) diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 52390f36a6d..b74f7309e2e 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -612,10 +612,10 @@ impl BeaconChain { start_slot, end_slot, || { - ( + Ok(( head.beacon_state.clone_with_only_committee_caches(), head.beacon_block_root, - ) + )) }, &self.spec, )?; @@ -709,10 +709,10 @@ impl BeaconChain { start_slot, end_slot, || { - ( + Ok(( head.beacon_state.clone_with_only_committee_caches(), head.beacon_state_root(), - ) + )) }, &self.spec, )?; diff --git a/beacon_node/store/src/forwards_iter.rs b/beacon_node/store/src/forwards_iter.rs index 353be6bf058..a78b2b469fc 100644 --- a/beacon_node/store/src/forwards_iter.rs +++ b/beacon_node/store/src/forwards_iter.rs @@ -150,7 +150,7 @@ impl<'a, E: EthSpec, F: Root, Hot: ItemStore, Cold: ItemStore> store: &'a HotColdDB, start_slot: Slot, end_slot: Option, - get_state: impl FnOnce() -> (BeaconState, Hash256), + get_state: impl FnOnce() -> Result<(BeaconState, Hash256)>, spec: &ChainSpec, ) -> Result { use HybridForwardsIterator::*; @@ -172,7 +172,7 @@ impl<'a, E: EthSpec, F: Root, Hot: ItemStore, Cold: ItemStore> if end_slot.map_or(false, |end_slot| end_slot < latest_restore_point_slot) { None } else { - Some(Box::new(get_state())) + Some(Box::new(get_state()?)) }; PreFinalization { iter, @@ -180,7 +180,7 @@ impl<'a, E: EthSpec, F: Root, Hot: ItemStore, Cold: ItemStore> } } else { PostFinalizationLazy { - continuation_data: Some(Box::new(get_state())), + continuation_data: Some(Box::new(get_state()?)), store, start_slot, } diff --git a/beacon_node/store/src/hot_cold_store.rs b/beacon_node/store/src/hot_cold_store.rs index 4d00b836a5a..2c60205eb42 100644 --- a/beacon_node/store/src/hot_cold_store.rs +++ b/beacon_node/store/src/hot_cold_store.rs @@ -657,7 +657,7 @@ impl, Cold: ItemStore> HotColdDB self, start_slot, None, - || (end_state, end_block_root), + || Ok((end_state, end_block_root)), spec, ) } @@ -666,7 +666,7 @@ impl, Cold: ItemStore> HotColdDB &self, start_slot: Slot, end_slot: Slot, - get_state: impl FnOnce() -> (BeaconState, Hash256), + get_state: impl FnOnce() -> Result<(BeaconState, Hash256), Error>, spec: &ChainSpec, ) -> Result, Error> { HybridForwardsBlockRootsIterator::new(self, start_slot, Some(end_slot), get_state, spec) @@ -683,7 +683,7 @@ impl, Cold: ItemStore> HotColdDB self, start_slot, None, - || (end_state, end_state_root), + || Ok((end_state, end_state_root)), spec, ) } @@ -692,7 +692,7 @@ impl, Cold: ItemStore> HotColdDB &self, start_slot: Slot, end_slot: Slot, - get_state: impl FnOnce() -> (BeaconState, Hash256), + get_state: impl FnOnce() -> Result<(BeaconState, Hash256), Error>, spec: &ChainSpec, ) -> Result, Error> { HybridForwardsStateRootsIterator::new(self, start_slot, Some(end_slot), get_state, spec) @@ -1067,7 +1067,7 @@ impl, Cold: ItemStore> HotColdDB let state_root_iter = self.forwards_state_roots_iterator_until( low_restore_point.slot(), slot, - || (high_restore_point, Hash256::zero()), + || Ok((high_restore_point, Hash256::zero())), &self.spec, )?; @@ -1694,9 +1694,7 @@ impl, Cold: ItemStore> HotColdDB /// Try to prune blobs older than the data availability boundary. pub fn try_prune_blobs(&self, force: bool) -> Result<(), Error> { let blob_info = match self.get_blob_info() { - Some(old_blob_info) => { - old_blob_info - } + Some(old_blob_info) => old_blob_info, None => { return Ok(()); } @@ -1709,14 +1707,6 @@ impl, Cold: ItemStore> HotColdDB let dab_state_root = blob_info.data_availability_boundary.state_root; - // Load the state from which to prune blobs so we can backtrack. - let dab_state = self - .get_state(&dab_state_root, None)? - .ok_or(HotColdDBError::MissingStateToPruneBlobs(dab_state_root))?; - - let dab_block_root = dab_state.get_latest_block_root(dab_state_root); - let dab_slot = dab_state.slot(); - // Iterate block roots backwards to oldest blob slot. warn!( self.log, @@ -1727,9 +1717,19 @@ impl, Cold: ItemStore> HotColdDB let mut ops = vec![]; let mut last_pruned_block_root = None; - for res in std::iter::once(Ok((dab_block_root, dab_slot))) - .chain(BlockRootsIterator::new(self, &dab_state)) - { + for res in self.forwards_block_roots_iterator_until( + blob_info.oldest_blob_slot, + blob_info.data_availability_boundary.slot, + || { + let dab_state = self + .get_state(&dab_state_root, None)? + .ok_or(HotColdDBError::MissingStateToPruneBlobs(dab_state_root))?; + let dab_block_root = dab_state.get_latest_block_root(dab_state_root); + + Ok((dab_state, dab_block_root)) + }, + &self.spec, + )? { let (block_root, slot) = match res { Ok(tuple) => tuple, Err(e) => { @@ -1761,7 +1761,6 @@ impl, Cold: ItemStore> HotColdDB "Blobs sidecar pruning reached earliest available blobs sidecar"; "slot" => slot ); - blob_info.oldest_blob_slot = dab_slot + 1; break; } } @@ -1774,8 +1773,12 @@ impl, Cold: ItemStore> HotColdDB "blobs_sidecars_pruned" => blobs_sidecars_pruned, ); - blob_info.last_pruned_epoch = dab_state.current_epoch(); - self.compare_and_set_blob_info_with_write(self.get_blob_info(), Some(blob_info))?; + if let Some(mut new_blob_info) = self.get_blob_info() { + new_blob_info.last_pruned_epoch = + (blob_info.data_availability_boundary.slot + 1).epoch(E::slots_per_epoch()); + new_blob_info.oldest_blob_slot = blob_info.data_availability_boundary.slot + 1; + self.compare_and_set_blob_info_with_write(self.get_blob_info(), Some(new_blob_info))?; + } Ok(()) } From 94aa2cef67be27a648862e6db60f0fbcc3b51e06 Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Sat, 14 Jan 2023 16:40:38 +0100 Subject: [PATCH 17/64] Log info loaded from disk --- beacon_node/store/src/hot_cold_store.rs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/beacon_node/store/src/hot_cold_store.rs b/beacon_node/store/src/hot_cold_store.rs index 2c60205eb42..caa7c92a304 100644 --- a/beacon_node/store/src/hot_cold_store.rs +++ b/beacon_node/store/src/hot_cold_store.rs @@ -166,7 +166,11 @@ impl HotColdDB, LevelDB> { let mut db = HotColdDB { split: RwLock::new(Split::default()), anchor_info: RwLock::new(None), - blob_info: RwLock::new(None), + blob_info: RwLock::new( + spec.eip4844_fork_epoch + .is_some() + .then(|| BlobInfo::default()), + ), cold_db: LevelDB::open(cold_path)?, hot_db: LevelDB::open(hot_path)?, block_cache: Mutex::new(LruCache::new(config.block_cache_size)), @@ -212,8 +216,17 @@ impl HotColdDB, LevelDB> { ); } - if db.spec.eip4844_fork_epoch.is_some() { - *db.blob_info.write() = db.load_blob_info()?.or(Some(BlobInfo::default())); + if let Some(blob_info) = db.load_blob_info()? { + let dab_slot = blob_info.data_availability_boundary.slot; + let dab_state_root = blob_info.data_availability_boundary.state_root; + *db.blob_info.write() = Some(blob_info); + + info!( + db.log, + "Blob info loaded from disk"; + "data_availability_boundary_slot" => dab_slot, + "data_availability_boundary_state" => ?dab_state_root, + ); } // Ensure that the schema version of the on-disk database matches the software. From c7f53a9062deb04a8872cbb80832a27aa076ad91 Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Sat, 14 Jan 2023 16:41:20 +0100 Subject: [PATCH 18/64] Delete blobs that conflict with finalization --- beacon_node/beacon_chain/src/migrate.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/beacon_node/beacon_chain/src/migrate.rs b/beacon_node/beacon_chain/src/migrate.rs index 66f082742eb..3ef0b265a55 100644 --- a/beacon_node/beacon_chain/src/migrate.rs +++ b/beacon_node/beacon_chain/src/migrate.rs @@ -572,6 +572,7 @@ impl, Cold: ItemStore> BackgroundMigrator Date: Sat, 14 Jan 2023 17:18:55 +0100 Subject: [PATCH 19/64] Store orphan block roots --- beacon_node/beacon_chain/src/migrate.rs | 1 + beacon_node/store/src/hot_cold_store.rs | 8 ++++++++ beacon_node/store/src/lib.rs | 4 ++++ 3 files changed, 13 insertions(+) diff --git a/beacon_node/beacon_chain/src/migrate.rs b/beacon_node/beacon_chain/src/migrate.rs index 3ef0b265a55..4aa55f404eb 100644 --- a/beacon_node/beacon_chain/src/migrate.rs +++ b/beacon_node/beacon_chain/src/migrate.rs @@ -573,6 +573,7 @@ impl, Cold: ItemStore> BackgroundMigrator, Cold: ItemStore> HotColdDB let key = get_key_for_col(DBColumn::ExecPayload.into(), block_root.as_bytes()); key_value_batch.push(KeyValueStoreOp::DeleteKey(key)); } + + StoreOp::PutOrphanedBlobs(block_root) => { + let db_key = + get_key_for_col(DBColumn::BeaconBlobOrphan.into(), block_root.as_bytes()); + key_value_batch.push(KeyValueStoreOp::PutKeyValue(db_key, [].into())); + } } } Ok(key_value_batch) @@ -862,6 +868,8 @@ impl, Cold: ItemStore> HotColdDB StoreOp::DeleteState(_, _) => (), StoreOp::DeleteExecutionPayload(_) => (), + + StoreOp::PutOrphanedBlobs(_) => (), } } diff --git a/beacon_node/store/src/lib.rs b/beacon_node/store/src/lib.rs index e1b2c948a9e..7d244f5aed7 100644 --- a/beacon_node/store/src/lib.rs +++ b/beacon_node/store/src/lib.rs @@ -158,6 +158,7 @@ pub enum StoreOp<'a, E: EthSpec> { PutBlock(Hash256, Arc>), PutState(Hash256, &'a BeaconState), PutBlobs(Hash256, Arc>), + PutOrphanedBlobs(Hash256), PutStateSummary(Hash256, HotStateSummary), PutStateTemporaryFlag(Hash256), DeleteStateTemporaryFlag(Hash256), @@ -177,6 +178,9 @@ pub enum DBColumn { BeaconBlock, #[strum(serialize = "blb")] BeaconBlob, + /// Block roots of orphaned beacon blobs. + #[strum(serialize = "blbo")] + BeaconBlobOrphan, /// For full `BeaconState`s in the hot database (finalized or fork-boundary states). #[strum(serialize = "ste")] BeaconState, From 2f565d25b2639b63c5201fb1fba1ce80cc51f9d0 Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Sat, 14 Jan 2023 17:24:18 +0100 Subject: [PATCH 20/64] Prune blobs in bg after canonical head update --- beacon_node/beacon_chain/src/canonical_head.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/beacon_node/beacon_chain/src/canonical_head.rs b/beacon_node/beacon_chain/src/canonical_head.rs index 19eddf60263..83fe1940ad9 100644 --- a/beacon_node/beacon_chain/src/canonical_head.rs +++ b/beacon_node/beacon_chain/src/canonical_head.rs @@ -793,6 +793,19 @@ impl BeaconChain { .execution_status .is_optimistic_or_invalid(); + if self.store.get_config().prune_blobs { + let store = self.store.clone(); + let log = self.log.clone(); + self.task_executor.spawn_blocking( + move || { + if let Err(e) = store.try_prune_blobs(false) { + error!(log, "Error pruning blobs in background"; "error" => ?e); + } + }, + "prune_blobs_background", + ); + } + // Detect and potentially report any re-orgs. let reorg_distance = detect_reorg( &old_snapshot.beacon_state, From 6346c30158bd1063e379abad164b137bcc0219e1 Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Sat, 14 Jan 2023 18:20:08 +0100 Subject: [PATCH 21/64] Enable skipping blob pruning at each epoch --- beacon_node/src/cli.rs | 11 ++++++++++- beacon_node/src/config.rs | 6 ++++++ beacon_node/store/src/config.rs | 4 ++++ beacon_node/store/src/hot_cold_store.rs | 12 ++++++++---- 4 files changed, 28 insertions(+), 5 deletions(-) diff --git a/beacon_node/src/cli.rs b/beacon_node/src/cli.rs index 8932b503f60..510882c366d 100644 --- a/beacon_node/src/cli.rs +++ b/beacon_node/src/cli.rs @@ -555,10 +555,19 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> { Arg::with_name("prune-blobs") .long("prune-blobs") .help("Prune blobs from Lighthouse's database when they are older than the data \ - data availability boundary relative to the current head.") + data availability boundary relative to the current epoch.") .takes_value(true) .default_value("true") ) + .arg( + Arg::with_name("epochs-per-blob-prune") + .long("epochs-per-blob-prune") + .help("The epoch interval with which to prune blobs from Lighthouse's \ + database when they are older than the data data availability \ + boundary relative to the current epoch.") + .takes_value(true) + .default_value("1") + ) /* * Misc. diff --git a/beacon_node/src/config.rs b/beacon_node/src/config.rs index a435a42fd3f..14d40db03b7 100644 --- a/beacon_node/src/config.rs +++ b/beacon_node/src/config.rs @@ -415,6 +415,12 @@ pub fn get_config( client_config.store.prune_blobs = prune_blobs; } + if let Some(epochs_per_blob_prune) = + clap_utils::parse_optional(cli_args, "epochs-per-blob-prune")? + { + client_config.store.epochs_per_blob_prune = epochs_per_blob_prune; + } + /* * Zero-ports * diff --git a/beacon_node/store/src/config.rs b/beacon_node/store/src/config.rs index 13ac674dfff..b3b52852629 100644 --- a/beacon_node/store/src/config.rs +++ b/beacon_node/store/src/config.rs @@ -8,6 +8,7 @@ pub const PREV_DEFAULT_SLOTS_PER_RESTORE_POINT: u64 = 2048; pub const DEFAULT_SLOTS_PER_RESTORE_POINT: u64 = 8192; pub const DEFAULT_BLOCK_CACHE_SIZE: usize = 5; pub const DEFAULT_BLOB_CACHE_SIZE: usize = 5; +pub const DEFAULT_EPOCHS_PER_BLOB_PRUNE: u64 = 1; /// Database configuration parameters. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] @@ -28,6 +29,8 @@ pub struct StoreConfig { pub prune_payloads: bool, /// Whether to prune blobs older than the blob data availability boundary. pub prune_blobs: bool, + /// Frequency of blob pruning. Default: every epoch. + pub epochs_per_blob_prune: u64, } /// Variant of `StoreConfig` that gets written to disk. Contains immutable configuration params. @@ -53,6 +56,7 @@ impl Default for StoreConfig { compact_on_prune: true, prune_payloads: true, prune_blobs: true, + epochs_per_blob_prune: DEFAULT_EPOCHS_PER_BLOB_PRUNE, } } } diff --git a/beacon_node/store/src/hot_cold_store.rs b/beacon_node/store/src/hot_cold_store.rs index 927f5fe8a78..22af4b3b5b7 100644 --- a/beacon_node/store/src/hot_cold_store.rs +++ b/beacon_node/store/src/hot_cold_store.rs @@ -1721,9 +1721,13 @@ impl, Cold: ItemStore> HotColdDB } }; - if blob_info.last_pruned_epoch == blob_info.next_epoch_to_prune && !force { - info!(self.log, "Blobs sidecars are pruned"); - return Ok(()); + if !force { + let epochs_per_blob_prune = + Epoch::new(self.get_config().epochs_per_blob_prune * E::slots_per_epoch()); + if blob_info.last_pruned_epoch + epochs_per_blob_prune > blob_info.next_epoch_to_prune { + info!(self.log, "Blobs sidecars are pruned"); + return Ok(()); + } } let dab_state_root = blob_info.data_availability_boundary.state_root; @@ -1840,7 +1844,7 @@ pub fn migrate_database, Cold: ItemStore>( } // Prune blobs before migration. - store.try_prune_blobs(false)?; + store.try_prune_blobs(true)?; let mut hot_db_ops: Vec> = Vec::new(); From d58a30b3de19469465bc317ace25c9df9c78cde3 Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Sat, 14 Jan 2023 18:29:03 +0100 Subject: [PATCH 22/64] fixup! Store orphan block roots --- beacon_node/beacon_chain/src/migrate.rs | 2 +- beacon_node/store/src/hot_cold_store.rs | 4 ++-- beacon_node/store/src/lib.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/beacon_node/beacon_chain/src/migrate.rs b/beacon_node/beacon_chain/src/migrate.rs index 4aa55f404eb..8fb00c105f9 100644 --- a/beacon_node/beacon_chain/src/migrate.rs +++ b/beacon_node/beacon_chain/src/migrate.rs @@ -573,7 +573,7 @@ impl, Cold: ItemStore> BackgroundMigrator, Cold: ItemStore> HotColdDB key_value_batch.push(KeyValueStoreOp::DeleteKey(key)); } - StoreOp::PutOrphanedBlobs(block_root) => { + StoreOp::PutOrphanedBlobsKey(block_root) => { let db_key = get_key_for_col(DBColumn::BeaconBlobOrphan.into(), block_root.as_bytes()); key_value_batch.push(KeyValueStoreOp::PutKeyValue(db_key, [].into())); @@ -869,7 +869,7 @@ impl, Cold: ItemStore> HotColdDB StoreOp::DeleteExecutionPayload(_) => (), - StoreOp::PutOrphanedBlobs(_) => (), + StoreOp::PutOrphanedBlobsKey(_) => (), } } diff --git a/beacon_node/store/src/lib.rs b/beacon_node/store/src/lib.rs index 7d244f5aed7..b30f41cb29a 100644 --- a/beacon_node/store/src/lib.rs +++ b/beacon_node/store/src/lib.rs @@ -158,7 +158,7 @@ pub enum StoreOp<'a, E: EthSpec> { PutBlock(Hash256, Arc>), PutState(Hash256, &'a BeaconState), PutBlobs(Hash256, Arc>), - PutOrphanedBlobs(Hash256), + PutOrphanedBlobsKey(Hash256), PutStateSummary(Hash256, HotStateSummary), PutStateTemporaryFlag(Hash256), DeleteStateTemporaryFlag(Hash256), From fb2ce909f66e0bbfa2d02bd34914008901f3275e Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Sun, 15 Jan 2023 10:13:01 +0100 Subject: [PATCH 23/64] Avoid repeteadly updating blob info for multiple head candidates --- beacon_node/beacon_chain/src/beacon_chain.rs | 28 --------------- .../beacon_chain/src/canonical_head.rs | 35 ++++++++++++++++++- 2 files changed, 34 insertions(+), 29 deletions(-) diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index b74f7309e2e..250c64a1d37 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -50,7 +50,6 @@ use crate::persisted_fork_choice::PersistedForkChoice; use crate::pre_finalization_cache::PreFinalizationBlockCache; use crate::shuffling_cache::{BlockShufflingIds, ShufflingCache}; use crate::snapshot_cache::{BlockProductionPreState, SnapshotCache}; -use crate::store::Split; use crate::sync_committee_verification::{ Error as SyncCommitteeError, VerifiedSyncCommitteeMessage, VerifiedSyncContribution, }; @@ -3029,33 +3028,6 @@ impl BeaconChain { } } - if Some(current_epoch) - > self.spec.eip4844_fork_epoch.map(|eip4844_fork_epoch| { - eip4844_fork_epoch + *MIN_EPOCHS_FOR_BLOBS_SIDECARS_REQUESTS - }) - { - let current_epoch_start_slot = current_epoch.start_slot(T::EthSpec::slots_per_epoch()); - - // Update db's metadata for blobs pruning. - if current_slot == current_epoch_start_slot { - if let Some(mut blob_info) = self.store.get_blob_info() { - if let Some(data_availability_boundary) = self.data_availability_boundary() { - let dab_slot = - data_availability_boundary.end_slot(T::EthSpec::slots_per_epoch()); - if let Some(dab_state_root) = self.state_root_at_slot(dab_slot)? { - blob_info.data_availability_boundary = - Split::new(dab_slot, dab_state_root); - - self.store.compare_and_set_blob_info_with_write( - self.store.get_blob_info(), - Some(blob_info), - )?; - } - } - } - } - } - let txn_lock = self.store.hot_db.begin_rw_transaction(); kv_store_ops.extend(self.store.convert_to_kv_batch(ops)?); diff --git a/beacon_node/beacon_chain/src/canonical_head.rs b/beacon_node/beacon_chain/src/canonical_head.rs index 83fe1940ad9..1bd998261e7 100644 --- a/beacon_node/beacon_chain/src/canonical_head.rs +++ b/beacon_node/beacon_chain/src/canonical_head.rs @@ -54,8 +54,9 @@ use slog::{crit, debug, error, warn, Logger}; use slot_clock::SlotClock; use std::sync::Arc; use std::time::Duration; -use store::{iter::StateRootsIterator, KeyValueStoreOp, StoreItem}; +use store::{iter::StateRootsIterator, KeyValueStoreOp, Split, StoreItem}; use task_executor::{JoinHandle, ShutdownReason}; +use types::consts::eip4844::MIN_EPOCHS_FOR_BLOBS_SIDECARS_REQUESTS; use types::*; /// Simple wrapper around `RwLock` that uses private visibility to prevent any other modules from @@ -794,8 +795,40 @@ impl BeaconChain { .is_optimistic_or_invalid(); if self.store.get_config().prune_blobs { + let current_slot = self.slot()?; + let current_epoch = current_slot.epoch(T::EthSpec::slots_per_epoch()); + if Some(current_epoch) + > self.spec.eip4844_fork_epoch.map(|eip4844_fork_epoch| { + eip4844_fork_epoch + *MIN_EPOCHS_FOR_BLOBS_SIDECARS_REQUESTS + }) + { + let current_epoch_start_slot = + current_epoch.start_slot(T::EthSpec::slots_per_epoch()); + + // Update db's metadata for blobs pruning. + if current_slot == current_epoch_start_slot { + if let Some(mut blob_info) = self.store.get_blob_info() { + if let Some(data_availability_boundary) = self.data_availability_boundary() + { + let dab_slot = + data_availability_boundary.end_slot(T::EthSpec::slots_per_epoch()); + if let Some(dab_state_root) = self.state_root_at_slot(dab_slot)? { + blob_info.data_availability_boundary = + Split::new(dab_slot, dab_state_root); + + self.store.compare_and_set_blob_info_with_write( + self.store.get_blob_info(), + Some(blob_info), + )?; + } + } + } + } + } + let store = self.store.clone(); let log = self.log.clone(); + self.task_executor.spawn_blocking( move || { if let Err(e) = store.try_prune_blobs(false) { From b5abfe620a12bb9ac9c1a2819c89422d7995f244 Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Mon, 16 Jan 2023 08:35:09 +0100 Subject: [PATCH 24/64] Convert epochs_per_blob_prune to Epoch once --- beacon_node/src/config.rs | 3 ++- beacon_node/store/src/hot_cold_store.rs | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/beacon_node/src/config.rs b/beacon_node/src/config.rs index 14d40db03b7..6758ba3b480 100644 --- a/beacon_node/src/config.rs +++ b/beacon_node/src/config.rs @@ -418,7 +418,8 @@ pub fn get_config( if let Some(epochs_per_blob_prune) = clap_utils::parse_optional(cli_args, "epochs-per-blob-prune")? { - client_config.store.epochs_per_blob_prune = epochs_per_blob_prune; + client_config.store.epochs_per_blob_prune = + Epoch::new(epochs_per_blob_prune * E::slots_per_epoch()); } /* diff --git a/beacon_node/store/src/hot_cold_store.rs b/beacon_node/store/src/hot_cold_store.rs index b81610c9045..6b2b193f8f5 100644 --- a/beacon_node/store/src/hot_cold_store.rs +++ b/beacon_node/store/src/hot_cold_store.rs @@ -1722,9 +1722,9 @@ impl, Cold: ItemStore> HotColdDB }; if !force { - let epochs_per_blob_prune = - Epoch::new(self.get_config().epochs_per_blob_prune * E::slots_per_epoch()); - if blob_info.last_pruned_epoch + epochs_per_blob_prune > blob_info.next_epoch_to_prune { + if blob_info.last_pruned_epoch + self.get_config().epochs_per_blob_prune + > blob_info.next_epoch_to_prune + { info!(self.log, "Blobs sidecars are pruned"); return Ok(()); } From 0d13932663fdd0234c1b3632383717b533538c65 Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Mon, 16 Jan 2023 13:59:39 +0100 Subject: [PATCH 25/64] Fix epoch constructor misconception --- beacon_node/src/config.rs | 3 +-- beacon_node/store/src/hot_cold_store.rs | 4 ++-- consensus/types/src/slot_epoch.rs | 4 ++-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/beacon_node/src/config.rs b/beacon_node/src/config.rs index 6758ba3b480..14d40db03b7 100644 --- a/beacon_node/src/config.rs +++ b/beacon_node/src/config.rs @@ -418,8 +418,7 @@ pub fn get_config( if let Some(epochs_per_blob_prune) = clap_utils::parse_optional(cli_args, "epochs-per-blob-prune")? { - client_config.store.epochs_per_blob_prune = - Epoch::new(epochs_per_blob_prune * E::slots_per_epoch()); + client_config.store.epochs_per_blob_prune = epochs_per_blob_prune; } /* diff --git a/beacon_node/store/src/hot_cold_store.rs b/beacon_node/store/src/hot_cold_store.rs index 6b2b193f8f5..e53499438e0 100644 --- a/beacon_node/store/src/hot_cold_store.rs +++ b/beacon_node/store/src/hot_cold_store.rs @@ -1722,8 +1722,8 @@ impl, Cold: ItemStore> HotColdDB }; if !force { - if blob_info.last_pruned_epoch + self.get_config().epochs_per_blob_prune - > blob_info.next_epoch_to_prune + if blob_info.last_pruned_epoch.as_u64() + self.get_config().epochs_per_blob_prune + > blob_info.next_epoch_to_prune.as_u64() { info!(self.log, "Blobs sidecars are pruned"); return Ok(()); diff --git a/consensus/types/src/slot_epoch.rs b/consensus/types/src/slot_epoch.rs index 2716367c7eb..06f99b98888 100644 --- a/consensus/types/src/slot_epoch.rs +++ b/consensus/types/src/slot_epoch.rs @@ -76,8 +76,8 @@ impl Slot { } impl Epoch { - pub const fn new(slot: u64) -> Epoch { - Epoch(slot) + pub const fn new(epoch: u64) -> Epoch { + Epoch(epoch) } pub fn max_value() -> Epoch { From 7103a257cecd9a11e75c2b29cb73155a8ef32288 Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Mon, 16 Jan 2023 20:53:09 +0100 Subject: [PATCH 26/64] Simplify conceptual design --- beacon_node/beacon_chain/src/builder.rs | 6 +- .../beacon_chain/src/canonical_head.rs | 64 +++++------------ beacon_node/store/src/hot_cold_store.rs | 71 ++++++++++--------- beacon_node/store/src/metadata.rs | 10 +-- database_manager/src/lib.rs | 6 +- 5 files changed, 66 insertions(+), 91 deletions(-) diff --git a/beacon_node/beacon_chain/src/builder.rs b/beacon_node/beacon_chain/src/builder.rs index fe62f8094a5..f4e6fc91d08 100644 --- a/beacon_node/beacon_chain/src/builder.rs +++ b/beacon_node/beacon_chain/src/builder.rs @@ -918,9 +918,13 @@ where if beacon_chain.store.get_config().prune_blobs { let store = beacon_chain.store.clone(); let log = log.clone(); + let current_slot = beacon_chain + .slot() + .map_err(|e| format!("Failed to get current slot: {:?}", e))?; + let current_epoch = current_slot.epoch(TEthSpec::slots_per_epoch()); beacon_chain.task_executor.spawn_blocking( move || { - if let Err(e) = store.try_prune_blobs(false) { + if let Err(e) = store.try_prune_blobs(false, Some(current_epoch)) { error!(log, "Error pruning blobs in background"; "error" => ?e); } }, diff --git a/beacon_node/beacon_chain/src/canonical_head.rs b/beacon_node/beacon_chain/src/canonical_head.rs index 1bd998261e7..63cb143d3b2 100644 --- a/beacon_node/beacon_chain/src/canonical_head.rs +++ b/beacon_node/beacon_chain/src/canonical_head.rs @@ -54,9 +54,8 @@ use slog::{crit, debug, error, warn, Logger}; use slot_clock::SlotClock; use std::sync::Arc; use std::time::Duration; -use store::{iter::StateRootsIterator, KeyValueStoreOp, Split, StoreItem}; +use store::{iter::StateRootsIterator, KeyValueStoreOp, StoreItem}; use task_executor::{JoinHandle, ShutdownReason}; -use types::consts::eip4844::MIN_EPOCHS_FOR_BLOBS_SIDECARS_REQUESTS; use types::*; /// Simple wrapper around `RwLock` that uses private visibility to prevent any other modules from @@ -794,51 +793,6 @@ impl BeaconChain { .execution_status .is_optimistic_or_invalid(); - if self.store.get_config().prune_blobs { - let current_slot = self.slot()?; - let current_epoch = current_slot.epoch(T::EthSpec::slots_per_epoch()); - if Some(current_epoch) - > self.spec.eip4844_fork_epoch.map(|eip4844_fork_epoch| { - eip4844_fork_epoch + *MIN_EPOCHS_FOR_BLOBS_SIDECARS_REQUESTS - }) - { - let current_epoch_start_slot = - current_epoch.start_slot(T::EthSpec::slots_per_epoch()); - - // Update db's metadata for blobs pruning. - if current_slot == current_epoch_start_slot { - if let Some(mut blob_info) = self.store.get_blob_info() { - if let Some(data_availability_boundary) = self.data_availability_boundary() - { - let dab_slot = - data_availability_boundary.end_slot(T::EthSpec::slots_per_epoch()); - if let Some(dab_state_root) = self.state_root_at_slot(dab_slot)? { - blob_info.data_availability_boundary = - Split::new(dab_slot, dab_state_root); - - self.store.compare_and_set_blob_info_with_write( - self.store.get_blob_info(), - Some(blob_info), - )?; - } - } - } - } - } - - let store = self.store.clone(); - let log = self.log.clone(); - - self.task_executor.spawn_blocking( - move || { - if let Err(e) = store.try_prune_blobs(false) { - error!(log, "Error pruning blobs in background"; "error" => ?e); - } - }, - "prune_blobs_background", - ); - } - // Detect and potentially report any re-orgs. let reorg_distance = detect_reorg( &old_snapshot.beacon_state, @@ -1060,6 +1014,22 @@ impl BeaconChain { // Take a write-lock on the canonical head and signal for it to prune. self.canonical_head.fork_choice_write_lock().prune()?; + // Prune blobs. + if self.store.get_config().prune_blobs { + let store = self.store.clone(); + let log = self.log.clone(); + let current_slot = self.slot()?; + let current_epoch = current_slot.epoch(T::EthSpec::slots_per_epoch()); + self.task_executor.spawn_blocking( + move || { + if let Err(e) = store.try_prune_blobs(false, Some(current_epoch)) { + error!(log, "Error pruning blobs in background"; "error" => ?e); + } + }, + "prune_blobs_background", + ); + } + Ok(()) } diff --git a/beacon_node/store/src/hot_cold_store.rs b/beacon_node/store/src/hot_cold_store.rs index e53499438e0..1982ab2e8e1 100644 --- a/beacon_node/store/src/hot_cold_store.rs +++ b/beacon_node/store/src/hot_cold_store.rs @@ -38,6 +38,7 @@ use std::marker::PhantomData; use std::path::Path; use std::sync::Arc; use std::time::Duration; +use types::consts::eip4844::MIN_EPOCHS_FOR_BLOBS_SIDECARS_REQUESTS; use types::*; /// On-disk database that stores finalized states efficiently. @@ -80,6 +81,7 @@ pub enum HotColdDBError { target_version: SchemaVersion, current_version: SchemaVersion, }, + UnsupportedDataAvailabilityBoundary, /// Recoverable error indicating that the database freeze point couldn't be updated /// due to the finalized block not lying on an epoch boundary (should be infrequent). FreezeSlotUnaligned(Slot), @@ -94,7 +96,6 @@ pub enum HotColdDBError { MissingHotStateSummary(Hash256), MissingEpochBoundaryState(Hash256), MissingSplitState(Hash256, Slot), - MissingStateToPruneBlobs(Hash256), MissingExecutionPayload(Hash256), MissingFullBlockExecutionPayloadPruned(Hash256, Slot), MissingAnchorInfo, @@ -217,15 +218,13 @@ impl HotColdDB, LevelDB> { } if let Some(blob_info) = db.load_blob_info()? { - let dab_slot = blob_info.data_availability_boundary.slot; - let dab_state_root = blob_info.data_availability_boundary.state_root; + let oldest_blob_slot = blob_info.oldest_blob_slot; *db.blob_info.write() = Some(blob_info); info!( db.log, "Blob info loaded from disk"; - "data_availability_boundary_slot" => dab_slot, - "data_availability_boundary_state" => ?dab_state_root, + "oldest_blob_slot" => oldest_blob_slot, ); } @@ -1375,7 +1374,7 @@ impl, Cold: ItemStore> HotColdDB *blob_info = new_value; Ok(kv_op) } else { - Err(Error::AnchorInfoConcurrentMutation) + Err(Error::BlobInfoConcurrentMutation) } } @@ -1713,25 +1712,42 @@ impl, Cold: ItemStore> HotColdDB } /// Try to prune blobs older than the data availability boundary. - pub fn try_prune_blobs(&self, force: bool) -> Result<(), Error> { + pub fn try_prune_blobs( + &self, + force: bool, + data_availability_boundary: Option, + ) -> Result<(), Error> { let blob_info = match self.get_blob_info() { - Some(old_blob_info) => old_blob_info, + Some(blob_info) => blob_info, None => { return Ok(()); } }; + let oldest_blob_slot = blob_info.oldest_blob_slot; + // The last entirely pruned epoch, blobs sidecar pruning may have stopped early in the + // middle of an epoch. + let last_pruned_epoch = oldest_blob_slot.epoch(E::slots_per_epoch()) - 1; + let next_epoch_to_prune = match data_availability_boundary { + Some(epoch) => epoch, + None => { + // The split slot is set upon finalization and is the first slot in the latest + // finalized epoch, hence current_epoch = split_epoch + 1 + let current_epoch = + self.get_split_slot().epoch(E::slots_per_epoch()) + Epoch::new(1); + current_epoch - *MIN_EPOCHS_FOR_BLOBS_SIDECARS_REQUESTS + } + }; + if !force { - if blob_info.last_pruned_epoch.as_u64() + self.get_config().epochs_per_blob_prune - > blob_info.next_epoch_to_prune.as_u64() + if last_pruned_epoch.as_u64() + self.get_config().epochs_per_blob_prune + > next_epoch_to_prune.as_u64() { info!(self.log, "Blobs sidecars are pruned"); return Ok(()); } } - let dab_state_root = blob_info.data_availability_boundary.state_root; - // Iterate block roots backwards to oldest blob slot. warn!( self.log, @@ -1741,18 +1757,12 @@ impl, Cold: ItemStore> HotColdDB let mut ops = vec![]; let mut last_pruned_block_root = None; + let end_slot = next_epoch_to_prune.start_slot(E::slots_per_epoch()); for res in self.forwards_block_roots_iterator_until( - blob_info.oldest_blob_slot, - blob_info.data_availability_boundary.slot, - || { - let dab_state = self - .get_state(&dab_state_root, None)? - .ok_or(HotColdDBError::MissingStateToPruneBlobs(dab_state_root))?; - let dab_block_root = dab_state.get_latest_block_root(dab_state_root); - - Ok((dab_state, dab_block_root)) - }, + oldest_blob_slot, + end_slot, + || Err(HotColdDBError::UnsupportedDataAvailabilityBoundary.into()), &self.spec, )? { let (block_root, slot) = match res { @@ -1780,7 +1790,7 @@ impl, Cold: ItemStore> HotColdDB ops.push(StoreOp::DeleteBlobs(block_root)); } - if slot <= blob_info.oldest_blob_slot { + if slot >= end_slot { info!( self.log, "Blobs sidecar pruning reached earliest available blobs sidecar"; @@ -1798,12 +1808,12 @@ impl, Cold: ItemStore> HotColdDB "blobs_sidecars_pruned" => blobs_sidecars_pruned, ); - if let Some(mut new_blob_info) = self.get_blob_info() { - new_blob_info.last_pruned_epoch = - (blob_info.data_availability_boundary.slot + 1).epoch(E::slots_per_epoch()); - new_blob_info.oldest_blob_slot = blob_info.data_availability_boundary.slot + 1; - self.compare_and_set_blob_info_with_write(self.get_blob_info(), Some(new_blob_info))?; - } + self.compare_and_set_blob_info_with_write( + Some(blob_info), + Some(BlobInfo { + oldest_blob_slot: end_slot + 1, + }), + )?; Ok(()) } @@ -1843,9 +1853,6 @@ pub fn migrate_database, Cold: ItemStore>( return Err(HotColdDBError::FreezeSlotUnaligned(frozen_head.slot()).into()); } - // Prune blobs before migration. - store.try_prune_blobs(true)?; - let mut hot_db_ops: Vec> = Vec::new(); // 1. Copy all of the states between the head and the split slot, from the hot DB diff --git a/beacon_node/store/src/metadata.rs b/beacon_node/store/src/metadata.rs index 37d2e56459d..4e7b0df7be8 100644 --- a/beacon_node/store/src/metadata.rs +++ b/beacon_node/store/src/metadata.rs @@ -1,8 +1,8 @@ -use crate::{DBColumn, Error, Split, StoreItem}; +use crate::{DBColumn, Error, StoreItem}; use serde_derive::{Deserialize, Serialize}; use ssz::{Decode, Encode}; use ssz_derive::{Decode, Encode}; -use types::{Checkpoint, Epoch, Hash256, Slot}; +use types::{Checkpoint, Hash256, Slot}; pub const CURRENT_SCHEMA_VERSION: SchemaVersion = SchemaVersion(15); @@ -122,12 +122,6 @@ impl StoreItem for AnchorInfo { /// Database parameters relevant to blob sync. #[derive(Debug, PartialEq, Eq, Clone, Encode, Decode, Serialize, Deserialize, Default)] pub struct BlobInfo { - /// The latest epoch that blobs were pruned. - pub last_pruned_epoch: Epoch, - /// The next epoch to prune blobs from. - pub next_epoch_to_prune: Epoch, - /// The state root and slot of the next blobs to prune from. - pub data_availability_boundary: Split, /// The slot before which blobs are available. pub oldest_blob_slot: Slot, } diff --git a/database_manager/src/lib.rs b/database_manager/src/lib.rs index 1cd6a3e08a0..fcf36e540c3 100644 --- a/database_manager/src/lib.rs +++ b/database_manager/src/lib.rs @@ -312,9 +312,9 @@ pub fn prune_blobs( log, )?; - // If we're triggering a prune manually then ignore the check on the split's parent that bails - // out early by passing true to the force parameter. - db.try_prune_blobs(true) + // If we're triggering a prune manually then ignore the check on `epochs_per_blob_prune` that + // bails out early by passing true to the force parameter. + db.try_prune_blobs(true, None) } /// Run the database manager, returning an error string if the operation did not succeed. From 20567750c15a053eaaf138387d56bff45a0feaed Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Mon, 16 Jan 2023 21:41:24 +0100 Subject: [PATCH 27/64] fixup! Simplify conceptual design --- beacon_node/beacon_chain/src/builder.rs | 7 ++----- beacon_node/beacon_chain/src/canonical_head.rs | 5 ++--- beacon_node/store/src/hot_cold_store.rs | 17 ++++++++++++++--- 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/beacon_node/beacon_chain/src/builder.rs b/beacon_node/beacon_chain/src/builder.rs index f4e6fc91d08..75e677cdff6 100644 --- a/beacon_node/beacon_chain/src/builder.rs +++ b/beacon_node/beacon_chain/src/builder.rs @@ -918,13 +918,10 @@ where if beacon_chain.store.get_config().prune_blobs { let store = beacon_chain.store.clone(); let log = log.clone(); - let current_slot = beacon_chain - .slot() - .map_err(|e| format!("Failed to get current slot: {:?}", e))?; - let current_epoch = current_slot.epoch(TEthSpec::slots_per_epoch()); + let data_availability_boundary = beacon_chain.data_availability_boundary(); beacon_chain.task_executor.spawn_blocking( move || { - if let Err(e) = store.try_prune_blobs(false, Some(current_epoch)) { + if let Err(e) = store.try_prune_blobs(false, data_availability_boundary) { error!(log, "Error pruning blobs in background"; "error" => ?e); } }, diff --git a/beacon_node/beacon_chain/src/canonical_head.rs b/beacon_node/beacon_chain/src/canonical_head.rs index 63cb143d3b2..46a5c5b23a3 100644 --- a/beacon_node/beacon_chain/src/canonical_head.rs +++ b/beacon_node/beacon_chain/src/canonical_head.rs @@ -1018,11 +1018,10 @@ impl BeaconChain { if self.store.get_config().prune_blobs { let store = self.store.clone(); let log = self.log.clone(); - let current_slot = self.slot()?; - let current_epoch = current_slot.epoch(T::EthSpec::slots_per_epoch()); + let data_availability_boundary = self.data_availability_boundary(); self.task_executor.spawn_blocking( move || { - if let Err(e) = store.try_prune_blobs(false, Some(current_epoch)) { + if let Err(e) = store.try_prune_blobs(false, data_availability_boundary) { error!(log, "Error pruning blobs in background"; "error" => ?e); } }, diff --git a/beacon_node/store/src/hot_cold_store.rs b/beacon_node/store/src/hot_cold_store.rs index 1982ab2e8e1..5155d0cc3c9 100644 --- a/beacon_node/store/src/hot_cold_store.rs +++ b/beacon_node/store/src/hot_cold_store.rs @@ -1735,7 +1735,7 @@ impl, Cold: ItemStore> HotColdDB // finalized epoch, hence current_epoch = split_epoch + 1 let current_epoch = self.get_split_slot().epoch(E::slots_per_epoch()) + Epoch::new(1); - current_epoch - *MIN_EPOCHS_FOR_BLOBS_SIDECARS_REQUESTS + current_epoch.saturating_sub(*MIN_EPOCHS_FOR_BLOBS_SIDECARS_REQUESTS) } }; @@ -1757,12 +1757,23 @@ impl, Cold: ItemStore> HotColdDB let mut ops = vec![]; let mut last_pruned_block_root = None; - let end_slot = next_epoch_to_prune.start_slot(E::slots_per_epoch()); + let end_slot = next_epoch_to_prune.end_slot(E::slots_per_epoch()); + // todo(emhane): In the future, if the data availability boundary is less than the split + // epoch, this code will have to change to account for head candidates. for res in self.forwards_block_roots_iterator_until( oldest_blob_slot, end_slot, - || Err(HotColdDBError::UnsupportedDataAvailabilityBoundary.into()), + || { + let split = self.get_split_info(); + + let split_state = self.get_state(&split.state_root, Some(split.slot))?.ok_or( + HotColdDBError::MissingSplitState(split.state_root, split.slot), + )?; + let split_block_root = split_state.get_latest_block_root(split.state_root); + + Ok((split_state, split_block_root)) + }, &self.spec, )? { let (block_root, slot) = match res { From 44ec331452d9cf642d00fca73c1b64e4d03cfa14 Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Tue, 17 Jan 2023 14:58:23 +0100 Subject: [PATCH 28/64] fixup! Simplify conceptual design --- beacon_node/store/src/hot_cold_store.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/beacon_node/store/src/hot_cold_store.rs b/beacon_node/store/src/hot_cold_store.rs index 5155d0cc3c9..988dd1cc0d8 100644 --- a/beacon_node/store/src/hot_cold_store.rs +++ b/beacon_node/store/src/hot_cold_store.rs @@ -1732,9 +1732,9 @@ impl, Cold: ItemStore> HotColdDB Some(epoch) => epoch, None => { // The split slot is set upon finalization and is the first slot in the latest - // finalized epoch, hence current_epoch = split_epoch + 1 + // finalized epoch, hence current_epoch = split_epoch + 2 let current_epoch = - self.get_split_slot().epoch(E::slots_per_epoch()) + Epoch::new(1); + self.get_split_slot().epoch(E::slots_per_epoch()) + Epoch::new(2); current_epoch.saturating_sub(*MIN_EPOCHS_FOR_BLOBS_SIDECARS_REQUESTS) } }; From 3d93dad0e238b2e1024979624a0682c7e8d26f3f Mon Sep 17 00:00:00 2001 From: Emilia Hane <58548332+emhane@users.noreply.github.com> Date: Tue, 17 Jan 2023 23:04:52 +0100 Subject: [PATCH 29/64] Fix type bug Co-authored-by: realbigsean --- beacon_node/store/src/hot_cold_store.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beacon_node/store/src/hot_cold_store.rs b/beacon_node/store/src/hot_cold_store.rs index 988dd1cc0d8..8149a1e7475 100644 --- a/beacon_node/store/src/hot_cold_store.rs +++ b/beacon_node/store/src/hot_cold_store.rs @@ -496,7 +496,7 @@ impl, Cold: ItemStore> HotColdDB /// Check if the blobs sidecar for a block exists on disk. pub fn blobs_sidecar_exists(&self, block_root: &Hash256) -> Result { - self.get_item::(block_root) + self.get_item::(block_root) .map(|blobs| blobs.is_some()) } From 74172ed160fc4c37257750298f5e946c00db19bd Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Tue, 17 Jan 2023 15:46:43 +0100 Subject: [PATCH 30/64] Ignore IDE file --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index ae9f83c46dd..2c656632ada 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,6 @@ genesis.ssz # IntelliJ /*.iml + +# VSCode +/.vscode \ No newline at end of file From 83a9520761cf90958772f9d93345801320182c94 Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Wed, 18 Jan 2023 20:23:21 +0100 Subject: [PATCH 31/64] Clarify hybrid blob prune solution and fix error handling --- beacon_node/store/src/hot_cold_store.rs | 64 ++++++++++++++++++------- database_manager/src/lib.rs | 2 +- 2 files changed, 47 insertions(+), 19 deletions(-) diff --git a/beacon_node/store/src/hot_cold_store.rs b/beacon_node/store/src/hot_cold_store.rs index 8149a1e7475..3cefe977544 100644 --- a/beacon_node/store/src/hot_cold_store.rs +++ b/beacon_node/store/src/hot_cold_store.rs @@ -1711,37 +1711,65 @@ impl, Cold: ItemStore> HotColdDB Ok(()) } + // + pub fn try_prune_most_blobs(&self, force: bool) -> Result<(), Error> { + let eip4844_fork = match self.spec.eip4844_fork_epoch { + Some(epoch) => epoch, + None => { + debug!(self.log, "Eip4844 fork is disabled"); + return Ok(()); + } + }; + // At best, current_epoch = split_epoch + 2. However, if finalization doesn't advance, the + // `split.slot` is not updated and current_epoch > split_epoch + 2. + let at_most_current_epoch = + self.get_split_slot().epoch(E::slots_per_epoch()) + Epoch::new(2); + let at_most_data_availability_boundary = std::cmp::max( + eip4844_fork, + at_most_current_epoch.saturating_sub(*MIN_EPOCHS_FOR_BLOBS_SIDECARS_REQUESTS), + ); + + self.try_prune_blobs(force, Some(at_most_data_availability_boundary)) + } + /// Try to prune blobs older than the data availability boundary. pub fn try_prune_blobs( &self, force: bool, data_availability_boundary: Option, ) -> Result<(), Error> { - let blob_info = match self.get_blob_info() { - Some(blob_info) => blob_info, - None => { - return Ok(()); + let (data_availability_boundary, eip4844_fork) = + match (data_availability_boundary, self.spec.eip4844_fork_epoch) { + (Some(boundary_epoch), Some(fork_epoch)) => (boundary_epoch, fork_epoch), + _ => { + debug!(self.log, "Eip4844 fork is disabled"); + return Ok(()); + } + }; + + let blob_info = || -> BlobInfo { + if let Some(blob_info) = self.get_blob_info() { + if blob_info.oldest_blob_slot.epoch(E::slots_per_epoch()) >= eip4844_fork { + return blob_info; + } } - }; + // If BlobInfo is uninitialized this is probably the first time pruning blobs, or + // maybe oldest_blob_info has been initialized with Epoch::default. + // start from the eip4844 fork epoch. No new blobs are imported into the beacon + // chain that are older than the data availability boundary. + BlobInfo { + oldest_blob_slot: eip4844_fork.start_slot(E::slots_per_epoch()), + } + }(); let oldest_blob_slot = blob_info.oldest_blob_slot; // The last entirely pruned epoch, blobs sidecar pruning may have stopped early in the - // middle of an epoch. + // middle of an epoch otherwise the oldest blob slot is a start slot. let last_pruned_epoch = oldest_blob_slot.epoch(E::slots_per_epoch()) - 1; - let next_epoch_to_prune = match data_availability_boundary { - Some(epoch) => epoch, - None => { - // The split slot is set upon finalization and is the first slot in the latest - // finalized epoch, hence current_epoch = split_epoch + 2 - let current_epoch = - self.get_split_slot().epoch(E::slots_per_epoch()) + Epoch::new(2); - current_epoch.saturating_sub(*MIN_EPOCHS_FOR_BLOBS_SIDECARS_REQUESTS) - } - }; if !force { if last_pruned_epoch.as_u64() + self.get_config().epochs_per_blob_prune - > next_epoch_to_prune.as_u64() + > data_availability_boundary.as_u64() { info!(self.log, "Blobs sidecars are pruned"); return Ok(()); @@ -1757,7 +1785,7 @@ impl, Cold: ItemStore> HotColdDB let mut ops = vec![]; let mut last_pruned_block_root = None; - let end_slot = next_epoch_to_prune.end_slot(E::slots_per_epoch()); + let end_slot = data_availability_boundary.end_slot(E::slots_per_epoch()); // todo(emhane): In the future, if the data availability boundary is less than the split // epoch, this code will have to change to account for head candidates. diff --git a/database_manager/src/lib.rs b/database_manager/src/lib.rs index fcf36e540c3..852da1af77a 100644 --- a/database_manager/src/lib.rs +++ b/database_manager/src/lib.rs @@ -314,7 +314,7 @@ pub fn prune_blobs( // If we're triggering a prune manually then ignore the check on `epochs_per_blob_prune` that // bails out early by passing true to the force parameter. - db.try_prune_blobs(true, None) + db.try_prune_most_blobs(true) } /// Run the database manager, returning an error string if the operation did not succeed. From 54699f808c7dcfafadf8b8895c880696e65d5c32 Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Wed, 18 Jan 2023 20:29:00 +0100 Subject: [PATCH 32/64] fixup! Clarify hybrid blob prune solution and fix error handling --- beacon_node/store/src/hot_cold_store.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beacon_node/store/src/hot_cold_store.rs b/beacon_node/store/src/hot_cold_store.rs index 3cefe977544..1aa073788ca 100644 --- a/beacon_node/store/src/hot_cold_store.rs +++ b/beacon_node/store/src/hot_cold_store.rs @@ -1711,7 +1711,7 @@ impl, Cold: ItemStore> HotColdDB Ok(()) } - // + /// Try to prune blobs approximating data availability boundary when it is not at hand. pub fn try_prune_most_blobs(&self, force: bool) -> Result<(), Error> { let eip4844_fork = match self.spec.eip4844_fork_epoch { Some(epoch) => epoch, From 3bede06c9bdae4039a4e0b0a2557178cc76d152c Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Wed, 18 Jan 2023 20:38:10 +0100 Subject: [PATCH 33/64] Fix typo --- beacon_node/store/src/hot_cold_store.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beacon_node/store/src/hot_cold_store.rs b/beacon_node/store/src/hot_cold_store.rs index 1aa073788ca..71015f2a6cf 100644 --- a/beacon_node/store/src/hot_cold_store.rs +++ b/beacon_node/store/src/hot_cold_store.rs @@ -496,7 +496,7 @@ impl, Cold: ItemStore> HotColdDB /// Check if the blobs sidecar for a block exists on disk. pub fn blobs_sidecar_exists(&self, block_root: &Hash256) -> Result { - self.get_item::(block_root) + self.get_item::>(block_root) .map(|blobs| blobs.is_some()) } From a875bec5f2b4f06bf4a98d228d74dcfdd3bb446c Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Wed, 18 Jan 2023 20:57:10 +0100 Subject: [PATCH 34/64] Fix blobs store bug --- beacon_node/store/src/hot_cold_store.rs | 10 +++++++--- beacon_node/store/src/impls/execution_payload.rs | 3 ++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/beacon_node/store/src/hot_cold_store.rs b/beacon_node/store/src/hot_cold_store.rs index 71015f2a6cf..54d21a7c27e 100644 --- a/beacon_node/store/src/hot_cold_store.rs +++ b/beacon_node/store/src/hot_cold_store.rs @@ -496,7 +496,7 @@ impl, Cold: ItemStore> HotColdDB /// Check if the blobs sidecar for a block exists on disk. pub fn blobs_sidecar_exists(&self, block_root: &Hash256) -> Result { - self.get_item::>(block_root) + self.get_item::>(block_root) .map(|blobs| blobs.is_some()) } @@ -543,7 +543,7 @@ impl, Cold: ItemStore> HotColdDB pub fn blobs_as_kv_store_ops( &self, key: &Hash256, - blobs: &BlobsSidecar, + blobs: BlobsSidecar, ops: &mut Vec, ) { let db_key = get_key_for_col(DBColumn::BeaconBlob.into(), key.as_bytes()); @@ -778,7 +778,11 @@ impl, Cold: ItemStore> HotColdDB } StoreOp::PutBlobs(block_root, blobs) => { - self.blobs_as_kv_store_ops(&block_root, &blobs, &mut key_value_batch); + self.blobs_as_kv_store_ops( + &block_root, + (&*blobs).clone(), + &mut key_value_batch, + ); } StoreOp::PutStateSummary(state_root, summary) => { diff --git a/beacon_node/store/src/impls/execution_payload.rs b/beacon_node/store/src/impls/execution_payload.rs index ad68d1fba09..01a2dba0b0a 100644 --- a/beacon_node/store/src/impls/execution_payload.rs +++ b/beacon_node/store/src/impls/execution_payload.rs @@ -1,7 +1,7 @@ use crate::{DBColumn, Error, StoreItem}; use ssz::{Decode, Encode}; use types::{ - EthSpec, ExecutionPayload, ExecutionPayloadCapella, ExecutionPayloadEip4844, + BlobsSidecar, EthSpec, ExecutionPayload, ExecutionPayloadCapella, ExecutionPayloadEip4844, ExecutionPayloadMerge, }; @@ -25,6 +25,7 @@ macro_rules! impl_store_item { impl_store_item!(ExecutionPayloadMerge); impl_store_item!(ExecutionPayloadCapella); impl_store_item!(ExecutionPayloadEip4844); +impl_store_item!(BlobsSidecar); /// This fork-agnostic implementation should be only used for writing. /// From caa04db58a3c41ae49d780f0f945846390a4adec Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Wed, 18 Jan 2023 21:26:17 +0100 Subject: [PATCH 35/64] Run prune blobs on migrator thread --- beacon_node/beacon_chain/src/migrate.rs | 27 +++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/beacon_node/beacon_chain/src/migrate.rs b/beacon_node/beacon_chain/src/migrate.rs index 8fb00c105f9..fa07be42a8e 100644 --- a/beacon_node/beacon_chain/src/migrate.rs +++ b/beacon_node/beacon_chain/src/migrate.rs @@ -86,6 +86,7 @@ pub enum PruningError { pub enum Notification { Finalization(FinalizationNotification), Reconstruction, + PruneBlobs(Option), } pub struct FinalizationNotification { @@ -152,6 +153,14 @@ impl, Cold: ItemStore> BackgroundMigrator) { + if let Some(Notification::PruneBlobs(data_availability_boundary)) = + self.send_background_notification(Notification::PruneBlobs(data_availability_boundary)) + { + Self::run_prune_blobs(self.db.clone(), data_availability_boundary, &self.log); + } + } + pub fn run_reconstruction(db: Arc>, log: &Logger) { if let Err(e) = db.reconstruct_historic_states() { error!( @@ -162,6 +171,20 @@ impl, Cold: ItemStore> BackgroundMigrator>, + data_availability_boundary: Option, + log: &Logger, + ) { + if let Err(e) = db.try_prune_blobs(false, data_availability_boundary) { + error!( + log, + "Blobs pruning failed"; + "error" => ?e, + ); + } + } + /// If configured to run in the background, send `notif` to the background thread. /// /// Return `None` if the message was sent to the background thread, `Some(notif)` otherwise. @@ -320,11 +343,15 @@ impl, Cold: ItemStore> BackgroundMigrator best, + (Notification::PruneBlobs(_), Notification::Finalization(_)) => other, + (Notification::PruneBlobs(_), Notification::PruneBlobs(_)) => best, }); match notif { Notification::Reconstruction => Self::run_reconstruction(db.clone(), &log), Notification::Finalization(fin) => Self::run_migration(db.clone(), fin, &log), + Notification::PruneBlobs(dab) => Self::run_prune_blobs(db.clone(), dab, &log), } } }); From 0bdc291490a5ec759c14f223bcc7391b6b9cce00 Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Thu, 19 Jan 2023 20:49:15 +0100 Subject: [PATCH 36/64] Only store non-empty orphaned blobs --- beacon_node/beacon_chain/src/migrate.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/beacon_node/beacon_chain/src/migrate.rs b/beacon_node/beacon_chain/src/migrate.rs index fa07be42a8e..cf4dd7216b1 100644 --- a/beacon_node/beacon_chain/src/migrate.rs +++ b/beacon_node/beacon_chain/src/migrate.rs @@ -596,12 +596,18 @@ impl, Cold: ItemStore> BackgroundMigrator Date: Fri, 20 Jan 2023 09:00:17 +0100 Subject: [PATCH 37/64] Fix typo --- consensus/fork_choice/src/fork_choice.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/consensus/fork_choice/src/fork_choice.rs b/consensus/fork_choice/src/fork_choice.rs index f795b07907a..f614007ae12 100644 --- a/consensus/fork_choice/src/fork_choice.rs +++ b/consensus/fork_choice/src/fork_choice.rs @@ -768,7 +768,7 @@ where .ok_or_else(|| Error::InvalidBlock(InvalidBlock::UnknownParent(block.parent_root())))?; // Blocks cannot be in the future. If they are, their consideration must be delayed until - // the are in the past. + // they are in the past. // // Note: presently, we do not delay consideration. We just drop the block. if block.slot() > current_slot { From d7fc24a9d5fdb162bbf67a19ace52978501e71ab Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Fri, 20 Jan 2023 12:12:32 +0100 Subject: [PATCH 38/64] Plug in running blob pruning in migrator, related bug fixes and add todos --- beacon_node/beacon_chain/src/beacon_chain.rs | 6 ++-- beacon_node/beacon_chain/src/builder.rs | 16 ++-------- .../beacon_chain/src/canonical_head.rs | 19 +++--------- beacon_node/store/src/hot_cold_store.rs | 31 ++++++++++++++----- 4 files changed, 34 insertions(+), 38 deletions(-) diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 250c64a1d37..4629d8d13af 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -3015,9 +3015,11 @@ impl BeaconChain { ops.push(StoreOp::PutBlock(block_root, signed_block.clone())); ops.push(StoreOp::PutState(block.state_root(), &state)); + // Only store blobs at the data availability boundary or younger. + // + // todo(emhane): Should we add a marginal of one epoch here to ensure data availability + // consistency across network at epoch boundaries? let block_epoch = block.slot().epoch(T::EthSpec::slots_per_epoch()); - - // Only store blobs that haven't passed the data availability boundary. if Some(block_epoch) >= self.data_availability_boundary() { if let Some(blobs) = blobs? { if blobs.blobs.len() > 0 { diff --git a/beacon_node/beacon_chain/src/builder.rs b/beacon_node/beacon_chain/src/builder.rs index 75e677cdff6..0fd016e0b5a 100644 --- a/beacon_node/beacon_chain/src/builder.rs +++ b/beacon_node/beacon_chain/src/builder.rs @@ -915,19 +915,9 @@ where } // Prune blobs sidecars older than the blob data availability boundary in the background. - if beacon_chain.store.get_config().prune_blobs { - let store = beacon_chain.store.clone(); - let log = log.clone(); - let data_availability_boundary = beacon_chain.data_availability_boundary(); - beacon_chain.task_executor.spawn_blocking( - move || { - if let Err(e) = store.try_prune_blobs(false, data_availability_boundary) { - error!(log, "Error pruning blobs in background"; "error" => ?e); - } - }, - "prune_blobs_background", - ); - } + beacon_chain + .store_migrator + .process_prune_blobs(beacon_chain.data_availability_boundary()); Ok(beacon_chain) } diff --git a/beacon_node/beacon_chain/src/canonical_head.rs b/beacon_node/beacon_chain/src/canonical_head.rs index 46a5c5b23a3..0d14e3819b0 100644 --- a/beacon_node/beacon_chain/src/canonical_head.rs +++ b/beacon_node/beacon_chain/src/canonical_head.rs @@ -751,6 +751,10 @@ impl BeaconChain { // Drop the old cache head nice and early to try and free the memory as soon as possible. drop(old_cached_head); + // Prune blobs in the background. + self.store_migrator + .process_prune_blobs(self.data_availability_boundary()); + // If the finalized checkpoint changed, perform some updates. // // The `after_finalization` function will take a write-lock on `fork_choice`, therefore it @@ -1014,21 +1018,6 @@ impl BeaconChain { // Take a write-lock on the canonical head and signal for it to prune. self.canonical_head.fork_choice_write_lock().prune()?; - // Prune blobs. - if self.store.get_config().prune_blobs { - let store = self.store.clone(); - let log = self.log.clone(); - let data_availability_boundary = self.data_availability_boundary(); - self.task_executor.spawn_blocking( - move || { - if let Err(e) = store.try_prune_blobs(false, data_availability_boundary) { - error!(log, "Error pruning blobs in background"; "error" => ?e); - } - }, - "prune_blobs_background", - ); - } - Ok(()) } diff --git a/beacon_node/store/src/hot_cold_store.rs b/beacon_node/store/src/hot_cold_store.rs index 54d21a7c27e..09a2de9a2fb 100644 --- a/beacon_node/store/src/hot_cold_store.rs +++ b/beacon_node/store/src/hot_cold_store.rs @@ -1367,7 +1367,7 @@ impl, Cold: ItemStore> HotColdDB /// /// Return an `BlobInfoConcurrentMutation` error if the `prev_value` provided /// is not correct. - pub fn compare_and_set_blob_info( + fn compare_and_set_blob_info( &self, prev_value: Option, new_value: Option, @@ -1383,7 +1383,7 @@ impl, Cold: ItemStore> HotColdDB } /// As for `compare_and_set_blob_info`, but also writes the blob info to disk immediately. - pub fn compare_and_set_blob_info_with_write( + fn compare_and_set_blob_info_with_write( &self, prev_value: Option, new_value: Option, @@ -1751,6 +1751,14 @@ impl, Cold: ItemStore> HotColdDB } }; + let should_prune_blobs = self.get_config().prune_blobs; + if !should_prune_blobs && !force { + debug!(self.log, "Blob pruning is disabled"; + "prune_blobs" => should_prune_blobs + ); + return Ok(()); + } + let blob_info = || -> BlobInfo { if let Some(blob_info) = self.get_blob_info() { if blob_info.oldest_blob_slot.epoch(E::slots_per_epoch()) >= eip4844_fork { @@ -1758,14 +1766,15 @@ impl, Cold: ItemStore> HotColdDB } } // If BlobInfo is uninitialized this is probably the first time pruning blobs, or - // maybe oldest_blob_info has been initialized with Epoch::default. - // start from the eip4844 fork epoch. No new blobs are imported into the beacon - // chain that are older than the data availability boundary. + // maybe oldest_blob_info has been initialized with Epoch::default. Start from the + // eip4844 fork epoch. BlobInfo { oldest_blob_slot: eip4844_fork.start_slot(E::slots_per_epoch()), } }(); + // todo(emhane): Should we add a marginal for how old blobs we import? If so needs to be + // reflected here when choosing which oldest slot to prune from. let oldest_blob_slot = blob_info.oldest_blob_slot; // The last entirely pruned epoch, blobs sidecar pruning may have stopped early in the // middle of an epoch otherwise the oldest blob slot is a start slot. @@ -1789,14 +1798,20 @@ impl, Cold: ItemStore> HotColdDB let mut ops = vec![]; let mut last_pruned_block_root = None; - let end_slot = data_availability_boundary.end_slot(E::slots_per_epoch()); + // Prune up until the data availability boundary. + let end_slot = (data_availability_boundary - 1).end_slot(E::slots_per_epoch()); - // todo(emhane): In the future, if the data availability boundary is less than the split - // epoch, this code will have to change to account for head candidates. for res in self.forwards_block_roots_iterator_until( oldest_blob_slot, end_slot, || { + // todo(emhane): In the future, if the data availability boundary is less than the + // split (finalized) epoch, this code will have to change to decide what to do + // with pruned blobs in our not-yet-finalized canonical chain and not-yet-orphaned + // forks (see DBColumn::BeaconBlobOrphan). + // + // Related to review and the spec PRs linked in it: + // https://github.com/sigp/lighthouse/pull/3852#pullrequestreview-1244785136 let split = self.get_split_info(); let split_state = self.get_state(&split.state_root, Some(split.slot))?.ok_or( From 1812301c9c3dfd4b937ce09653fcb204ec7ae678 Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Fri, 20 Jan 2023 13:03:46 +0100 Subject: [PATCH 39/64] Allow user to set an epoch margin for pruning --- beacon_node/client/src/config.rs | 4 ++++ beacon_node/src/cli.rs | 8 ++++++++ beacon_node/src/config.rs | 8 ++++++++ beacon_node/store/src/config.rs | 7 ++++++- beacon_node/store/src/hot_cold_store.rs | 16 ++++++++++++++-- database_manager/src/lib.rs | 18 ++++++++++++++++++ 6 files changed, 58 insertions(+), 3 deletions(-) diff --git a/beacon_node/client/src/config.rs b/beacon_node/client/src/config.rs index 6c3a98a46d7..5f79d290ae2 100644 --- a/beacon_node/client/src/config.rs +++ b/beacon_node/client/src/config.rs @@ -201,6 +201,10 @@ impl Config { pub fn create_data_dir(&self) -> Result { ensure_dir_exists(self.get_data_dir()) } + + pub fn get_blob_prune_margin_epochs(&self) -> Option { + self.store.blob_prune_margin_epochs + } } /// Ensure that the directory at `path` exists, by creating it and all parents if necessary. diff --git a/beacon_node/src/cli.rs b/beacon_node/src/cli.rs index 510882c366d..1ce3b995cd0 100644 --- a/beacon_node/src/cli.rs +++ b/beacon_node/src/cli.rs @@ -568,6 +568,14 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> { .takes_value(true) .default_value("1") ) + .arg( + Arg::with_name("blob-prune-margin-epochs") + .long("blob-prune-margin-epochs") + .help("The margin for blob pruning in epochs. The oldest blobs are pruned \ + up until data_availability_boundary - blob_prune_margin_epochs.") + .takes_value(true) + .default_value("0") + ) /* * Misc. diff --git a/beacon_node/src/config.rs b/beacon_node/src/config.rs index 14d40db03b7..4974acc2528 100644 --- a/beacon_node/src/config.rs +++ b/beacon_node/src/config.rs @@ -421,6 +421,14 @@ pub fn get_config( client_config.store.epochs_per_blob_prune = epochs_per_blob_prune; } + if let Some(blob_prune_margin_epochs) = + clap_utils::parse_optional(cli_args, "blob-prune-margin-epochs")? + { + if blob_prune_margin_epochs > 0 { + client_config.store.blob_prune_margin_epochs = Some(blob_prune_margin_epochs); + } + } + /* * Zero-ports * diff --git a/beacon_node/store/src/config.rs b/beacon_node/store/src/config.rs index b3b52852629..a7f3d177afc 100644 --- a/beacon_node/store/src/config.rs +++ b/beacon_node/store/src/config.rs @@ -9,6 +9,7 @@ pub const DEFAULT_SLOTS_PER_RESTORE_POINT: u64 = 8192; pub const DEFAULT_BLOCK_CACHE_SIZE: usize = 5; pub const DEFAULT_BLOB_CACHE_SIZE: usize = 5; pub const DEFAULT_EPOCHS_PER_BLOB_PRUNE: u64 = 1; +pub const DEFAULT_BLOB_PUNE_MARGIN_EPOCHS: Option = None; /// Database configuration parameters. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] @@ -29,8 +30,11 @@ pub struct StoreConfig { pub prune_payloads: bool, /// Whether to prune blobs older than the blob data availability boundary. pub prune_blobs: bool, - /// Frequency of blob pruning. Default: every epoch. + /// Frequency of blob pruning in epochs. Default: every epoch. pub epochs_per_blob_prune: u64, + /// The margin for blob pruning in epochs. The oldest blobs are pruned up until + /// data_availability_boundary - blob_prune_margin_epochs. Default: 0. + pub blob_prune_margin_epochs: Option, } /// Variant of `StoreConfig` that gets written to disk. Contains immutable configuration params. @@ -57,6 +61,7 @@ impl Default for StoreConfig { prune_payloads: true, prune_blobs: true, epochs_per_blob_prune: DEFAULT_EPOCHS_PER_BLOB_PRUNE, + blob_prune_margin_epochs: DEFAULT_BLOB_PUNE_MARGIN_EPOCHS, } } } diff --git a/beacon_node/store/src/hot_cold_store.rs b/beacon_node/store/src/hot_cold_store.rs index 09a2de9a2fb..4f37d30888b 100644 --- a/beacon_node/store/src/hot_cold_store.rs +++ b/beacon_node/store/src/hot_cold_store.rs @@ -1798,8 +1798,20 @@ impl, Cold: ItemStore> HotColdDB let mut ops = vec![]; let mut last_pruned_block_root = None; - // Prune up until the data availability boundary. - let end_slot = (data_availability_boundary - 1).end_slot(E::slots_per_epoch()); + + // At most prune up until the data availability boundary epoch, leaving at least blobs in + // the data availability boundary epoch and younger. + let end_slot = { + let earliest_prunable_epoch = data_availability_boundary - 1; + // Stop pruning before reaching the data availability boundary if a margin is + // configured. + let end_epoch = if let Some(margin) = self.get_config().blob_prune_margin_epochs { + earliest_prunable_epoch - margin + } else { + earliest_prunable_epoch + }; + end_epoch.end_slot(E::slots_per_epoch()) + }; for res in self.forwards_block_roots_iterator_until( oldest_blob_slot, diff --git a/database_manager/src/lib.rs b/database_manager/src/lib.rs index 852da1af77a..58d2103acce 100644 --- a/database_manager/src/lib.rs +++ b/database_manager/src/lib.rs @@ -94,6 +94,16 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> { .help("Data directory for the freezer database.") .takes_value(true), ) + .arg( + Arg::with_name("blob-prune-margin-epochs") + .long("blob-prune-margin-epochs") + .help( + "The margin for blob pruning in epochs. The oldest blobs are pruned \ + up until data_availability_boundary - blob_prune_margin_epochs.", + ) + .takes_value(true) + .default_value("0"), + ) .subcommand(migrate_cli_app()) .subcommand(version_cli_app()) .subcommand(inspect_cli_app()) @@ -117,6 +127,14 @@ fn parse_client_config( client_config.store.slots_per_restore_point = sprp; client_config.store.slots_per_restore_point_set_explicitly = sprp_explicit; + if let Some(blob_prune_margin_epochs) = + clap_utils::parse_optional(cli_args, "blob-prune-margin-epochs")? + { + if blob_prune_margin_epochs > 0 { + client_config.store.blob_prune_margin_epochs = Some(blob_prune_margin_epochs); + } + } + Ok(client_config) } From 4de523fb75e0dd41fdcceda40cd18046b3c9a4f4 Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Fri, 20 Jan 2023 18:42:57 +0100 Subject: [PATCH 40/64] fixup! Allow user to set an epoch margin for pruning --- beacon_node/store/src/hot_cold_store.rs | 30 ++++++++++++------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/beacon_node/store/src/hot_cold_store.rs b/beacon_node/store/src/hot_cold_store.rs index 4f37d30888b..36b29a1616f 100644 --- a/beacon_node/store/src/hot_cold_store.rs +++ b/beacon_node/store/src/hot_cold_store.rs @@ -1780,9 +1780,22 @@ impl, Cold: ItemStore> HotColdDB // middle of an epoch otherwise the oldest blob slot is a start slot. let last_pruned_epoch = oldest_blob_slot.epoch(E::slots_per_epoch()) - 1; + // At most prune up until the data availability boundary epoch, leaving at least blobs in + // the data availability boundary epoch and younger. + let end_epoch = { + let earliest_prunable_epoch = data_availability_boundary - 1; + // Stop pruning before reaching the data availability boundary if a margin is + // configured. + if let Some(margin) = self.get_config().blob_prune_margin_epochs { + earliest_prunable_epoch - margin + } else { + earliest_prunable_epoch + } + }; + if !force { if last_pruned_epoch.as_u64() + self.get_config().epochs_per_blob_prune - > data_availability_boundary.as_u64() + > end_epoch.as_u64() { info!(self.log, "Blobs sidecars are pruned"); return Ok(()); @@ -1798,20 +1811,7 @@ impl, Cold: ItemStore> HotColdDB let mut ops = vec![]; let mut last_pruned_block_root = None; - - // At most prune up until the data availability boundary epoch, leaving at least blobs in - // the data availability boundary epoch and younger. - let end_slot = { - let earliest_prunable_epoch = data_availability_boundary - 1; - // Stop pruning before reaching the data availability boundary if a margin is - // configured. - let end_epoch = if let Some(margin) = self.get_config().blob_prune_margin_epochs { - earliest_prunable_epoch - margin - } else { - earliest_prunable_epoch - }; - end_epoch.end_slot(E::slots_per_epoch()) - }; + let end_slot = end_epoch.end_slot(E::slots_per_epoch()); for res in self.forwards_block_roots_iterator_until( oldest_blob_slot, From 756c881857c40be478b6dd40006006faa1ca0df6 Mon Sep 17 00:00:00 2001 From: Emilia Hane <58548332+emhane@users.noreply.github.com> Date: Tue, 24 Jan 2023 10:49:41 +0100 Subject: [PATCH 41/64] Keep uniform size small keys Co-authored-by: Michael Sproul --- beacon_node/store/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beacon_node/store/src/lib.rs b/beacon_node/store/src/lib.rs index b30f41cb29a..7857f256e8b 100644 --- a/beacon_node/store/src/lib.rs +++ b/beacon_node/store/src/lib.rs @@ -179,7 +179,7 @@ pub enum DBColumn { #[strum(serialize = "blb")] BeaconBlob, /// Block roots of orphaned beacon blobs. - #[strum(serialize = "blbo")] + #[strum(serialize = "blo")] BeaconBlobOrphan, /// For full `BeaconState`s in the hot database (finalized or fork-boundary states). #[strum(serialize = "ste")] From e4b447395ac27fab1975483681358336635a2744 Mon Sep 17 00:00:00 2001 From: Emilia Hane <58548332+emhane@users.noreply.github.com> Date: Tue, 24 Jan 2023 10:50:39 +0100 Subject: [PATCH 42/64] Clarify wording Co-authored-by: Michael Sproul --- beacon_node/store/src/metadata.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beacon_node/store/src/metadata.rs b/beacon_node/store/src/metadata.rs index 4e7b0df7be8..ef402d1c078 100644 --- a/beacon_node/store/src/metadata.rs +++ b/beacon_node/store/src/metadata.rs @@ -122,7 +122,7 @@ impl StoreItem for AnchorInfo { /// Database parameters relevant to blob sync. #[derive(Debug, PartialEq, Eq, Clone, Encode, Decode, Serialize, Deserialize, Default)] pub struct BlobInfo { - /// The slot before which blobs are available. + /// The slot after which blobs are available (>=). pub oldest_blob_slot: Slot, } From f6346f89c11348ab3571672fc7a2b6a33d17aae5 Mon Sep 17 00:00:00 2001 From: Emilia Hane <58548332+emhane@users.noreply.github.com> Date: Tue, 24 Jan 2023 11:13:23 +0100 Subject: [PATCH 43/64] Clarify comment Co-authored-by: Michael Sproul --- beacon_node/store/src/config.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beacon_node/store/src/config.rs b/beacon_node/store/src/config.rs index a7f3d177afc..f32afdecfd5 100644 --- a/beacon_node/store/src/config.rs +++ b/beacon_node/store/src/config.rs @@ -30,7 +30,7 @@ pub struct StoreConfig { pub prune_payloads: bool, /// Whether to prune blobs older than the blob data availability boundary. pub prune_blobs: bool, - /// Frequency of blob pruning in epochs. Default: every epoch. + /// Frequency of blob pruning in epochs. Default: 1 (every epoch). pub epochs_per_blob_prune: u64, /// The margin for blob pruning in epochs. The oldest blobs are pruned up until /// data_availability_boundary - blob_prune_margin_epochs. Default: 0. From c50f83116e6f08762598532fbafd4f8462387ba4 Mon Sep 17 00:00:00 2001 From: Emilia Hane <58548332+emhane@users.noreply.github.com> Date: Tue, 24 Jan 2023 11:13:55 +0100 Subject: [PATCH 44/64] Fix wording Co-authored-by: Michael Sproul --- beacon_node/store/src/hot_cold_store.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beacon_node/store/src/hot_cold_store.rs b/beacon_node/store/src/hot_cold_store.rs index 36b29a1616f..90e91747452 100644 --- a/beacon_node/store/src/hot_cold_store.rs +++ b/beacon_node/store/src/hot_cold_store.rs @@ -1817,7 +1817,7 @@ impl, Cold: ItemStore> HotColdDB oldest_blob_slot, end_slot, || { - // todo(emhane): In the future, if the data availability boundary is less than the + // todo(emhane): In the future, if the data availability boundary is more recent than the // split (finalized) epoch, this code will have to change to decide what to do // with pruned blobs in our not-yet-finalized canonical chain and not-yet-orphaned // forks (see DBColumn::BeaconBlobOrphan). From 63ca3bfb296273aa40bbd72d20ecf13b3557f342 Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Tue, 24 Jan 2023 09:41:23 +0100 Subject: [PATCH 45/64] Prune from highest data availability boundary --- beacon_node/beacon_chain/src/migrate.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/beacon_node/beacon_chain/src/migrate.rs b/beacon_node/beacon_chain/src/migrate.rs index cf4dd7216b1..51cd8843475 100644 --- a/beacon_node/beacon_chain/src/migrate.rs +++ b/beacon_node/beacon_chain/src/migrate.rs @@ -345,7 +345,13 @@ impl, Cold: ItemStore> BackgroundMigrator best, (Notification::PruneBlobs(_), Notification::Finalization(_)) => other, - (Notification::PruneBlobs(_), Notification::PruneBlobs(_)) => best, + (Notification::PruneBlobs(dab1), Notification::PruneBlobs(dab2)) => { + if dab2 > dab2 { + other + } else { + best + } + } }); match notif { From 43c3c74a48a74713cf62731e40b99b5529b23515 Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Tue, 24 Jan 2023 09:50:24 +0100 Subject: [PATCH 46/64] fixup! Fix blobs store bug --- beacon_node/store/src/hot_cold_store.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/beacon_node/store/src/hot_cold_store.rs b/beacon_node/store/src/hot_cold_store.rs index 90e91747452..bf375b26186 100644 --- a/beacon_node/store/src/hot_cold_store.rs +++ b/beacon_node/store/src/hot_cold_store.rs @@ -543,7 +543,7 @@ impl, Cold: ItemStore> HotColdDB pub fn blobs_as_kv_store_ops( &self, key: &Hash256, - blobs: BlobsSidecar, + blobs: &BlobsSidecar, ops: &mut Vec, ) { let db_key = get_key_for_col(DBColumn::BeaconBlob.into(), key.as_bytes()); @@ -778,11 +778,7 @@ impl, Cold: ItemStore> HotColdDB } StoreOp::PutBlobs(block_root, blobs) => { - self.blobs_as_kv_store_ops( - &block_root, - (&*blobs).clone(), - &mut key_value_batch, - ); + self.blobs_as_kv_store_ops(&block_root, &blobs, &mut key_value_batch); } StoreOp::PutStateSummary(state_root, summary) => { From d4795601f241833e79feac42fa6cbaad723cae42 Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Tue, 24 Jan 2023 10:08:15 +0100 Subject: [PATCH 47/64] fixup! Prune from highest data availability boundary --- beacon_node/beacon_chain/src/migrate.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beacon_node/beacon_chain/src/migrate.rs b/beacon_node/beacon_chain/src/migrate.rs index 51cd8843475..0690d0767f9 100644 --- a/beacon_node/beacon_chain/src/migrate.rs +++ b/beacon_node/beacon_chain/src/migrate.rs @@ -346,7 +346,7 @@ impl, Cold: ItemStore> BackgroundMigrator best, (Notification::PruneBlobs(_), Notification::Finalization(_)) => other, (Notification::PruneBlobs(dab1), Notification::PruneBlobs(dab2)) => { - if dab2 > dab2 { + if dab2 > dab1 { other } else { best From 9c2e623555c1019636748acdbf26afe856a08e08 Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Tue, 24 Jan 2023 10:10:11 +0100 Subject: [PATCH 48/64] Reflect use of prune margin epochs at import --- beacon_node/beacon_chain/src/beacon_chain.rs | 28 ++++++++++++-------- beacon_node/client/src/config.rs | 4 --- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 4629d8d13af..2a1da6e4a98 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -3015,17 +3015,23 @@ impl BeaconChain { ops.push(StoreOp::PutBlock(block_root, signed_block.clone())); ops.push(StoreOp::PutState(block.state_root(), &state)); - // Only store blobs at the data availability boundary or younger. - // - // todo(emhane): Should we add a marginal of one epoch here to ensure data availability - // consistency across network at epoch boundaries? - let block_epoch = block.slot().epoch(T::EthSpec::slots_per_epoch()); - if Some(block_epoch) >= self.data_availability_boundary() { - if let Some(blobs) = blobs? { - if blobs.blobs.len() > 0 { - //FIXME(sean) using this for debugging for now - info!(self.log, "Writing blobs to store"; "block_root" => ?block_root); - ops.push(StoreOp::PutBlobs(block_root, blobs)); + // Only consider blobs if the eip4844 fork is enabled. + if let Some(data_availability_boundary) = self.data_availability_boundary() { + let block_epoch = block.slot().epoch(T::EthSpec::slots_per_epoch()); + let import_boundary = match self.store.get_config().blob_prune_margin_epochs { + Some(margin_epochs) => data_availability_boundary - margin_epochs, + None => data_availability_boundary, + }; + + // Only store blobs at the data availability boundary, minus any configured epochs + // margin, or younger (of higher epoch number). + if block_epoch >= import_boundary { + if let Some(blobs) = blobs? { + if blobs.blobs.len() > 0 { + //FIXME(sean) using this for debugging for now + info!(self.log, "Writing blobs to store"; "block_root" => ?block_root); + ops.push(StoreOp::PutBlobs(block_root, blobs)); + } } } } diff --git a/beacon_node/client/src/config.rs b/beacon_node/client/src/config.rs index 5f79d290ae2..6c3a98a46d7 100644 --- a/beacon_node/client/src/config.rs +++ b/beacon_node/client/src/config.rs @@ -201,10 +201,6 @@ impl Config { pub fn create_data_dir(&self) -> Result { ensure_dir_exists(self.get_data_dir()) } - - pub fn get_blob_prune_margin_epochs(&self) -> Option { - self.store.blob_prune_margin_epochs - } } /// Ensure that the directory at `path` exists, by creating it and all parents if necessary. From 5d2480c762fd8ab734ad4c234ed2ff62734152fe Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Tue, 24 Jan 2023 10:21:14 +0100 Subject: [PATCH 49/64] Improve naming --- beacon_node/store/src/hot_cold_store.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/beacon_node/store/src/hot_cold_store.rs b/beacon_node/store/src/hot_cold_store.rs index bf375b26186..0ea2a95a5b4 100644 --- a/beacon_node/store/src/hot_cold_store.rs +++ b/beacon_node/store/src/hot_cold_store.rs @@ -1711,7 +1711,8 @@ impl, Cold: ItemStore> HotColdDB Ok(()) } - /// Try to prune blobs approximating data availability boundary when it is not at hand. + /// Try to prune blobs, approximating the current epoch from lower epoch numbers end (older + /// end) and is useful when the data availability boundary is not at hand. pub fn try_prune_most_blobs(&self, force: bool) -> Result<(), Error> { let eip4844_fork = match self.spec.eip4844_fork_epoch { Some(epoch) => epoch, @@ -1722,14 +1723,13 @@ impl, Cold: ItemStore> HotColdDB }; // At best, current_epoch = split_epoch + 2. However, if finalization doesn't advance, the // `split.slot` is not updated and current_epoch > split_epoch + 2. - let at_most_current_epoch = - self.get_split_slot().epoch(E::slots_per_epoch()) + Epoch::new(2); - let at_most_data_availability_boundary = std::cmp::max( + let min_current_epoch = self.get_split_slot().epoch(E::slots_per_epoch()) + Epoch::new(2); + let min_data_availability_boundary = std::cmp::max( eip4844_fork, - at_most_current_epoch.saturating_sub(*MIN_EPOCHS_FOR_BLOBS_SIDECARS_REQUESTS), + min_current_epoch.saturating_sub(*MIN_EPOCHS_FOR_BLOBS_SIDECARS_REQUESTS), ); - self.try_prune_blobs(force, Some(at_most_data_availability_boundary)) + self.try_prune_blobs(force, Some(min_data_availability_boundary)) } /// Try to prune blobs older than the data availability boundary. From 6dff69bde9da8a68a328a557064b1cb4df341bf8 Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Tue, 24 Jan 2023 10:27:30 +0100 Subject: [PATCH 50/64] Atomically update blob info with pruned blobs --- beacon_node/store/src/hot_cold_store.rs | 23 +++++++++++++++-------- beacon_node/store/src/lib.rs | 1 + 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/beacon_node/store/src/hot_cold_store.rs b/beacon_node/store/src/hot_cold_store.rs index 0ea2a95a5b4..712c2e966f4 100644 --- a/beacon_node/store/src/hot_cold_store.rs +++ b/beacon_node/store/src/hot_cold_store.rs @@ -827,6 +827,10 @@ impl, Cold: ItemStore> HotColdDB get_key_for_col(DBColumn::BeaconBlobOrphan.into(), block_root.as_bytes()); key_value_batch.push(KeyValueStoreOp::PutKeyValue(db_key, [].into())); } + + StoreOp::PutRawKVStoreOp(kv_store_op) => { + key_value_batch.push(kv_store_op); + } } } Ok(key_value_batch) @@ -869,6 +873,8 @@ impl, Cold: ItemStore> HotColdDB StoreOp::DeleteExecutionPayload(_) => (), StoreOp::PutOrphanedBlobsKey(_) => (), + + StoreOp::PutRawKVStoreOp(_) => (), } } @@ -1865,21 +1871,22 @@ impl, Cold: ItemStore> HotColdDB break; } } - let blobs_sidecars_pruned = ops.len(); - self.do_atomically(ops)?; - info!( - self.log, - "Blobs sidecar pruning complete"; - "blobs_sidecars_pruned" => blobs_sidecars_pruned, - ); - self.compare_and_set_blob_info_with_write( + let update_blob_info = self.compare_and_set_blob_info( Some(blob_info), Some(BlobInfo { oldest_blob_slot: end_slot + 1, }), )?; + ops.push(StoreOp::PutRawKVStoreOp(update_blob_info)); + + self.do_atomically(ops)?; + info!( + self.log, + "Blobs sidecar pruning complete"; + "blobs_sidecars_pruned" => blobs_sidecars_pruned, + ); Ok(()) } diff --git a/beacon_node/store/src/lib.rs b/beacon_node/store/src/lib.rs index 7857f256e8b..18e3b3ca345 100644 --- a/beacon_node/store/src/lib.rs +++ b/beacon_node/store/src/lib.rs @@ -166,6 +166,7 @@ pub enum StoreOp<'a, E: EthSpec> { DeleteBlobs(Hash256), DeleteState(Hash256, Option), DeleteExecutionPayload(Hash256), + PutRawKVStoreOp(KeyValueStoreOp), } /// A unique column identifier. From 9ee9b6df76ef6be5b81a6e80f04038da15dadde7 Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Tue, 24 Jan 2023 10:40:53 +0100 Subject: [PATCH 51/64] Remove unused stuff --- beacon_node/store/src/hot_cold_store.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/beacon_node/store/src/hot_cold_store.rs b/beacon_node/store/src/hot_cold_store.rs index 712c2e966f4..15242cdd40e 100644 --- a/beacon_node/store/src/hot_cold_store.rs +++ b/beacon_node/store/src/hot_cold_store.rs @@ -81,7 +81,6 @@ pub enum HotColdDBError { target_version: SchemaVersion, current_version: SchemaVersion, }, - UnsupportedDataAvailabilityBoundary, /// Recoverable error indicating that the database freeze point couldn't be updated /// due to the finalized block not lying on an epoch boundary (should be infrequent). FreezeSlotUnaligned(Slot), @@ -1775,8 +1774,6 @@ impl, Cold: ItemStore> HotColdDB } }(); - // todo(emhane): Should we add a marginal for how old blobs we import? If so needs to be - // reflected here when choosing which oldest slot to prune from. let oldest_blob_slot = blob_info.oldest_blob_slot; // The last entirely pruned epoch, blobs sidecar pruning may have stopped early in the // middle of an epoch otherwise the oldest blob slot is a start slot. From 1e59cb9dea8d7e1e472434db4aac1f256bf18b1a Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Tue, 24 Jan 2023 11:04:42 +0100 Subject: [PATCH 52/64] Add tests for blob pruning flags --- lighthouse/tests/beacon_node.rs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/lighthouse/tests/beacon_node.rs b/lighthouse/tests/beacon_node.rs index 59f03064a1b..f6db01a7068 100644 --- a/lighthouse/tests/beacon_node.rs +++ b/lighthouse/tests/beacon_node.rs @@ -1354,6 +1354,32 @@ fn prune_blobs_on_startup_false() { .with_config(|config| assert!(!config.store.prune_blobs)); } #[test] +fn epochs_per_blob_prune_default() { + CommandLineTest::new() + .run_with_zero_port() + .with_config(|config| assert!(config.epochs_per_blob_prune == 1)); +} +#[test] +fn epochs_per_blob_prune_on_startup_five() { + CommandLineTest::new() + .flag("epochs-per-blob-prune", Some(5)) + .run_with_zero_port() + .with_config(|config| assert!(!config.epochs_per_blob_prune == 5)); +} +#[test] +fn blob_prune_margin_epochs_default() { + CommandLineTest::new() + .run_with_zero_port() + .with_config(|config| assert!(config.blob_prune_margin_epochs.is_none())); +} +#[test] +fn blob_prune_margin_epochs_on_startup_ten() { + CommandLineTest::new() + .flag("blob-prune-margin-epochs", Some(10)) + .run_with_zero_port() + .with_config(|config| assert!(!config.blob_prune_margin_epochs == Some(10))); +} +#[test] fn reconstruct_historic_states_flag() { CommandLineTest::new() .flag("reconstruct-historic-states", None) From a2eda76291923fab8ad9264e475fefa9307a8cca Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Tue, 24 Jan 2023 11:12:33 +0100 Subject: [PATCH 53/64] Correct comment --- beacon_node/store/src/hot_cold_store.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beacon_node/store/src/hot_cold_store.rs b/beacon_node/store/src/hot_cold_store.rs index 15242cdd40e..c86f9f663b9 100644 --- a/beacon_node/store/src/hot_cold_store.rs +++ b/beacon_node/store/src/hot_cold_store.rs @@ -1801,7 +1801,7 @@ impl, Cold: ItemStore> HotColdDB } } - // Iterate block roots backwards to oldest blob slot. + // Iterate block roots forwards from the oldest blob slot. warn!( self.log, "Pruning blobs sidecars stored longer than data availability boundary"; From 8f137df02e857db4c8ddec2dfd4a300d1709531a Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Tue, 24 Jan 2023 11:24:42 +0100 Subject: [PATCH 54/64] fixup! Allow user to set an epoch margin for pruning --- beacon_node/beacon_chain/src/beacon_chain.rs | 6 ++---- beacon_node/src/config.rs | 4 +--- beacon_node/store/src/config.rs | 4 ++-- beacon_node/store/src/hot_cold_store.rs | 18 ++++++------------ database_manager/src/lib.rs | 4 +--- lighthouse/tests/beacon_node.rs | 2 +- 6 files changed, 13 insertions(+), 25 deletions(-) diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 2a1da6e4a98..514b8410d06 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -3018,10 +3018,8 @@ impl BeaconChain { // Only consider blobs if the eip4844 fork is enabled. if let Some(data_availability_boundary) = self.data_availability_boundary() { let block_epoch = block.slot().epoch(T::EthSpec::slots_per_epoch()); - let import_boundary = match self.store.get_config().blob_prune_margin_epochs { - Some(margin_epochs) => data_availability_boundary - margin_epochs, - None => data_availability_boundary, - }; + let margin_epochs = self.store.get_config().blob_prune_margin_epochs; + let import_boundary = data_availability_boundary - margin_epochs; // Only store blobs at the data availability boundary, minus any configured epochs // margin, or younger (of higher epoch number). diff --git a/beacon_node/src/config.rs b/beacon_node/src/config.rs index 4974acc2528..7ced9127443 100644 --- a/beacon_node/src/config.rs +++ b/beacon_node/src/config.rs @@ -424,9 +424,7 @@ pub fn get_config( if let Some(blob_prune_margin_epochs) = clap_utils::parse_optional(cli_args, "blob-prune-margin-epochs")? { - if blob_prune_margin_epochs > 0 { - client_config.store.blob_prune_margin_epochs = Some(blob_prune_margin_epochs); - } + client_config.store.blob_prune_margin_epochs = blob_prune_margin_epochs; } /* diff --git a/beacon_node/store/src/config.rs b/beacon_node/store/src/config.rs index f32afdecfd5..ec5ee382b3f 100644 --- a/beacon_node/store/src/config.rs +++ b/beacon_node/store/src/config.rs @@ -9,7 +9,7 @@ pub const DEFAULT_SLOTS_PER_RESTORE_POINT: u64 = 8192; pub const DEFAULT_BLOCK_CACHE_SIZE: usize = 5; pub const DEFAULT_BLOB_CACHE_SIZE: usize = 5; pub const DEFAULT_EPOCHS_PER_BLOB_PRUNE: u64 = 1; -pub const DEFAULT_BLOB_PUNE_MARGIN_EPOCHS: Option = None; +pub const DEFAULT_BLOB_PUNE_MARGIN_EPOCHS: u64 = 0; /// Database configuration parameters. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] @@ -34,7 +34,7 @@ pub struct StoreConfig { pub epochs_per_blob_prune: u64, /// The margin for blob pruning in epochs. The oldest blobs are pruned up until /// data_availability_boundary - blob_prune_margin_epochs. Default: 0. - pub blob_prune_margin_epochs: Option, + pub blob_prune_margin_epochs: u64, } /// Variant of `StoreConfig` that gets written to disk. Contains immutable configuration params. diff --git a/beacon_node/store/src/hot_cold_store.rs b/beacon_node/store/src/hot_cold_store.rs index c86f9f663b9..a614edad207 100644 --- a/beacon_node/store/src/hot_cold_store.rs +++ b/beacon_node/store/src/hot_cold_store.rs @@ -1779,18 +1779,12 @@ impl, Cold: ItemStore> HotColdDB // middle of an epoch otherwise the oldest blob slot is a start slot. let last_pruned_epoch = oldest_blob_slot.epoch(E::slots_per_epoch()) - 1; - // At most prune up until the data availability boundary epoch, leaving at least blobs in - // the data availability boundary epoch and younger. - let end_epoch = { - let earliest_prunable_epoch = data_availability_boundary - 1; - // Stop pruning before reaching the data availability boundary if a margin is - // configured. - if let Some(margin) = self.get_config().blob_prune_margin_epochs { - earliest_prunable_epoch - margin - } else { - earliest_prunable_epoch - } - }; + // At most prune blobs up until the data availability boundary epoch, leaving at least + // blobs of the data availability boundary epoch and younger. + let earliest_prunable_epoch = data_availability_boundary - 1; + // Stop pruning before reaching the data availability boundary if a margin is configured. + let margin_epochs = self.get_config().blob_prune_margin_epochs; + let end_epoch = earliest_prunable_epoch - margin_epochs; if !force { if last_pruned_epoch.as_u64() + self.get_config().epochs_per_blob_prune diff --git a/database_manager/src/lib.rs b/database_manager/src/lib.rs index 58d2103acce..a33e6c14989 100644 --- a/database_manager/src/lib.rs +++ b/database_manager/src/lib.rs @@ -130,9 +130,7 @@ fn parse_client_config( if let Some(blob_prune_margin_epochs) = clap_utils::parse_optional(cli_args, "blob-prune-margin-epochs")? { - if blob_prune_margin_epochs > 0 { - client_config.store.blob_prune_margin_epochs = Some(blob_prune_margin_epochs); - } + client_config.store.blob_prune_margin_epochs = blob_prune_margin_epochs; } Ok(client_config) diff --git a/lighthouse/tests/beacon_node.rs b/lighthouse/tests/beacon_node.rs index f6db01a7068..237ca2db517 100644 --- a/lighthouse/tests/beacon_node.rs +++ b/lighthouse/tests/beacon_node.rs @@ -1370,7 +1370,7 @@ fn epochs_per_blob_prune_on_startup_five() { fn blob_prune_margin_epochs_default() { CommandLineTest::new() .run_with_zero_port() - .with_config(|config| assert!(config.blob_prune_margin_epochs.is_none())); + .with_config(|config| assert!(config.blob_prune_margin_epochs == 0)); } #[test] fn blob_prune_margin_epochs_on_startup_ten() { From 00ca21e84c552e4a12b9643d26e99abe25851a80 Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Tue, 24 Jan 2023 17:29:10 +0100 Subject: [PATCH 55/64] Make implementation of BlobInfo more coder friendly --- beacon_node/store/src/hot_cold_store.rs | 75 +++++++++---------------- beacon_node/store/src/metadata.rs | 2 +- 2 files changed, 29 insertions(+), 48 deletions(-) diff --git a/beacon_node/store/src/hot_cold_store.rs b/beacon_node/store/src/hot_cold_store.rs index a614edad207..201ee72d4eb 100644 --- a/beacon_node/store/src/hot_cold_store.rs +++ b/beacon_node/store/src/hot_cold_store.rs @@ -55,7 +55,7 @@ pub struct HotColdDB, Cold: ItemStore> { /// The starting slots for the range of blocks & states stored in the database. anchor_info: RwLock>, /// The starting slots for the range of blobs stored in the database. - blob_info: RwLock>, + blob_info: RwLock, pub(crate) config: StoreConfig, /// Cold database containing compact historical data. pub cold_db: Cold, @@ -131,7 +131,7 @@ impl HotColdDB, MemoryStore> { let db = HotColdDB { split: RwLock::new(Split::default()), anchor_info: RwLock::new(None), - blob_info: RwLock::new(None), + blob_info: RwLock::new(BlobInfo::default()), cold_db: MemoryStore::open(), hot_db: MemoryStore::open(), block_cache: Mutex::new(LruCache::new(config.block_cache_size)), @@ -166,11 +166,7 @@ impl HotColdDB, LevelDB> { let mut db = HotColdDB { split: RwLock::new(Split::default()), anchor_info: RwLock::new(None), - blob_info: RwLock::new( - spec.eip4844_fork_epoch - .is_some() - .then(|| BlobInfo::default()), - ), + blob_info: RwLock::new(BlobInfo::default()), cold_db: LevelDB::open(cold_path)?, hot_db: LevelDB::open(hot_path)?, block_cache: Mutex::new(LruCache::new(config.block_cache_size)), @@ -218,12 +214,12 @@ impl HotColdDB, LevelDB> { if let Some(blob_info) = db.load_blob_info()? { let oldest_blob_slot = blob_info.oldest_blob_slot; - *db.blob_info.write() = Some(blob_info); + *db.blob_info.write() = blob_info; info!( db.log, "Blob info loaded from disk"; - "oldest_blob_slot" => oldest_blob_slot, + "oldest_blob_slot" => ?oldest_blob_slot, ); } @@ -1357,7 +1353,7 @@ impl, Cold: ItemStore> HotColdDB /// Get a clone of the store's blob info. /// /// To do mutations, use `compare_and_set_blob_info`. - pub fn get_blob_info(&self) -> Option { + pub fn get_blob_info(&self) -> BlobInfo { self.blob_info.read_recursive().clone() } @@ -1370,8 +1366,8 @@ impl, Cold: ItemStore> HotColdDB /// is not correct. fn compare_and_set_blob_info( &self, - prev_value: Option, - new_value: Option, + prev_value: BlobInfo, + new_value: BlobInfo, ) -> Result { let mut blob_info = self.blob_info.write(); if *blob_info == prev_value { @@ -1386,8 +1382,8 @@ impl, Cold: ItemStore> HotColdDB /// As for `compare_and_set_blob_info`, but also writes the blob info to disk immediately. fn compare_and_set_blob_info_with_write( &self, - prev_value: Option, - new_value: Option, + prev_value: BlobInfo, + new_value: BlobInfo, ) -> Result<(), Error> { let kv_store_op = self.compare_and_set_blob_info(prev_value, new_value)?; self.hot_db.do_atomically(vec![kv_store_op]) @@ -1402,15 +1398,8 @@ impl, Cold: ItemStore> HotColdDB /// /// The argument is intended to be `self.blob_info`, but is passed manually to avoid issues /// with recursive locking. - fn store_blob_info_in_batch(&self, blob_info: &Option) -> KeyValueStoreOp { - if let Some(ref blob_info) = blob_info { - blob_info.as_kv_store_op(BLOB_INFO_KEY) - } else { - KeyValueStoreOp::DeleteKey(get_key_for_col( - DBColumn::BeaconMeta.into(), - BLOB_INFO_KEY.as_bytes(), - )) - } + fn store_blob_info_in_batch(&self, blob_info: &BlobInfo) -> KeyValueStoreOp { + blob_info.as_kv_store_op(BLOB_INFO_KEY) } /// Return the slot-window describing the available historic states. @@ -1760,21 +1749,11 @@ impl, Cold: ItemStore> HotColdDB return Ok(()); } - let blob_info = || -> BlobInfo { - if let Some(blob_info) = self.get_blob_info() { - if blob_info.oldest_blob_slot.epoch(E::slots_per_epoch()) >= eip4844_fork { - return blob_info; - } - } - // If BlobInfo is uninitialized this is probably the first time pruning blobs, or - // maybe oldest_blob_info has been initialized with Epoch::default. Start from the - // eip4844 fork epoch. - BlobInfo { - oldest_blob_slot: eip4844_fork.start_slot(E::slots_per_epoch()), - } - }(); + let blob_info = self.get_blob_info(); // now returns `BlobInfo` not `Option<_>` + let oldest_blob_slot = blob_info + .oldest_blob_slot + .unwrap_or(eip4844_fork.start_slot(E::slots_per_epoch())); - let oldest_blob_slot = blob_info.oldest_blob_slot; // The last entirely pruned epoch, blobs sidecar pruning may have stopped early in the // middle of an epoch otherwise the oldest blob slot is a start slot. let last_pruned_epoch = oldest_blob_slot.epoch(E::slots_per_epoch()) - 1; @@ -1796,11 +1775,13 @@ impl, Cold: ItemStore> HotColdDB } // Iterate block roots forwards from the oldest blob slot. - warn!( + debug!( self.log, "Pruning blobs sidecars stored longer than data availability boundary"; - "info" => "you may notice degraded I/O performance while this runs" ); + // todo(emhane): If we notice degraded I/O for users switching modes (prune_blobs=true to + // prune_blobs=false) we could add a warning that only fires on a threshold, e.g. more + // than 2x epochs_per_blob_prune epochs without a prune. let mut ops = vec![]; let mut last_pruned_block_root = None; @@ -1810,10 +1791,10 @@ impl, Cold: ItemStore> HotColdDB oldest_blob_slot, end_slot, || { - // todo(emhane): In the future, if the data availability boundary is more recent than the - // split (finalized) epoch, this code will have to change to decide what to do - // with pruned blobs in our not-yet-finalized canonical chain and not-yet-orphaned - // forks (see DBColumn::BeaconBlobOrphan). + // todo(emhane): In the future, if the data availability boundary is more recent + // than the split (finalized) epoch, this code will have to change to decide what + // to do with pruned blobs in our not-yet-finalized canonical chain and + // not-yet-orphaned forks (see DBColumn::BeaconBlobOrphan). // // Related to review and the spec PRs linked in it: // https://github.com/sigp/lighthouse/pull/3852#pullrequestreview-1244785136 @@ -1865,10 +1846,10 @@ impl, Cold: ItemStore> HotColdDB let blobs_sidecars_pruned = ops.len(); let update_blob_info = self.compare_and_set_blob_info( - Some(blob_info), - Some(BlobInfo { - oldest_blob_slot: end_slot + 1, - }), + blob_info, + BlobInfo { + oldest_blob_slot: Some(end_slot + 1), + }, )?; ops.push(StoreOp::PutRawKVStoreOp(update_blob_info)); diff --git a/beacon_node/store/src/metadata.rs b/beacon_node/store/src/metadata.rs index ef402d1c078..b5de0048f2f 100644 --- a/beacon_node/store/src/metadata.rs +++ b/beacon_node/store/src/metadata.rs @@ -123,7 +123,7 @@ impl StoreItem for AnchorInfo { #[derive(Debug, PartialEq, Eq, Clone, Encode, Decode, Serialize, Deserialize, Default)] pub struct BlobInfo { /// The slot after which blobs are available (>=). - pub oldest_blob_slot: Slot, + pub oldest_blob_slot: Option, } impl StoreItem for BlobInfo { From b2abec5d352c980f69b0effe8ca5063f23d3492a Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Tue, 24 Jan 2023 17:58:54 +0100 Subject: [PATCH 56/64] Verify StoreConfig --- beacon_node/src/cli.rs | 4 ++-- beacon_node/store/src/hot_cold_store.rs | 19 ++++++++++++++++++- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/beacon_node/src/cli.rs b/beacon_node/src/cli.rs index 1ce3b995cd0..e711dfca93f 100644 --- a/beacon_node/src/cli.rs +++ b/beacon_node/src/cli.rs @@ -563,8 +563,8 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> { Arg::with_name("epochs-per-blob-prune") .long("epochs-per-blob-prune") .help("The epoch interval with which to prune blobs from Lighthouse's \ - database when they are older than the data data availability \ - boundary relative to the current epoch.") + database when they are older than the data availability boundary \ + relative to the current epoch.") .takes_value(true) .default_value("1") ) diff --git a/beacon_node/store/src/hot_cold_store.rs b/beacon_node/store/src/hot_cold_store.rs index 201ee72d4eb..2cffa4571fe 100644 --- a/beacon_node/store/src/hot_cold_store.rs +++ b/beacon_node/store/src/hot_cold_store.rs @@ -109,6 +109,7 @@ pub enum HotColdDBError { slots_per_historical_root: u64, slots_per_epoch: u64, }, + ZeroEpochsPerBlobPrune, RestorePointBlockHashError(BeaconStateError), IterationError { unexpected_key: BytesKey, @@ -126,7 +127,7 @@ impl HotColdDB, MemoryStore> { spec: ChainSpec, log: Logger, ) -> Result, MemoryStore>, Error> { - Self::verify_slots_per_restore_point(config.slots_per_restore_point)?; + Self::verify_config(&config)?; let db = HotColdDB { split: RwLock::new(Split::default()), @@ -1522,6 +1523,12 @@ impl, Cold: ItemStore> HotColdDB self.hot_db.get(state_root) } + /// Verify that a parsed config. + fn verify_config(config: &StoreConfig) -> Result<(), HotColdDBError> { + Self::verify_slots_per_restore_point(config.slots_per_restore_point)?; + Self::verify_epochs_per_blob_prune(config.epochs_per_blob_prune) + } + /// Check that the restore point frequency is valid. /// /// Specifically, check that it is: @@ -1552,6 +1559,16 @@ impl, Cold: ItemStore> HotColdDB } } + // Check that epochs_per_blob_prune is at least 1 epoch to avoid attempting to prune the same + // epochs over and over again. + fn verify_epochs_per_blob_prune(epochs_per_blob_prune: u64) -> Result<(), HotColdDBError> { + if epochs_per_blob_prune > 0 { + Ok(()) + } else { + Err(HotColdDBError::ZeroEpochsPerBlobPrune) + } + } + /// Run a compaction pass to free up space used by deleted states. pub fn compact(&self) -> Result<(), Error> { self.hot_db.compact()?; From 56c84178f273ca61ce24f9d134e2fdf3fb8347af Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Mon, 6 Feb 2023 11:11:02 +0100 Subject: [PATCH 57/64] Fix conflicts rebasing eip4844 --- beacon_node/beacon_chain/src/beacon_chain.rs | 2 +- .../network/src/beacon_processor/worker/rpc_methods.rs | 10 ++++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 514b8410d06..622ba407ad8 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -3024,7 +3024,7 @@ impl BeaconChain { // Only store blobs at the data availability boundary, minus any configured epochs // margin, or younger (of higher epoch number). if block_epoch >= import_boundary { - if let Some(blobs) = blobs? { + if let Some(blobs) = blobs { if blobs.blobs.len() > 0 { //FIXME(sean) using this for debugging for now info!(self.log, "Writing blobs to store"; "block_root" => ?block_root); diff --git a/beacon_node/network/src/beacon_processor/worker/rpc_methods.rs b/beacon_node/network/src/beacon_processor/worker/rpc_methods.rs index 0bd4eebcc1e..01b7cb43b18 100644 --- a/beacon_node/network/src/beacon_processor/worker/rpc_methods.rs +++ b/beacon_node/network/src/beacon_processor/worker/rpc_methods.rs @@ -688,12 +688,10 @@ impl Worker { let serve_blobs_from_slot = if start_epoch < data_availability_boundary { // Attempt to serve from the earliest block in our database, falling back to the data // availability boundary - let oldest_blob_slot = self - .chain - .store - .get_blob_info() - .map(|blob_info| blob_info.oldest_blob_slot) - .unwrap_or(data_availability_boundary.start_slot(T::EthSpec::slots_per_epoch())); + let oldest_blob_slot = + self.chain.store.get_blob_info().oldest_blob_slot.unwrap_or( + data_availability_boundary.start_slot(T::EthSpec::slots_per_epoch()), + ); debug!( self.log, From 577262ccbf5f0864e65c3234dc610ec723362bb3 Mon Sep 17 00:00:00 2001 From: Emilia Hane <58548332+emhane@users.noreply.github.com> Date: Tue, 7 Feb 2023 11:06:49 +0100 Subject: [PATCH 58/64] Improve use of whitespace Co-authored-by: Michael Sproul --- beacon_node/store/src/hot_cold_store.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/beacon_node/store/src/hot_cold_store.rs b/beacon_node/store/src/hot_cold_store.rs index 2cffa4571fe..5f15fb84bc3 100644 --- a/beacon_node/store/src/hot_cold_store.rs +++ b/beacon_node/store/src/hot_cold_store.rs @@ -1760,7 +1760,9 @@ impl, Cold: ItemStore> HotColdDB let should_prune_blobs = self.get_config().prune_blobs; if !should_prune_blobs && !force { - debug!(self.log, "Blob pruning is disabled"; + debug!( + self.log, + "Blob pruning is disabled"; "prune_blobs" => should_prune_blobs ); return Ok(()); From d599e41f3dea1b7d28494101a73f406a191e2bbf Mon Sep 17 00:00:00 2001 From: Emilia Hane <58548332+emhane@users.noreply.github.com> Date: Tue, 7 Feb 2023 11:08:10 +0100 Subject: [PATCH 59/64] Remove debug comment Co-authored-by: Michael Sproul --- beacon_node/store/src/hot_cold_store.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beacon_node/store/src/hot_cold_store.rs b/beacon_node/store/src/hot_cold_store.rs index 5f15fb84bc3..35dbbe39cd9 100644 --- a/beacon_node/store/src/hot_cold_store.rs +++ b/beacon_node/store/src/hot_cold_store.rs @@ -1768,7 +1768,7 @@ impl, Cold: ItemStore> HotColdDB return Ok(()); } - let blob_info = self.get_blob_info(); // now returns `BlobInfo` not `Option<_>` + let blob_info = self.get_blob_info(); let oldest_blob_slot = blob_info .oldest_blob_slot .unwrap_or(eip4844_fork.start_slot(E::slots_per_epoch())); From d7eb9441cfa6c7b55f405e684b727a14b34bc8e3 Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Tue, 7 Feb 2023 11:20:04 +0100 Subject: [PATCH 60/64] Reorder loading of db metadata from disk to allow for future changes to schema --- beacon_node/store/src/hot_cold_store.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/beacon_node/store/src/hot_cold_store.rs b/beacon_node/store/src/hot_cold_store.rs index 35dbbe39cd9..6825ee707e4 100644 --- a/beacon_node/store/src/hot_cold_store.rs +++ b/beacon_node/store/src/hot_cold_store.rs @@ -213,17 +213,6 @@ impl HotColdDB, LevelDB> { ); } - if let Some(blob_info) = db.load_blob_info()? { - let oldest_blob_slot = blob_info.oldest_blob_slot; - *db.blob_info.write() = blob_info; - - info!( - db.log, - "Blob info loaded from disk"; - "oldest_blob_slot" => ?oldest_blob_slot, - ); - } - // Ensure that the schema version of the on-disk database matches the software. // If the version is mismatched, an automatic migration will be attempted. let db = Arc::new(db); @@ -239,6 +228,17 @@ impl HotColdDB, LevelDB> { db.store_schema_version(CURRENT_SCHEMA_VERSION)?; } + if let Some(blob_info) = db.load_blob_info()? { + let oldest_blob_slot = blob_info.oldest_blob_slot; + *db.blob_info.write() = blob_info; + + info!( + db.log, + "Blob info loaded from disk"; + "oldest_blob_slot" => ?oldest_blob_slot, + ); + } + // Ensure that any on-disk config is compatible with the supplied config. if let Some(disk_config) = db.load_config()? { db.config.check_compatibility(&disk_config)?; From 9d919917f5f796860c690c0af24810d27e8de796 Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Tue, 7 Feb 2023 11:21:10 +0100 Subject: [PATCH 61/64] Removed unused code --- beacon_node/store/src/hot_cold_store.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/beacon_node/store/src/hot_cold_store.rs b/beacon_node/store/src/hot_cold_store.rs index 6825ee707e4..c7a3b440c6a 100644 --- a/beacon_node/store/src/hot_cold_store.rs +++ b/beacon_node/store/src/hot_cold_store.rs @@ -2044,12 +2044,6 @@ impl StoreItem for Split { } } -impl Split { - pub fn new(slot: Slot, state_root: Hash256) -> Self { - Split { slot, state_root } - } -} - /// Type hint. fn no_state_root_iter() -> Option>> { None From ac4b5b580cf7811f4bfd57fb8b9a66f9a8730b81 Mon Sep 17 00:00:00 2001 From: Michael Sproul Date: Mon, 30 Jan 2023 17:55:20 +1100 Subject: [PATCH 62/64] Fix regression in DB write atomicity --- beacon_node/beacon_chain/src/beacon_chain.rs | 40 ++++++++++++++----- .../src/validator_pubkey_cache.rs | 17 +++++--- beacon_node/store/src/hot_cold_store.rs | 6 +-- beacon_node/store/src/lib.rs | 2 +- 4 files changed, 44 insertions(+), 21 deletions(-) diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 622ba407ad8..8603f6c2de9 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -2878,7 +2878,7 @@ impl BeaconChain { // is so we don't have to think about lock ordering with respect to the fork choice lock. // There are a bunch of places where we lock both fork choice and the pubkey cache and it // would be difficult to check that they all lock fork choice first. - let mut kv_store_ops = self + let mut ops = self .validator_pubkey_cache .try_write_for(VALIDATOR_PUBKEY_CACHE_LOCK_TIMEOUT) .ok_or(Error::ValidatorPubkeyCacheLockTimeout)? @@ -2981,9 +2981,14 @@ impl BeaconChain { // ---------------------------- BLOCK PROBABLY ATTESTABLE ---------------------------------- // Most blocks are now capable of being attested to thanks to the `early_attester_cache` // cache above. Resume non-essential processing. + // + // It is important NOT to return errors here before the database commit, because the block + // has already been added to fork choice and the database would be left in an inconsistent + // state if we returned early without committing. In other words, an error here would + // corrupt the node's database permanently. // ----------------------------------------------------------------------------------------- - self.import_block_update_shuffling_cache(block_root, &mut state)?; + self.import_block_update_shuffling_cache(block_root, &mut state); self.import_block_observe_attestations( block, &state, @@ -3008,10 +3013,11 @@ impl BeaconChain { // See https://github.com/sigp/lighthouse/issues/2028 let (signed_block, blobs) = signed_block.deconstruct(); let block = signed_block.message(); - let mut ops: Vec<_> = confirmed_state_roots - .into_iter() - .map(StoreOp::DeleteStateTemporaryFlag) - .collect(); + ops.extend( + confirmed_state_roots + .into_iter() + .map(StoreOp::DeleteStateTemporaryFlag), + ); ops.push(StoreOp::PutBlock(block_root, signed_block.clone())); ops.push(StoreOp::PutState(block.state_root(), &state)); @@ -3036,9 +3042,7 @@ impl BeaconChain { let txn_lock = self.store.hot_db.begin_rw_transaction(); - kv_store_ops.extend(self.store.convert_to_kv_batch(ops)?); - - if let Err(e) = self.store.hot_db.do_atomically(kv_store_ops) { + if let Err(e) = self.store.do_atomically(ops) { error!( self.log, "Database write failed!"; @@ -3467,13 +3471,27 @@ impl BeaconChain { } } + // For the current and next epoch of this state, ensure we have the shuffling from this + // block in our cache. fn import_block_update_shuffling_cache( &self, block_root: Hash256, state: &mut BeaconState, + ) { + if let Err(e) = self.import_block_update_shuffling_cache_fallible(block_root, state) { + warn!( + self.log, + "Failed to prime shuffling cache"; + "error" => ?e + ); + } + } + + fn import_block_update_shuffling_cache_fallible( + &self, + block_root: Hash256, + state: &mut BeaconState, ) -> Result<(), BlockError> { - // For the current and next epoch of this state, ensure we have the shuffling from this - // block in our cache. for relative_epoch in [RelativeEpoch::Current, RelativeEpoch::Next] { let shuffling_id = AttestationShufflingId::new(block_root, state, relative_epoch)?; diff --git a/beacon_node/beacon_chain/src/validator_pubkey_cache.rs b/beacon_node/beacon_chain/src/validator_pubkey_cache.rs index 26aea2d2722..79910df2923 100644 --- a/beacon_node/beacon_chain/src/validator_pubkey_cache.rs +++ b/beacon_node/beacon_chain/src/validator_pubkey_cache.rs @@ -4,7 +4,7 @@ use ssz::{Decode, Encode}; use std::collections::HashMap; use std::convert::TryInto; use std::marker::PhantomData; -use store::{DBColumn, Error as StoreError, KeyValueStore, KeyValueStoreOp, StoreItem}; +use store::{DBColumn, Error as StoreError, StoreItem, StoreOp}; use types::{BeaconState, Hash256, PublicKey, PublicKeyBytes}; /// Provides a mapping of `validator_index -> validator_publickey`. @@ -38,7 +38,7 @@ impl ValidatorPubkeyCache { }; let store_ops = cache.import_new_pubkeys(state)?; - store.hot_db.do_atomically(store_ops)?; + store.do_atomically(store_ops)?; Ok(cache) } @@ -79,7 +79,7 @@ impl ValidatorPubkeyCache { pub fn import_new_pubkeys( &mut self, state: &BeaconState, - ) -> Result, BeaconChainError> { + ) -> Result>, BeaconChainError> { if state.validators().len() > self.pubkeys.len() { self.import( state.validators()[self.pubkeys.len()..] @@ -92,7 +92,10 @@ impl ValidatorPubkeyCache { } /// Adds zero or more validators to `self`. - fn import(&mut self, validator_keys: I) -> Result, BeaconChainError> + fn import( + &mut self, + validator_keys: I, + ) -> Result>, BeaconChainError> where I: Iterator + ExactSizeIterator, { @@ -112,7 +115,9 @@ impl ValidatorPubkeyCache { // It will be committed atomically when the block that introduced it is written to disk. // Notably it is NOT written while the write lock on the cache is held. // See: https://github.com/sigp/lighthouse/issues/2327 - store_ops.push(DatabasePubkey(pubkey).as_kv_store_op(DatabasePubkey::key_for_index(i))); + store_ops.push(StoreOp::KeyValueOp( + DatabasePubkey(pubkey).as_kv_store_op(DatabasePubkey::key_for_index(i)), + )); self.pubkeys.push( (&pubkey) @@ -294,7 +299,7 @@ mod test { let ops = cache .import_new_pubkeys(&state) .expect("should import pubkeys"); - store.hot_db.do_atomically(ops).unwrap(); + store.do_atomically(ops).unwrap(); check_cache_get(&cache, &keypairs[..]); drop(cache); diff --git a/beacon_node/store/src/hot_cold_store.rs b/beacon_node/store/src/hot_cold_store.rs index c7a3b440c6a..f508734d4ee 100644 --- a/beacon_node/store/src/hot_cold_store.rs +++ b/beacon_node/store/src/hot_cold_store.rs @@ -824,8 +824,8 @@ impl, Cold: ItemStore> HotColdDB key_value_batch.push(KeyValueStoreOp::PutKeyValue(db_key, [].into())); } - StoreOp::PutRawKVStoreOp(kv_store_op) => { - key_value_batch.push(kv_store_op); + StoreOp::KeyValueOp(kv_op) => { + key_value_batch.push(kv_op); } } } @@ -870,7 +870,7 @@ impl, Cold: ItemStore> HotColdDB StoreOp::PutOrphanedBlobsKey(_) => (), - StoreOp::PutRawKVStoreOp(_) => (), + StoreOp::KeyValueOp(_) => (), } } diff --git a/beacon_node/store/src/lib.rs b/beacon_node/store/src/lib.rs index 18e3b3ca345..1d7e92b80a9 100644 --- a/beacon_node/store/src/lib.rs +++ b/beacon_node/store/src/lib.rs @@ -166,7 +166,7 @@ pub enum StoreOp<'a, E: EthSpec> { DeleteBlobs(Hash256), DeleteState(Hash256, Option), DeleteExecutionPayload(Hash256), - PutRawKVStoreOp(KeyValueStoreOp), + KeyValueOp(KeyValueStoreOp), } /// A unique column identifier. From bc468b4ce5e7f6a52e6a5dd61918c39f59ece81b Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Tue, 7 Feb 2023 11:34:25 +0100 Subject: [PATCH 63/64] fixup! Improve use of whitespace --- beacon_node/beacon_chain/src/beacon_chain.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 8603f6c2de9..6757d1f9a3d 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -3033,7 +3033,10 @@ impl BeaconChain { if let Some(blobs) = blobs { if blobs.blobs.len() > 0 { //FIXME(sean) using this for debugging for now - info!(self.log, "Writing blobs to store"; "block_root" => ?block_root); + info!( + self.log, "Writing blobs to store"; + "block_root" => ?block_root + ); ops.push(StoreOp::PutBlobs(block_root, blobs)); } } From 6a37e843993ff858134da495830b8eca7d3b88b8 Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Tue, 7 Feb 2023 11:39:52 +0100 Subject: [PATCH 64/64] fixup! Fix regression in DB write atomicity --- beacon_node/beacon_chain/src/beacon_chain.rs | 2 +- beacon_node/store/src/hot_cold_store.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 6757d1f9a3d..741d9a95b7c 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -3034,7 +3034,7 @@ impl BeaconChain { if blobs.blobs.len() > 0 { //FIXME(sean) using this for debugging for now info!( - self.log, "Writing blobs to store"; + self.log, "Writing blobs to store"; "block_root" => ?block_root ); ops.push(StoreOp::PutBlobs(block_root, blobs)); diff --git a/beacon_node/store/src/hot_cold_store.rs b/beacon_node/store/src/hot_cold_store.rs index f508734d4ee..99b516ee99c 100644 --- a/beacon_node/store/src/hot_cold_store.rs +++ b/beacon_node/store/src/hot_cold_store.rs @@ -1870,7 +1870,7 @@ impl, Cold: ItemStore> HotColdDB oldest_blob_slot: Some(end_slot + 1), }, )?; - ops.push(StoreOp::PutRawKVStoreOp(update_blob_info)); + ops.push(StoreOp::KeyValueOp(update_blob_info)); self.do_atomically(ops)?; info!(