integration.rs
1 // Copyright (c) 2025 ADnet Contributors 2 // SPDX-License-Identifier: Apache-2.0 3 4 //! Integration tests for DeltaOS node 5 //! 6 //! These tests verify end-to-end functionality including: 7 //! - Consensus and block production 8 //! - P2P message handling 9 //! - Storage persistence and recovery 10 //! - Transaction validation and execution 11 12 use deltaos_node::{ 13 Storage, 14 consensus::{ 15 AccountState, BftConsensus, BlockProducer, ConsensusConfig, DeltaBlock, DeltaTransaction, 16 TransactionType, TxValidationResult, TxValidator, 17 }, 18 signing::{ValidatorIdentity, ValidatorKeyStore}, 19 }; 20 use std::collections::HashMap; 21 use std::time::Duration; 22 use tempfile::tempdir; 23 24 /// Helper to create storage (uses Storage::open) 25 fn create_storage(path: &str) -> Storage { 26 Storage::open(path).unwrap() 27 } 28 29 // ============================================================================ 30 // Consensus Integration Tests 31 // ============================================================================ 32 33 mod consensus_tests { 34 use super::*; 35 36 #[test] 37 fn test_block_producer_lifecycle() { 38 let config = ConsensusConfig { 39 block_time: Duration::from_millis(100), 40 max_txs_per_block: 100, 41 min_validators: 1, 42 }; 43 44 let mut producer = BlockProducer::new(config); 45 46 // Initially at height 0 47 assert_eq!(producer.current_height(), 0); 48 49 // Produce first block 50 let block1 = producer.produce_block("validator1"); 51 assert_eq!(block1.height, 1); 52 assert_eq!(producer.current_height(), 1); 53 54 // Produce second block 55 let block2 = producer.produce_block("validator1"); 56 assert_eq!(block2.height, 2); 57 assert_eq!(block2.previous_hash, block1.hash); 58 } 59 60 #[test] 61 fn test_genesis_block_creation() { 62 let genesis = BlockProducer::create_genesis_block("genesis_validator"); 63 64 assert_eq!(genesis.height, 1); 65 assert_eq!(genesis.previous_hash, [0u8; 32]); 66 assert!(genesis.transactions.is_empty()); 67 assert!(genesis.attestations.is_empty()); 68 assert_ne!(genesis.hash, [0u8; 32]); 69 } 70 71 #[test] 72 fn test_block_producer_recovery() { 73 let config = ConsensusConfig::default(); 74 let last_hash = [42u8; 32]; 75 76 let producer = BlockProducer::with_starting_state(config, 100, last_hash); 77 78 assert_eq!(producer.current_height(), 100); 79 assert_eq!(producer.last_block_hash(), last_hash); 80 } 81 82 #[test] 83 fn test_transaction_validation_flow() { 84 let mut validator = TxValidator::new(); 85 86 // Create an account 87 let identity = ValidatorIdentity::generate(); 88 validator.set_account( 89 identity.address().to_string(), 90 AccountState { 91 balance: 1_000_000, 92 nonce: 0, 93 pubkey: Some(identity.public_key().to_vec()), 94 }, 95 ); 96 97 // Create a valid transaction 98 let tx = create_signed_transaction(&identity, 0, 10000, 1000); 99 100 // Validate 101 match validator.validate(&tx) { 102 TxValidationResult::Valid => {} 103 TxValidationResult::Invalid(e) => panic!("Expected valid, got: {:?}", e), 104 } 105 } 106 107 #[test] 108 fn test_transaction_nonce_validation() { 109 let mut validator = TxValidator::new(); 110 111 let identity = ValidatorIdentity::generate(); 112 validator.set_account( 113 identity.address().to_string(), 114 AccountState { 115 balance: 1_000_000, 116 nonce: 5, // Expected nonce is 5 117 pubkey: Some(identity.public_key().to_vec()), 118 }, 119 ); 120 121 // Transaction with wrong nonce should fail 122 let tx_wrong_nonce = create_signed_transaction(&identity, 3, 10000, 1000); 123 match validator.validate(&tx_wrong_nonce) { 124 TxValidationResult::Invalid(e) => { 125 assert!(format!("{:?}", e).contains("InvalidNonce")); 126 } 127 TxValidationResult::Valid => panic!("Should have failed nonce validation"), 128 } 129 130 // Transaction with correct nonce should pass 131 let tx_correct = create_signed_transaction(&identity, 5, 10000, 1000); 132 match validator.validate(&tx_correct) { 133 TxValidationResult::Valid => {} 134 TxValidationResult::Invalid(e) => panic!("Expected valid, got: {:?}", e), 135 } 136 } 137 138 #[test] 139 fn test_transaction_balance_validation() { 140 let mut validator = TxValidator::new(); 141 142 let identity = ValidatorIdentity::generate(); 143 validator.set_account( 144 identity.address().to_string(), 145 AccountState { 146 balance: 5000, // Only 5000 balance 147 nonce: 0, 148 pubkey: Some(identity.public_key().to_vec()), 149 }, 150 ); 151 152 // Transaction exceeding balance should fail 153 let tx_over_balance = create_signed_transaction(&identity, 0, 10000, 1000); 154 match validator.validate(&tx_over_balance) { 155 TxValidationResult::Invalid(e) => { 156 assert!(format!("{:?}", e).contains("InsufficientBalance")); 157 } 158 TxValidationResult::Valid => panic!("Should have failed balance validation"), 159 } 160 } 161 162 #[test] 163 fn test_bft_consensus_leader_rotation() { 164 let mut validators = HashMap::new(); 165 validators.insert("val1".to_string(), 100); 166 validators.insert("val2".to_string(), 100); 167 validators.insert("val3".to_string(), 100); 168 169 let consensus = BftConsensus::new("val1".to_string(), validators); 170 171 // Leader should rotate based on height + round 172 let leader0 = consensus.get_leader().unwrap(); 173 assert!(!leader0.is_empty()); 174 } 175 176 #[test] 177 fn test_bft_consensus_quorum_calculation() { 178 let mut validators = HashMap::new(); 179 validators.insert("val1".to_string(), 100); 180 validators.insert("val2".to_string(), 100); 181 validators.insert("val3".to_string(), 100); 182 183 let consensus = BftConsensus::new("val1".to_string(), validators); 184 185 // Quorum should be > 2/3 of total power (300) 186 // 300 * 2 / 3 + 1 = 201 187 assert_eq!(consensus.quorum_power(), 201); 188 } 189 190 #[test] 191 fn test_bft_with_validator_identity() { 192 let identity = ValidatorIdentity::generate(); 193 let mut validators = HashMap::new(); 194 validators.insert(identity.address().to_string(), 100); 195 196 let mut consensus = BftConsensus::with_identity(identity.clone(), validators); 197 198 // Register the validator's key 199 consensus.register_validator_key(identity.address().to_string(), identity.public_key()); 200 201 assert_eq!(consensus.node_address, identity.address()); 202 assert!(consensus.identity.is_some()); 203 } 204 205 // Helper function to create a signed transaction 206 fn create_signed_transaction( 207 identity: &ValidatorIdentity, 208 nonce: u64, 209 amount: u64, 210 fee: u64, 211 ) -> DeltaTransaction { 212 let tx_type = TransactionType::Trading; 213 let data = vec![]; 214 215 // Create signing message 216 let signing_message = deltaos_node::signing::transaction_signing_message( 217 tx_type.as_byte(), 218 Some(identity.address()), 219 nonce, 220 amount, 221 fee, 222 &data, 223 ); 224 225 // Sign it 226 let signature = identity.sign_transaction(&signing_message); 227 228 // Create transaction 229 let mut tx = DeltaTransaction { 230 id: [0u8; 32], 231 tx_type, 232 sender: Some(identity.address().to_string()), 233 sender_pubkey: Some(identity.public_key().to_vec()), 234 nonce, 235 amount, 236 fee_limit: fee, 237 data, 238 signature: signature.to_vec(), 239 }; 240 241 // Compute ID 242 tx.id = tx.compute_id(); 243 tx 244 } 245 } 246 247 // ============================================================================ 248 // Storage Integration Tests 249 // ============================================================================ 250 251 mod storage_tests { 252 use super::*; 253 254 #[test] 255 fn test_storage_block_persistence() { 256 let dir = tempdir().unwrap(); 257 let storage = create_storage(dir.path().to_str().unwrap()); 258 259 // Create and store a block 260 let block = create_test_block(1); 261 storage.put_block(&block).unwrap(); 262 263 // Retrieve and verify 264 let retrieved = storage.get_block(1).unwrap(); 265 assert_eq!(retrieved.height, block.height); 266 assert_eq!(retrieved.hash, block.hash); 267 } 268 269 #[test] 270 fn test_storage_chain_height_tracking() { 271 let dir = tempdir().unwrap(); 272 let storage = create_storage(dir.path().to_str().unwrap()); 273 274 // Initially no blocks 275 assert_eq!(storage.get_chain_height().unwrap(), 0); 276 277 // Store blocks and verify height updates 278 for height in 1..=5 { 279 let block = create_test_block(height); 280 storage.put_block(&block).unwrap(); 281 assert_eq!(storage.get_chain_height().unwrap(), height); 282 } 283 } 284 285 #[test] 286 fn test_storage_account_operations() { 287 let dir = tempdir().unwrap(); 288 let storage = create_storage(dir.path().to_str().unwrap()); 289 290 let address = "dx1testaddress123456789012345678901234567890123456789"; 291 let state = AccountState { 292 balance: 1_000_000, 293 nonce: 5, 294 pubkey: Some(vec![1, 2, 3, 4]), 295 }; 296 297 // Store account 298 storage.put_account(address, &state).unwrap(); 299 300 // Retrieve and verify 301 let retrieved = storage.get_account(address).unwrap(); 302 assert_eq!(retrieved.balance, state.balance); 303 assert_eq!(retrieved.nonce, state.nonce); 304 } 305 306 #[test] 307 fn test_storage_snapshot_creation() { 308 let dir = tempdir().unwrap(); 309 let storage = create_storage(dir.path().to_str().unwrap()); 310 311 // Store some blocks 312 let mut last_hash = [0u8; 32]; 313 for height in 1..=10 { 314 let block = create_test_block(height); 315 last_hash = block.hash; 316 storage.put_block(&block).unwrap(); 317 } 318 319 // Create snapshot with accounts 320 let accounts: Vec<(String, AccountState)> = vec![( 321 "dx1test123".to_string(), 322 AccountState { 323 balance: 1000, 324 nonce: 0, 325 pubkey: None, 326 }, 327 )]; 328 storage.create_snapshot(10, last_hash, accounts).unwrap(); 329 330 // Verify snapshot can be loaded 331 let snapshot = storage.load_snapshot(10).unwrap(); 332 assert!(snapshot.is_some()); 333 } 334 335 #[test] 336 fn test_storage_recovery_from_snapshot() { 337 let dir = tempdir().unwrap(); 338 let storage = create_storage(dir.path().to_str().unwrap()); 339 340 // Store blocks and an account 341 let mut last_hash = [0u8; 32]; 342 for height in 1..=5 { 343 let block = create_test_block(height); 344 last_hash = block.hash; 345 storage.put_block(&block).unwrap(); 346 } 347 348 let address = "dx1testrecovery1234567890123456789012345678901234567"; 349 storage 350 .put_account( 351 address, 352 &AccountState { 353 balance: 500_000, 354 nonce: 2, 355 pubkey: None, 356 }, 357 ) 358 .unwrap(); 359 360 // Create snapshot with the account 361 let accounts = vec![( 362 address.to_string(), 363 AccountState { 364 balance: 500_000, 365 nonce: 2, 366 pubkey: None, 367 }, 368 )]; 369 storage.create_snapshot(5, last_hash, accounts).unwrap(); 370 371 // Load snapshot and verify 372 let snapshot = storage.load_snapshot(5).unwrap().unwrap(); 373 assert_eq!(snapshot.height, 5); 374 assert_eq!(snapshot.block_hash, last_hash); 375 assert_eq!(snapshot.accounts.len(), 1); 376 assert_eq!(snapshot.accounts[0].1.balance, 500_000); 377 } 378 379 fn create_test_block(height: u32) -> DeltaBlock { 380 let mut hasher = blake3::Hasher::new(); 381 hasher.update(&height.to_le_bytes()); 382 let hash: [u8; 32] = hasher.finalize().into(); 383 384 DeltaBlock { 385 height, 386 previous_hash: if height == 1 { [0u8; 32] } else { [1u8; 32] }, 387 timestamp: 1000000 + height as u64, 388 producer: "test_producer".to_string(), 389 transactions: vec![], 390 attestations: vec![], 391 hash, 392 } 393 } 394 } 395 396 // ============================================================================ 397 // Signing Integration Tests 398 // ============================================================================ 399 400 mod signing_tests { 401 use super::*; 402 403 #[test] 404 fn test_full_transaction_signing_flow() { 405 let identity = ValidatorIdentity::generate(); 406 407 // Create a transaction 408 let tx_type = TransactionType::Trading; 409 let nonce = 0u64; 410 let amount = 10000u64; 411 let fee = 1000u64; 412 let data = b"test data".to_vec(); 413 414 // Create signing message 415 let signing_message = deltaos_node::signing::transaction_signing_message( 416 tx_type.as_byte(), 417 Some(identity.address()), 418 nonce, 419 amount, 420 fee, 421 &data, 422 ); 423 424 // Sign 425 let signature = identity.sign_transaction(&signing_message); 426 427 // Verify 428 assert!(deltaos_node::signing::verify_signature( 429 &identity.public_key(), 430 &signing_message, 431 &signature 432 )); 433 } 434 435 #[test] 436 fn test_validator_key_store_integration() { 437 let validator1 = ValidatorIdentity::generate(); 438 let validator2 = ValidatorIdentity::generate(); 439 440 let mut key_store = ValidatorKeyStore::new(); 441 key_store.register(validator1.address().to_string(), validator1.public_key()); 442 key_store.register(validator2.address().to_string(), validator2.public_key()); 443 444 // Validator 1 signs a vote 445 let height = 100; 446 let round = 0; 447 let block_hash = Some([1u8; 32]); 448 let vote_type = 0u8; 449 450 let sig1 = validator1.sign_vote(height, round, block_hash, vote_type); 451 452 // Verify with key store 453 assert!(key_store.verify_vote( 454 validator1.address(), 455 height, 456 round, 457 block_hash, 458 vote_type, 459 &sig1 460 )); 461 462 // Validator 2's signature should fail for validator 1's address 463 let sig2 = validator2.sign_vote(height, round, block_hash, vote_type); 464 assert!(!key_store.verify_vote( 465 validator1.address(), 466 height, 467 round, 468 block_hash, 469 vote_type, 470 &sig2 471 )); 472 } 473 474 #[test] 475 fn test_cross_validator_signature_rejection() { 476 let validator1 = ValidatorIdentity::generate(); 477 let validator2 = ValidatorIdentity::generate(); 478 479 // Sign with validator1 480 let message = b"important consensus message"; 481 let hash: [u8; 32] = blake3::hash(message).into(); 482 let sig = validator1.sign_transaction(&hash); 483 484 // Verify fails with validator2's key 485 assert!(!deltaos_node::signing::verify_signature( 486 &validator2.public_key(), 487 &hash, 488 &sig 489 )); 490 } 491 } 492 493 // ============================================================================ 494 // Multi-Component Integration Tests 495 // ============================================================================ 496 497 mod multi_component_tests { 498 use super::*; 499 500 #[test] 501 fn test_block_production_with_storage() { 502 let dir = tempdir().unwrap(); 503 let storage = create_storage(dir.path().to_str().unwrap()); 504 505 let config = ConsensusConfig::default(); 506 let mut producer = BlockProducer::new(config); 507 508 // Produce and store blocks 509 for _ in 0..5 { 510 let block = producer.produce_block("test_validator"); 511 storage.put_block(&block).unwrap(); 512 } 513 514 // Verify storage has all blocks 515 assert_eq!(storage.get_chain_height().unwrap(), 5); 516 517 // Verify blocks can be retrieved 518 for height in 1..=5 { 519 let block = storage.get_block(height).unwrap(); 520 assert_eq!(block.height, height); 521 } 522 } 523 524 #[test] 525 fn test_consensus_recovery_from_storage() { 526 let dir = tempdir().unwrap(); 527 let storage = create_storage(dir.path().to_str().unwrap()); 528 529 // Produce some blocks with first producer 530 let config = ConsensusConfig::default(); 531 let mut producer1 = BlockProducer::new(config.clone()); 532 533 let mut last_hash = [0u8; 32]; 534 for _ in 0..10 { 535 let block = producer1.produce_block("validator1"); 536 last_hash = block.hash; 537 storage.put_block(&block).unwrap(); 538 } 539 540 // Simulate restart: create new producer from stored state 541 let height = storage.get_chain_height().unwrap(); 542 let producer2 = BlockProducer::with_starting_state(config, height, last_hash); 543 544 assert_eq!(producer2.current_height(), 10); 545 assert_eq!(producer2.last_block_hash(), last_hash); 546 } 547 548 #[test] 549 fn test_transaction_flow_with_validator_accounts() { 550 let mut validator = TxValidator::new(); 551 552 // Create multiple validators 553 let validators: Vec<ValidatorIdentity> = 554 (0..3).map(|_| ValidatorIdentity::generate()).collect(); 555 556 // Fund each validator 557 for val in &validators { 558 validator.set_account( 559 val.address().to_string(), 560 AccountState { 561 balance: 1_000_000, 562 nonce: 0, 563 pubkey: Some(val.public_key().to_vec()), 564 }, 565 ); 566 } 567 568 // Each validator creates and validates a transaction 569 for (i, val) in validators.iter().enumerate() { 570 let tx = create_signed_tx(val, 0, 1000, 2000); // fee must be >= 1000 571 match validator.validate(&tx) { 572 TxValidationResult::Valid => {} 573 TxValidationResult::Invalid(e) => { 574 panic!("Validator {} transaction failed: {:?}", i, e) 575 } 576 } 577 } 578 } 579 580 fn create_signed_tx( 581 identity: &ValidatorIdentity, 582 nonce: u64, 583 amount: u64, 584 fee: u64, 585 ) -> DeltaTransaction { 586 let tx_type = TransactionType::Trading; 587 let data = vec![]; 588 589 let signing_message = deltaos_node::signing::transaction_signing_message( 590 tx_type.as_byte(), 591 Some(identity.address()), 592 nonce, 593 amount, 594 fee, 595 &data, 596 ); 597 598 let signature = identity.sign_transaction(&signing_message); 599 600 let mut tx = DeltaTransaction { 601 id: [0u8; 32], 602 tx_type, 603 sender: Some(identity.address().to_string()), 604 sender_pubkey: Some(identity.public_key().to_vec()), 605 nonce, 606 amount, 607 fee_limit: fee, 608 data, 609 signature: signature.to_vec(), 610 }; 611 612 tx.id = tx.compute_id(); 613 tx 614 } 615 }