Skip to content

Commit

Permalink
Feat: Time-related checks on blocks and tests for it.
Browse files Browse the repository at this point in the history
Signed-off-by: jaoleal <[email protected]>
  • Loading branch information
jaoleal committed Oct 11, 2024
1 parent 1781f6f commit ee1c8cf
Show file tree
Hide file tree
Showing 12 changed files with 814 additions and 258 deletions.
3 changes: 3 additions & 0 deletions crates/floresta-chain/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,7 @@ zstd = "0.12.3"
hex = "0.4.3"

[features]
default = ["std", "bitcoinconsensus"]
std = []
no-std = []
bitcoinconsensus = ["bitcoin/bitcoinconsensus"]
107 changes: 54 additions & 53 deletions crates/floresta-chain/src/pruned_utreexo/chain_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,8 @@ use bitcoin::hashes::sha256;
use bitcoin::hashes::Hash;
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 @@ -43,8 +41,11 @@ use super::chainstore::KvChainStore;
use super::consensus::Consensus;
use super::error::BlockValidationErrors;
use super::error::BlockchainError;
use super::nodetime::NodeTime;
use super::nodetime::HOUR;
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 @@ -169,14 +170,26 @@ impl<PersistedState: ChainStore> ChainState<PersistedState> {
fn update_header(&self, header: &DiskBlockHeader) -> Result<(), BlockchainError> {
Ok(read_lock!(self).chainstore.save_header(header)?)
}
fn validate_header(&self, block_header: &BlockHeader) -> Result<BlockHash, BlockchainError> {
/// Validates a given header.
fn validate_header(
&self,
block_header: &BlockHeader,
node_time: &impl NodeTime,
) -> Result<BlockHash, BlockchainError> {
let prev_block = self.get_disk_block_header(&block_header.prev_blockhash)?;
let prev_block_height = prev_block.height();
if prev_block_height.is_none() {
return Err(BlockValidationErrors::BlockExtendsAnOrphanChain.into());
}
let height = prev_block_height.unwrap() + 1;

// Check the time.
//
// The block can only be 2 hours ahead the node.
if node_time.get_time() + 2 * HOUR < block_header.time && node_time.get_time() != 0 {
return Err(BlockValidationErrors::BlockTimeTooNew.into());
}

// Check pow
let expected_target = self.get_next_required_work(&prev_block, height, block_header);

Expand Down Expand Up @@ -314,7 +327,7 @@ impl<PersistedState: ChainStore> ChainState<PersistedState> {
header.block_hash()
))));
}
None => {
_ => {
return Err(BlockchainError::InvalidTip(format(format_args!(
"Block {} isn't in our storage",
header.block_hash()
Expand Down Expand Up @@ -663,7 +676,9 @@ impl<PersistedState: ChainStore> ChainState<PersistedState> {
inner.best_block.best_block = best_block;
inner.best_block.depth = height;
}
fn verify_script(&self, height: u32) -> bool {

/// Returns whether we should validate signatures in this block.
fn validate_signatures(&self, height: u32) -> bool {
let inner = self.inner.read();

inner.assume_valid.map_or(true, |hash| {
Expand Down Expand Up @@ -714,42 +729,22 @@ impl<PersistedState: ChainStore> ChainState<PersistedState> {
&self,
block: &Block,
height: u32,
inputs: HashMap<OutPoint, TxOut>,
inputs: UtxoMap,
validate_script: bool,
chain_time: &impl NodeTime,
) -> Result<(), BlockchainError> {
if !block.check_merkle_root() {
return Err(BlockchainError::BlockValidation(
BlockValidationErrors::BadMerkleRoot,
));
}
if height >= self.chain_params().bip34_activation_height
&& block.bip34_block_height() != Ok(height as u64)
{
return Err(BlockchainError::BlockValidation(
BlockValidationErrors::BadBip34,
));
}
if !block.check_witness_commitment() {
return Err(BlockchainError::BlockValidation(
BlockValidationErrors::BadWitnessCommitment,
));
}

// Validate block transactions
let subsidy = read_lock!(self).consensus.get_subsidy(height);
let verify_script = self.verify_script(height);
#[cfg(feature = "bitcoinconsensus")]
let consensus = &self.inner.read().consensus;
let flags = self.get_validation_flags(height);
#[cfg(not(feature = "bitcoinconsensus"))]
let flags = 0;
Consensus::verify_block_transactions(
let prev_block = self.get_block_header(&block.header.prev_blockhash)?;
consensus.validate_block(
&block,
&prev_block,
height,
inputs,
&block.txdata,
subsidy,
verify_script,
flags,
)?;
Ok(())
validate_script,
chain_time,
)
}
}

Expand Down Expand Up @@ -786,25 +781,24 @@ 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,
chain_time: &impl NodeTime,
) -> Result<(), Self::Error> {
// verify the proof
let del_hashes = del_hashes
.iter()
.map(|hash| NodeHash::from(hash.as_byte_array()))
.collect::<Vec<_>>();

if !acc.verify(&proof, &del_hashes)? {
let acc = self.acc();
if acc.verify(&proof, &del_hashes)? {
return Err(BlockValidationErrors::InvalidProof.into());
}

let height = self
.get_block_height(&block.block_hash())?
.ok_or(BlockchainError::BlockNotPresent)?;

self.validate_block(block, height, inputs)
let validate_script = self.validate_signatures(height);
self.validate_block(block, height, inputs, validate_script, chain_time)
}

fn get_block_locator_for_tip(&self, tip: BlockHash) -> Result<Vec<BlockHash>, BlockchainError> {
Expand Down Expand Up @@ -1029,8 +1023,9 @@ impl<PersistedState: ChainStore> UpdatableChainstate for ChainState<PersistedSta
&self,
block: &Block,
proof: Proof,
inputs: HashMap<OutPoint, TxOut>,
inputs: UtxoMap,
del_hashes: Vec<sha256::Hash>,
chain_time: &impl NodeTime,
) -> Result<u32, BlockchainError> {
let header = self.get_disk_block_header(&block.block_hash())?;
let height = match header {
Expand All @@ -1051,7 +1046,7 @@ impl<PersistedState: ChainStore> UpdatableChainstate for ChainState<PersistedSta
return Ok(height);
}

self.validate_block(block, height, inputs)?;
self.validate_block(block, height, inputs, true, chain_time)?;
let acc = Consensus::update_acc(&self.acc(), block, height, proof, del_hashes)?;

self.update_view(height, &block.header, acc)?;
Expand Down Expand Up @@ -1083,7 +1078,11 @@ impl<PersistedState: ChainStore> UpdatableChainstate for ChainState<PersistedSta
Ok(())
}

fn accept_header(&self, header: BlockHeader) -> Result<(), BlockchainError> {
fn accept_header(
&self,
header: BlockHeader,
node_time: &impl NodeTime,
) -> Result<(), BlockchainError> {
trace!("Accepting header {header:?}");
let disk_header = self.get_disk_block_header(&header.block_hash());

Expand All @@ -1105,7 +1104,7 @@ impl<PersistedState: ChainStore> UpdatableChainstate for ChainState<PersistedSta
let best_block = self.get_best_block()?;

// Do validation in this header
let block_hash = self.validate_header(&header)?;
let block_hash = self.validate_header(&header, node_time)?;

// Update our current tip
if header.prev_blockhash == best_block.1 {
Expand Down Expand Up @@ -1161,7 +1160,7 @@ impl<PersistedState: ChainStore> UpdatableChainstate for ChainState<PersistedSta
},
current_acc: acc,
final_height,
assume_valid: false,
validate_signatures: false,
initial_height,
current_height: initial_height,
};
Expand Down Expand Up @@ -1298,6 +1297,7 @@ mod test {
use super::UpdatableChainstate;
use crate::prelude::HashMap;
use crate::pruned_utreexo::consensus::Consensus;
use crate::pruned_utreexo::nodetime::DisableTime;
use crate::AssumeValidArg;
use crate::KvChainStore;
use crate::Network;
Expand All @@ -1317,7 +1317,7 @@ mod test {
AssumeValidArg::Hardcoded,
);
while let Ok(header) = BlockHeader::consensus_decode(&mut cursor) {
chain.accept_header(header).unwrap();
chain.accept_header(header, &DisableTime).unwrap();
}
}
#[test]
Expand All @@ -1332,7 +1332,7 @@ mod test {
let chain =
ChainState::<KvChainStore>::new(chainstore, Network::Signet, AssumeValidArg::Hardcoded);
while let Ok(header) = BlockHeader::consensus_decode(&mut cursor) {
chain.accept_header(header).unwrap();
chain.accept_header(header, &DisableTime).unwrap();
}
}
#[test]
Expand Down Expand Up @@ -1363,12 +1363,13 @@ mod test {
let blocks = include_str!("./testdata/test_reorg.json");
let blocks: Vec<Vec<&str>> = serde_json::from_str(blocks).unwrap();

let time = crate::pruned_utreexo::nodetime::DisableTime;
for block in blocks[0].iter() {
let block = Vec::from_hex(block).unwrap();
let block: Block = deserialize(&block).unwrap();
chain.accept_header(block.header).unwrap();
chain.accept_header(block.header, &DisableTime).unwrap();
chain
.connect_block(&block, Proof::default(), HashMap::new(), Vec::new())
.connect_block(&block, Proof::default(), HashMap::new(), Vec::new(), &time)
.unwrap();
}
assert_eq!(
Expand All @@ -1385,7 +1386,7 @@ mod test {
for fork in blocks[1].iter() {
let block = Vec::from_hex(fork).unwrap();
let block: Block = deserialize(&block).unwrap();
chain.accept_header(block.header).unwrap();
chain.accept_header(block.header, &DisableTime).unwrap();
}
let best_block = chain.get_best_block().unwrap();
assert_eq!(
Expand Down
24 changes: 23 additions & 1 deletion crates/floresta-chain/src/pruned_utreexo/chainparams.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ pub struct ChainParams {
pub pow_target_timespan: u64,
/// We wait this many blocks before a coinbase output can be spent
pub coinbase_maturity: u32,
/// The height at which bip32 is activated
/// The height at which bip34 is activated
pub bip34_activation_height: u32,
/// The height at which bip65 is activated
pub bip65_activation_height: u32,
Expand All @@ -52,6 +52,12 @@ pub struct ChainParams {
/// A list of exceptions to the rules, where the key is the block hash and the value is the
/// verification flags
pub exceptions: HashMap<BlockHash, c_uint>,
/// The maximum amount of wu's a block can have.
pub max_block_wu: u64,
/// the static weight of a block header in wu.
pub block_header_wu: u64,
/// The value of a single coin in satoshis.
pub coin_value: u64,
}

/// A dns seed is a authoritative DNS server that returns the IP addresses of nodes that are
Expand Down Expand Up @@ -226,6 +232,7 @@ impl From<Network> for ChainParams {
Network::Bitcoin => ChainParams {
genesis,
max_target,

pow_allow_min_diff: false,
pow_allow_no_retarget: false,
pow_target_spacing: 10 * 60, // One block every 600 seconds (10 minutes)
Expand All @@ -237,11 +244,15 @@ impl From<Network> for ChainParams {
bip66_activation_height: 363725,
segwit_activation_height: 481824,
csv_activation_height: 419328,
max_block_wu: 4_000_000,
block_header_wu: 320,
coin_value: 100_000_000,
exceptions,
},
Network::Testnet => ChainParams {
genesis,
max_target,

pow_allow_min_diff: true,
pow_allow_no_retarget: false,
pow_target_spacing: 10 * 60, // One block every 600 seconds (10 minutes)
Expand All @@ -253,11 +264,15 @@ impl From<Network> for ChainParams {
bip66_activation_height: 330_776,
segwit_activation_height: 834_624,
csv_activation_height: 770_112,
max_block_wu: 4_000_000,
block_header_wu: 320,
coin_value: 100_000_000,
exceptions,
},
Network::Signet => ChainParams {
genesis,
max_target,

pow_allow_min_diff: false,
pow_allow_no_retarget: false,
pow_target_spacing: 10 * 60, // One block every 600 seconds (10 minutes)
Expand All @@ -269,11 +284,15 @@ impl From<Network> for ChainParams {
bip65_activation_height: 1,
bip66_activation_height: 1,
segwit_activation_height: 1,
max_block_wu: 4_000_000,
block_header_wu: 320,
coin_value: 100_000_000,
exceptions,
},
Network::Regtest => ChainParams {
genesis,
max_target,

pow_allow_min_diff: false,
pow_allow_no_retarget: true,
pow_target_spacing: 10 * 60, // One block every 600 seconds (10 minutes)
Expand All @@ -285,6 +304,9 @@ impl From<Network> for ChainParams {
bip65_activation_height: 0,
bip66_activation_height: 0,
segwit_activation_height: 0,
max_block_wu: 4_000_000,
block_header_wu: 320,
coin_value: 100_000_000,
exceptions,
},
}
Expand Down
Loading

0 comments on commit ee1c8cf

Please sign in to comment.