Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tree states to support per-slot state diffs #4652

Merged
merged 12 commits into from
Sep 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 55 additions & 4 deletions beacon_node/beacon_chain/tests/store_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@ use std::collections::HashSet;
use std::convert::TryInto;
use std::sync::Arc;
use std::time::Duration;
use store::hdiff::HierarchyConfig;
use store::{
config::StoreConfigError,
iter::{BlockRootsIterator, StateRootsIterator},
HotColdDB, LevelDB, StoreConfig,
Error as StoreError, HotColdDB, LevelDB, StoreConfig,
};
use tempfile::{tempdir, TempDir};
use types::test_utils::{SeedableRng, XorShiftRng};
Expand All @@ -49,13 +51,25 @@ fn get_store_with_spec(
db_path: &TempDir,
spec: ChainSpec,
) -> Arc<HotColdDB<E, LevelDB<E>, LevelDB<E>>> {
let config = StoreConfig {
// More frequent snapshots and hdiffs in tests for testing
hierarchy_config: HierarchyConfig {
exponents: vec![1, 3, 5],
},
..Default::default()
};
try_get_store_with_spec_and_config(db_path, spec, config).expect("disk store should initialize")
}

fn try_get_store_with_spec_and_config(
db_path: &TempDir,
spec: ChainSpec,
config: StoreConfig,
) -> Result<Arc<HotColdDB<E, LevelDB<E>, LevelDB<E>>>, StoreError> {
let hot_path = db_path.path().join("hot_db");
let cold_path = db_path.path().join("cold_db");
let config = StoreConfig::default();
let log = test_logger();

HotColdDB::open(&hot_path, &cold_path, |_, _, _| Ok(()), config, spec, log)
.expect("disk store should initialize")
}

