From d5adc2ebc52fee73fed0bd23d0eeec0827bd3a96 Mon Sep 17 00:00:00 2001 From: Michael Sproul Date: Fri, 9 Sep 2022 12:56:27 +1000 Subject: [PATCH] Implement on-demand pruning operation --- beacon_node/beacon_chain/src/builder.rs | 5 ++ beacon_node/store/src/hot_cold_store.rs | 95 ++++++++++++++++++++++++- 2 files changed, 99 insertions(+), 1 deletion(-) diff --git a/beacon_node/beacon_chain/src/builder.rs b/beacon_node/beacon_chain/src/builder.rs index a578629b69..62df1eb1fb 100644 --- a/beacon_node/beacon_chain/src/builder.rs +++ b/beacon_node/beacon_chain/src/builder.rs @@ -266,6 +266,11 @@ where self.genesis_time = Some(genesis_state.genesis_time()); + // Prune finalized execution payloads. + store + .try_prune_execution_payloads(false) + .map_err(|e| format!("Error pruning execution payloads: {e:?}"))?; + self.op_pool = Some( store .get_item::>(&OP_POOL_DB_KEY) diff --git a/beacon_node/store/src/hot_cold_store.rs b/beacon_node/store/src/hot_cold_store.rs index a2a811cd94..8a29e36d42 100644 --- a/beacon_node/store/src/hot_cold_store.rs +++ b/beacon_node/store/src/hot_cold_store.rs @@ -7,7 +7,7 @@ use crate::config::{ }; use crate::forwards_iter::{HybridForwardsBlockRootsIterator, HybridForwardsStateRootsIterator}; use crate::impls::beacon_state::{get_full_state, store_full_state}; -use crate::iter::{ParentRootBlockIterator, RootsIterator}; +use crate::iter::{BlockRootsIterator, ParentRootBlockIterator, RootsIterator}; use crate::leveldb_store::BytesKey; use crate::leveldb_store::LevelDB; use crate::memory_store::MemoryStore; @@ -438,6 +438,12 @@ impl, Cold: ItemStore> HotColdDB .ok_or_else(|| HotColdDBError::MissingExecutionPayload(*block_root).into()) } + /// Check if the execution payload for a block exists on disk. + pub fn execution_payload_exists(&self, block_root: &Hash256) -> Result { + self.get_item::>(block_root) + .map(|payload| payload.is_some()) + } + /// Determine whether a block exists in the database. pub fn block_exists(&self, block_root: &Hash256) -> Result { self.hot_db @@ -1418,6 +1424,93 @@ impl, Cold: ItemStore> HotColdDB &CompactionTimestamp(compaction_timestamp.as_secs()), ) } + + /// Try to prune all execution payloads, returning early if there is no need to prune. + pub fn try_prune_execution_payloads(&self, force: bool) -> Result<(), Error> { + let split = self.get_split_info(); + + if split.slot == 0 { + return Ok(()); + } + + let bellatrix_fork_slot = if let Some(epoch) = self.spec.bellatrix_fork_epoch { + epoch.start_slot(E::slots_per_epoch()) + } else { + return Ok(()); + }; + + // Load the split state so we can backtrack to find execution payloads. + 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 execution payload stored, depending on + // whether it was at a skipped slot. However for a fully pruned database its parent + // should *always* have been pruned. + let split_parent_block_root = split_state.get_block_root(split.slot - 1)?; + if !self.execution_payload_exists(&split_parent_block_root)? && !force { + info!(self.log, "Execution payloads are pruned"); + return Ok(()); + } + + // Iterate block roots backwards to the Bellatrix fork or the anchor slot, whichever comes + // first. + let split_block_root = split_state.get_latest_block_root(split.state_root); + let anchor_slot = self.get_anchor_info().map(|info| info.anchor_slot); + + let mut ops = vec![]; + + 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 backtrack early"; + "error" => ?e, + ); + break; + } + }; + + if slot < bellatrix_fork_slot { + info!( + self.log, + "Finished backtrack to Bellatrix fork"; + ); + break; + } + + if self.execution_payload_exists(&block_root)? { + debug!( + self.log, + "Pruning execution payload"; + "slot" => slot, + "block_root" => ?block_root, + ); + ops.push(StoreOp::DeleteExecutionPayload(block_root)); + } + + if Some(slot) == anchor_slot { + info!( + self.log, + "Finished backtrack to anchor state"; + "slot" => slot + ); + break; + } + } + let payloads_pruned = ops.len(); + self.do_atomically(ops)?; + info!( + self.log, + "Execution payload pruning complete"; + "payloads_pruned" => payloads_pruned, + ); + Ok(()) + } } /// Advance the split point of the store, moving new finalized states to the freezer.