From 567e0029e9f06541e0b1660839f22deb322d5705 Mon Sep 17 00:00:00 2001 From: jaoleal Date: Thu, 5 Dec 2024 15:19:36 -0300 Subject: [PATCH] add: time-related validation for blocks and transaction locks. --- .../benches/chain_state_bench.rs | 5 +- .../src/pruned_utreexo/chain_state.rs | 69 ++++- .../src/pruned_utreexo/consensus.rs | 253 +++++++++++++++++- .../src/pruned_utreexo/error.rs | 21 ++ .../floresta-chain/src/pruned_utreexo/mod.rs | 99 ++++++- .../src/pruned_utreexo/partial_chain.rs | 20 +- .../src/pruned_utreexo/udata.rs | 19 +- crates/floresta-wire/src/p2p_wire/node.rs | 2 +- .../src/p2p_wire/running_node.rs | 5 + .../floresta-wire/src/p2p_wire/sync_node.rs | 5 + 10 files changed, 462 insertions(+), 36 deletions(-) diff --git a/crates/floresta-chain/benches/chain_state_bench.rs b/crates/floresta-chain/benches/chain_state_bench.rs index 5271be63..e4fbf016 100644 --- a/crates/floresta-chain/benches/chain_state_bench.rs +++ b/crates/floresta-chain/benches/chain_state_bench.rs @@ -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; @@ -44,7 +45,7 @@ fn setup_test_chain<'a>( fn decode_block_and_inputs( block_file: File, stxos_file: File, -) -> (Block, HashMap) { +) -> (Block, HashMap) { let block_bytes = zstd::decode_all(block_file).unwrap(); let block: Block = deserialize(&block_bytes).unwrap(); @@ -58,7 +59,7 @@ 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, stxos.remove(0).into())) .collect(); assert!(stxos.is_empty(), "Moved all stxos to the inputs map"); diff --git a/crates/floresta-chain/src/pruned_utreexo/chain_state.rs b/crates/floresta-chain/src/pruned_utreexo/chain_state.rs index a36b7cd8..14e07928 100644 --- a/crates/floresta-chain/src/pruned_utreexo/chain_state.rs +++ b/crates/floresta-chain/src/pruned_utreexo/chain_state.rs @@ -21,10 +21,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; @@ -44,8 +42,10 @@ use super::chainstore::KvChainStore; use super::consensus::Consensus; use super::error::BlockValidationErrors; use super::error::BlockchainError; +use super::nodetime::DisableTime; use super::partial_chain::PartialChainState; use super::partial_chain::PartialChainStateInner; +use super::utxo_data::UtxoMap; use super::BlockchainInterface; use super::ChainStore; use super::UpdatableChainstate; @@ -750,8 +750,16 @@ impl ChainState { &self, block: &Block, height: u32, - inputs: HashMap, + inputs: UtxoMap, ) -> Result<(), BlockchainError> { + if let Ok(mtp) = self.get_mtp(height) { + #[cfg(not(feature = "std"))] + let time = DisableTime; + #[cfg(feature = "std")] + let time = StdNodeTime; + Consensus::validate_block_time(block.header.time, mtp, time)?; + } + if !block.check_merkle_root() { return Err(BlockValidationErrors::BadMerkleRoot.into()); } @@ -773,12 +781,14 @@ impl ChainState { // 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, @@ -826,7 +836,7 @@ impl BlockchainInterface for ChainState, + inputs: UtxoMap, del_hashes: Vec, acc: Stump, ) -> Result<(), Self::Error> { @@ -997,6 +1007,41 @@ impl BlockchainInterface for ChainState Result { + 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 needed_blocks: u32 = height.saturating_sub(11); + + let mut ret = initial_array + .iter() + .filter(|t| **t != 0) + .collect::>(); + + if ret.len() == needed_blocks as usize { + 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 UpdatableChainstate for ChainState { fn switch_chain(&self, new_tip: BlockHash) -> Result<(), BlockchainError> { @@ -1069,7 +1114,7 @@ impl UpdatableChainstate for ChainState, + inputs: UtxoMap, del_hashes: Vec, ) -> Result { let header = self.get_disk_block_header(&block.block_hash())?; @@ -1349,6 +1394,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; @@ -1365,7 +1411,7 @@ mod test { fn decode_block_and_inputs( block_file: File, stxos_file: File, - ) -> (Block, HashMap) { + ) -> (Block, HashMap) { let block_bytes = zstd::decode_all(block_file).unwrap(); let block: Block = deserialize(&block_bytes).unwrap(); @@ -1379,7 +1425,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"); diff --git a/crates/floresta-chain/src/pruned_utreexo/consensus.rs b/crates/floresta-chain/src/pruned_utreexo/consensus.rs index c0d42f3b..124f77bf 100644 --- a/crates/floresta-chain/src/pruned_utreexo/consensus.rs +++ b/crates/floresta-chain/src/pruned_utreexo/consensus.rs @@ -13,7 +13,6 @@ use bitcoin::hashes::Hash; use bitcoin::Block; use bitcoin::BlockHash; use bitcoin::CompactTarget; -use bitcoin::OutPoint; use bitcoin::ScriptBuf; use bitcoin::Target; use bitcoin::Transaction; @@ -30,8 +29,13 @@ use sha2::Sha512_256; use super::chainparams::ChainParams; use super::error::BlockValidationErrors; use super::error::BlockchainError; +use super::nodetime::NodeTime; +use super::nodetime::HOUR; +use super::utxo_data::UtxoMap; use crate::TransactionError; +pub const SEQUENCE_LOCKTIME_MASK: u32 = 0x0000_ffff; + /// The value of a single coin in satoshis. pub const COIN_VALUE: u64 = 100_000_000; @@ -110,10 +114,10 @@ impl Consensus { /// - The transaction must not have duplicate inputs /// - The transaction must not spend more coins than it claims in the inputs /// - The transaction must have valid scripts - #[allow(unused)] pub fn verify_block_transactions( height: u32, - mut utxos: HashMap, + block_time: u32, + mut utxos: UtxoMap, transactions: &[Transaction], subsidy: u64, verify_script: bool, @@ -170,6 +174,14 @@ impl Consensus { error, } })?; + + Self::validate_locktime(input, transaction, &utxos, height, block_time).map_err( + |error| TransactionError { + txid: transaction.compute_txid(), + error, + }, + )?; + // TODO check also witness script size } @@ -193,7 +205,11 @@ impl Consensus { #[cfg(feature = "bitcoinconsensus")] if verify_script { transaction - .verify_with_flags(|outpoint| utxos.remove(outpoint), flags) + .verify_with_flags( + // TO-DO: Make this less horrible to understand + |outpoint| utxos.remove(outpoint).map(|utxodata| utxodata.txout), + flags, + ) .map_err(|err| TransactionError { txid: transaction.compute_txid(), error: BlockValidationErrors::ScriptValidationError(err.to_string()), @@ -216,12 +232,9 @@ impl Consensus { /// Returns the TxOut being spent by the given input. /// /// Fails if the UTXO is not present in the given hashmap. - fn get_utxo<'a>( - input: &TxIn, - utxos: &'a HashMap, - ) -> Result<&'a TxOut, BlockValidationErrors> { + fn get_utxo<'a>(input: &TxIn, utxos: &'a UtxoMap) -> Result<&'a TxOut, BlockValidationErrors> { match utxos.get(&input.previous_output) { - Some(txout) => Ok(txout), + Some(txout) => Ok(&txout.txout), None => Err( // This is the case when the spender: // - Spends an UTXO that doesn't exist @@ -230,14 +243,78 @@ impl Consensus { ), } } - #[allow(unused)] + + /// From a block timestamp, with a given MTP and a real world time, validate if a block timestamp + /// is correct. + pub fn validate_block_time( + block_timestamp: u32, + mtp: u32, + time: impl NodeTime, + ) -> Result<(), BlockValidationErrors> { + if time.get_time() == 0 { + return Ok(()); + } // The check for skipping time validation. + + let its_too_old = mtp > block_timestamp; + let its_too_new = block_timestamp > (time.get_time() + (2 * HOUR)); + if its_too_old { + return Err(BlockValidationErrors::BlockTooNew); + } + if its_too_new { + return Err(BlockValidationErrors::BlockTooNew); + } + Ok(()) + } + + /// Validate if the transaction doesnt have any timelock invalidating its inclusion inside blocks. fn validate_locktime( input: &TxIn, transaction: &Transaction, + out_map: &UtxoMap, height: u32, + block_time: u32, ) -> Result<(), BlockValidationErrors> { - unimplemented!("validate_locktime") + let is_relative_locked = + input.sequence.is_relative_lock_time() && transaction.version.0 >= 2; + + if is_relative_locked { + let prevout = match out_map.get(&input.previous_output) { + Some(po) => po, + None => { + return Err(BlockValidationErrors::UtxoNotFound(input.previous_output)); + } + }; + + match input.sequence.to_relative_lock_time() { + Some(lock) => { + if !lock.is_satisfied_by( + bitcoin::relative::Height::from_height( + (height - prevout.commited_height) as u16, + ), + bitcoin::relative::Time::from_512_second_intervals( + (block_time - prevout.commited_time) as u16, + ), + ) { + return Err(BlockValidationErrors::BadRelativeLockTime); + } + } + None => return Err(BlockValidationErrors::BadRelativeLockTime), + } + } + + let is_absolute_locked = transaction.is_lock_time_enabled(); + + let is_locktime_satisfied = transaction.is_absolute_timelock_satisfied( + bitcoin::absolute::Height::from_consensus(height).unwrap(), + bitcoin::absolute::Time::from_consensus(block_time).unwrap(), + ); + + if !is_locktime_satisfied && is_absolute_locked { + return Err(BlockValidationErrors::BadAbsoluteLockTime); + } + Ok(()) } + /// Validates the script size and the number of sigops in a scriptpubkey or scriptsig. fn validate_script_size(script: &ScriptBuf) -> Result<(), BlockValidationErrors> { // The maximum script size for non-taproot spends is 10,000 bytes @@ -250,6 +327,7 @@ impl Consensus { } Ok(()) } + fn verify_coinbase(transaction: &Transaction) -> Result<(), BlockValidationErrors> { // The prevout input of a coinbase must be all zeroes if transaction.input[0].previous_output.txid != Txid::all_zeros() { @@ -359,6 +437,7 @@ mod tests { use bitcoin::Witness; use super::*; + use crate::pruned_utreexo::utxo_data::UtxoData; fn coinbase(is_valid: bool) -> Transaction { // This coinbase transactions was retrieved from https://learnmeabitcoin.com/explorer/block/0000000000000a0f82f8be9ec24ebfca3d5373fde8dc4d9b9a949d538e9ff679 @@ -471,4 +550,156 @@ mod tests { )), ); } + /// Validates a relative timelock transaction and return its errors if any. + /// + /// utxo_lock: is the lock value which can be either a height or a timestamp. Defines the creation time/height of the UTXO. Values > 500,000,000 will be considered a unix timestamp, otherwise a height. + /// + /// sequence: a be u32 sequence to be put in a fake transaction that has only 1 input spending the utxo locked. + /// + /// actual_tip_lock: the height/time to be considered as the tip of the chain. Values > 500,000,000 will be considered a unix timestamp, otherwise a height. + fn test_reltimelock( + utxo_lock: u32, + sequence: u32, + actual_tip_lock: u32, + ) -> Option { + use bitcoin::hashes::Hash; + let base_tx = Transaction { + version: Version(02), + lock_time: LockTime::ZERO, + input: vec![TxIn { + previous_output: OutPoint { + txid: Hash::all_zeros(), + vout: 0, + }, + sequence: Sequence::from_consensus(sequence), + script_sig: ScriptBuf::default(), + witness: Witness::default(), + }], + output: vec![TxOut { + value: Amount::from_sat(0), + script_pubkey: ScriptBuf::default(), + }], + }; + // Check if the value is set for time and resets the default value for height and vice versa. + let mut actual_time = actual_tip_lock; + let mut actual_height = actual_tip_lock; + if actual_tip_lock > 500_000_000 { + actual_height = 0; + } else { + actual_time = 500_000_000; + } + + // same for utxo_lock + let mut utxo_time = utxo_lock; + let mut utxo_height = utxo_lock; + if utxo_lock > 500_000_000 { + utxo_height = 0; + } else { + utxo_time = 500_000_000; + } + + let mut utxo_state: UtxoMap = HashMap::new(); + + utxo_state.insert( + OutPoint { + txid: Hash::all_zeros(), + vout: 0, + }, + UtxoData { + txout: TxOut { + value: Amount::from_sat(0), + script_pubkey: ScriptBuf::default(), + }, + commited_time: utxo_time, + commited_height: utxo_height, + }, + ); + + match Consensus::validate_locktime( + &base_tx.input[0], + &base_tx, + &utxo_state, + actual_height, + actual_time, + ) { + Ok(_) => None, + Err(e) => Some(e), + } + } + + /// Validates a timelocked transaction with its timelock set as `height_tx_lock` or `seconds_tx_lock` against `lock` + /// + /// when testing `height_tx_lock`, `seconds_tx_lock` needs to be `0` and vice-versa. + fn test_abstimelock( + lock: u32, + height_tx_lock: u32, + seconds_tx_lock: u32, + ) -> Option { + use bitcoin::hashes::Hash; + let mut base_tx = Transaction { + version: Version(02), + lock_time: LockTime::ZERO, + input: vec![TxIn { + previous_output: OutPoint { + txid: Hash::all_zeros(), + vout: 0, + }, + sequence: Sequence::ENABLE_LOCKTIME_NO_RBF, + script_sig: ScriptBuf::default(), + witness: Witness::default(), + }], + output: vec![TxOut { + value: Amount::from_sat(0), + script_pubkey: ScriptBuf::default(), + }], + }; + + base_tx.lock_time = LockTime::from_consensus(lock); + + // This is necessary because of the seconds gate in timelock... + let mut seconds_tx_lock = seconds_tx_lock; + + if seconds_tx_lock == 0 { + seconds_tx_lock = 500000000; + } + + match Consensus::validate_locktime( + &base_tx.input[0], + &base_tx, + &HashMap::new(), + height_tx_lock, + seconds_tx_lock, + ) { + Ok(_) => None, + Err(e) => Some(e), + } + } + #[test] + fn test_timelock_validation() { + assert!(test_abstimelock(800, 800 + 1, 0).is_none()); //height locked sucess case. + assert_eq!( + test_abstimelock(800, 800 - 1, 0), //height locked fail case. + Some(BlockValidationErrors::BadAbsoluteLockTime) + ); + + assert!(test_abstimelock(1358114045, 0, 1358114045 + 1).is_none()); //time locked sucess case + assert_eq!( + test_abstimelock(1358114045, 0, 1358114045 - 1), //time locked fail case + Some(BlockValidationErrors::BadAbsoluteLockTime) + ); + + assert!(test_reltimelock(800_000, 0x00000064 as u32, 800_200).is_none()); // relative height locked success case + + assert_eq!( + test_reltimelock(800_000, 0x00000064 as u32, 800_099), // relative height locked fail case + Some(BlockValidationErrors::BadRelativeLockTime) + ); + + assert_eq!( + test_reltimelock(1358114045, 0x00400003 as u32, 1358114045), // relative time locked fail case + Some(BlockValidationErrors::BadRelativeLockTime) + ); + assert!(test_reltimelock(1358114045, 0x00400001 as u32, 1358114045 + 7680).is_none()) + // relative time locked sucess case + } } diff --git a/crates/floresta-chain/src/pruned_utreexo/error.rs b/crates/floresta-chain/src/pruned_utreexo/error.rs index 33e7758e..d1f0ea30 100644 --- a/crates/floresta-chain/src/pruned_utreexo/error.rs +++ b/crates/floresta-chain/src/pruned_utreexo/error.rs @@ -35,6 +35,10 @@ pub struct TransactionError { #[derive(Clone, Debug, PartialEq)] pub enum BlockValidationErrors { + BlockTooNew, + BlockTooOld, + InvalidBlockTimestamp, + InvalidCoinbase(String), UtxoNotFound(OutPoint), ScriptValidationError(String), @@ -53,6 +57,8 @@ pub enum BlockValidationErrors { BadBip34, InvalidProof, CoinbaseNotMatured, + BadRelativeLockTime, + BadAbsoluteLockTime, } impl Display for TransactionError { @@ -64,6 +70,21 @@ impl Display for TransactionError { impl Display for BlockValidationErrors { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { + BlockValidationErrors::BlockTooNew => { + write!(f, "A block is too new for the MTP at its height") + } + BlockValidationErrors::BlockTooOld => { + write!(f, "A block is too old for the MTP at its height") + } + BlockValidationErrors::InvalidBlockTimestamp => { + write!(f, "A block contains a invalid timestamp") + } + BlockValidationErrors::BadAbsoluteLockTime => { + write!(f, "A transaction contains a invalid absolute lock time.",) + } + BlockValidationErrors::BadRelativeLockTime => { + write!(f, "A transaction contains a invalid relative lock time.",) + } BlockValidationErrors::ScriptValidationError(e) => { write!(f, "{}", e) } diff --git a/crates/floresta-chain/src/pruned_utreexo/mod.rs b/crates/floresta-chain/src/pruned_utreexo/mod.rs index 0822074d..e7b0cdc5 100644 --- a/crates/floresta-chain/src/pruned_utreexo/mod.rs +++ b/crates/floresta-chain/src/pruned_utreexo/mod.rs @@ -15,7 +15,6 @@ use bitcoin::block::Header as BlockHeader; use bitcoin::hashes::sha256; use bitcoin::Block; use bitcoin::BlockHash; -use bitcoin::OutPoint; use bitcoin::Transaction; use bitcoin::TxOut; use rustreexo::accumulator::node_hash::NodeHash; @@ -23,6 +22,7 @@ use rustreexo::accumulator::proof::Proof; use rustreexo::accumulator::stump::Stump; use self::partial_chain::PartialChainState; +use self::utxo_data::UtxoMap; use crate::prelude::*; use crate::BestChain; use crate::BlockConsumer; @@ -88,13 +88,18 @@ pub trait BlockchainInterface { &self, block: &Block, proof: Proof, - inputs: HashMap, + inputs: UtxoMap, del_hashes: Vec, acc: Stump, ) -> Result<(), Self::Error>; fn get_fork_point(&self, block: BlockHash) -> Result; fn get_params(&self) -> bitcoin::params::Params; + + /// Get the Median Time Past of the last 11 blocks from the given height + /// + /// Fails if all the 11 blocks are not present in the chain. + fn get_mtp(&self, height: u32) -> Result; } /// [UpdatableChainstate] is a contract that a is expected from a chainstate /// implementation, that wishes to be updated. Using those methods, a backend like the p2p-node, @@ -108,7 +113,7 @@ pub trait UpdatableChainstate { &self, block: &Block, proof: Proof, - inputs: HashMap, + inputs: UtxoMap, del_hashes: Vec, ) -> Result; @@ -213,7 +218,7 @@ impl UpdatableChainstate for Arc { &self, block: &Block, proof: Proof, - inputs: HashMap, + inputs: UtxoMap, del_hashes: Vec, ) -> Result { T::connect_block(self, block, proof, inputs, del_hashes) @@ -263,6 +268,9 @@ impl BlockchainInterface for Arc { fn get_tx(&self, txid: &bitcoin::Txid) -> Result, Self::Error> { T::get_tx(self, txid) } + fn get_mtp(&self, height: u32) -> Result { + T::get_mtp(self, height) + } fn get_params(&self) -> bitcoin::params::Params { T::get_params(self) @@ -347,7 +355,7 @@ impl BlockchainInterface for Arc { &self, block: &Block, proof: Proof, - inputs: HashMap, + inputs: UtxoMap, del_hashes: Vec, acc: Stump, ) -> Result<(), Self::Error> { @@ -358,3 +366,84 @@ impl BlockchainInterface for Arc { T::get_fork_point(self, block) } } +/// Module to delegate local-time context. +/// +/// The consumer of `Floresta-chain` has the option to implement [`NodeTime`] if on a non-std environment.([`get_time()`] implementation that returns 0u32 will disable time checks.) +/// +/// On std you can just use a instance [`StdNodeTime`] as input. +pub mod nodetime { + /// Disable empty struct. + /// + /// Meant to be used in cases to disable time verifications + pub struct DisableTime; + /// One Hour in seconds constant. + pub const HOUR: u32 = 60 * 60; + /// Trait to return time-related context of the chain. + /// + /// [`get_time()`] should return a the latest [unix timestamp](https://en.wikipedia.org/wiki/Unix_time) when the consumer has time-notion. + /// + /// if the consumer does not have any time notion or MTP control, its safe to use `0u32` to disable any validations on time. + pub trait NodeTime { + /// Should return a unix timestamp or 0 to skip any time related validation. + fn get_time(&self) -> u32; + } + impl NodeTime for DisableTime { + fn get_time(&self) -> u32 { + // we simply return zero to disable time checks + 0 + } + } + #[cfg(feature = "std")] + /// A module to provide the standard implementation of [`NodeTime`] trait. It uses [`std::time::SystemTime`] to get the current time. + pub mod standard_node_time { + extern crate std; + use std::time; + /// A empty struct to implement [`NodeTime`] trait using [`std::time::SystemTime`] + pub struct StdNodeTime; + impl super::NodeTime for StdNodeTime { + fn get_time(&self) -> u32 { + time::SystemTime::now() + .duration_since(time::UNIX_EPOCH) + .unwrap() + .as_secs() as u32 + } + } + } +} +///Module to hold methods and structs related to UTXO data. +pub mod utxo_data { + + use bitcoin::OutPoint; + + use super::*; + + #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] + /// A struct to hold unverified UTXOs and its metadata. + pub struct UtxoData { + /// The transaction output that created this UTXO. + pub(crate) txout: bitcoin::TxOut, + /// The lock value of the utxo. + pub(crate) commited_height: u32, + pub(crate) commited_time: u32, + } + + impl From for UtxoData { + fn from(txout: TxOut) -> Self { + UtxoData { + txout, + commited_height: 0, + commited_time: 0, + } + } + } + impl From<&TxOut> for UtxoData { + fn from(txout: &TxOut) -> Self { + UtxoData { + txout: txout.clone(), + commited_height: 0, + commited_time: 0, + } + } + } + pub(crate) type UtxoMap = HashMap; +} diff --git a/crates/floresta-chain/src/pruned_utreexo/partial_chain.rs b/crates/floresta-chain/src/pruned_utreexo/partial_chain.rs index c7950599..a90c20bb 100644 --- a/crates/floresta-chain/src/pruned_utreexo/partial_chain.rs +++ b/crates/floresta-chain/src/pruned_utreexo/partial_chain.rs @@ -11,7 +11,7 @@ //! This choice removes the use of costly atomic operations, but opens space for design flaws //! and memory unsoundness, so here are some tips about this module and how people looking for //! extend or use this code should proceed: -//! +//! //! - Shared ownership is forbidden: if you have two threads or tasks owning this, you'll have //! data race. If you want to hold shared ownership for this module, you need to place a //! [PartialChainState] inside an `Arc` yourself. Don't just Arc this and expect it to @@ -36,6 +36,7 @@ use super::chainparams::ChainParams; use super::consensus::Consensus; use super::error::BlockValidationErrors; use super::error::BlockchainError; +use super::utxo_data::UtxoMap; use super::BlockchainInterface; use super::UpdatableChainstate; use crate::UtreexoBlock; @@ -171,7 +172,7 @@ impl PartialChainStateInner { &mut self, block: &bitcoin::Block, proof: rustreexo::accumulator::proof::Proof, - inputs: HashMap, + inputs: UtxoMap, del_hashes: Vec, ) -> Result { let height = self.current_height + 1; @@ -208,7 +209,7 @@ impl PartialChainStateInner { &self, block: &bitcoin::Block, height: u32, - inputs: HashMap, + inputs: UtxoMap, ) -> Result<(), BlockchainError> { if !block.check_merkle_root() { return Err(BlockchainError::BlockValidation( @@ -244,6 +245,7 @@ impl PartialChainStateInner { let flags = 0; Consensus::verify_block_transactions( height, + block.header.time, inputs, &block.txdata, subsidy, @@ -262,7 +264,7 @@ impl PartialChainState { /// [PartialChainState] is through our APIs, and we make sure this [UnsafeCell] is /// always valid. /// The reference returned here **should not** leak through the API, as there's no - /// synchronization mechanims for it. + /// synchronization mechanism for it. #[inline(always)] #[must_use] #[doc(hidden)] @@ -277,7 +279,7 @@ impl PartialChainState { /// [PartialChainState] is through our APIs, and we make sure this [UnsafeCell] is /// always valid. /// The reference returned here **should not** leak through the API, as there's no - /// synchronization mechanims for it. + /// synchronization mechanism for it. #[inline(always)] #[allow(clippy::mut_from_ref)] #[must_use] @@ -311,7 +313,7 @@ impl UpdatableChainstate for PartialChainState { &self, block: &bitcoin::Block, proof: rustreexo::accumulator::proof::Proof, - inputs: HashMap, + inputs: UtxoMap, del_hashes: Vec, ) -> Result { self.inner_mut() @@ -372,6 +374,10 @@ impl UpdatableChainstate for PartialChainState { impl BlockchainInterface for PartialChainState { type Error = BlockchainError; + fn get_mtp(&self, _height: u32) -> Result { + unimplemented!("a partialChainState will probably give an incomplete MTP") + } + fn get_params(&self) -> bitcoin::params::Params { self.inner().chain_params().params } @@ -429,7 +435,7 @@ impl BlockchainInterface for PartialChainState { &self, _block: &bitcoin::Block, _proof: rustreexo::accumulator::proof::Proof, - _inputs: HashMap, + _inputs: UtxoMap, _del_hashes: Vec, _acc: Stump, ) -> Result<(), Self::Error> { diff --git a/crates/floresta-chain/src/pruned_utreexo/udata.rs b/crates/floresta-chain/src/pruned_utreexo/udata.rs index ead19e73..313fa808 100644 --- a/crates/floresta-chain/src/pruned_utreexo/udata.rs +++ b/crates/floresta-chain/src/pruned_utreexo/udata.rs @@ -300,6 +300,7 @@ pub mod proof_util { use super::LeafData; use crate::prelude::*; + use crate::pruned_utreexo::utxo_data::UtxoData; use crate::pruned_utreexo::BlockchainInterface; use crate::CompactLeafData; use crate::ScriptPubkeyType; @@ -333,7 +334,7 @@ pub mod proof_util { udata: &UData, transactions: &[Transaction], chain: &Chain, - ) -> Result<(Proof, Vec, HashMap), Chain::Error> { + ) -> Result<(Proof, Vec, HashMap), Chain::Error> { let targets = udata.proof.targets.iter().map(|target| target.0).collect(); let hashes = udata .proof @@ -357,7 +358,11 @@ pub mod proof_util { txid, vout: vout as u32, }, - out.clone(), + UtxoData { + txout: out.clone(), + commited_height: 0, + commited_time: 0, + }, ); } @@ -366,10 +371,18 @@ pub mod proof_util { if let Some(leaf) = leaves_iter.next() { let height = leaf.header_code >> 1; let hash = chain.get_block_hash(height)?; + let time = chain.get_block_header(&hash)?.time; let leaf = reconstruct_leaf_data(&leaf, input, hash).expect("Invalid proof"); hashes.push(leaf._get_leaf_hashes()); - inputs.insert(leaf.prevout, leaf.utxo); + inputs.insert( + leaf.prevout, + UtxoData { + txout: leaf.utxo, + commited_height: height, + commited_time: time, + }, + ); } } } diff --git a/crates/floresta-wire/src/p2p_wire/node.rs b/crates/floresta-wire/src/p2p_wire/node.rs index 1f161485..914d3b35 100644 --- a/crates/floresta-wire/src/p2p_wire/node.rs +++ b/crates/floresta-wire/src/p2p_wire/node.rs @@ -227,7 +227,7 @@ where .fixed_peer .as_ref() .map(|address| { - Self::resolve_connect_host(&address, Self::get_port(config.network.into())) + Self::resolve_connect_host(address, Self::get_port(config.network.into())) }) .transpose()?; diff --git a/crates/floresta-wire/src/p2p_wire/running_node.rs b/crates/floresta-wire/src/p2p_wire/running_node.rs index dc7e0b88..55ca9efa 100644 --- a/crates/floresta-wire/src/p2p_wire/running_node.rs +++ b/crates/floresta-wire/src/p2p_wire/running_node.rs @@ -677,11 +677,16 @@ where // to be invalidated. match e { BlockValidationErrors::InvalidCoinbase(_) + | BlockValidationErrors::InvalidBlockTimestamp | BlockValidationErrors::UtxoNotFound(_) + | BlockValidationErrors::BadAbsoluteLockTime + | BlockValidationErrors::BadRelativeLockTime | BlockValidationErrors::ScriptValidationError(_) | BlockValidationErrors::InvalidOutput | BlockValidationErrors::ScriptError | BlockValidationErrors::BlockTooBig + | BlockValidationErrors::BlockTooNew + | BlockValidationErrors::BlockTooOld | BlockValidationErrors::NotEnoughPow | BlockValidationErrors::TooManyCoins | BlockValidationErrors::BadMerkleRoot diff --git a/crates/floresta-wire/src/p2p_wire/sync_node.rs b/crates/floresta-wire/src/p2p_wire/sync_node.rs index 73133552..5081d227 100644 --- a/crates/floresta-wire/src/p2p_wire/sync_node.rs +++ b/crates/floresta-wire/src/p2p_wire/sync_node.rs @@ -220,11 +220,16 @@ where // to be invalidated. match e { BlockValidationErrors::InvalidCoinbase(_) + | BlockValidationErrors::InvalidBlockTimestamp | BlockValidationErrors::UtxoNotFound(_) + | BlockValidationErrors::BadAbsoluteLockTime + | BlockValidationErrors::BadRelativeLockTime | BlockValidationErrors::ScriptValidationError(_) | BlockValidationErrors::InvalidOutput | BlockValidationErrors::ScriptError | BlockValidationErrors::BlockTooBig + | BlockValidationErrors::BlockTooNew + | BlockValidationErrors::BlockTooOld | BlockValidationErrors::NotEnoughPow | BlockValidationErrors::TooManyCoins | BlockValidationErrors::BadMerkleRoot