/ node / tests / integration.rs
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  }