Skip to content

Commit

Permalink
add assumed field to DiskBlockHeader
Browse files Browse the repository at this point in the history
If a block wasn't validated, but assumed valid by assumeutreexo or
pow-fraud-proofs we mark it as Assumed. This behaves almost identical to
a fully-valid tag, but is useful for backfilling
  • Loading branch information
Davidson-Souza committed Apr 29, 2024
1 parent 44ae910 commit d642bb4
Show file tree
Hide file tree
Showing 8 changed files with 105 additions and 41 deletions.
30 changes: 27 additions & 3 deletions crates/floresta-chain/src/pruned_utreexo/chain_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,9 @@ impl<PersistedState: ChainStore> ChainState<PersistedState> {
header = *self.get_ancestor(&block)?;
continue;
}
Some(DiskBlockHeader::AssumedValid(block, _)) => {
return Ok(block);
}
Some(DiskBlockHeader::Orphan(header)) => {
return Err(BlockchainError::InvalidTip(format(format_args!(
"Block {} doesn't have a known ancestor (i.e an orphan block)",
Expand Down Expand Up @@ -361,7 +364,9 @@ impl<PersistedState: ChainStore> ChainState<PersistedState> {
while !self.is_genesis(&header) {
let _header = self.get_ancestor(&header)?;
match _header {
DiskBlockHeader::FullyValid(_, _) => return Ok(header.block_hash()),
DiskBlockHeader::FullyValid(_, _) | DiskBlockHeader::AssumedValid(_, _) => {
return Ok(header.block_hash())
}
DiskBlockHeader::Orphan(_) => {
return Err(BlockchainError::InvalidTip(format(format_args!(
"Block {} doesn't have a known ancestor (i.e an orphan block)",
Expand Down Expand Up @@ -986,7 +991,14 @@ impl<PersistedState: ChainStore> UpdatableChainstate for ChainState<PersistedSta
self.reorg(new_tip)
}

fn mark_chain_as_valid(&self, acc: Stump) -> Result<bool, BlockchainError> {
fn mark_block_as_valid(&self, block: BlockHash) -> Result<(), BlockchainError> {
let header = self.get_disk_block_header(&block)?;
let height = header.height().unwrap();
let new_header = DiskBlockHeader::FullyValid(*header, height);
self.update_header(&new_header)
}

fn mark_chain_as_assumed(&self, acc: Stump) -> Result<bool, BlockchainError> {
let assumed_hash = self.get_best_block()?.1;

let mut curr_header = self.get_block_header(&assumed_hash)?;
Expand Down Expand Up @@ -1058,6 +1070,7 @@ impl<PersistedState: ChainStore> UpdatableChainstate for ChainState<PersistedSta
}
Ok(())
}

fn connect_block(
&self,
block: &Block,
Expand All @@ -1073,6 +1086,7 @@ impl<PersistedState: ChainStore> UpdatableChainstate for ChainState<PersistedSta
}
// If it's valid or orphan, we don't validate
DiskBlockHeader::Orphan(_)
| DiskBlockHeader::AssumedValid(_, _) // this will be validated by a partial chain
| DiskBlockHeader::InFork(_, _)
| DiskBlockHeader::InvalidChain(_) => return Ok(0),
DiskBlockHeader::HeadersOnly(_, height) => height,
Expand Down Expand Up @@ -1179,6 +1193,7 @@ impl<PersistedState: ChainStore> UpdatableChainstate for ChainState<PersistedSta

Ok(())
}

fn get_root_hashes(&self) -> Vec<NodeHash> {
let inner = read_lock!(self);
inner.acc.roots.clone()
Expand All @@ -1191,7 +1206,16 @@ impl<PersistedState: ChainStore> UpdatableChainstate for ChainState<PersistedSta
acc: Stump,
) -> Result<super::partial_chain::PartialChainState, BlockchainError> {
let blocks = (initial_height..=final_height)
.map(|height| self.get_block_header_by_height(height))
.flat_map(|height| {
let hash = self
.get_block_hash(height)
.expect("Block should be present");
self.get_disk_block_header(&hash)
})
.filter_map(|header| match header {
DiskBlockHeader::FullyValid(header, _) => Some(header),
_ => None,
})
.collect();

let inner = PartialChainStateInner {
Expand Down
13 changes: 13 additions & 0 deletions crates/floresta-chain/src/pruned_utreexo/chainstore.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use crate::prelude::*;
#[derive(Debug)]
pub enum DiskBlockHeader {
FullyValid(BlockHeader, u32),
AssumedValid(BlockHeader, u32),
Orphan(BlockHeader),
HeadersOnly(BlockHeader, u32),
InFork(BlockHeader, u32),
Expand All @@ -29,6 +30,7 @@ impl DiskBlockHeader {
DiskBlockHeader::HeadersOnly(_, height) => Some(*height),
DiskBlockHeader::InFork(_, height) => Some(*height),
DiskBlockHeader::InvalidChain(_) => None,
DiskBlockHeader::AssumedValid(_, height) => Some(*height),
}
}
}
Expand All @@ -41,6 +43,7 @@ impl Deref for DiskBlockHeader {
DiskBlockHeader::HeadersOnly(header, _) => header,
DiskBlockHeader::InFork(header, _) => header,
DiskBlockHeader::InvalidChain(header) => header,
DiskBlockHeader::AssumedValid(header, _) => header,
}
}
}
Expand All @@ -67,6 +70,10 @@ impl Decodable for DiskBlockHeader {
Ok(Self::InFork(header, height))
}
0x04 => Ok(Self::InvalidChain(header)),
0x05 => {
let height = u32::consensus_decode(reader)?;
Ok(Self::AssumedValid(header, height))
}
_ => unreachable!(),
}
}
Expand Down Expand Up @@ -104,6 +111,12 @@ impl Encodable for DiskBlockHeader {
0x04_u8.consensus_encode(writer)?;
header.consensus_encode(writer)?;
}
DiskBlockHeader::AssumedValid(header, height) => {
0x05_u8.consensus_encode(writer)?;
header.consensus_encode(writer)?;
height.consensus_encode(writer)?;
len += 4;
}
};
Ok(len)
}
Expand Down
17 changes: 12 additions & 5 deletions crates/floresta-chain/src/pruned_utreexo/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,9 @@ pub trait UpdatableChainstate {
fn toggle_ibd(&self, is_ibd: bool);
/// Tells this blockchain to consider this block invalid, and not build on top of it
fn invalidate_block(&self, block: BlockHash) -> Result<(), BlockchainError>;
/// Marks one block as being fully validated, this overrides a block that was explicitly
/// marked as invalid.
fn mark_block_as_valid(&self, block: BlockHash) -> Result<(), BlockchainError>;
/// Gives a requested block for rescan
fn process_rescan_block(&self, block: &Block) -> Result<(), BlockchainError>;
/// Returns the root hashes of our utreexo forest
Expand All @@ -156,7 +159,7 @@ pub trait UpdatableChainstate {
///
/// This mimics the behavour of checking every block before this block, and continues
/// from this point
fn mark_chain_as_valid(&self, acc: Stump) -> Result<bool, BlockchainError>;
fn mark_chain_as_assumed(&self, acc: Stump) -> Result<bool, BlockchainError>;
}

/// [ChainStore] is a trait defining how we interact with our chain database. This definitions
Expand Down Expand Up @@ -246,17 +249,21 @@ impl<T: UpdatableChainstate> UpdatableChainstate for Arc<T> {
T::handle_transaction(self)
}

fn mark_chain_as_valid(&self, acc: Stump) -> Result<bool, BlockchainError> {
T::mark_chain_as_valid(self, acc)
}

fn switch_chain(&self, new_tip: BlockHash) -> Result<(), BlockchainError> {
T::switch_chain(self, new_tip)
}

fn process_rescan_block(&self, block: &Block) -> Result<(), BlockchainError> {
T::process_rescan_block(self, block)
}

fn mark_block_as_valid(&self, block: BlockHash) -> Result<(), BlockchainError> {
T::mark_block_as_valid(self, block)
}

fn mark_chain_as_assumed(&self, acc: Stump) -> Result<bool, BlockchainError> {
T::mark_chain_as_assumed(self, acc)
}
}

impl<T: BlockchainInterface> BlockchainInterface for Arc<T> {
Expand Down
25 changes: 24 additions & 1 deletion crates/floresta-chain/src/pruned_utreexo/partial_chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,25 @@ impl PartialChainState {
fn inner_mut(&self) -> &mut PartialChainStateInner {
unsafe { self.0.get().as_mut().expect("this pointer is valid") }
}

/// Returns all blocks in this partial chain
pub fn list_blocks(&self) -> &[BlockHeader] {
&self.inner().blocks
}

/// Returns all block we have validated so far in this chain
pub fn list_valid_blocks(&self) -> Vec<&BlockHeader> {
self.inner()
.blocks
.iter()
.take(self.inner().current_height as usize)
.collect()
}

/// Returns whether any block inside this interval is invalid
pub fn has_invalid_blocks(&self) -> bool {
self.inner().error.is_some()
}
}

impl UpdatableChainstate for PartialChainState {
Expand Down Expand Up @@ -341,7 +360,11 @@ impl UpdatableChainstate for PartialChainState {
unimplemented!("we don't do rescan")
}

fn mark_chain_as_valid(&self, _acc: Stump) -> Result<bool, BlockchainError> {
fn mark_chain_as_assumed(&self, _acc: Stump) -> Result<bool, BlockchainError> {
unimplemented!("no need to mark as valid")
}

fn mark_block_as_valid(&self, _block: BlockHash) -> Result<(), BlockchainError> {
unimplemented!("no need to mark as valid")
}
}
Expand Down
4 changes: 2 additions & 2 deletions crates/floresta-wire/src/p2p_wire/chain_selector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -451,7 +451,7 @@ where
leaves: assume_utreexo.leaves,
roots: assume_utreexo.roots.clone(),
};
self.chain.mark_chain_as_valid(acc)?;
self.chain.mark_chain_as_assumed(acc)?;
}

if self.config.pow_fraud_proofs {
Expand Down Expand Up @@ -519,7 +519,7 @@ where
);

self.1.state = ChainSelectorState::Done;
self.chain.mark_chain_as_valid(acc).unwrap();
self.chain.mark_chain_as_assumed(acc).unwrap();
self.chain.toggle_ibd(false);
}
// if we have more than one tip, we need to check if our best chain has an invalid block
Expand Down
4 changes: 2 additions & 2 deletions crates/floresta-wire/src/p2p_wire/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -270,8 +270,8 @@ where
return Ok(());
}
info!(
"New peer id={} version={} blocks={}",
version.id, version.user_agent, version.blocks
"New peer id={} version={} blocks={} services={}",
version.id, version.user_agent, version.blocks, version.services
);
self.inflight.remove(&InflightRequests::Connect(peer));

Expand Down
2 changes: 1 addition & 1 deletion crates/floresta-wire/src/p2p_wire/node_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ pub trait NodeContext {
/// Attempt to open a new connection (if needed) every TRY_NEW_CONNECTION seconds
const TRY_NEW_CONNECTION: u64 = 10; // 10 seconds
/// If ASSUME_STALE seconds passed since our last tip update, treat it as stale
const ASSUME_STALE: u64 = 30 * 60; // 30 minutes
const ASSUME_STALE: u64 = 15; // 15 minutes
/// While on IBD, if we've been without blocks for this long, ask for headers again
const IBD_REQUEST_BLOCKS_AGAIN: u64 = 30; // 30 seconds
/// How often we broadcast transactions
Expand Down
51 changes: 24 additions & 27 deletions crates/floresta-wire/src/p2p_wire/running_node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -283,46 +283,42 @@ where
return;
}

// download all blocks from the network
let mut sync = UtreexoNode(ibd.0, SyncNode::default());
self = UtreexoNode(ibd.0, self.1);

if sync.config.backfill && startup_tip == 0 {
let end = sync.0.chain.get_validation_index().unwrap();
let chain = sync
// download all blocks from the network
if self.config.backfill && startup_tip == 0 {
let end = self.0.chain.get_validation_index().unwrap();
let chain = self
.chain
.get_partial_chain(startup_tip, end, Stump::default())
.unwrap();

let mut backfill = UtreexoNode::<SyncNode, PartialChainState>::new(
sync.config.clone(),
self.config.clone(),
chain,
sync.mempool.clone(),
self.mempool.clone(),
);

UtreexoNode::<SyncNode, PartialChainState>::run(
&mut backfill,
kill_signal.clone(),
|chain| {
if chain.get_height().unwrap() != end {
panic!("Backfill didn't reach the end of the chain");
|chain: &PartialChainState| {
if chain.has_invalid_blocks() {
panic!(
"We assumed a chain with invalid blocks, something went really wrong"
);
}

for block in chain.list_valid_blocks() {
self.chain
.mark_block_as_valid(block.block_hash())
.expect("Failed to mark block as valid");
}
},
)
.await;
}

if sync.0.chain.get_height().unwrap() > sync.0.chain.get_validation_index().unwrap() {
UtreexoNode::<SyncNode, Chain>::run(&mut sync, kill_signal.clone(), |_| {}).await;

if *kill_signal.read().await {
self = UtreexoNode(sync.0, self.1);
self.shutdown().await;
return;
}
}

// Then take the final state and run the node
self = UtreexoNode(sync.0, self.1);
self.last_block_request = self.chain.get_validation_index().unwrap_or(0);

info!("starting running node...");
Expand Down Expand Up @@ -407,11 +403,6 @@ where

try_and_log!(self.request_rescan_block().await);

// requests that need a utreexo peer
if self.utreexo_peers.is_empty() {
continue;
}

// Check whether we are in a stale tip
periodic_job!(
self.check_for_stale_tip().await,
Expand All @@ -420,6 +411,11 @@ where
RunningNode
);

// requests that need a utreexo peer
if self.utreexo_peers.is_empty() {
continue;
}

// Check if we haven't missed any block
if self.inflight.len() < 10 {
try_and_log!(self.ask_missed_block().await);
Expand Down Expand Up @@ -501,6 +497,7 @@ where
/// been more than 15 minutes, try to update it.
async fn check_for_stale_tip(&mut self) -> Result<(), WireError> {
warn!("Potential stale tip detected, trying extra peers");
self.last_tip_update = Instant::now();
self.create_connection(false).await;
self.send_to_random_peer(
NodeRequest::GetHeaders(self.chain.get_block_locator().unwrap()),
Expand Down

0 comments on commit d642bb4

Please sign in to comment.