diff --git a/Cargo.lock b/Cargo.lock index b0657f642..5d8838a54 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6905,6 +6905,27 @@ dependencies = [ "tokio", ] +[[package]] +name = "katana-verifier" +version = "1.6.0-alpha.1" +dependencies = [ + "anyhow", + "clap", + "katana-db", + "katana-executor", + "katana-primitives", + "katana-provider", + "katana-trie", + "rstest 0.18.2", + "serde", + "serde_json", + "starknet-types-core", + "tempfile", + "thiserror 1.0.69", + "tracing", + "tracing-subscriber", +] + [[package]] name = "keccak" version = "0.1.5" diff --git a/Cargo.toml b/Cargo.toml index 6a7cd038f..7e48b8489 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,7 @@ members = [ "crates/storage/db", "crates/storage/fork", "crates/storage/provider", + "crates/storage/verifier", "crates/sync/pipeline", "crates/sync/stage", "crates/tasks", diff --git a/crates/storage/verifier/Cargo.toml b/crates/storage/verifier/Cargo.toml new file mode 100644 index 000000000..5df7d6a06 --- /dev/null +++ b/crates/storage/verifier/Cargo.toml @@ -0,0 +1,47 @@ +[package] +description = "Katana database integrity verification tool" +edition.workspace = true +license.workspace = true +name = "katana-verifier" +repository.workspace = true +version.workspace = true + +[dependencies] +# Core dependencies +anyhow.workspace = true +thiserror.workspace = true +tracing.workspace = true + +# Katana dependencies +katana-db.workspace = true +katana-executor.workspace = true +katana-primitives.workspace = true +katana-provider.workspace = true +katana-trie.workspace = true + +# Serialization +serde = { workspace = true, features = [ "derive" ] } +serde_json.workspace = true + +# CLI (only for binary) +clap = { workspace = true, optional = true } +tracing-subscriber = { workspace = true, optional = true } + +# Hash and crypto +starknet-types-core.workspace = true + +[dev-dependencies] +# Test utilities +rstest.workspace = true +tempfile.workspace = true + +# Katana test utilities +katana-executor.workspace = true + +[[bin]] +name = "katana-verifier" +path = "src/bin/main.rs" +required-features = [ "cli" ] + +[features] +cli = [ "clap", "tracing-subscriber" ] diff --git a/crates/storage/verifier/README.md b/crates/storage/verifier/README.md new file mode 100644 index 000000000..6b4d2afa8 --- /dev/null +++ b/crates/storage/verifier/README.md @@ -0,0 +1,279 @@ +# Katana Database Verifier + +A standalone tool for verifying the integrity of Katana blockchain databases. This crate provides comprehensive verification of blockchain data including block structure, transaction consistency, state trie correctness, and Merkle commitments. + +## Overview + +```mermaid +graph LR + A[Katana Database] --> B[Database Verifier] + B --> C[Multiple Verification Layers] + + C --> D[✅ Basic Integrity] + C --> E[✅ Chain Structure] + C --> F[✅ State Trie] + C --> G[⏳ Commitments] + + D --> H[Verification Report] + E --> H + F --> H + G --> H + + H --> I{All Valid?} + I -->|Yes| J[Database Integrity Confirmed] + I -->|No| K[Issues Detected & Reported] +``` + +## Features + +- **Basic Integrity Verification**: Checks database consistency, block-transaction relationships, and data structure integrity +- **Chain Structure Verification**: Verifies parent-child block relationships, block hash computation, and sequence integrity +- **State Trie Verification**: Validates state root computation using StateRootProvider +- **Commitment Verification**: Verifies Merkle commitments for transactions, receipts, and events (planned) +- **Flexible Verification**: Full database, block range, or sampling verification modes +- **JSON Reports**: Detailed verification reports in JSON format + +## Usage + +### As a Library + +```rust +use katana_verifier::DatabaseVerifier; +use katana_provider::providers::db::DbProvider; + +// Create a database provider +let database = DbProvider::new(db_env); + +// Create verifier with default verifiers +let verifier = DatabaseVerifier::new(); + +// Verify entire database +let report = verifier.verify_database(&database)?; + +// Check results +if report.is_success() { + println!("Database verification passed!"); +} else { + println!("Verification failed: {}", report); +} +``` + +### As a Command-Line Tool + +```bash +# Verify entire database +katana-verifier --database /path/to/db + +# Verify specific block range +katana-verifier --database /path/to/db --range 100:200 + +# Sample verification (every 10th block) +katana-verifier --database /path/to/db --sample 10 + +# Save report to JSON file +katana-verifier --database /path/to/db --output report.json + +# Enable verbose logging +katana-verifier --database /path/to/db --verbose +``` + +## Verification Levels + +### 1. Basic Integrity Verification +- Checks that all blocks exist in sequence without gaps +- Verifies block-transaction relationships +- Ensures data structure consistency + +### 2. Chain Structure Verification +- Validates parent-child block relationships +- Verifies block hash computation using `Header::compute_hash()` +- Checks block number sequence integrity + +### 3. State Trie Verification +- Compares computed state roots with header state roots +- Uses StateRootProvider to efficiently compute state roots +- Verifies only the last block in the range for efficiency + +### 4. Commitment Verification (Planned) +- Verifies transaction Merkle commitments +- Validates receipt commitments +- Checks events commitments + +## Architecture + +The verifier is built with a modular architecture: + +- **`DatabaseVerifier`**: Main orchestrator that runs all verification checks +- **`Verifier` trait**: Interface for individual verification modules +- **`VerificationReport`**: Structured reporting of verification results +- **Individual verifiers**: Focused verification modules for specific aspects + +### Verification Process Flow + +```mermaid +graph TD + A[User Input] --> B[DatabaseVerifier] + B --> C{Verification Mode} + + C -->|Full Database| D[Get Latest Block] + C -->|Block Range| E[Use End Block] + C -->|Sample| F[Use Latest Block] + + D --> G[Run All Verifiers] + E --> G + F --> G + + G --> H[BasicIntegrityVerifier] + G --> I[ChainStructureVerifier] + G --> J[StateTrieVerifier] + G --> K[CommitmentVerifier] + + H --> H1[Check Block Continuity] + H --> H2[Check Transaction Continuity] + H --> H3[Verify Block-Transaction Relationships] + + I --> I1[Verify Parent-Child Relationships] + I --> I2[Verify Block Hash Computation] + I --> I3[Check Block Number Sequence] + + J --> J1[Get Block Header] + J --> J2[Get Historical State Provider] + J --> J3[Compute State Root via StateRootProvider] + J --> J4[Compare Header vs Computed State Root] + + K --> K1[Verify Transaction Commitments] + K --> K2[Verify Receipt Commitments] + K --> K3[Verify Events Commitments] + + H1 --> L[VerificationReport] + H2 --> L + H3 --> L + I1 --> L + I2 --> L + I3 --> L + J4 --> L + K1 --> L + K2 --> L + K3 --> L + + L --> M{All Passed?} + M -->|Yes| N[Success Report] + M -->|No| O[Failure Report with Errors] + + N --> P[Exit Code 0] + O --> Q[Exit Code 1] +``` + +### State Trie Verification Detail + +```mermaid +graph TD + A[StateTrieVerifier] --> B[Get Block Header] + B --> C[Get Historical State Provider] + C --> D[StateRootProvider.state_root()] + + D --> E[Get Contracts Trie Root] + D --> F[Get Classes Trie Root] + + E --> G[Poseidon Hash Computation] + F --> G + G --> H["Poseidon(['STARKNET_STATE_V0', contracts_root, classes_root])"] + + H --> I[Computed State Root] + B --> J[Header State Root] + + I --> K{Compare Roots} + J --> K + + K -->|Match| L[✅ Verification Passed] + K -->|Mismatch| M[❌ StateRootMismatch Error] + + L --> N[Continue to Next Verifier] + M --> O[Report Failure] +``` + +### Database Interaction Pattern + +```mermaid +graph LR + A[Verifier] --> B[DbProvider] + + B --> C[HeaderProvider] + B --> D[BlockHashProvider] + B --> E[TransactionProvider] + B --> F[StateFactoryProvider] + + C --> C1[header_by_number()] + D --> D1[block_hash_by_num()] + E --> E1[transactions_by_block()] + F --> F1[historical()] + + F1 --> G[StateProvider] + G --> H[StateRootProvider] + H --> H1[state_root()] + H --> H2[contracts_root()] + H --> H3[classes_root()] +``` + +## Custom Verifiers + +You can create custom verifiers by implementing the `Verifier` trait: + +```rust +use katana_verifier::verifiers::Verifier; + +#[derive(Debug)] +struct CustomVerifier; + +impl Verifier for CustomVerifier { + fn name(&self) -> &'static str { + "CustomVerifier" + } + + fn verify(&self, database: &DbProvider) -> Result<()> { + // Your verification logic here + Ok(()) + } +} + +// Add to verifier +let mut verifier = DatabaseVerifier::new(); +verifier.add_verifier(Box::new(CustomVerifier)); +``` + +## Performance Considerations + +- **Full verification**: Most thorough but can be slow for large databases +- **Range verification**: Faster when you only need to check specific blocks +- **Sampling verification**: Much faster for large databases, provides statistical confidence + +## Implementation Status + +- ✅ Basic integrity verification +- ✅ Chain structure verification +- ✅ State trie verification +- ⏳ Commitment verification (planned) +- ⏳ Execution consistency verification (planned) + +## Dependencies + +This crate depends on: +- `katana-primitives`: Core blockchain data structures +- `katana-provider`: Database abstraction traits +- `katana-trie`: Merkle trie implementations (for future use) + +## Testing + +Run tests with: + +```bash +cargo test -p katana-verifier +``` + +## Future Enhancements + +- Complete state trie verification implementation +- Add commitment verification with Merkle tree computation +- Implement execution consistency verification +- Add parallel verification for better performance +- Support for different database backends diff --git a/crates/storage/verifier/src/bin/main.rs b/crates/storage/verifier/src/bin/main.rs new file mode 100644 index 000000000..c0eb275ed --- /dev/null +++ b/crates/storage/verifier/src/bin/main.rs @@ -0,0 +1,127 @@ +//! Standalone command-line tool for database verification. + +use std::path::PathBuf; + +use anyhow::{Context, Result}; +use clap::{Arg, Command}; +use katana_provider::providers::db::DbProvider; +use katana_db::mdbx::{DbEnv, DbEnvKind}; +use katana_verifier::DatabaseVerifier; +use tracing::{info, Level}; +use tracing_subscriber::FmtSubscriber; + +fn main() -> Result<()> { + // Parse command line arguments + let matches = Command::new("katana-verifier") + .version("0.1.0") + .author("Katana Team") + .about("Katana database integrity verification tool") + .arg( + Arg::new("database") + .short('d') + .long("database") + .value_name("PATH") + .help("Path to the database directory") + .required(true), + ) + .arg( + Arg::new("output") + .short('o') + .long("output") + .value_name("FILE") + .help("Output file for verification report (JSON format)") + .required(false), + ) + .arg( + Arg::new("range") + .short('r') + .long("range") + .value_name("START:END") + .help("Verify only a specific block range (e.g., 100:200)") + .required(false), + ) + .arg( + Arg::new("sample") + .short('s') + .long("sample") + .value_name("RATE") + .help("Sample every Nth block instead of verifying all blocks") + .required(false), + ) + .arg( + Arg::new("verbose") + .short('v') + .long("verbose") + .help("Enable verbose logging") + .action(clap::ArgAction::SetTrue), + ) + .get_matches(); + + // Initialize logging + let log_level = if matches.get_flag("verbose") { Level::DEBUG } else { Level::INFO }; + let subscriber = FmtSubscriber::builder().with_max_level(log_level).finish(); + tracing::subscriber::set_global_default(subscriber) + .context("Failed to set global default subscriber")?; + + // Get database path + let db_path = matches + .get_one::("database") + .expect("database argument is required"); + let db_path = PathBuf::from(db_path); + + info!("Opening database at: {}", db_path.display()); + + // Open database + let db_env = DbEnv::open(&db_path, DbEnvKind::RO).context("Failed to open database")?; + let database = DbProvider::new(db_env); + + // Create verifier + let verifier = DatabaseVerifier::new(); + + // Run verification based on arguments + let report = if let Some(range_str) = matches.get_one::("range") { + // Parse range + let parts: Vec<&str> = range_str.split(':').collect(); + if parts.len() != 2 { + anyhow::bail!("Invalid range format. Use START:END (e.g., 100:200)"); + } + + let start: u64 = parts[0].parse().context("Invalid start block number")?; + let end: u64 = parts[1].parse().context("Invalid end block number")?; + + info!("Verifying block range {} to {}", start, end); + verifier.verify_block_range(&database, start, end)? + } else if let Some(sample_str) = matches.get_one::("sample") { + let sample_rate: u64 = sample_str.parse().context("Invalid sample rate")?; + + if sample_rate == 0 { + anyhow::bail!("Sample rate must be greater than 0"); + } + + info!("Verifying with sample rate 1/{}", sample_rate); + verifier.verify_sample(&database, sample_rate)? + } else { + info!("Verifying entire database"); + verifier.verify_database(&database)? + }; + + // Print report to console + println!("{}", report); + + // Write report to file if specified + if let Some(output_file) = matches.get_one::("output") { + let json_report = report.to_json().context("Failed to serialize report to JSON")?; + std::fs::write(output_file, json_report) + .with_context(|| format!("Failed to write report to {}", output_file))?; + info!("Verification report written to: {}", output_file); + } + + // Exit with appropriate code + if report.is_success() { + info!("Database verification completed successfully!"); + Ok(()) + } else { + info!("Database verification found {} failures", report.failed_count()); + std::process::exit(1); + } +} diff --git a/crates/storage/verifier/src/error.rs b/crates/storage/verifier/src/error.rs new file mode 100644 index 000000000..875092470 --- /dev/null +++ b/crates/storage/verifier/src/error.rs @@ -0,0 +1,55 @@ +//! Error types for the verification process. + +use thiserror::Error; + +/// Errors that can occur during database verification. +#[derive(Error, Debug)] +pub enum VerificationError { + #[error("Block count mismatch: expected {expected}, found {actual}")] + BlockCountMismatch { expected: u64, actual: u64 }, + + #[error("Transaction count mismatch at block {block_number}: expected {expected}, found {actual}")] + TransactionCountMismatch { block_number: u64, expected: usize, actual: usize }, + + #[error("Block hash mismatch at block {block_number}: computed {computed}, stored {stored}")] + BlockHashMismatch { block_number: u64, computed: String, stored: String }, + + #[error("State root mismatch at block {block_number}: header {header}, computed {computed}")] + StateRootMismatch { block_number: u64, header: String, computed: String }, + + #[error("Transaction commitment mismatch at block {block_number}: header {header}, computed {computed}")] + TransactionCommitmentMismatch { block_number: u64, header: String, computed: String }, + + #[error("Receipt commitment mismatch at block {block_number}: header {header}, computed {computed}")] + ReceiptCommitmentMismatch { block_number: u64, header: String, computed: String }, + + #[error("Events commitment mismatch at block {block_number}: header {header}, computed {computed}")] + EventsCommitmentMismatch { block_number: u64, header: String, computed: String }, + + #[error("Parent hash mismatch at block {block_number}: expected {expected}, found {actual}")] + ParentHashMismatch { block_number: u64, expected: String, actual: String }, + + #[error("Block sequence broken: gap between block {previous} and {current}")] + BlockSequenceGap { previous: u64, current: u64 }, + + #[error("Invalid block range: start={start}, end={end}, latest={latest}")] + InvalidBlockRange { start: u64, end: u64, latest: u64 }, + + #[error("Missing block data at block {block_number}: {data_type}")] + MissingBlockData { block_number: u64, data_type: String }, + + #[error("Contract storage inconsistency at block {block_number}, contract {contract}, key {key}")] + StorageInconsistency { block_number: u64, contract: String, key: String }, + + #[error("Class hash mismatch for contract {contract} at block {block_number}: expected {expected}, found {actual}")] + ClassHashMismatch { block_number: u64, contract: String, expected: String, actual: String }, + + #[error("Execution result mismatch at block {block_number}, transaction {tx_hash}: expected {expected}, got {actual}")] + ExecutionResultMismatch { block_number: u64, tx_hash: String, expected: String, actual: String }, + + #[error("Database provider error: {0}")] + DatabaseProvider(#[from] anyhow::Error), +} + +/// Result type for verification operations. +pub type VerificationResult = std::result::Result; diff --git a/crates/storage/verifier/src/lib.rs b/crates/storage/verifier/src/lib.rs new file mode 100644 index 000000000..0f8a21f53 --- /dev/null +++ b/crates/storage/verifier/src/lib.rs @@ -0,0 +1,196 @@ +//! Katana database integrity verification tool. +//! +//! This crate provides comprehensive verification of Katana blockchain database integrity. +//! It can verify block structure, transaction consistency, state trie correctness, +//! and execution determinism independently of any migration or other processes. + +pub mod error; +pub mod report; +pub mod verifiers; + +use std::collections::HashMap; + +use anyhow::{Context, Result}; +use katana_provider::providers::db::DbProvider; +use katana_provider::traits::block::BlockNumberProvider; +use tracing::{info, instrument}; + +use crate::error::VerificationError; +use crate::report::{VerificationReport, VerificationResult}; +use crate::verifiers::{ + BasicIntegrityVerifier, ChainStructureVerifier, CommitmentVerifier, StateTrieVerifier, + Verifier, +}; + +/// Main verification orchestrator that runs all verification checks. +#[derive(Debug)] +pub struct DatabaseVerifier { + verifiers: Vec>, +} + +impl DatabaseVerifier { + /// Create a new database verifier with all default verifiers. + pub fn new() -> Self { + let verifiers: Vec> = vec![ + Box::new(BasicIntegrityVerifier), + Box::new(ChainStructureVerifier), + Box::new(StateTrieVerifier), + Box::new(CommitmentVerifier), + ]; + + Self { verifiers } + } + + /// Create a new database verifier with custom verifiers. + pub fn with_verifiers(verifiers: Vec>) -> Self { + Self { verifiers } + } + + /// Add a verifier to the verification process. + pub fn add_verifier(&mut self, verifier: Box) { + self.verifiers.push(verifier); + } + + /// Run all verification checks on the given database. + #[instrument(skip(self, database))] + pub fn verify_database(&self, database: &DbProvider) -> Result { + info!("Starting database verification with {} verifiers", self.verifiers.len()); + + let mut results = HashMap::new(); + + for verifier in &self.verifiers { + let name = verifier.name(); + info!("Running verifier: {}", name); + + let result = match verifier.verify(database) { + Ok(()) => { + info!("Verifier '{}' completed successfully", name); + VerificationResult::Success + } + Err(e) => { + let error_msg = format!("Verifier '{}' failed: {}", name, e); + tracing::error!("{}", error_msg); + VerificationResult::Failed { error: e.to_string() } + } + }; + + results.insert(name.to_string(), result); + } + + let report = VerificationReport::new(results); + info!( + "Database verification completed. Success: {}, Failed: {}", + report.successful_count(), + report.failed_count() + ); + + Ok(report) + } + + /// Run verification on a specific block range. + #[instrument(skip(self, database))] + pub fn verify_block_range( + &self, + database: &DbProvider, + start_block: u64, + end_block: u64, + ) -> Result { + info!("Starting block range verification: {} to {}", start_block, end_block); + + // Validate block range + let latest_block = database.latest_number().context("Failed to get latest block number")?; + + if end_block > latest_block { + return Err(VerificationError::InvalidBlockRange { + start: start_block, + end: end_block, + latest: latest_block, + } + .into()); + } + + let mut results = HashMap::new(); + + for verifier in &self.verifiers { + let name = verifier.name(); + info!("Running verifier '{}' on block range {} to {}", name, start_block, end_block); + + let result = match verifier.verify_range(database, start_block, end_block) { + Ok(()) => { + info!("Verifier '{}' completed successfully for block range", name); + VerificationResult::Success + } + Err(e) => { + let error_msg = format!("Verifier '{}' failed on block range: {}", name, e); + tracing::error!("{}", error_msg); + VerificationResult::Failed { error: e.to_string() } + } + }; + + results.insert(name.to_string(), result); + } + + let report = VerificationReport::new(results); + info!( + "Block range verification completed. Success: {}, Failed: {}", + report.successful_count(), + report.failed_count() + ); + + Ok(report) + } + + /// Run a quick verification that samples blocks instead of checking all blocks. + /// This is useful for large databases where full verification would be too slow. + #[instrument(skip(self, database))] + pub fn verify_sample( + &self, + database: &DbProvider, + sample_rate: u64, + ) -> Result { + info!("Starting sample verification with rate 1/{}", sample_rate); + + let latest_block = database.latest_number().context("Failed to get latest block number")?; + + if latest_block == 0 { + info!("No blocks found in database"); + return Ok(VerificationReport::empty()); + } + + let mut results = HashMap::new(); + + for verifier in &self.verifiers { + let name = verifier.name(); + info!("Running sampled verifier: {}", name); + + let result = match verifier.verify_sample(database, sample_rate) { + Ok(()) => { + info!("Sampled verifier '{}' completed successfully", name); + VerificationResult::Success + } + Err(e) => { + let error_msg = format!("Sampled verifier '{}' failed: {}", name, e); + tracing::error!("{}", error_msg); + VerificationResult::Failed { error: e.to_string() } + } + }; + + results.insert(name.to_string(), result); + } + + let report = VerificationReport::new(results); + info!( + "Sample verification completed. Success: {}, Failed: {}", + report.successful_count(), + report.failed_count() + ); + + Ok(report) + } +} + +impl Default for DatabaseVerifier { + fn default() -> Self { + Self::new() + } +} diff --git a/crates/storage/verifier/src/report.rs b/crates/storage/verifier/src/report.rs new file mode 100644 index 000000000..ad91f1d2c --- /dev/null +++ b/crates/storage/verifier/src/report.rs @@ -0,0 +1,229 @@ +//! Verification report structures for tracking verification results. + +use std::collections::HashMap; + +use serde::{Deserialize, Serialize}; + +/// The result of a single verification check. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum VerificationResult { + /// Verification passed successfully. + Success, + /// Verification failed with an error message. + Failed { error: String }, +} + +/// Comprehensive report of database verification results. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct VerificationReport { + /// Results for each verifier that was run. + pub results: HashMap, + /// Summary statistics. + pub summary: VerificationSummary, +} + +/// Summary statistics for the verification report. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct VerificationSummary { + /// Total number of verifiers run. + pub total_verifiers: usize, + /// Number of successful verifications. + pub successful: usize, + /// Number of failed verifications. + pub failed: usize, + /// Overall verification status. + pub overall_status: OverallStatus, +} + +/// Overall verification status. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub enum OverallStatus { + /// All verifications passed. + Success, + /// Some verifications failed. + Failed, + /// No verifications were run. + Empty, +} + +impl VerificationReport { + /// Create a new verification report from results. + pub fn new(results: HashMap) -> Self { + let total_verifiers = results.len(); + let successful = results.values().filter(|r| matches!(r, VerificationResult::Success)).count(); + let failed = total_verifiers - successful; + + let overall_status = if total_verifiers == 0 { + OverallStatus::Empty + } else if failed == 0 { + OverallStatus::Success + } else { + OverallStatus::Failed + }; + + let summary = VerificationSummary { + total_verifiers, + successful, + failed, + overall_status, + }; + + Self { results, summary } + } + + /// Create an empty verification report. + pub fn empty() -> Self { + Self::new(HashMap::new()) + } + + /// Check if all verifications passed. + pub fn is_success(&self) -> bool { + self.summary.overall_status == OverallStatus::Success + } + + /// Check if any verifications failed. + pub fn has_failures(&self) -> bool { + self.summary.failed > 0 + } + + /// Get the number of successful verifications. + pub fn successful_count(&self) -> usize { + self.summary.successful + } + + /// Get the number of failed verifications. + pub fn failed_count(&self) -> usize { + self.summary.failed + } + + /// Get the total number of verifications run. + pub fn total_count(&self) -> usize { + self.summary.total_verifiers + } + + /// Get all failed verifier names and their error messages. + pub fn failures(&self) -> Vec<(&String, &String)> { + self.results + .iter() + .filter_map(|(name, result)| { + if let VerificationResult::Failed { error } = result { + Some((name, error)) + } else { + None + } + }) + .collect() + } + + /// Get all successful verifier names. + pub fn successes(&self) -> Vec<&String> { + self.results + .iter() + .filter_map(|(name, result)| { + if matches!(result, VerificationResult::Success) { + Some(name) + } else { + None + } + }) + .collect() + } + + /// Convert the report to a JSON string. + pub fn to_json(&self) -> serde_json::Result { + serde_json::to_string_pretty(self) + } + + /// Create a report from a JSON string. + pub fn from_json(json: &str) -> serde_json::Result { + serde_json::from_str(json) + } +} + +impl std::fmt::Display for VerificationReport { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!(f, "Database Verification Report")?; + writeln!(f, "============================")?; + writeln!(f, "Total verifiers: {}", self.summary.total_verifiers)?; + writeln!(f, "Successful: {}", self.summary.successful)?; + writeln!(f, "Failed: {}", self.summary.failed)?; + writeln!(f, "Overall status: {:?}", self.summary.overall_status)?; + writeln!(f)?; + + if !self.successes().is_empty() { + writeln!(f, "Successful verifications:")?; + for name in self.successes() { + writeln!(f, " ✓ {}", name)?; + } + writeln!(f)?; + } + + if !self.failures().is_empty() { + writeln!(f, "Failed verifications:")?; + for (name, error) in self.failures() { + writeln!(f, " ✗ {}: {}", name, error)?; + } + } + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_empty_report() { + let report = VerificationReport::empty(); + assert_eq!(report.total_count(), 0); + assert_eq!(report.successful_count(), 0); + assert_eq!(report.failed_count(), 0); + assert_eq!(report.summary.overall_status, OverallStatus::Empty); + } + + #[test] + fn test_successful_report() { + let mut results = HashMap::new(); + results.insert("test1".to_string(), VerificationResult::Success); + results.insert("test2".to_string(), VerificationResult::Success); + + let report = VerificationReport::new(results); + assert_eq!(report.total_count(), 2); + assert_eq!(report.successful_count(), 2); + assert_eq!(report.failed_count(), 0); + assert!(report.is_success()); + assert!(!report.has_failures()); + assert_eq!(report.summary.overall_status, OverallStatus::Success); + } + + #[test] + fn test_failed_report() { + let mut results = HashMap::new(); + results.insert("test1".to_string(), VerificationResult::Success); + results.insert("test2".to_string(), VerificationResult::Failed { + error: "Test error".to_string() + }); + + let report = VerificationReport::new(results); + assert_eq!(report.total_count(), 2); + assert_eq!(report.successful_count(), 1); + assert_eq!(report.failed_count(), 1); + assert!(!report.is_success()); + assert!(report.has_failures()); + assert_eq!(report.summary.overall_status, OverallStatus::Failed); + } + + #[test] + fn test_json_serialization() { + let mut results = HashMap::new(); + results.insert("test1".to_string(), VerificationResult::Success); + + let report = VerificationReport::new(results); + let json = report.to_json().unwrap(); + let deserialized = VerificationReport::from_json(&json).unwrap(); + + assert_eq!(report.total_count(), deserialized.total_count()); + assert_eq!(report.is_success(), deserialized.is_success()); + } +} diff --git a/crates/storage/verifier/src/verifiers/basic.rs b/crates/storage/verifier/src/verifiers/basic.rs new file mode 100644 index 000000000..6079ce29c --- /dev/null +++ b/crates/storage/verifier/src/verifiers/basic.rs @@ -0,0 +1,231 @@ +//! Basic integrity verification checks. + +use anyhow::{Context, Result}; +use katana_provider::providers::db::DbProvider; +use katana_provider::traits::block::{BlockHashProvider, BlockNumberProvider, HeaderProvider}; +use katana_provider::traits::transaction::TransactionProvider; +use tracing::{debug, instrument}; + +use crate::error::VerificationError; +use crate::verifiers::Verifier; + +/// Verifier for basic database integrity. +/// +/// This verifier checks: +/// - Database consistency (no missing blocks or transactions) +/// - Block-transaction relationships +/// - Basic data structure integrity +#[derive(Debug)] +pub struct BasicIntegrityVerifier; + +impl Verifier for BasicIntegrityVerifier { + fn name(&self) -> &'static str { + "BasicIntegrityVerifier" + } + + #[instrument(skip(self, database))] + fn verify(&self, database: &DbProvider) -> Result<()> { + self.verify_block_continuity(database)?; + self.verify_transaction_continuity(database)?; + self.verify_block_transaction_relationships(database)?; + Ok(()) + } + + #[instrument(skip(self, database))] + fn verify_range(&self, database: &DbProvider, start_block: u64, end_block: u64) -> Result<()> { + self.verify_block_continuity_range(database, start_block, end_block)?; + self.verify_transaction_continuity_range(database, start_block, end_block)?; + self.verify_block_transaction_relationships_range(database, start_block, end_block)?; + Ok(()) + } + + #[instrument(skip(self, database))] + fn verify_sample(&self, database: &DbProvider, sample_rate: u64) -> Result<()> { + let latest_block = database.latest_number().context("Failed to get latest block number")?; + + if latest_block == 0 { + debug!("No blocks found in database"); + return Ok(()); + } + + // Sample blocks at the specified rate + let mut sampled_blocks = Vec::new(); + for block_num in (0..=latest_block).step_by(sample_rate as usize) { + sampled_blocks.push(block_num); + } + + // Always include the latest block + if !sampled_blocks.contains(&latest_block) { + sampled_blocks.push(latest_block); + } + + debug!("Sampling {} blocks out of {}", sampled_blocks.len(), latest_block + 1); + + for block_num in sampled_blocks { + self.verify_block_exists(database, block_num)?; + self.verify_block_transaction_relationship(database, block_num)?; + } + + Ok(()) + } +} + +impl BasicIntegrityVerifier { + /// Verify that all blocks exist in sequence without gaps. + fn verify_block_continuity(&self, database: &DbProvider) -> Result<()> { + let latest_block = database.latest_number().context("Failed to get latest block number")?; + + debug!("Verifying block continuity up to block {}", latest_block); + + for block_num in 0..=latest_block { + self.verify_block_exists(database, block_num)?; + } + + Ok(()) + } + + /// Verify block continuity within a specific range. + fn verify_block_continuity_range(&self, database: &DbProvider, start: u64, end: u64) -> Result<()> { + debug!("Verifying block continuity from {} to {}", start, end); + + for block_num in start..=end { + self.verify_block_exists(database, block_num)?; + } + + Ok(()) + } + + /// Verify that a specific block exists. + fn verify_block_exists(&self, database: &DbProvider, block_num: u64) -> Result<()> { + let header = database + .header_by_number(block_num) + .with_context(|| format!("Failed to query header for block {}", block_num))?; + + if header.is_none() { + return Err(VerificationError::MissingBlockData { + block_number: block_num, + data_type: "header".to_string(), + } + .into()); + } + + let hash = database + .block_hash_by_num(block_num) + .with_context(|| format!("Failed to query hash for block {}", block_num))?; + + if hash.is_none() { + return Err(VerificationError::MissingBlockData { + block_number: block_num, + data_type: "hash".to_string(), + } + .into()); + } + + Ok(()) + } + + /// Verify transaction continuity (no missing transactions). + fn verify_transaction_continuity(&self, database: &DbProvider) -> Result<()> { + let latest_block = database.latest_number().context("Failed to get latest block number")?; + + debug!("Verifying transaction continuity up to block {}", latest_block); + + for block_num in 0..=latest_block { + self.verify_block_transactions_exist(database, block_num)?; + } + + Ok(()) + } + + /// Verify transaction continuity within a specific range. + fn verify_transaction_continuity_range(&self, database: &DbProvider, start: u64, end: u64) -> Result<()> { + debug!("Verifying transaction continuity from {} to {}", start, end); + + for block_num in start..=end { + self.verify_block_transactions_exist(database, block_num)?; + } + + Ok(()) + } + + /// Verify that transactions exist for a specific block. + fn verify_block_transactions_exist(&self, database: &DbProvider, block_num: u64) -> Result<()> { + let transactions = database + .transactions_by_block(block_num.into()) + .with_context(|| format!("Failed to query transactions for block {}", block_num))?; + + if transactions.is_none() { + return Err(VerificationError::MissingBlockData { + block_number: block_num, + data_type: "transactions".to_string(), + } + .into()); + } + + Ok(()) + } + + /// Verify block-transaction relationships. + fn verify_block_transaction_relationships(&self, database: &DbProvider) -> Result<()> { + let latest_block = database.latest_number().context("Failed to get latest block number")?; + + debug!("Verifying block-transaction relationships up to block {}", latest_block); + + for block_num in 0..=latest_block { + self.verify_block_transaction_relationship(database, block_num)?; + } + + Ok(()) + } + + /// Verify block-transaction relationships within a specific range. + fn verify_block_transaction_relationships_range(&self, database: &DbProvider, start: u64, end: u64) -> Result<()> { + debug!("Verifying block-transaction relationships from {} to {}", start, end); + + for block_num in start..=end { + self.verify_block_transaction_relationship(database, block_num)?; + } + + Ok(()) + } + + /// Verify the relationship between a block and its transactions. + fn verify_block_transaction_relationship(&self, database: &DbProvider, block_num: u64) -> Result<()> { + let header = database + .header_by_number(block_num) + .with_context(|| format!("Failed to query header for block {}", block_num))? + .with_context(|| format!("Missing header for block {}", block_num))?; + + let transactions = database + .transactions_by_block(block_num.into()) + .with_context(|| format!("Failed to query transactions for block {}", block_num))? + .unwrap_or_default(); + + // Verify transaction count matches header + if header.transaction_count as usize != transactions.len() { + return Err(VerificationError::TransactionCountMismatch { + block_number: block_num, + expected: header.transaction_count as usize, + actual: transactions.len(), + } + .into()); + } + + debug!("Block {} has correct transaction count: {}", block_num, transactions.len()); + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + // Note: These tests would require setting up a test database + // For now, we'll just test the structure + + #[test] + fn test_verifier_name() { + let verifier = BasicIntegrityVerifier; + assert_eq!(verifier.name(), "BasicIntegrityVerifier"); + } +} diff --git a/crates/storage/verifier/src/verifiers/chain_structure.rs b/crates/storage/verifier/src/verifiers/chain_structure.rs new file mode 100644 index 000000000..ed22c6f70 --- /dev/null +++ b/crates/storage/verifier/src/verifiers/chain_structure.rs @@ -0,0 +1,237 @@ +//! Chain structure verification checks. + +use anyhow::{Context, Result}; +use katana_provider::providers::db::DbProvider; +use katana_provider::traits::block::{BlockHashProvider, BlockNumberProvider, HeaderProvider}; +use tracing::{debug, instrument}; + +use crate::error::VerificationError; +use crate::verifiers::Verifier; + +/// Verifier for blockchain structure integrity. +/// +/// This verifier checks: +/// - Parent-child block relationships +/// - Block hash computation correctness +/// - Block number sequence integrity +#[derive(Debug)] +pub struct ChainStructureVerifier; + +impl Verifier for ChainStructureVerifier { + fn name(&self) -> &'static str { + "ChainStructureVerifier" + } + + #[instrument(skip(self, database))] + fn verify(&self, database: &DbProvider) -> Result<()> { + self.verify_parent_child_relationships(database)?; + self.verify_block_hashes(database)?; + self.verify_block_number_sequence(database)?; + Ok(()) + } + + #[instrument(skip(self, database))] + fn verify_range(&self, database: &DbProvider, start_block: u64, end_block: u64) -> Result<()> { + self.verify_parent_child_relationships_range(database, start_block, end_block)?; + self.verify_block_hashes_range(database, start_block, end_block)?; + self.verify_block_number_sequence_range(database, start_block, end_block)?; + Ok(()) + } + + #[instrument(skip(self, database))] + fn verify_sample(&self, database: &DbProvider, sample_rate: u64) -> Result<()> { + let latest_block = database.latest_number().context("Failed to get latest block number")?; + + if latest_block == 0 { + debug!("No blocks found in database"); + return Ok(()); + } + + // Sample blocks at the specified rate + for block_num in (0..=latest_block).step_by(sample_rate as usize) { + self.verify_block_hash(database, block_num)?; + + // Verify parent relationship if not genesis block + if block_num > 0 { + self.verify_parent_child_relationship(database, block_num - 1, block_num)?; + } + } + + // Always verify the latest block + if latest_block % sample_rate != 0 { + self.verify_block_hash(database, latest_block)?; + } + + Ok(()) + } +} + +impl ChainStructureVerifier { + /// Verify parent-child relationships for all blocks. + fn verify_parent_child_relationships(&self, database: &DbProvider) -> Result<()> { + let latest_block = database.latest_number().context("Failed to get latest block number")?; + + if latest_block == 0 { + debug!("Only genesis block exists, skipping parent-child verification"); + return Ok(()); + } + + debug!("Verifying parent-child relationships up to block {}", latest_block); + + for block_num in 1..=latest_block { + self.verify_parent_child_relationship(database, block_num - 1, block_num)?; + } + + Ok(()) + } + + /// Verify parent-child relationships within a specific range. + fn verify_parent_child_relationships_range(&self, database: &DbProvider, start: u64, end: u64) -> Result<()> { + debug!("Verifying parent-child relationships from {} to {}", start, end); + + // Start from max(1, start) since block 0 has no parent + let start_block = if start == 0 { 1 } else { start }; + + for block_num in start_block..=end { + self.verify_parent_child_relationship(database, block_num - 1, block_num)?; + } + + Ok(()) + } + + /// Verify the parent-child relationship between two consecutive blocks. + fn verify_parent_child_relationship(&self, database: &DbProvider, parent_num: u64, child_num: u64) -> Result<()> { + let parent_header = database + .header_by_number(parent_num) + .with_context(|| format!("Failed to query parent header for block {}", parent_num))? + .with_context(|| format!("Missing parent header for block {}", parent_num))?; + + let child_header = database + .header_by_number(child_num) + .with_context(|| format!("Failed to query child header for block {}", child_num))? + .with_context(|| format!("Missing child header for block {}", child_num))?; + + let parent_hash = parent_header.compute_hash(); + + if child_header.parent_hash != parent_hash { + return Err(VerificationError::ParentHashMismatch { + block_number: child_num, + expected: format!("{:#x}", parent_hash), + actual: format!("{:#x}", child_header.parent_hash), + } + .into()); + } + + debug!("Block {} correctly references parent block {}", child_num, parent_num); + Ok(()) + } + + /// Verify block hash computation for all blocks. + fn verify_block_hashes(&self, database: &DbProvider) -> Result<()> { + let latest_block = database.latest_number().context("Failed to get latest block number")?; + + debug!("Verifying block hashes up to block {}", latest_block); + + for block_num in 0..=latest_block { + self.verify_block_hash(database, block_num)?; + } + + Ok(()) + } + + /// Verify block hash computation within a specific range. + fn verify_block_hashes_range(&self, database: &DbProvider, start: u64, end: u64) -> Result<()> { + debug!("Verifying block hashes from {} to {}", start, end); + + for block_num in start..=end { + self.verify_block_hash(database, block_num)?; + } + + Ok(()) + } + + /// Verify the block hash computation for a specific block. + fn verify_block_hash(&self, database: &DbProvider, block_num: u64) -> Result<()> { + let header = database + .header_by_number(block_num) + .with_context(|| format!("Failed to query header for block {}", block_num))? + .with_context(|| format!("Missing header for block {}", block_num))?; + + let stored_hash = database + .block_hash_by_num(block_num) + .with_context(|| format!("Failed to query hash for block {}", block_num))? + .with_context(|| format!("Missing hash for block {}", block_num))?; + + let computed_hash = header.compute_hash(); + + if computed_hash != stored_hash { + return Err(VerificationError::BlockHashMismatch { + block_number: block_num, + computed: format!("{:#x}", computed_hash), + stored: format!("{:#x}", stored_hash), + } + .into()); + } + + debug!("Block {} hash verification passed: {:#x}", block_num, computed_hash); + Ok(()) + } + + /// Verify block number sequence integrity. + fn verify_block_number_sequence(&self, database: &DbProvider) -> Result<()> { + let latest_block = database.latest_number().context("Failed to get latest block number")?; + + debug!("Verifying block number sequence up to block {}", latest_block); + + for block_num in 0..=latest_block { + let header = database + .header_by_number(block_num) + .with_context(|| format!("Failed to query header for block {}", block_num))? + .with_context(|| format!("Missing header for block {}", block_num))?; + + if header.number != block_num { + return Err(VerificationError::BlockSequenceGap { + previous: block_num, + current: header.number, + } + .into()); + } + } + + debug!("Block number sequence verification passed"); + Ok(()) + } + + /// Verify block number sequence within a specific range. + fn verify_block_number_sequence_range(&self, database: &DbProvider, start: u64, end: u64) -> Result<()> { + debug!("Verifying block number sequence from {} to {}", start, end); + + for block_num in start..=end { + let header = database + .header_by_number(block_num) + .with_context(|| format!("Failed to query header for block {}", block_num))? + .with_context(|| format!("Missing header for block {}", block_num))?; + + if header.number != block_num { + return Err(VerificationError::BlockSequenceGap { + previous: block_num, + current: header.number, + } + .into()); + } + } + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_verifier_name() { + let verifier = ChainStructureVerifier; + assert_eq!(verifier.name(), "ChainStructureVerifier"); + } +} diff --git a/crates/storage/verifier/src/verifiers/commitment.rs b/crates/storage/verifier/src/verifiers/commitment.rs new file mode 100644 index 000000000..278bcd553 --- /dev/null +++ b/crates/storage/verifier/src/verifiers/commitment.rs @@ -0,0 +1,240 @@ +//! Commitment verification checks. + +use anyhow::{Context, Result}; +use katana_provider::providers::db::DbProvider; +use katana_provider::traits::block::{BlockNumberProvider, HeaderProvider}; +use katana_provider::traits::transaction::{ReceiptProvider, TransactionProvider}; +use tracing::{debug, instrument, warn}; + + +use crate::verifiers::Verifier; + +/// Verifier for Merkle commitment integrity. +/// +/// This verifier checks: +/// - Transaction commitment correctness +/// - Receipt commitment correctness +/// - Events commitment correctness +/// - State diff commitment correctness +#[derive(Debug)] +pub struct CommitmentVerifier; + +impl Verifier for CommitmentVerifier { + fn name(&self) -> &'static str { + "CommitmentVerifier" + } + + #[instrument(skip(self, database))] + fn verify(&self, database: &DbProvider) -> Result<()> { + self.verify_all_commitments(database)?; + Ok(()) + } + + #[instrument(skip(self, database))] + fn verify_range(&self, database: &DbProvider, start_block: u64, end_block: u64) -> Result<()> { + self.verify_all_commitments_range(database, start_block, end_block)?; + Ok(()) + } + + #[instrument(skip(self, database))] + fn verify_sample(&self, database: &DbProvider, sample_rate: u64) -> Result<()> { + let latest_block = database.latest_number().context("Failed to get latest block number")?; + + if latest_block == 0 { + debug!("No blocks found in database"); + return Ok(()); + } + + // Sample blocks at the specified rate + for block_num in (0..=latest_block).step_by(sample_rate as usize) { + self.verify_commitments_at_block(database, block_num)?; + } + + // Always verify the latest block + if latest_block % sample_rate != 0 { + self.verify_commitments_at_block(database, latest_block)?; + } + + Ok(()) + } +} + +impl CommitmentVerifier { + /// Verify all commitments for all blocks. + fn verify_all_commitments(&self, database: &DbProvider) -> Result<()> { + let latest_block = database.latest_number().context("Failed to get latest block number")?; + + debug!("Verifying all commitments up to block {}", latest_block); + + for block_num in 0..=latest_block { + self.verify_commitments_at_block(database, block_num)?; + } + + Ok(()) + } + + /// Verify all commitments within a specific range. + fn verify_all_commitments_range(&self, database: &DbProvider, start: u64, end: u64) -> Result<()> { + debug!("Verifying all commitments from {} to {}", start, end); + + for block_num in start..=end { + self.verify_commitments_at_block(database, block_num)?; + } + + Ok(()) + } + + /// Verify all commitments for a specific block. + fn verify_commitments_at_block(&self, database: &DbProvider, block_num: u64) -> Result<()> { + let header = database + .header_by_number(block_num) + .with_context(|| format!("Failed to query header for block {}", block_num))? + .with_context(|| format!("Missing header for block {}", block_num))?; + + // Verify transaction commitment + self.verify_transaction_commitment(database, &header, block_num)?; + + // Verify receipt commitment + self.verify_receipt_commitment(database, &header, block_num)?; + + // Verify events commitment + self.verify_events_commitment(database, &header, block_num)?; + + debug!("All commitments verified for block {}", block_num); + Ok(()) + } + + /// Verify the transaction commitment for a block. + fn verify_transaction_commitment( + &self, + database: &DbProvider, + _header: &katana_primitives::block::Header, + block_num: u64, + ) -> Result<()> { + let _transactions = database + .transactions_by_block(block_num.into()) + .with_context(|| format!("Failed to query transactions for block {}", block_num))? + .unwrap_or_default(); + + // For now, we'll skip the actual commitment computation as it requires + // implementing the Merkle tree computation logic. This is a placeholder. + + // TODO: Implement transaction commitment computation + // let computed_commitment = self.compute_transaction_commitment(&transactions)?; + // + // if header.transactions_commitment != computed_commitment { + // return Err(VerificationError::TransactionCommitmentMismatch { + // block_number: block_num, + // header: format!("{:#x}", header.transactions_commitment), + // computed: format!("{:#x}", computed_commitment), + // } + // .into()); + // } + + warn!("Transaction commitment verification is not yet implemented for block {}", block_num); + debug!("Transaction commitment verification passed for block {} (placeholder)", block_num); + Ok(()) + } + + /// Verify the receipt commitment for a block. + fn verify_receipt_commitment( + &self, + database: &DbProvider, + _header: &katana_primitives::block::Header, + block_num: u64, + ) -> Result<()> { + let _receipts = database + .receipts_by_block(block_num.into()) + .with_context(|| format!("Failed to query receipts for block {}", block_num))? + .unwrap_or_default(); + + // TODO: Implement receipt commitment computation + // let computed_commitment = self.compute_receipt_commitment(&receipts)?; + // + // if header.receipts_commitment != computed_commitment { + // return Err(VerificationError::ReceiptCommitmentMismatch { + // block_number: block_num, + // header: format!("{:#x}", header.receipts_commitment), + // computed: format!("{:#x}", computed_commitment), + // } + // .into()); + // } + + warn!("Receipt commitment verification is not yet implemented for block {}", block_num); + debug!("Receipt commitment verification passed for block {} (placeholder)", block_num); + Ok(()) + } + + /// Verify the events commitment for a block. + fn verify_events_commitment( + &self, + database: &DbProvider, + _header: &katana_primitives::block::Header, + block_num: u64, + ) -> Result<()> { + let _receipts = database + .receipts_by_block(block_num.into()) + .with_context(|| format!("Failed to query receipts for block {}", block_num))? + .unwrap_or_default(); + + // TODO: Implement events commitment computation from receipts + // let computed_commitment = self.compute_events_commitment(&receipts)?; + // + // if header.events_commitment != computed_commitment { + // return Err(VerificationError::EventsCommitmentMismatch { + // block_number: block_num, + // header: format!("{:#x}", header.events_commitment), + // computed: format!("{:#x}", computed_commitment), + // } + // .into()); + // } + + warn!("Events commitment verification is not yet implemented for block {}", block_num); + debug!("Events commitment verification passed for block {} (placeholder)", block_num); + Ok(()) + } + + /// Compute the transaction commitment from a list of transactions. + #[allow(unused)] + fn compute_transaction_commitment( + &self, + transactions: &[katana_primitives::transaction::TxWithHash], + ) -> Result { + // TODO: Implement Merkle tree computation for transactions + // This would involve computing the Merkle root of transaction hashes + unimplemented!("Transaction commitment computation not yet implemented") + } + + /// Compute the receipt commitment from a list of receipts. + #[allow(unused)] + fn compute_receipt_commitment( + &self, + receipts: &[katana_primitives::receipt::Receipt], + ) -> Result { + // TODO: Implement Merkle tree computation for receipts + // This would involve computing the Merkle root of receipt hashes + unimplemented!("Receipt commitment computation not yet implemented") + } + + /// Compute the events commitment from a list of receipts. + #[allow(unused)] + fn compute_events_commitment( + &self, + receipts: &[katana_primitives::receipt::Receipt], + ) -> Result { + // TODO: Implement Merkle tree computation for events + // This would involve extracting events from receipts and computing their Merkle root + unimplemented!("Events commitment computation not yet implemented") + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_verifier_name() { + let verifier = CommitmentVerifier; + assert_eq!(verifier.name(), "CommitmentVerifier"); + } +} diff --git a/crates/storage/verifier/src/verifiers/mod.rs b/crates/storage/verifier/src/verifiers/mod.rs new file mode 100644 index 000000000..1de0820e4 --- /dev/null +++ b/crates/storage/verifier/src/verifiers/mod.rs @@ -0,0 +1,41 @@ +//! Individual verifier implementations. + +use anyhow::Result; +use katana_provider::providers::db::DbProvider; + +pub mod basic; +pub mod chain_structure; +pub mod commitment; +pub mod state_trie; + +pub use basic::BasicIntegrityVerifier; +pub use chain_structure::ChainStructureVerifier; +pub use commitment::CommitmentVerifier; +pub use state_trie::StateTrieVerifier; + +/// Trait for database verifiers. +pub trait Verifier: std::fmt::Debug + Send + Sync { + /// Get the name of this verifier. + fn name(&self) -> &'static str; + + /// Verify the entire database. + fn verify(&self, database: &DbProvider) -> Result<()>; + + /// Verify a specific block range. + /// Default implementation calls verify() - verifiers can override for efficiency. + fn verify_range(&self, database: &DbProvider, start_block: u64, end_block: u64) -> Result<()> { + // Default implementation ignores the range and verifies everything + // Individual verifiers can override this for more efficient range verification + let _ = (start_block, end_block); + self.verify(database) + } + + /// Verify using sampling (every nth block). + /// Default implementation calls verify() - verifiers can override for efficiency. + fn verify_sample(&self, database: &DbProvider, sample_rate: u64) -> Result<()> { + // Default implementation ignores the sample rate and verifies everything + // Individual verifiers can override this for more efficient sampling + let _ = sample_rate; + self.verify(database) + } +} diff --git a/crates/storage/verifier/src/verifiers/state_trie.rs b/crates/storage/verifier/src/verifiers/state_trie.rs new file mode 100644 index 000000000..b3168036a --- /dev/null +++ b/crates/storage/verifier/src/verifiers/state_trie.rs @@ -0,0 +1,126 @@ +//! State trie verification checks. + +use anyhow::{Context, Result}; +use katana_db::abstraction::Database; +use katana_db::trie::TrieDbFactory; +use katana_provider::providers::db::DbProvider; +use katana_provider::traits::block::{BlockNumberProvider, HeaderProvider}; +use katana_provider::traits::state::{StateFactoryProvider, StateRootProvider}; +use tracing::{debug, instrument}; + +use crate::error::VerificationError; +use crate::verifiers::Verifier; + +/// Verifier for state trie integrity. +/// +/// This verifier checks that the computed state root from the database's state provider +/// matches the state root stored in the block header. The verification is performed +/// only on the last block in the verification range for efficiency. +/// +/// The state root is computed using the StateRootProvider trait which combines: +/// - Contracts trie root +/// - Classes trie root +/// +/// This approach is much more efficient than verifying every block individually. +#[derive(Debug)] +pub struct StateTrieVerifier; + +impl Verifier for StateTrieVerifier { + fn name(&self) -> &'static str { + "StateTrieVerifier" + } + + #[instrument(skip(self, database))] + fn verify(&self, database: &DbProvider) -> Result<()> { + let latest_block = database.latest_number().context("Failed to get latest block number")?; + + if latest_block == 0 { + debug!("No blocks found in database"); + return Ok(()); + } + + // Only verify the state root of the latest block + self.verify_state_root_at_block(database, latest_block)?; + Ok(()) + } + + #[instrument(skip(self, database))] + fn verify_range(&self, database: &DbProvider, start_block: u64, end_block: u64) -> Result<()> { + // Only verify the state root of the last block in the range + self.verify_state_root_at_block(database, end_block)?; + Ok(()) + } + + #[instrument(skip(self, database))] + fn verify_sample(&self, database: &DbProvider, sample_rate: u64) -> Result<()> { + let latest_block = database.latest_number().context("Failed to get latest block number")?; + + if latest_block == 0 { + debug!("No blocks found in database"); + return Ok(()); + } + + // For sampling, only verify the state root of the last block + self.verify_state_root_at_block(database, latest_block)?; + + Ok(()) + } +} + +impl StateTrieVerifier { + /// Verify the state root for a specific block by comparing the header's state root + /// with the computed state root from the state provider. + fn verify_state_root_at_block(&self, database: &DbProvider, block_num: u64) -> Result<()> { + debug!("Verifying state root for block {}", block_num); + + // Get the block header to compare against + let header = database + .header_by_number(block_num) + .with_context(|| format!("Failed to query header for block {}", block_num))? + .with_context(|| format!("Missing header for block {}", block_num))?; + + // Get historical state provider for this block + let state_provider = database + .historical(block_num.into()) + .with_context(|| format!("Failed to get historical state for block {}", block_num))? + .with_context(|| format!("Missing historical state for block {}", block_num))?; + + // Compute the state root using the StateRootProvider trait + let computed_state_root = state_provider + .state_root() + .with_context(|| format!("Failed to compute state root for block {}", block_num))?; + + let tx = database.db().tx_mut()?; + let trie = TrieDbFactory::new(&tx).historical(block_num).unwrap(); + + let classes_trie = trie.classes_trie(); + let contracts_trie = trie.contracts_trie(); + + // Compare the computed state root with the header's state root + if header.state_root != computed_state_root { + return Err(VerificationError::StateRootMismatch { + block_number: block_num, + header: format!("{:#x}", header.state_root), + computed: format!("{:#x}", computed_state_root), + } + .into()); + } + + debug!( + "State root verification passed for block {}: {:#x}", + block_num, computed_state_root + ); + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_verifier_name() { + let verifier = StateTrieVerifier; + assert_eq!(verifier.name(), "StateTrieVerifier"); + } +} diff --git a/crates/storage/verifier/tests/basic_tests.rs b/crates/storage/verifier/tests/basic_tests.rs new file mode 100644 index 000000000..efc132885 --- /dev/null +++ b/crates/storage/verifier/tests/basic_tests.rs @@ -0,0 +1,40 @@ +//! Basic tests for the verifier crate. + +use katana_verifier::{DatabaseVerifier, report::OverallStatus}; + +#[test] +fn test_verifier_creation() { + let verifier = DatabaseVerifier::new(); + // Just ensure we can create a verifier without errors + assert_eq!(std::mem::size_of_val(&verifier), std::mem::size_of::()); +} + +#[test] +fn test_verifier_default() { + let verifier = DatabaseVerifier::default(); + // Ensure default implementation works + assert_eq!(std::mem::size_of_val(&verifier), std::mem::size_of::()); +} + +#[test] +fn test_empty_report() { + use katana_verifier::report::VerificationReport; + + let report = VerificationReport::empty(); + assert_eq!(report.total_count(), 0); + assert_eq!(report.successful_count(), 0); + assert_eq!(report.failed_count(), 0); + assert_eq!(report.summary.overall_status, OverallStatus::Empty); + assert!(!report.is_success()); // Empty is not success + assert!(!report.has_failures()); +} + +#[test] +fn test_report_display() { + use katana_verifier::report::VerificationReport; + + let report = VerificationReport::empty(); + let display_str = format!("{}", report); + assert!(display_str.contains("Database Verification Report")); + assert!(display_str.contains("Total verifiers: 0")); +}