-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Remove discarded blocks and states from database by default #11983
Changes from 17 commits
bc5ba8e
d762b63
e425634
70b21b8
124f586
e2c5674
7bff3cb
da0c426
c519937
3f03c82
940d9db
458e078
a228701
6ae25a1
54a5a2b
9e3e8eb
ac58113
4c3810c
f4ea09b
2b0c25c
6cbc62e
df5ffec
cb62c23
79afe8b
3dff0f0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -32,11 +32,12 @@ pub struct PruningParams { | |
pub state_pruning: Option<String>, | ||
/// Specify the number of finalized blocks to keep in the database. | ||
/// | ||
/// Default is to keep all blocks. | ||
/// Default is to keep all of finalized blocks. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here we should add that 0 keep all blocks. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm,,It's really not very intuitive that 0 keep all blocks. IMO it's a compromise approach now.
hzy1919 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/// 0 keep all blocks. | ||
/// | ||
/// NOTE: only finalized blocks are subject for removal! | ||
#[clap(alias = "keep-blocks", long, value_name = "COUNT")] | ||
pub blocks_pruning: Option<u32>, | ||
pub blocks_pruning: Option<String>, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You also need to document the options available. |
||
} | ||
|
||
impl PruningParams { | ||
|
@@ -46,19 +47,29 @@ impl PruningParams { | |
.as_ref() | ||
.map(|s| match s.as_str() { | ||
"archive" => Ok(PruningMode::ArchiveAll), | ||
"archive-canonical" => Ok(PruningMode::ArchiveCanonical), | ||
bc => bc | ||
.parse() | ||
.map_err(|_| error::Error::Input("Invalid pruning mode specified".to_string())) | ||
.map_err(|_| error::Error::Input("Invalid state pruning mode specified".to_string())) | ||
.map(PruningMode::blocks_pruning), | ||
}) | ||
.transpose() | ||
} | ||
|
||
/// Get the block pruning value from the parameters | ||
pub fn blocks_pruning(&self) -> error::Result<BlocksPruning> { | ||
Ok(match self.blocks_pruning { | ||
Some(n) => BlocksPruning::Some(n), | ||
None => BlocksPruning::All, | ||
}) | ||
match self.blocks_pruning.as_ref() { | ||
Some(bp) => { | ||
match bp.as_str() { | ||
"archive" => Ok(BlocksPruning::KeepAll), | ||
"archive-canonical" => Ok(BlocksPruning::KeepFinalized), | ||
bc => bc | ||
.parse() | ||
.map_err(|_| error::Error::Input("Invalid blocks pruning mode specified".to_string())) | ||
.map(|n| BlocksPruning::Some(n)), | ||
} | ||
}, | ||
None => Ok(BlocksPruning::KeepFinalized), | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -320,10 +320,12 @@ pub struct DatabaseSettings { | |
} | ||
|
||
/// Block pruning settings. | ||
#[derive(Debug, Clone, Copy)] | ||
#[derive(Debug, Clone, Copy, PartialEq)] | ||
pub enum BlocksPruning { | ||
/// Keep full block history. | ||
All, | ||
/// Keep full block history, of every block that was ever imported. | ||
KeepAll, | ||
/// Keep full finalized block history. | ||
KeepFinalized, | ||
/// Keep N recent finalized blocks. | ||
Some(u32), | ||
} | ||
|
@@ -1078,6 +1080,24 @@ impl<Block: BlockT> Backend<Block> { | |
|
||
Self::new(db_setting, canonicalization_delay).expect("failed to create test-db") | ||
} | ||
#[cfg(any(test, feature = "test-helpers"))] | ||
pub fn new_test_with_tx_storage_2(blocks_pruning: BlocksPruning, canonicalization_delay: u64) -> Self { | ||
let db = kvdb_memorydb::create(crate::utils::NUM_COLUMNS); | ||
let db = sp_database::as_database(db); | ||
let state_pruning = match blocks_pruning { | ||
BlocksPruning::KeepAll => PruningMode::ArchiveAll , | ||
BlocksPruning::KeepFinalized => PruningMode::ArchiveCanonical , | ||
BlocksPruning::Some(n) => PruningMode::blocks_pruning(n) , | ||
}; | ||
let db_setting = DatabaseSettings { | ||
trie_cache_maximum_size: Some(16 * 1024 * 1024), | ||
state_pruning: Some(state_pruning), | ||
source: DatabaseSource::Custom { db, require_create_flag: true }, | ||
blocks_pruning: blocks_pruning, | ||
}; | ||
|
||
Self::new(db_setting, canonicalization_delay).expect("failed to create test-db") | ||
} | ||
|
||
/// Expose the Database that is used by this backend. | ||
/// The second argument is the Column that stores the State. | ||
|
@@ -1707,32 +1727,47 @@ impl<Block: BlockT> Backend<Block> { | |
finalized: NumberFor<Block>, | ||
displaced: &FinalizationOutcome<Block::Hash, NumberFor<Block>>, | ||
) -> ClientResult<()> { | ||
if let BlocksPruning::Some(blocks_pruning) = self.blocks_pruning { | ||
// Always keep the last finalized block | ||
let keep = std::cmp::max(blocks_pruning, 1); | ||
if finalized >= keep.into() { | ||
let number = finalized.saturating_sub(keep.into()); | ||
self.prune_block(transaction, BlockId::<Block>::number(number))?; | ||
} | ||
match self.blocks_pruning { | ||
BlocksPruning::KeepAll => {}, | ||
BlocksPruning::Some(blocks_pruning) => { | ||
// Always keep the last finalized block | ||
let keep = std::cmp::max(blocks_pruning, 1); | ||
if finalized >= keep.into() { | ||
let number = finalized.saturating_sub(keep.into()); | ||
self.prune_block(transaction, BlockId::<Block>::number(number))?; | ||
} | ||
self.prune_displaced_branches(transaction, finalized, displaced)?; | ||
}, | ||
BlocksPruning::KeepFinalized => { | ||
self.prune_displaced_branches(transaction, finalized, displaced)?; | ||
}, | ||
} | ||
Ok(()) | ||
} | ||
|
||
// Also discard all blocks from displaced branches | ||
for h in displaced.leaves() { | ||
let mut number = finalized; | ||
let mut hash = *h; | ||
// Follow displaced chains back until we reach a finalized block. | ||
// Since leaves are discarded due to finality, they can't have parents | ||
// that are canonical, but not yet finalized. So we stop deleting as soon as | ||
// we reach canonical chain. | ||
while self.blockchain.hash(number)? != Some(hash) { | ||
let id = BlockId::<Block>::hash(hash); | ||
match self.blockchain.header(id)? { | ||
Some(header) => { | ||
self.prune_block(transaction, id)?; | ||
number = header.number().saturating_sub(One::one()); | ||
hash = *header.parent_hash(); | ||
}, | ||
None => break, | ||
} | ||
fn prune_displaced_branches( | ||
&self, | ||
transaction: &mut Transaction<DbHash>, | ||
finalized: NumberFor<Block>, | ||
displaced: &FinalizationOutcome<Block::Hash, NumberFor<Block>>, | ||
) -> ClientResult<()> { | ||
// Discard all blocks from displaced branches | ||
for h in displaced.leaves() { | ||
let mut number = finalized; | ||
let mut hash = *h; | ||
// Follow displaced chains back until we reach a finalized block. | ||
// Since leaves are discarded due to finality, they can't have parents | ||
// that are canonical, but not yet finalized. So we stop deleting as soon as | ||
// we reach canonical chain. | ||
while self.blockchain.hash(number)? != Some(hash) { | ||
let id = BlockId::<Block>::hash(hash); | ||
match self.blockchain.header(id)? { | ||
Some(header) => { | ||
self.prune_block(transaction, id)?; | ||
number = header.number().saturating_sub(One::one()); | ||
hash = *header.parent_hash(); | ||
}, | ||
None => break, | ||
} | ||
} | ||
} | ||
|
@@ -1752,6 +1787,13 @@ impl<Block: BlockT> Backend<Block> { | |
columns::BODY, | ||
id, | ||
)?; | ||
utils::remove_from_db( | ||
transaction, | ||
&*self.storage.db, | ||
columns::KEY_LOOKUP, | ||
columns::JUSTIFICATIONS, | ||
id, | ||
)?; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. May be worth mentioning in release note that justification are now pruned with block (additionally to the new parameters). |
||
if let Some(index) = | ||
read_db(&*self.storage.db, columns::KEY_LOOKUP, columns::BODY_INDEX, id)? | ||
{ | ||
|
@@ -2506,7 +2548,7 @@ pub(crate) mod tests { | |
trie_cache_maximum_size: Some(16 * 1024 * 1024), | ||
state_pruning: Some(PruningMode::blocks_pruning(1)), | ||
source: DatabaseSource::Custom { db: backing, require_create_flag: false }, | ||
blocks_pruning: BlocksPruning::All, | ||
blocks_pruning: BlocksPruning::KeepFinalized, | ||
}, | ||
0, | ||
) | ||
|
@@ -3209,6 +3251,111 @@ pub(crate) mod tests { | |
assert_eq!(Some(vec![3.into()]), bc.body(BlockId::hash(blocks[3])).unwrap()); | ||
assert_eq!(Some(vec![4.into()]), bc.body(BlockId::hash(blocks[4])).unwrap()); | ||
} | ||
|
||
#[test] | ||
fn prune_blocks_on_finalize_in_keep_all() { | ||
let backend = Backend::<Block>::new_test_with_tx_storage_2(BlocksPruning::KeepAll, 0); | ||
let mut blocks = Vec::new(); | ||
let mut prev_hash = Default::default(); | ||
for i in 0..5 { | ||
let hash = insert_block( | ||
&backend, | ||
i, | ||
prev_hash, | ||
None, | ||
Default::default(), | ||
vec![i.into()], | ||
None, | ||
) | ||
.unwrap(); | ||
blocks.push(hash); | ||
prev_hash = hash; | ||
} | ||
|
||
let mut op = backend.begin_operation().unwrap(); | ||
backend.begin_state_operation(&mut op, BlockId::Hash(blocks[4])).unwrap(); | ||
for i in 1..3 { | ||
op.mark_finalized(BlockId::Hash(blocks[i]), None).unwrap(); | ||
} | ||
backend.commit_operation(op).unwrap(); | ||
|
||
let bc = backend.blockchain(); | ||
assert_eq!(Some(vec![0.into()]), bc.body(BlockId::hash(blocks[0])).unwrap()); | ||
assert_eq!(Some(vec![1.into()]), bc.body(BlockId::hash(blocks[1])).unwrap()); | ||
assert_eq!(Some(vec![2.into()]), bc.body(BlockId::hash(blocks[2])).unwrap()); | ||
assert_eq!(Some(vec![3.into()]), bc.body(BlockId::hash(blocks[3])).unwrap()); | ||
assert_eq!(Some(vec![4.into()]), bc.body(BlockId::hash(blocks[4])).unwrap()); | ||
} | ||
|
||
#[test] | ||
fn prune_blocks_on_finalize_with_fork_in_keep_all() { | ||
let backend = Backend::<Block>::new_test_with_tx_storage_2(BlocksPruning::KeepAll, 10); | ||
let mut blocks = Vec::new(); | ||
let mut prev_hash = Default::default(); | ||
for i in 0..5 { | ||
let hash = insert_block( | ||
&backend, | ||
i, | ||
prev_hash, | ||
None, | ||
Default::default(), | ||
vec![i.into()], | ||
None, | ||
) | ||
.unwrap(); | ||
blocks.push(hash); | ||
prev_hash = hash; | ||
} | ||
|
||
// insert a fork at block 2 | ||
let fork_hash_root = insert_block( | ||
&backend, | ||
2, | ||
blocks[1], | ||
None, | ||
sp_core::H256::random(), | ||
vec![2.into()], | ||
None, | ||
) | ||
.unwrap(); | ||
insert_block( | ||
&backend, | ||
3, | ||
fork_hash_root, | ||
None, | ||
H256::random(), | ||
vec![3.into(), 11.into()], | ||
None, | ||
) | ||
.unwrap(); | ||
|
||
let mut op = backend.begin_operation().unwrap(); | ||
backend.begin_state_operation(&mut op, BlockId::Hash(blocks[4])).unwrap(); | ||
op.mark_head(BlockId::Hash(blocks[4])).unwrap(); | ||
backend.commit_operation(op).unwrap(); | ||
|
||
let bc = backend.blockchain(); | ||
assert_eq!(Some(vec![2.into()]), bc.body(BlockId::hash(fork_hash_root)).unwrap()); | ||
|
||
for i in 1..5 { | ||
let mut op = backend.begin_operation().unwrap(); | ||
backend.begin_state_operation(&mut op, BlockId::Hash(blocks[i])).unwrap(); | ||
op.mark_finalized(BlockId::Hash(blocks[i]), None).unwrap(); | ||
backend.commit_operation(op).unwrap(); | ||
} | ||
|
||
assert_eq!(Some(vec![0.into()]), bc.body(BlockId::hash(blocks[0])).unwrap()); | ||
assert_eq!(Some(vec![1.into()]), bc.body(BlockId::hash(blocks[1])).unwrap()); | ||
assert_eq!(Some(vec![2.into()]), bc.body(BlockId::hash(blocks[2])).unwrap()); | ||
assert_eq!(Some(vec![3.into()]), bc.body(BlockId::hash(blocks[3])).unwrap()); | ||
assert_eq!(Some(vec![4.into()]), bc.body(BlockId::hash(blocks[4])).unwrap()); | ||
|
||
assert_eq!(Some(vec![2.into()]), bc.body(BlockId::hash(fork_hash_root)).unwrap()); | ||
assert_eq!(bc.info().best_number, 4); | ||
for i in 0..5 { | ||
assert!(bc.hash(i).unwrap().is_some()); | ||
} | ||
} | ||
|
||
#[test] | ||
fn prune_blocks_on_finalize_with_fork() { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The doc for this param should be updated with the new
final
option.