Skip to content

Commit

Permalink
add: time-related validation for blocks and transaction locks.
Browse files Browse the repository at this point in the history
  • Loading branch information
jaoleal committed Mar 4, 2025
1 parent 98cc85c commit d7c3cce
Show file tree
Hide file tree
Showing 14 changed files with 576 additions and 59 deletions.
9 changes: 5 additions & 4 deletions crates/floresta-chain/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@ rustreexo = "0.4"
sha2 = "^0.10.6"
log = "0.4"
kv = "0.24.0"
bitcoin = { version = "0.32", features = [
"serde",
], default-features = false }
bitcoin = { version = "0.32", features = ["serde"], default-features = false }
spin = "0.9.8"
core2 = { version = "0.4.0", default-features = false }
floresta-common = { path = "../floresta-common", default-features = false, features = ["std"] }
floresta-common = { path = "../floresta-common", default-features = false, features = [
"std",
] }
bitcoinconsensus = { version = "0.106.0", optional = true, default-features = false }
metrics = { path = "../../metrics", optional = true }

Expand All @@ -41,6 +41,7 @@ hex = "0.4.3"

[features]
default = []
std = []
bitcoinconsensus = ["bitcoin/bitcoinconsensus", "dep:bitcoinconsensus"]
metrics = ["dep:metrics"]

