Squash merge of 3565

Squashed commit of the following:

commit a4960ebfd7
Author: Michael Sproul <michael@sigmaprime.io>
Date:   Mon Sep 12 12:10:23 2022 +1000

    Clippy

commit b28e8d0848
Author: Michael Sproul <michael@sigmaprime.io>
Date:   Mon Sep 12 11:41:45 2022 +1000

    Add flag to disable prune on startup

commit de775d6aa5
Author: Michael Sproul <michael@sigmaprime.io>
Date:   Mon Sep 12 11:19:21 2022 +1000

    Fix and update beacon chain tests

commit 2289b20bca
Author: Michael Sproul <michael@sigmaprime.io>
Date:   Fri Sep 9 17:40:21 2022 +1000

    Implement DB manager command

commit d5adc2ebc5
Author: Michael Sproul <michael@sigmaprime.io>
Date:   Fri Sep 9 12:56:27 2022 +1000

    Implement on-demand pruning operation

commit 69d54741c1
Author: Michael Sproul <michael@sigmaprime.io>
Date:   Thu Sep 8 16:25:04 2022 +1000

    Delete finalized exec payloads while running
This commit is contained in:
Michael Sproul
2022-09-16 17:36:06 +10:00
parent 2bd784ef68
commit 0c75da5a01
9 changed files with 211 additions and 29 deletions

View File

@@ -23,6 +23,8 @@ pub struct StoreConfig {
pub compact_on_prune: bool,
/// Whether to store finalized blocks in the freezer database.
pub separate_blocks: bool,
/// Whether to try pruning execution payloads on initialization.
pub prune_payloads_on_init: bool,
}
/// Variant of `StoreConfig` that gets written to disk. Contains immutable configuration params.
@@ -48,6 +50,7 @@ impl Default for StoreConfig {
compact_on_init: false,
compact_on_prune: true,
separate_blocks: true,
prune_payloads_on_init: true,
}
}
}

View File

@@ -10,7 +10,7 @@ use crate::impls::{
beacon_state::{get_full_state, store_full_state},
frozen_block_slot::FrozenBlockSlot,
};
use crate::iter::{ParentRootBlockIterator, StateRootsIterator};
use crate::iter::{BlockRootsIterator, ParentRootBlockIterator, RootsIterator};
use crate::leveldb_store::BytesKey;
use crate::leveldb_store::LevelDB;
use crate::memory_store::MemoryStore;
@@ -532,6 +532,12 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> HotColdDB<E, Hot, Cold>
.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<bool, Error> {
self.get_item::<ExecutionPayload<E>>(block_root)
.map(|payload| payload.is_some())
}
/// Determine whether a block exists in the database.
pub fn block_exists(&self, block_root: &Hash256) -> Result<bool, Error> {
self.hot_db
@@ -1512,6 +1518,93 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> HotColdDB<E, Hot, Cold>
&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.
@@ -1552,16 +1645,16 @@ pub fn migrate_database<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>>(
let mut cold_db_block_ops: Vec<KeyValueStoreOps> = vec![];
// 1. Copy all of the states between the head and the split slot, from the hot DB
// to the cold DB.
let state_root_iter = StateRootsIterator::new(&store, frozen_head);
for maybe_pair in state_root_iter.take_while(|result| match result {
Ok((_, slot)) => {
// 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 >= &current_split_slot
&& anchor_slot.map_or(true, |anchor_slot| slot >= &anchor_slot)
}
Err(_) => true,
}) {
let (state_root, slot) = maybe_pair?;
let (block_root, state_root, slot) = maybe_tuple?;
let mut cold_db_ops: Vec<KeyValueStoreOp> = Vec::new();
@@ -1584,6 +1677,11 @@ pub fn migrate_database<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>>(
// Delete the old summary, and the full state if we lie on an epoch boundary.
hot_db_ops.push(StoreOp::DeleteState(state_root, Some(slot)));
// Delete the execution payload. Even if this execution payload is the payload of the
// new finalized block it is OK to delete it, as `try_get_full_block` looks at the split
// slot when determining whether to reconstruct payloads.
hot_db_ops.push(StoreOp::DeleteExecutionPayload(block_root));
}
// Warning: Critical section. We have to take care not to put any of the two databases in an