Skip to content

Commit 7862c71

Browse files
Fix tree-states sub-epoch diffs (sigp#5097)
1 parent 11461d8 commit 7862c71

File tree

12 files changed

+255
-65
lines changed

12 files changed

+255
-65
lines changed

beacon_node/beacon_chain/src/historical_blocks.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,8 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
130130
}
131131

132132
// Store block roots, including at all skip slots in the freezer DB.
133+
// The block root mapping for `block.slot()` itself was already written when the block
134+
// was stored, above.
133135
for slot in (block.slot().as_usize() + 1..prev_block_slot.as_usize()).rev() {
134136
cold_batch.push(KeyValueStoreOp::PutKeyValue(
135137
get_key_for_col(DBColumn::BeaconBlockRoots.into(), &slot.to_be_bytes()),

beacon_node/src/cli.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -593,9 +593,7 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
593593
Arg::with_name("slots-per-restore-point")
594594
.long("slots-per-restore-point")
595595
.value_name("SLOT_COUNT")
596-
.help("Specifies how often a freezer DB restore point should be stored. \
597-
Cannot be changed after initialization. \
598-
[default: 8192 (mainnet) or 64 (minimal)]")
596+
.help("Deprecated.")
599597
.takes_value(true)
600598
)
601599
.arg(

beacon_node/src/config.rs

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ use std::num::NonZeroU16;
2626
use std::path::{Path, PathBuf};
2727
use std::str::FromStr;
2828
use std::time::Duration;
29-
use store::hdiff::HierarchyConfig;
3029
use types::{Checkpoint, Epoch, EthSpec, Hash256, PublicKeyBytes, GRAFFITI_BYTES_LEN};
3130

3231
/// Gets the fully-initialized global client.
@@ -435,22 +434,8 @@ pub fn get_config<E: EthSpec>(
435434
client_config.store.epochs_per_state_diff = epochs_per_state_diff;
436435
}
437436

438-
if let Some(hierarchy_exponents) =
439-
clap_utils::parse_optional::<String>(cli_args, "hierarchy-exponents")?
440-
{
441-
let exponents = hierarchy_exponents
442-
.split(',')
443-
.map(|s| {
444-
s.parse()
445-
.map_err(|e| format!("invalid hierarchy-exponents: {e:?}"))
446-
})
447-
.collect::<Result<Vec<u8>, _>>()?;
448-
449-
if exponents.windows(2).any(|w| w[0] >= w[1]) {
450-
return Err("hierarchy-exponents must be in ascending order".to_string());
451-
}
452-
453-
client_config.store.hierarchy_config = HierarchyConfig { exponents };
437+
if let Some(hierarchy_config) = clap_utils::parse_optional(cli_args, "hierarchy-exponents")? {
438+
client_config.store.hierarchy_config = hierarchy_config;
454439
}
455440

456441
if let Some(epochs_per_migration) =

beacon_node/store/src/config.rs

Lines changed: 51 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::hdiff::HierarchyConfig;
2-
use crate::{DBColumn, Error, StoreItem};
2+
use crate::{AnchorInfo, DBColumn, Error, Split, StoreItem};
33
use serde::{Deserialize, Serialize};
44
use ssz::{Decode, Encode};
55
use ssz_derive::{Decode, Encode};
@@ -117,15 +117,22 @@ impl StoreConfig {
117117
pub fn check_compatibility(
118118
&self,
119119
on_disk_config: &OnDiskStoreConfig,
120+
split: &Split,
121+
anchor: Option<&AnchorInfo>,
120122
) -> Result<(), StoreConfigError> {
121123
let db_config = self.as_disk_config();
122-
if db_config.ne(on_disk_config) {
123-
return Err(StoreConfigError::IncompatibleStoreConfig {
124+
// Allow changing the hierarchy exponents if no historic states are stored.
125+
if db_config.linear_blocks == on_disk_config.linear_blocks
126+
&& (db_config.hierarchy_config == on_disk_config.hierarchy_config
127+
|| anchor.map_or(false, |anchor| anchor.no_historic_states_stored(split.slot)))
128+
{
129+
Ok(())
130+
} else {
131+
Err(StoreConfigError::IncompatibleStoreConfig {
124132
config: db_config,
125133
on_disk: on_disk_config.clone(),
126-
});
134+
})
127135
}
128-
Ok(())
129136
}
130137

131138
/// Check that the configuration is valid.
@@ -218,6 +225,8 @@ impl StoreItem for OnDiskStoreConfig {
218225
#[cfg(test)]
219226
mod test {
220227
use super::*;
228+
use crate::{metadata::STATE_UPPER_LIMIT_NO_RETAIN, AnchorInfo, Split};
229+
use types::{Hash256, Slot};
221230

222231
#[test]
223232
fn check_compatibility_ok() {
@@ -229,7 +238,10 @@ mod test {
229238
linear_blocks: true,
230239
hierarchy_config: store_config.hierarchy_config.clone(),
231240
};
232-
assert!(store_config.check_compatibility(&on_disk_config).is_ok());
241+
let split = Split::default();
242+
assert!(store_config
243+
.check_compatibility(&on_disk_config, &split, None)
244+
.is_ok());
233245
}
234246

235247
#[test]
@@ -242,7 +254,10 @@ mod test {
242254
linear_blocks: false,
243255
hierarchy_config: store_config.hierarchy_config.clone(),
244256
};
245-
assert!(store_config.check_compatibility(&on_disk_config).is_err());
257+
let split = Split::default();
258+
assert!(store_config
259+
.check_compatibility(&on_disk_config, &split, None)
260+
.is_err());
246261
}
247262

248263
#[test]
@@ -257,6 +272,34 @@ mod test {
257272
exponents: vec![5, 8, 11, 13, 16, 18, 21],
258273
},
259274
};
260-
assert!(store_config.check_compatibility(&on_disk_config).is_err());
275+
let split = Split::default();
276+
assert!(store_config
277+
.check_compatibility(&on_disk_config, &split, None)
278+
.is_err());
279+
}
280+
281+
#[test]
282+
fn check_compatibility_hierarchy_config_update() {
283+
let store_config = StoreConfig {
284+
linear_blocks: true,
285+
..Default::default()
286+
};
287+
let on_disk_config = OnDiskStoreConfig {
288+
linear_blocks: true,
289+
hierarchy_config: HierarchyConfig {
290+
exponents: vec![5, 8, 11, 13, 16, 18, 21],
291+
},
292+
};
293+
let split = Split::default();
294+
let anchor = AnchorInfo {
295+
anchor_slot: Slot::new(0),
296+
oldest_block_slot: Slot::new(0),
297+
oldest_block_parent: Hash256::zero(),
298+
state_upper_limit: STATE_UPPER_LIMIT_NO_RETAIN,
299+
state_lower_limit: Slot::new(0),
300+
};
301+
assert!(store_config
302+
.check_compatibility(&on_disk_config, &split, Some(&anchor))
303+
.is_ok());
261304
}
262305
}

beacon_node/store/src/hdiff.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use serde::{Deserialize, Serialize};
55
use ssz::{Decode, Encode};
66
use ssz_derive::{Decode, Encode};
77
use std::io::{Read, Write};
8+
use std::str::FromStr;
89
use types::{BeaconState, ChainSpec, EthSpec, Slot, VList};
910
use zstd::{Decoder, Encoder};
1011

@@ -22,6 +23,26 @@ pub struct HierarchyConfig {
2223
pub exponents: Vec<u8>,
2324
}
2425

26+
impl FromStr for HierarchyConfig {
27+
type Err = String;
28+
29+
fn from_str(s: &str) -> Result<Self, String> {
30+
let exponents = s
31+
.split(',')
32+
.map(|s| {
33+
s.parse()
34+
.map_err(|e| format!("invalid hierarchy-exponents: {e:?}"))
35+
})
36+
.collect::<Result<Vec<u8>, _>>()?;
37+
38+
if exponents.windows(2).any(|w| w[0] >= w[1]) {
39+
return Err("hierarchy-exponents must be in ascending order".to_string());
40+
}
41+
42+
Ok(HierarchyConfig { exponents })
43+
}
44+
}
45+
2546
#[derive(Debug)]
2647
pub struct HierarchyModuli {
2748
moduli: Vec<u64>,
@@ -267,6 +288,18 @@ impl HierarchyModuli {
267288
Ok((slot / last + 1) * last)
268289
}
269290
}
291+
292+
/// Return `true` if the database ops for this slot should be committed immediately.
293+
///
294+
/// This is the case for all diffs in the 2nd lowest layer and above, which are required by diffs
295+
/// in the 1st layer.
296+
pub fn should_commit_immediately(&self, slot: Slot) -> Result<bool, Error> {
297+
// If there's only 1 layer of snapshots, then commit only when writing a snapshot.
298+
self.moduli.get(1).map_or_else(
299+
|| Ok(slot == self.next_snapshot_slot(slot)?),
300+
|second_layer_moduli| Ok(slot % *second_layer_moduli == 0),
301+
)
302+
}
270303
}
271304

272305
#[cfg(test)]

beacon_node/store/src/hot_cold_store.rs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -346,7 +346,20 @@ impl<E: EthSpec> HotColdDB<E, LevelDB<E>, LevelDB<E>> {
346346

347347
// Ensure that any on-disk config is compatible with the supplied config.
348348
if let Some(disk_config) = db.load_config()? {
349-
db.config.check_compatibility(&disk_config)?;
349+
let split = db.get_split_info();
350+
let anchor = db.get_anchor_info();
351+
db.config
352+
.check_compatibility(&disk_config, &split, anchor.as_ref())?;
353+
354+
// Inform user if hierarchy config is changing.
355+
if db.config.hierarchy_config != disk_config.hierarchy_config {
356+
info!(
357+
db.log,
358+
"Updating historic state config";
359+
"previous_config" => ?disk_config.hierarchy_config,
360+
"new_config" => ?db.config.hierarchy_config,
361+
);
362+
}
350363
}
351364
db.store_config()?;
352365

@@ -2740,6 +2753,7 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> HotColdDB<E, Hot, Cold>
27402753
let columns = [
27412754
DBColumn::BeaconState,
27422755
DBColumn::BeaconStateSummary,
2756+
DBColumn::BeaconStateDiff,
27432757
DBColumn::BeaconRestorePoint,
27442758
DBColumn::BeaconStateRoots,
27452759
DBColumn::BeaconHistoricalRoots,
@@ -2780,6 +2794,9 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> HotColdDB<E, Hot, Cold>
27802794
self.cold_db.do_atomically(cold_ops)?;
27812795
}
27822796

2797+
// In order to reclaim space, we need to compact the freezer DB as well.
2798+
self.cold_db.compact()?;
2799+
27832800
Ok(())
27842801
}
27852802
}

beacon_node/store/src/leveldb_store.rs

Lines changed: 9 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -155,25 +155,15 @@ impl<E: EthSpec> KeyValueStore<E> for LevelDB<E> {
155155
self.transaction_mutex.lock()
156156
}
157157

158-
/// Compact all values in the states and states flag columns.
159-
fn compact(&self) -> Result<(), Error> {
160-
let endpoints = |column: DBColumn| {
161-
(
162-
BytesKey::from_vec(get_key_for_col(column.as_str(), Hash256::zero().as_bytes())),
163-
BytesKey::from_vec(get_key_for_col(
164-
column.as_str(),
165-
Hash256::repeat_byte(0xff).as_bytes(),
166-
)),
167-
)
168-
};
169-
170-
for (start_key, end_key) in [
171-
endpoints(DBColumn::BeaconState),
172-
endpoints(DBColumn::BeaconStateDiff),
173-
endpoints(DBColumn::BeaconStateSummary),
174-
] {
175-
self.db.compact(&start_key, &end_key);
176-
}
158+
fn compact_column(&self, column: DBColumn) -> Result<(), Error> {
159+
// Use key-size-agnostic keys [] and 0xff..ff with a minimum of 32 bytes to account for
160+
// columns that may change size between sub-databases or schema versions.
161+
let start_key = BytesKey::from_vec(get_key_for_col(column.as_str(), &[]));
162+
let end_key = BytesKey::from_vec(get_key_for_col(
163+
column.as_str(),
164+
&vec![0; std::cmp::max(column.key_size(), 32)],
165+
));
166+
self.db.compact(&start_key, &end_key);
177167
Ok(())
178168
}
179169

beacon_node/store/src/lib.rs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,23 @@ pub trait KeyValueStore<E: EthSpec>: Sync + Send + Sized + 'static {
7979
/// this method. In future we may implement a safer mandatory locking scheme.
8080
fn begin_rw_transaction(&self) -> MutexGuard<()>;
8181

82-
/// Compact the database, freeing space used by deleted items.
83-
fn compact(&self) -> Result<(), Error>;
82+
/// Compact a single column in the database, freeing space used by deleted items.
83+
fn compact_column(&self, column: DBColumn) -> Result<(), Error>;
84+
85+
/// Compact a default set of columns that are likely to free substantial space.
86+
fn compact(&self) -> Result<(), Error> {
87+
// Compact state and block related columns as they are likely to have the most churn,
88+
// i.e. entries being created and deleted.
89+
for column in [
90+
DBColumn::BeaconState,
91+
DBColumn::BeaconStateDiff,
92+
DBColumn::BeaconStateSummary,
93+
DBColumn::BeaconBlock,
94+
] {
95+
self.compact_column(column)?;
96+
}
97+
Ok(())
98+
}
8499

85100
/// Iterate through all keys and values in a particular column.
86101
fn iter_column<K: Key>(&self, column: DBColumn) -> ColumnIter<K> {

beacon_node/store/src/memory_store.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ impl<E: EthSpec> KeyValueStore<E> for MemoryStore<E> {
108108
self.transaction_mutex.lock()
109109
}
110110

111-
fn compact(&self) -> Result<(), Error> {
111+
fn compact_column(&self, _column: DBColumn) -> Result<(), Error> {
112112
Ok(())
113113
}
114114
}

beacon_node/store/src/metadata.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,11 @@ impl AnchorInfo {
108108
pub fn block_backfill_complete(&self, target_slot: Slot) -> bool {
109109
self.oldest_block_slot <= target_slot
110110
}
111+
112+
/// Return true if no historic states other than genesis are stored in the database.
113+
pub fn no_historic_states_stored(&self, split_slot: Slot) -> bool {
114+
self.state_lower_limit == 0 && self.state_upper_limit >= split_slot
115+
}
111116
}
112117

113118
impl StoreItem for AnchorInfo {

0 commit comments

Comments
 (0)