Skip to content

Commit

Permalink
GC trie
Browse files Browse the repository at this point in the history
  • Loading branch information
Kouprin committed Mar 4, 2020
1 parent 4774eba commit 2cb9aa5
Show file tree
Hide file tree
Showing 13 changed files with 501 additions and 173 deletions.
114 changes: 102 additions & 12 deletions chain/chain/src/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,14 @@ use near_primitives::transaction::{
};
use near_primitives::types::{
AccountId, Balance, BlockExtra, BlockHeight, BlockHeightDelta, ChunkExtra, EpochId, Gas,
NumBlocks, ShardId, ValidatorStake,
NumBlocks, ShardId, StateHeaderKey, ValidatorStake,
};
use near_primitives::unwrap_or_return;
use near_primitives::views::{
ExecutionOutcomeWithIdView, ExecutionStatusView, FinalExecutionOutcomeView,
FinalExecutionStatus, LightClientBlockView,
};
use near_store::{ColStateHeaders, ColStateParts, Store};
use near_store::{ColStateHeaders, ColStateParts, Store, Trie};

use crate::error::{Error, ErrorKind};
use crate::finality::{ApprovalVerificationError, FinalityGadget, FinalityGadgetQuorums};
Expand All @@ -40,7 +40,7 @@ use crate::store::{ChainStore, ChainStoreAccess, ChainStoreUpdate, ShardInfo, St
use crate::types::{
AcceptedBlock, ApplyTransactionResult, Block, BlockHeader, BlockStatus, Provenance,
ReceiptList, ReceiptProofResponse, ReceiptResponse, RootProof, RuntimeAdapter,
ShardStateSyncResponseHeader, StateHeaderKey, StatePartKey, Tip,
ShardStateSyncResponseHeader, StatePartKey, Tip,
};
use crate::validate::{
validate_challenge, validate_chunk_proofs, validate_chunk_transactions,
Expand Down Expand Up @@ -70,9 +70,6 @@ const NEAR_BASE: Balance = 1_000_000_000_000_000_000_000_000;
/// Number of epochs for which we keep store data
const NUM_EPOCHS_TO_KEEP_STORE_DATA: u64 = 5;

/// Number of heights to clear.
const HEIGHTS_TO_CLEAR: BlockHeightDelta = 10;

/// Block economics config taken from genesis config
pub struct BlockEconomicsConfig {
pub gas_price_adjustment_rate: u8,
Expand Down Expand Up @@ -526,20 +523,113 @@ impl Chain {
});
}

pub fn clear_old_data(&mut self) -> Result<(), Error> {
// GC CONTRACT
// ===
//
// Prerequisites, guaranteed by the System:
// 1. Genesis is always on the Canonical Chain, only one Genesis exists and its height is zero.
// 2. If block A is ancestor of block B, height of A is strictly less then height of B.
// 3. (Property 1). The oldest ancestor block of any fork is always on the Canonical Chain and
// has the least height among all blocks on the fork.
// 4. (Property 2). The oldest ancestor block of any fork never affected by Canonical Chain Switching
// and always stays on Canonical Chain.
//
// Overall:
// 1. GC procedure is handled by `clear_data()` function.
// 2. `clear_data()` runs GC process for all blocks from the lowest known block height (tail).
// 3. `clear_data()` contains of four parts:
// a. Block Reference Map creating, if it not exists
// b. Define clearing height as `height`; the highest height for which we want to clear the data
// c. Forks Clearing at height `height` down to tail.
// d. Canonical Chain Clearing from tail up to height `height`
// 4. Before actual clearing is started, Block Reference Map should be built.
// 5. It's recommended to execute `clear_data()` every time when block at new height is added.
//
// Forks Clearing:
// 1. Any fork which ends up on height `height` INCLUSIVELY and earlier will be completely deleted
// from the Store with all its ancestors up to the ancestor block where fork is happened
// EXCLUDING the ancestor block where fork is happened.
// 2. The oldest ancestor block always remains on the Canonical Chain by property 2.
// 3. All forks which end up on height `height + 1` and further are protected from deletion and
// no their ancestor will be deleted (even with lowest heights).
// 4. `clear_forks_data()` handles forks clearing for fixed height `height`.
//
// Canonical Chain Clearing:
// 1. Blocks on the Canonical Chain with the only descendant (if no forks started from them)
// are unlocked for Canonical Chain Clearing.
// 2. If Forks Clearing ended up on the Canonical Chain, the block may be unlocked
// for the Canonical Chain Clearing. There is no other reason to unlock the block exists.
// 3. All the unlocked blocks will be completely deleted
// from the tail up to the height `height` EXCLUSIVELY.
// 4. (Property 3, GC invariant). There is always only one block with the lowest height (tail)
// (based on property 1) and it's always on the Canonical Chain (based on property 2).
//
// Example:
//
// height: 101 102 103 104
// --------[A]---[B]---[C]---[D]
// \ \
// \ \---[E]
// \
// \-[F]---[G]
//
// 1. Let's define clearing height = 102. It this case fork A-F-G is protected from deletion
// because of G which is on height 103. Nothing will be deleted.
// 2. Let's define clearing height = 103. It this case Fork Clearing will be executed for A
// to delete blocks G and F, then Fork Clearing will be executed for B to delete block E.
// Then Canonical Chain Clearing will delete blocks A and B as unlocked.
// Block C is the only block of height 103 remains on the Canonical Chain (invariant).
//
pub fn clear_data(&mut self, trie: Arc<Trie>) -> Result<(), Error> {
let mut chain_store_update = self.store.store_update();
let head = chain_store_update.head()?;
let tail = chain_store_update.tail()?;
let height_diff = NUM_EPOCHS_TO_KEEP_STORE_DATA * self.epoch_length;
if head.height >= height_diff {
let last_height = head.height - height_diff;
for height in last_height.saturating_sub(HEIGHTS_TO_CLEAR)..last_height {
match chain_store_update.clear_old_data_on_height(height) {
// TODO build Reference Map better
chain_store_update.blocks_refcount = HashMap::new();
for height in tail..head.height {
let blocks_current_height =
match chain_store_update.get_all_block_hashes_by_height(height) {
Ok(blocks_current_height) => {
blocks_current_height.values().flatten().cloned().collect()
}
_ => vec![],
};
for block_hash in blocks_current_height.iter() {
let prev_hash = chain_store_update.get_block_header(&block_hash)?.prev_hash;
let refcount =
chain_store_update.blocks_refcount.get(&prev_hash).unwrap_or(&0).clone();
chain_store_update.blocks_refcount.insert(prev_hash, refcount);
}
}
if head.height > height_diff {
// Forks Cleaning
for height in (tail..(head.height - height_diff)).rev() {
if height == 0 {
break;
}
match chain_store_update.clear_forks_data(trie.clone(), height) {
Ok(_) => {}
Err(err) => {
error!(target: "client", "Error clearing old data on height {:?}, {:?}", height, err);
error!(target: "client", "Error at forks clearing on height {:?}, {:?}", height, err);
}
}
}
// Canonical Chain Clearing
for height in tail..(head.height - height_diff) {
if height == 0 {
continue;
}
let block_hash = match chain_store_update.get_block_hash_by_height(height) {
Ok(block_hash) => block_hash,
Err(_) => continue, // no block at height `height`
};
if chain_store_update.get_block_refcount(&block_hash) == 0 {
chain_store_update.clear_block_data(trie.clone(), block_hash, false)?;
} else {
break;
}
}
}
chain_store_update.commit()?;
Ok(())
Expand Down
Loading

0 comments on commit 2cb9aa5

Please sign in to comment.