Reorging the Chain
In the accept_header
method we have seen that, when receiving a header that doesn't extend the best chain, we may reorg. This is done with the maybe_reorg
method.
We have to choose between the two branches, represented by:
branch_tip
: The last header from the alternative chain.current_tip
: The last header from the current best chain.
// Path: floresta-chain/src/pruned_utreexo/chain_state.rs
fn maybe_reorg(&self, branch_tip: BlockHeader) -> Result<(), BlockchainError> {
let current_tip = self.get_block_header(&self.get_best_block().unwrap().1)?;
self.check_branch(&branch_tip)?;
let current_work = self.get_branch_work(¤t_tip)?;
let new_work = self.get_branch_work(&branch_tip)?;
// If the new branch has more work, it becomes the new best chain
if new_work > current_work {
self.reorg(branch_tip)?;
return Ok(());
}
// If the new branch has less work, we just store it as an alternative branch
// that might become the best chain in the future.
self.push_alt_tip(&branch_tip)?;
let parent_height = self.get_ancestor(&branch_tip)?.height().unwrap();
read_lock!(self)
.chainstore
.save_header(&super::chainstore::DiskBlockHeader::InFork(
branch_tip,
parent_height + 1,
))?;
Ok(())
}
We first call the check_branch
method to check if we know all the branch_tip
ancestors. In other words, we check if branch_tip
is indeed part of a branch, which requires that no ancestor is Orphan
.
Then we get the work in each chain tip with get_branch_work
and do the following:
- We reorg to the
branch_tip
if it has more work, and returnOk
early. - Else if
branch_tip
doesn't have more work we push its hash tobest_block.alternative_tips
via thepush_alt_tip
method and save the header asInFork
.
The push_alt_tip
method just checks if the branch_tip
parent hash is in alternative_tips
to remove it, as it's no longer the tip of the branch. Then we simply push the branch_tip
hash.
Reorg
Let's now dig into reorg logic, with reorg
. We start by querying the best block hash and use it to query its header. Then we get the header where the branch forks out with find_fork_point
.
// Path: floresta-chain/src/pruned_utreexo/chain_state.rs
fn reorg(&self, new_tip: BlockHeader) -> Result<(), BlockchainError> {
let current_best_block = self.get_best_block().unwrap().1;
let current_best_block = self.get_block_header(¤t_best_block)?;
let fork_point = self.find_fork_point(&new_tip)?;
self.mark_chain_as_inactive(¤t_best_block, fork_point.block_hash())?;
self.mark_chain_as_active(&new_tip, fork_point.block_hash())?;
let validation_index = self.get_last_valid_block(&new_tip)?;
let depth = self.get_chain_depth(&new_tip)?;
self.change_active_chain(&new_tip, validation_index, depth);
Ok(())
}
We use mark_chain_as_inactive
and mark_chain_as_active
to update the disk data (i.e. marking the previous InFork
headers as HeadersOnly
and vice versa, and linking the height indexes to the new branch block hashes).
Then we invoke get_last_valid_block
and get_chain_depth
to obtain said data from a branch, provided the branch header tip.
Note that we don't validate forks unless they become the best chain, so in this case the last validated block is the last common block between the two branches.
With this data we call change_active_chain
to update the best_block
field. It also re-initializes the accumulator as we will need to fetch the utreexo roots for the last common block to proceed with the new branch validation.
// Path: floresta-chain/src/pruned_utreexo/chain_state.rs
fn change_active_chain(&self, new_tip: &BlockHeader, last_valid: BlockHash, depth: u32) {
let mut inner = self.inner.write();
inner.best_block.best_block = new_tip.block_hash();
inner.best_block.validation_index = last_valid;
inner.best_block.depth = depth;
inner.acc = Stump::new();
}