Expand Down
14 changes: 12 additions & 2 deletions crates/floresta-chain/benches/chain_state_bench.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use criterion::criterion_main;
use criterion::BatchSize;
use criterion::Criterion;
use criterion::SamplingMode;
use floresta_chain::pruned_utreexo::utxo_data::UtxoData;
use floresta_chain::pruned_utreexo::UpdatableChainstate;
use floresta_chain::AssumeValidArg;
use floresta_chain::ChainState;
Expand Down Expand Up @@ -44,7 +45,7 @@ fn setup_test_chain<'a>(
fn decode_block_and_inputs(
block_file: File,
stxos_file: File,
) -> (Block, HashMap<OutPoint, TxOut>) {
) -> (Block, HashMap<OutPoint, UtxoData>) {
let block_bytes = zstd::decode_all(block_file).unwrap();
let block: Block = deserialize(&block_bytes).unwrap();

Expand All @@ -58,7 +59,16 @@ fn decode_block_and_inputs(
.iter()
.skip(1) // Skip the coinbase transaction
.flat_map(|tx| &tx.input)
.map(|txin| (txin.previous_output, stxos.remove(0)))
.map(|txin| {
(
txin.previous_output,
UtxoData {
txout: stxos.remove(0),
commited_height: 0,
commited_time: 0,
},
)
})
.collect();

assert!(stxos.is_empty(), "Moved all stxos to the inputs map");
Expand Down
2 changes: 1 addition & 1 deletion crates/floresta-chain/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
//! All data is stored in a `ChainStore` implementation, which is generic over the
//! underlying database. See the ChainStore trait for more information. For a
//! ready-to-use implementation, see the [KvChainStore] struct.
#![cfg_attr(not(test), no_std)]
#![cfg_attr(not(any(test, feature = "std")), no_std)]

pub mod pruned_utreexo;
pub(crate) use floresta_common::prelude;
Expand Down
79 changes: 72 additions & 7 deletions crates/floresta-chain/src/pruned_utreexo/chain_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,8 @@ use bitcoin::hashes::Hash;
use bitcoin::script;
use bitcoin::Block;
use bitcoin::BlockHash;
use bitcoin::OutPoint;
use bitcoin::Target;
use bitcoin::Transaction;
use bitcoin::TxOut;
use bitcoin::Work;
use floresta_common::Channel;
use log::info;
Expand All @@ -44,6 +42,7 @@ use super::error::BlockValidationErrors;
use super::error::BlockchainError;
use super::partial_chain::PartialChainState;
use super::partial_chain::PartialChainStateInner;
use super::utxo_data::UtxoMap;
use super::BlockchainInterface;
use super::ChainStore;
use super::UpdatableChainstate;
Expand Down Expand Up @@ -750,8 +749,16 @@ impl<PersistedState: ChainStore> ChainState<PersistedState> {
&self,
block: &Block,
height: u32,
inputs: HashMap<OutPoint, TxOut>,
inputs: UtxoMap,
) -> Result<(), BlockchainError> {
if let Ok(mtp) = self.get_mtp(height) {
#[cfg(not(feature = "std"))]
let time = super::nodetime::DisableTime;
#[cfg(feature = "std")]
let time = super::nodetime::standard_node_time::StdNodeTime;
Consensus::validate_block_time(block.header.time, mtp, time)?;
}

if !block.check_merkle_root() {
return Err(BlockValidationErrors::BadMerkleRoot.into());
}
Expand All @@ -773,12 +780,14 @@ impl<PersistedState: ChainStore> ChainState<PersistedState> {
// Validate block transactions
let subsidy = read_lock!(self).consensus.get_subsidy(height);
let verify_script = self.verify_script(height);

#[cfg(feature = "bitcoinconsensus")]
let flags = self.get_validation_flags(height, block.header.block_hash());
#[cfg(not(feature = "bitcoinconsensus"))]
let flags = 0;
Consensus::verify_block_transactions(
height,
block.header.time,
inputs,
&block.txdata,
subsidy,
Expand Down Expand Up @@ -826,7 +835,7 @@ impl<PersistedState: ChainStore> BlockchainInterface for ChainState<PersistedSta
&self,
block: &Block,
proof: Proof,
inputs: HashMap<OutPoint, TxOut>,
inputs: UtxoMap,
del_hashes: Vec<sha256::Hash>,
acc: Stump,
) -> Result<(), Self::Error> {
Expand Down Expand Up @@ -997,6 +1006,52 @@ impl<PersistedState: ChainStore> BlockchainInterface for ChainState<PersistedSta
let mut inner = write_lock!(self);
inner.broadcast_queue.drain(..).collect()
}
fn get_mtp(&self, height: u32) -> Result<u32, BlockchainError> {
let mut initial_array = [0u32; 11];

for i in 0..11 {
let time = match self.get_block_hash(height.saturating_sub(i)) {
Ok(hash) => self.get_block_header(&hash)?.time,
_ => {
info!("Block timestamp : {i} Not found");
0
}
};
initial_array[10 - i as usize] = time;
}

// This is the case where we didnt find even the block in the given height
if initial_array[10] == 0 {
return Err(BlockchainError::BlockNotPresent);
}

let mut ret = initial_array
.iter()
.filter(|t| **t != 0)
.collect::<Vec<_>>();

//This reinforces how many blocks we need to have found to get a valid mtp.
//This check is actually useless and will never occur *if* we are in a valid and complete chain.
let range_remainder = (11u32).saturating_sub(height);

if range_remainder > 0 {
// If the range of blocks is less than 11, it should be the same value as height
if range_remainder != height {
return Err(BlockchainError::BlockNotPresent);
}
} else {
// Otherwise we should have found exactly 11 blocks
if ret.len() != 11 {
return Err(BlockchainError::BlockNotPresent);
}
}

ret.sort();

// At this point we have at least 1 block inside the array so we can safely
// divide the length by 2 to have its median.
Ok(*ret[ret.len() / 2])
}
}
impl<PersistedState: ChainStore> UpdatableChainstate for ChainState<PersistedState> {
fn switch_chain(&self, new_tip: BlockHash) -> Result<(), BlockchainError> {
Expand Down Expand Up @@ -1069,7 +1124,7 @@ impl<PersistedState: ChainStore> UpdatableChainstate for ChainState<PersistedSta
&self,
block: &Block,
proof: Proof,
inputs: HashMap<OutPoint, TxOut>,
inputs: UtxoMap,
del_hashes: Vec<sha256::Hash>,
) -> Result<u32, BlockchainError> {
let header = self.get_disk_block_header(&block.block_hash())?;
Expand Down Expand Up @@ -1349,6 +1404,7 @@ mod test {
use super::UpdatableChainstate;
use crate::prelude::HashMap;
use crate::pruned_utreexo::consensus::Consensus;
use crate::pruned_utreexo::utxo_data::UtxoData;
use crate::AssumeValidArg;
use crate::KvChainStore;
use crate::Network;
Expand All @@ -1365,7 +1421,7 @@ mod test {
fn decode_block_and_inputs(
block_file: File,
stxos_file: File,
) -> (Block, HashMap<OutPoint, TxOut>) {
) -> (Block, HashMap<OutPoint, UtxoData>) {
let block_bytes = zstd::decode_all(block_file).unwrap();
let block: Block = deserialize(&block_bytes).unwrap();

Expand All @@ -1379,7 +1435,16 @@ mod test {
.iter()
.skip(1) // Skip the coinbase transaction
.flat_map(|tx| &tx.input)
.map(|txin| (txin.previous_output, stxos.remove(0)))
.map(|txin| {
(
txin.previous_output,
UtxoData {
txout: stxos.remove(0),
commited_height: 0,
commited_time: 0,
},
)
})
.collect();

assert!(stxos.is_empty(), "Moved all stxos to the inputs map");
Expand Down
Loading

0 comments on commit d7c3cce

Please sign in to comment.