Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions dash-spv/src/client/chainlock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use crate::error::{Result, SpvError};
use crate::network::NetworkManager;
use crate::storage::StorageManager;
use crate::types::SpvEvent;
use crate::validation::{InstantLockValidator, Validator};
use key_wallet_manager::wallet_interface::WalletInterface;

use super::DashSpvClient;
Expand Down Expand Up @@ -99,8 +100,8 @@ impl<W: WalletInterface, N: NetworkManager, S: StorageManager> DashSpvClient<W,

// Validate the InstantLock (structure + BLS signature)
// This is REQUIRED for security - never accept InstantLocks without signature verification
let validator = crate::validation::InstantLockValidator::new();
if let Err(e) = validator.validate(&islock, masternode_engine) {
let validator = InstantLockValidator::new(masternode_engine);
if let Err(e) = validator.validate(&islock) {
// Penalize the peer that relayed the invalid InstantLock
let reason = format!("Invalid InstantLock: {}", e);
tracing::warn!("{}", reason);
Expand Down
4 changes: 2 additions & 2 deletions dash-spv/src/sync/headers/manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ use crate::client::ClientConfig;
use crate::error::{SyncError, SyncResult};
use crate::network::NetworkManager;
use crate::storage::StorageManager;
use crate::sync::headers::validate_headers;
use crate::types::{ChainState, HashedBlockHeader};
use crate::validation::{BlockHeaderValidator, Validator};
use crate::ValidationMode;
use std::sync::Arc;
use tokio::sync::RwLock;
Expand Down Expand Up @@ -180,7 +180,7 @@ impl<S: StorageManager, N: NetworkManager> HeaderSyncManager<S, N> {
}

if self.config.validation_mode != ValidationMode::None {
validate_headers(&cached_headers).map_err(|e| {
BlockHeaderValidator::new().validate(&cached_headers).map_err(|e| {
let error = format!("Header validation failed: {}", e);
tracing::error!(error);
SyncError::Validation(error)
Expand Down
2 changes: 0 additions & 2 deletions dash-spv/src/sync/headers/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
//! Header synchronization with fork detection and reorganization handling.

mod manager;
pub mod validation;

pub use manager::{HeaderSyncManager, ReorgConfig};
pub use validation::validate_headers;
Original file line number Diff line number Diff line change
@@ -1,53 +1,59 @@
//! Header validation functionality.

use rayon::prelude::*;
use std::time::Instant;

use crate::error::{ValidationError, ValidationResult};
use crate::types::HashedBlockHeader;
use crate::validation::Validator;

/// Validate a chain of headers.
pub fn validate_headers(hashed_headers: &[HashedBlockHeader]) -> ValidationResult<()> {
let start = Instant::now();

// Check PoW of i and continuity of i-1 to i in parallel
hashed_headers.par_iter().enumerate().try_for_each(|(i, header)| {
// For the first header, skip chain continuity check since we don't have i-1 here
if i > 0 && header.header().prev_blockhash != *hashed_headers[i - 1].hash() {
return Err(ValidationError::InvalidHeaderChain(format!(
"Header {:?} does not connect to {:?}",
hashed_headers[i - 1],
header
)));
}
// Check if PoW target is met
if !header.header().target().is_met_by(*header.hash()) {
return Err(ValidationError::InvalidProofOfWork);
}
Ok(())
})?;
#[derive(Default)]
pub struct BlockHeaderValidator {}

tracing::trace!(
"Header chain validation passed for {} headers, duration: {:?}",
hashed_headers.len(),
start.elapsed(),
);
impl BlockHeaderValidator {
pub fn new() -> Self {
Self {}
}
}

impl Validator<&[HashedBlockHeader]> for BlockHeaderValidator {
fn validate(&self, hashed_headers: &[HashedBlockHeader]) -> ValidationResult<()> {
let start = Instant::now();

// Check PoW of i and continuity of i-1 to i in parallel
hashed_headers.par_iter().enumerate().try_for_each(|(i, header)| {
// For the first header, skip chain continuity check since we don't have i-1 here
if i > 0 && header.header().prev_blockhash != *hashed_headers[i - 1].hash() {
return Err(ValidationError::InvalidHeaderChain(format!(
"Header {:?} does not connect to {:?}",
hashed_headers[i - 1],
header
)));
}
// Check if PoW target is met
if !header.header().target().is_met_by(*header.hash()) {
return Err(ValidationError::InvalidProofOfWork);
}
Ok(())
})?;

tracing::trace!(
"Header chain validation passed for {} headers, duration: {:?}",
hashed_headers.len(),
start.elapsed(),
);

Ok(())
Ok(())
}
}

#[cfg(test)]
mod tests {
use super::validate_headers;
use crate::error::ValidationError;
use crate::types::HashedBlockHeader;
use dashcore::{
block::{Header as BlockHeader, Version},
blockdata::constants::genesis_block,
CompactTarget, Network,
block::Version, constants::genesis_block, CompactTarget, Header as BlockHeader, Network,
};
use dashcore_hashes::Hash;

use super::*;

// Very easy target to pass PoW checks for continuity tests
const MAX_TARGET: u32 = 0x2100ffff;

Expand All @@ -64,17 +70,23 @@ mod tests {

#[test]
fn test_empty_headers() {
assert!(validate_headers(&[]).is_ok());
let validator = BlockHeaderValidator::new();

assert!(validator.validate(&[]).is_ok());
}

#[test]
fn test_single_header() {
let validator = BlockHeaderValidator::new();

let header = create_test_header(dashcore::BlockHash::all_zeros(), 0);
assert!(validate_headers(&[header]).is_ok());
assert!(validator.validate(&[header]).is_ok());
}

#[test]
fn test_valid_chain() {
let validator = BlockHeaderValidator::new();

let mut headers = vec![];
let mut prev_hash = dashcore::BlockHash::all_zeros();

Expand All @@ -84,22 +96,26 @@ mod tests {
headers.push(header);
}

assert!(validate_headers(&headers).is_ok());
assert!(validator.validate(&headers).is_ok());
}

#[test]
fn test_broken_chain() {
let validator = BlockHeaderValidator::new();

let header1 = create_test_header(dashcore::BlockHash::all_zeros(), 0);
let header2 = create_test_header(*header1.hash(), 1);
// header3 doesn't connect to header2
let header3 = create_test_header(dashcore::BlockHash::all_zeros(), 2);

let result = validate_headers(&[header1, header2, header3]);
let result = validator.validate(&[header1, header2, header3]);
assert!(matches!(result, Err(ValidationError::InvalidHeaderChain(_))));
}

#[test]
fn test_invalid_pow() {
let validator = BlockHeaderValidator::new();

let header = HashedBlockHeader::from(BlockHeader {
version: Version::from_consensus(1),
prev_blockhash: dashcore::BlockHash::all_zeros(),
Expand All @@ -109,16 +125,18 @@ mod tests {
nonce: 0,
});

let result = validate_headers(&[header]);
let result = validator.validate(&[header]);
assert!(matches!(result, Err(ValidationError::InvalidProofOfWork)));
}

#[test]
fn test_genesis_blocks() {
let validator = BlockHeaderValidator::new();

for network in [Network::Dash, Network::Testnet, Network::Regtest] {
let genesis = HashedBlockHeader::from(genesis_block(network).header);
assert!(
validate_headers(&[genesis]).is_ok(),
validator.validate(&[genesis]).is_ok(),
"Genesis block for {:?} should validate",
network
);
Expand All @@ -127,6 +145,8 @@ mod tests {

#[test]
fn test_invalid_pow_mid_chain() {
let validator = BlockHeaderValidator::new();

let header1 = create_test_header(dashcore::BlockHash::all_zeros(), 0);
let header2 = create_test_header(*header1.hash(), 1);

Expand All @@ -142,7 +162,7 @@ mod tests {

let header4 = create_test_header(*header3.hash(), 3);

let result = validate_headers(&[header1, header2, header3, header4]);
let result = validator.validate(&[header1, header2, header3, header4]);
assert!(matches!(result, Err(ValidationError::InvalidProofOfWork)));
}
}
Loading
Loading