floresta_wire/p2p_wire/node/
blocks.rsuse std::time::Instant;
use bitcoin::p2p::ServiceFlags;
use bitcoin::Block;
use bitcoin::BlockHash;
use floresta_chain::proof_util;
use floresta_chain::proof_util::UtreexoLeafError;
use floresta_chain::BlockValidationErrors;
use floresta_chain::BlockchainError;
use floresta_chain::ChainBackend;
use floresta_chain::CompactLeafData;
use floresta_common::service_flags;
use rustreexo::proof::Proof;
use tracing::debug;
use tracing::error;
use tracing::warn;
use super::try_and_log;
use super::InflightRequests;
use super::NodeRequest;
use super::UtreexoNode;
use crate::block_proof::Bitmap;
use crate::block_proof::UtreexoProof;
use crate::node_context::NodeContext;
use crate::node_context::PeerId;
use crate::p2p_wire::error::WireError;
type UtreexoData = (Vec<CompactLeafData>, Proof, PeerId);
#[derive(Debug)]
pub(crate) struct InflightBlock {
pub peer: PeerId,
pub block: Block,
pub aux_data: Option<UtreexoData>,
}
impl InflightBlock {
fn new(block: Block, peer: PeerId) -> Self {
let aux_data = match block.txdata.len() {
1 => Some((Vec::new(), Proof::default(), peer)),
_ => None, };
Self {
peer,
block,
aux_data,
}
}
fn add_utreexo_data(&mut self, leaf_data: Vec<CompactLeafData>, proof: Proof, peer: PeerId) {
self.aux_data = Some((leaf_data, proof, peer));
}
}
impl<T, Chain> UtreexoNode<Chain, T>
where
T: 'static + Default + NodeContext,
Chain: ChainBackend + 'static,
WireError: From<Chain::Error>,
{
pub(crate) fn request_blocks(&mut self, blocks: Vec<BlockHash>) -> Result<(), WireError> {
let should_request = |block: &BlockHash| {
let is_inflight = self
.inflight
.contains_key(&InflightRequests::Blocks(*block));
let is_pending = self.blocks.contains_key(block);
!(is_inflight || is_pending)
};
let blocks: Vec<_> = blocks.into_iter().filter(should_request).collect();
if blocks.is_empty() {
return Ok(());
}
let peer =
self.send_to_fast_peer(NodeRequest::GetBlock(blocks.clone()), ServiceFlags::NETWORK)?;
for block in blocks.iter() {
self.inflight
.insert(InflightRequests::Blocks(*block), (peer, Instant::now()));
}
Ok(())
}
pub(crate) fn request_block_proof(
&mut self,
block: Block,
peer: PeerId,
) -> Result<(), WireError> {
let block_hash = block.block_hash();
self.inflight.remove(&InflightRequests::Blocks(block_hash));
let Some(block) = self.check_is_user_block_and_reply(block)? else {
return Ok(());
};
let txdata_len = block.txdata.len();
debug!("Received block {block_hash} from peer {peer}, with {txdata_len} txs");
self.blocks
.insert(block_hash, InflightBlock::new(block, peer));
if txdata_len != 1 {
let utreexo_peer = self.send_to_fast_peer(
NodeRequest::GetBlockProof((block_hash, Bitmap::new(), Bitmap::new())),
service_flags::UTREEXO.into(),
)?;
self.inflight.insert(
InflightRequests::UtreexoProof(block_hash),
(utreexo_peer, Instant::now()),
);
}
Ok(())
}
pub(crate) fn attach_proof(
&mut self,
uproof: UtreexoProof,
peer: PeerId,
) -> Result<(), WireError> {
debug!("Received utreexo proof for block {}", uproof.block_hash);
self.inflight
.remove(&InflightRequests::UtreexoProof(uproof.block_hash));
let Some(block) = self.blocks.get_mut(&uproof.block_hash) else {
warn!(
"Received utreexo proof for block {}, but we don't have it",
uproof.block_hash
);
self.increase_banscore(peer, 5)?;
return Ok(());
};
let proof = Proof {
hashes: uproof.proof_hashes,
targets: uproof.targets,
};
block.add_utreexo_data(uproof.leaf_data, proof, peer);
Ok(())
}
pub(crate) fn ask_for_missed_proofs(&mut self) -> Result<(), WireError> {
if !self.has_utreexo_peers() {
return Ok(());
}
let pending_blocks = self
.blocks
.iter()
.filter_map(|(hash, block)| {
if block.aux_data.is_some() {
return None;
}
if !self
.inflight
.contains_key(&InflightRequests::UtreexoProof(*hash))
{
return Some(*hash);
}
None
})
.collect::<Vec<_>>();
for block_hash in pending_blocks {
let peer = self.send_to_fast_peer(
NodeRequest::GetBlockProof((block_hash, Bitmap::new(), Bitmap::new())),
service_flags::UTREEXO.into(),
)?;
self.inflight.insert(
InflightRequests::UtreexoProof(block_hash),
(peer, Instant::now()),
);
}
Ok(())
}
pub(crate) fn process_pending_blocks(&mut self) -> Result<(), WireError>
where
Chain::Error: From<UtreexoLeafError>,
{
loop {
let best_block = self.chain.get_best_block()?.0;
let next_block = self.chain.get_validation_index()? + 1;
if next_block > best_block {
return Ok(());
}
let next_block_hash = self.chain.get_block_hash(next_block)?;
let Some(block) = self.blocks.get(&next_block_hash) else {
return Ok(());
};
if block.aux_data.is_none() {
return Ok(());
}
let start = Instant::now();
self.process_block(next_block, next_block_hash)?;
let elapsed = start.elapsed().as_secs_f64();
self.block_sync_avg.add(elapsed);
#[cfg(feature = "metrics")]
{
use metrics::get_metrics;
let avg = self.block_sync_avg.value().expect("at least one sample");
let metrics = get_metrics();
metrics.avg_block_processing_time.set(avg);
}
}
}
fn process_block(&mut self, block_height: u32, block_hash: BlockHash) -> Result<(), WireError>
where
Chain::Error: From<UtreexoLeafError>,
{
debug!("processing block {block_hash}");
let inflight = self
.blocks
.remove(&block_hash)
.ok_or(WireError::BlockNotFound)?;
let block = inflight.block;
let peer = inflight.peer;
let (leaf_data, proof, utreexo_peer) =
inflight.aux_data.ok_or(WireError::BlockProofNotFound)?;
let (del_hashes, inputs) =
proof_util::process_proof(&leaf_data, &block.txdata, block_height, |h| {
self.chain.get_block_hash(h)
})?;
if let Err(chain_err) = self.chain.connect_block(&block, proof, inputs, del_hashes) {
error!(
"Validation failed for block with {:?}, received by peer {peer}. Reason: {chain_err}",
block.header,
);
let Some(e) = Self::block_validation_err(chain_err) else {
return Ok(());
};
return match self.handle_validation_errors(e, block, peer, utreexo_peer) {
Some(blamed_peer) => {
self.disconnect_and_ban(blamed_peer)?;
Err(WireError::PeerMisbehaving)
}
None => Ok(()),
};
}
self.last_tip_update = Instant::now();
Ok(())
}
fn block_validation_err(e: BlockchainError) -> Option<BlockValidationErrors> {
match e {
BlockchainError::TransactionError(tx_err) => Some(tx_err.error),
BlockchainError::BlockValidation(block_err) => Some(block_err),
BlockchainError::UtreexoError(_) | BlockchainError::InvalidProof => {
Some(BlockValidationErrors::InvalidProof)
}
_ => None,
}
}
fn handle_validation_errors(
&mut self,
e: BlockValidationErrors,
block: Block,
block_peer: PeerId,
utreexo_peer: PeerId,
) -> Option<PeerId> {
let hash = block.block_hash();
match e {
BlockValidationErrors::InvalidProof => {
self.blocks
.insert(hash, InflightBlock::new(block, block_peer));
warn!("Proof for block {hash} is invalid, banning peer {utreexo_peer}");
Some(utreexo_peer)
}
BlockValidationErrors::UtxoNotFound(_) => {
self.blocks
.insert(hash, InflightBlock::new(block, block_peer));
warn!("Leaf data for block {hash} is invalid, banning peer {utreexo_peer}");
Some(utreexo_peer)
}
BlockValidationErrors::InvalidCoinbase(_)
| BlockValidationErrors::ScriptValidationError(_)
| BlockValidationErrors::NullPrevOut
| BlockValidationErrors::EmptyInputs
| BlockValidationErrors::EmptyOutputs
| BlockValidationErrors::ScriptError
| BlockValidationErrors::BlockTooBig
| BlockValidationErrors::NotEnoughPow
| BlockValidationErrors::TooManyCoins
| BlockValidationErrors::NotEnoughMoney
| BlockValidationErrors::FirstTxIsNotCoinbase
| BlockValidationErrors::BadCoinbaseOutValue
| BlockValidationErrors::EmptyBlock
| BlockValidationErrors::BadBip34
| BlockValidationErrors::BIP94TimeWarp
| BlockValidationErrors::UnspendableUTXO
| BlockValidationErrors::CoinbaseNotMatured => {
try_and_log!(self.chain.invalidate_block(hash));
warn!("Block {hash} is invalid, banning peer {block_peer}");
Some(block_peer)
}
BlockValidationErrors::BadMerkleRoot | BlockValidationErrors::BadWitnessCommitment => {
Some(block_peer)
}
BlockValidationErrors::BlockExtendsAnOrphanChain
| BlockValidationErrors::BlockDoesntExtendTip => {
self.last_block_request = self.chain.get_validation_index().unwrap_or(0);
None
}
}
}
}