fn get_harness(
Expand Down Expand Up @@ -2481,6 +2495,43 @@ async fn revert_minority_fork_on_resume() {
assert_eq!(heads.len(), 1);
}

#[tokio::test]
#[ignore]
// FIXME(jimmy): Ignoring this now as the test is flaky :/ It intermittently fails with an IO error
// "..cold_db/LOCK file held by another process".
// There seems to be some race condition between dropping the lock file and and re-opening the db.
// There's a higher chance this test would fail when the entire test suite is run. Maybe it isn't
// fast enough at dropping the cold_db LOCK file before the test attempts to open it again.
async fn should_not_initialize_incompatible_store_config() {
let validator_count = 16;
let spec = MinimalEthSpec::default_spec();
let db_path = tempdir().unwrap();
let store_config = StoreConfig::default();
let store = try_get_store_with_spec_and_config(&db_path, spec.clone(), store_config.clone())
.expect("disk store should initialize");
let harness = BeaconChainHarness::builder(MinimalEthSpec)
.spec(spec.clone())
.deterministic_keypairs(validator_count)
.fresh_disk_store(store)
.build();

// Resume from disk with a different store config.
drop(harness);
let different_store_config = StoreConfig {
linear_blocks: !store_config.linear_blocks,
..store_config
};
let maybe_err =
try_get_store_with_spec_and_config(&db_path, spec, different_store_config).err();

assert!(matches!(
maybe_err,
Some(StoreError::ConfigError(
StoreConfigError::IncompatibleStoreConfig { .. }
))
));
}

// This test checks whether the schema downgrade from the latest version to some minimum supported
// version is correct. This is the easiest schema test to write without historic versions of
// Lighthouse on-hand, but has the disadvantage that the min version needs to be adjusted manually
Expand Down
15 changes: 15 additions & 0 deletions beacon_node/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,21 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
[default: 8192 (mainnet) or 64 (minimal)]")
.takes_value(true)
)
.arg(
Arg::with_name("hierarchy-exponents")
.long("hierarchy-exponents")
.value_name("EXPONENTS")
.help("Specifies the frequency for storing full state snapshots and hierarchical \
diffs in the freezer DB. Accepts a comma-separated list of ascending \
exponents. Each exponent defines an interval for storing diffs to the layer \
above. The last exponent defines the interval for full snapshots. \
For example, a config of '4,8,12' would store a full snapshot every \
4096 (2^12) slots, first-level diffs every 256 (2^8) slots, and second-level \
diffs every 16 (2^4) slots. \
Cannot be changed after initialization. \
[default: 5,9,11,13,16,18,21]")
Comment on lines +540 to +548
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a fantastic explanation, thanks!

.takes_value(true)
)
.arg(
Arg::with_name("epochs-per-migration")
.long("epochs-per-migration")
Expand Down
19 changes: 19 additions & 0 deletions beacon_node/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ use std::net::{IpAddr, Ipv4Addr, ToSocketAddrs};
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::time::Duration;
use store::hdiff::HierarchyConfig;
use types::{Checkpoint, Epoch, EthSpec, Hash256, PublicKeyBytes, GRAFFITI_BYTES_LEN};

/// Gets the fully-initialized global client.
Expand Down Expand Up @@ -422,6 +423,24 @@ pub fn get_config<E: EthSpec>(
client_config.store.epochs_per_state_diff = epochs_per_state_diff;
}

if let Some(hierarchy_exponents) =
clap_utils::parse_optional::<String>(cli_args, "hierarchy-exponents")?
{
let exponents = hierarchy_exponents
.split(',')
.map(|s| {
s.parse()
.map_err(|e| format!("invalid hierarchy-exponents: {e:?}"))
})
.collect::<Result<Vec<u8>, _>>()?;

if exponents.windows(2).any(|w| w[0] >= w[1]) {
return Err("hierarchy-exponents must be in ascending order".to_string());
}

client_config.store.hierarchy_config = HierarchyConfig { exponents };
}

if let Some(epochs_per_migration) =
clap_utils::parse_optional(cli_args, "epochs-per-migration")?
{
Expand Down
75 changes: 68 additions & 7 deletions beacon_node/store/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,16 +45,25 @@ pub struct StoreConfig {

/// Variant of `StoreConfig` that gets written to disk. Contains immutable configuration params.
#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode)]
// FIXME(sproul): schema migration, add hdiff
// FIXME(sproul): schema migration
pub struct OnDiskStoreConfig {
pub linear_blocks: bool,
pub linear_restore_points: bool,
pub hierarchy_config: HierarchyConfig,
}

#[derive(Debug, Clone)]
pub enum StoreConfigError {
MismatchedSlotsPerRestorePoint { config: u64, on_disk: u64 },
InvalidCompressionLevel { level: i32 },
MismatchedSlotsPerRestorePoint {
config: u64,
on_disk: u64,
},
InvalidCompressionLevel {
level: i32,
},
IncompatibleStoreConfig {
config: OnDiskStoreConfig,
on_disk: OnDiskStoreConfig,
},
}

impl Default for StoreConfig {
Expand All @@ -80,15 +89,21 @@ impl StoreConfig {
pub fn as_disk_config(&self) -> OnDiskStoreConfig {
OnDiskStoreConfig {
linear_blocks: self.linear_blocks,
linear_restore_points: self.linear_restore_points,
hierarchy_config: self.hierarchy_config.clone(),
}
}

pub fn check_compatibility(
&self,
_on_disk_config: &OnDiskStoreConfig,
on_disk_config: &OnDiskStoreConfig,
) -> Result<(), StoreConfigError> {
// FIXME(sproul): TODO
let db_config = self.as_disk_config();
if db_config.ne(on_disk_config) {
return Err(StoreConfigError::IncompatibleStoreConfig {
config: db_config,
on_disk: on_disk_config.clone(),
});
}
Ok(())
}

Expand Down Expand Up @@ -146,3 +161,49 @@ impl StoreItem for OnDiskStoreConfig {
Ok(Self::from_ssz_bytes(bytes)?)
}
}

#[cfg(test)]
mod test {
use super::*;

#[test]
fn check_compatibility_ok() {
let store_config = StoreConfig {
linear_blocks: true,
..Default::default()
};
let on_disk_config = OnDiskStoreConfig {
linear_blocks: true,
hierarchy_config: store_config.hierarchy_config.clone(),
};
assert!(store_config.check_compatibility(&on_disk_config).is_ok());
}

#[test]
fn check_compatibility_linear_blocks_mismatch() {
let store_config = StoreConfig {
linear_blocks: true,
..Default::default()
};
let on_disk_config = OnDiskStoreConfig {
linear_blocks: false,
hierarchy_config: store_config.hierarchy_config.clone(),
};
assert!(store_config.check_compatibility(&on_disk_config).is_err());
}

#[test]
fn check_compatibility_hierarchy_config_incompatible() {
let store_config = StoreConfig {
linear_blocks: true,
..Default::default()
};
let on_disk_config = OnDiskStoreConfig {
linear_blocks: true,
hierarchy_config: HierarchyConfig {
exponents: vec![5, 8, 11, 13, 16, 18, 21],
},
};
assert!(store_config.check_compatibility(&on_disk_config).is_err());
}
}
2 changes: 1 addition & 1 deletion beacon_node/store/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ pub enum Error {
},
MissingStateRoot(Slot),
MissingState(Hash256),
MissingSnapshot(Epoch),
MissingSnapshot(Slot),
MissingDiff(Epoch),
NoBaseStateFound(Hash256),
BlockReplayError(BlockReplayError),
Expand Down
Loading
Loading