lunalib 1.7.3__py3-none-any.whl → 1.7.9__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. lunalib/.gitignore +3 -0
  2. lunalib/__pycache__/__init__.cpython-310.pyc +0 -0
  3. lunalib/core/__pycache__/__init__.cpython-310.pyc +0 -0
  4. lunalib/core/__pycache__/blockchain.cpython-310.pyc +0 -0
  5. lunalib/core/__pycache__/crypto.cpython-310.pyc +0 -0
  6. lunalib/core/__pycache__/mempool.cpython-310.pyc +0 -0
  7. lunalib/core/__pycache__/wallet.cpython-310.pyc +0 -0
  8. lunalib/core/mempool.py +1 -0
  9. lunalib/core/wallet.py +119 -24
  10. lunalib/core/wallet_db.py +48 -47
  11. lunalib/gtx/__pycache__/__init__.cpython-310.pyc +0 -0
  12. lunalib/gtx/__pycache__/bill_registry.cpython-310.pyc +0 -0
  13. lunalib/gtx/__pycache__/digital_bill.cpython-310.pyc +0 -0
  14. lunalib/gtx/__pycache__/genesis.cpython-310.pyc +0 -0
  15. lunalib/mining/__pycache__/__init__.cpython-310.pyc +0 -0
  16. lunalib/mining/__pycache__/cuda_manager.cpython-310.pyc +0 -0
  17. lunalib/mining/__pycache__/difficulty.cpython-310.pyc +0 -0
  18. lunalib/mining/__pycache__/miner.cpython-310.pyc +0 -0
  19. lunalib/storage/__pycache__/__init__.cpython-310.pyc +0 -0
  20. lunalib/storage/__pycache__/cache.cpython-310.pyc +0 -0
  21. lunalib/storage/__pycache__/database.cpython-310.pyc +0 -0
  22. lunalib/storage/__pycache__/encryption.cpython-310.pyc +0 -0
  23. lunalib/tests/__pycache__/conftest.cpython-310-pytest-9.0.1.pyc +0 -0
  24. lunalib/tests/__pycache__/test_blockchain.cpython-310-pytest-9.0.1.pyc +0 -0
  25. lunalib/tests/__pycache__/test_crypto.cpython-310-pytest-9.0.1.pyc +0 -0
  26. lunalib/tests/__pycache__/test_gtx.cpython-310-pytest-9.0.1.pyc +0 -0
  27. lunalib/tests/__pycache__/test_mining.cpython-310-pytest-9.0.1.pyc +0 -0
  28. lunalib/tests/__pycache__/test_storage.cpython-310-pytest-9.0.1.pyc +0 -0
  29. lunalib/tests/__pycache__/test_transactions.cpython-310-pytest-9.0.1.pyc +0 -0
  30. lunalib/tests/__pycache__/test_wallet.cpython-310-pytest-9.0.1.pyc +0 -0
  31. lunalib/tests/conftest.py +41 -0
  32. lunalib/tests/init.py +0 -0
  33. lunalib/tests/integration/__pycache__/test_integration.cpython-310-pytest-9.0.1.pyc +0 -0
  34. lunalib/tests/integration/test_integration.py +62 -0
  35. lunalib/tests/test_blockchain.py +34 -0
  36. lunalib/tests/test_crypto.py +42 -0
  37. lunalib/tests/test_gtx.py +135 -0
  38. lunalib/tests/test_mining.py +244 -0
  39. lunalib/tests/test_security_suite.py +832 -0
  40. lunalib/tests/test_storage.py +84 -0
  41. lunalib/tests/test_transactions.py +103 -0
  42. lunalib/tests/test_wallet.py +91 -0
  43. lunalib/transactions/__pycache__/__init__.cpython-310.pyc +0 -0
  44. lunalib/transactions/__pycache__/security.cpython-310.pyc +0 -0
  45. lunalib/transactions/__pycache__/transactions.cpython-310.pyc +0 -0
  46. lunalib/transactions/__pycache__/validator.cpython-310.pyc +0 -0
  47. {lunalib-1.7.3.dist-info → lunalib-1.7.9.dist-info}/METADATA +1 -1
  48. lunalib-1.7.9.dist-info/RECORD +77 -0
  49. lunalib-1.7.3.dist-info/RECORD +0 -34
  50. {lunalib-1.7.3.dist-info → lunalib-1.7.9.dist-info}/WHEEL +0 -0
  51. {lunalib-1.7.3.dist-info → lunalib-1.7.9.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,832 @@
1
+ import sys
2
+ def safe_print(*args, **kwargs):
3
+ encoding = sys.stdout.encoding or 'utf-8'
4
+ try:
5
+ print(*args, **kwargs)
6
+ except UnicodeEncodeError:
7
+ print(*(str(a).encode(encoding, errors='replace').decode(encoding) for a in args), **kwargs)
8
+ """
9
+ Comprehensive Security and Integration Test Suite
10
+
11
+ Tests for:
12
+ - Reward-Difficulty Correlation
13
+ - Transaction Signature Verification
14
+ - Address Spoofing Prevention
15
+ - DDoS/Spam Protection
16
+ - Multi-wallet State Management
17
+ - Blockchain Integrity
18
+ """
19
+
20
+ import unittest
21
+ import time
22
+ import hashlib
23
+ import threading
24
+ from unittest.mock import Mock, patch, MagicMock
25
+ from typing import List, Dict
26
+
27
+ # Import components
28
+ from lunalib.core.wallet import LunaWallet
29
+ from lunalib.core.blockchain import BlockchainManager
30
+ from lunalib.core.mempool import MempoolManager
31
+ from lunalib.core.wallet_manager import WalletStateManager, Transaction, TransactionStatus
32
+ from lunalib.transactions.transactions import TransactionManager
33
+
34
+
35
+ # ============================================================================
36
+ # TEST SUITE 1: REWARD-DIFFICULTY CORRELATION
37
+ # ============================================================================
38
+
39
+ class TestRewardDifficultyCorrelation(unittest.TestCase):
40
+ """
41
+ Ensure rewards are correctly correlated to mining difficulty.
42
+ Difficulty N should ALWAYS result in N LKC reward.
43
+ """
44
+
45
+ def setUp(self):
46
+ """Setup test fixtures"""
47
+ self.blockchain = BlockchainManager()
48
+ self.tx_manager = TransactionManager()
49
+
50
+ def test_difficulty_1_equals_1_lkc_reward(self):
51
+ """Difficulty 1 should produce exactly 1 LKC reward"""
52
+ difficulty = 1
53
+ expected_reward = 1.0
54
+
55
+ # Simulate mined block with difficulty 1
56
+ block = self._create_test_block(difficulty=difficulty)
57
+ reward_tx = self._extract_reward_transaction(block)
58
+
59
+ self.assertEqual(reward_tx['amount'], expected_reward,
60
+ f"Difficulty {difficulty} should reward {expected_reward} LKC")
61
+
62
+ def test_difficulty_2_equals_2_lkc_reward(self):
63
+ """Difficulty 2 should produce exactly 2 LKC reward"""
64
+ difficulty = 2
65
+ expected_reward = 2.0
66
+
67
+ block = self._create_test_block(difficulty=difficulty)
68
+ reward_tx = self._extract_reward_transaction(block)
69
+
70
+ self.assertEqual(reward_tx['amount'], expected_reward)
71
+
72
+ def test_difficulty_9_equals_9_lkc_reward(self):
73
+ """Difficulty 9 should produce exactly 9 LKC reward"""
74
+ difficulty = 9
75
+ expected_reward = 9.0
76
+
77
+ block = self._create_test_block(difficulty=difficulty)
78
+ reward_tx = self._extract_reward_transaction(block)
79
+
80
+ self.assertEqual(reward_tx['amount'], expected_reward)
81
+
82
+ def test_reward_scaling_linear(self):
83
+ """Test that reward scales linearly with difficulty"""
84
+ rewards = {}
85
+
86
+ for difficulty in range(1, 10):
87
+ block = self._create_test_block(difficulty=difficulty)
88
+ reward_tx = self._extract_reward_transaction(block)
89
+ rewards[difficulty] = reward_tx['amount']
90
+
91
+ # Verify linear relationship: reward = difficulty
92
+ for difficulty, reward in rewards.items():
93
+ self.assertEqual(reward, float(difficulty),
94
+ f"Reward for difficulty {difficulty} should be {difficulty}")
95
+
96
+ def test_reward_hash_verified(self):
97
+ """Reward transaction must be cryptographically verified"""
98
+ block = self._create_test_block(difficulty=5)
99
+ reward_tx = self._extract_reward_transaction(block)
100
+
101
+ # Verify signature
102
+ self.assertIn('signature', reward_tx)
103
+ self.assertNotEqual(reward_tx['signature'], 'unsigned',
104
+ "Reward must be signed")
105
+
106
+ # Verify it hasn't been tampered with
107
+ original_hash = reward_tx['hash']
108
+ self.assertEqual(len(original_hash), 64,
109
+ "Transaction hash must be valid SHA-256 (64 chars)")
110
+
111
+ def test_reward_tampering_detection(self):
112
+ """Tampering with reward amount should be detectable"""
113
+ block = self._create_test_block(difficulty=5)
114
+ reward_tx = self._extract_reward_transaction(block)
115
+
116
+ original_hash = reward_tx['hash']
117
+ original_amount = reward_tx['amount']
118
+
119
+ # Tamper with amount
120
+ reward_tx['amount'] = 999.0
121
+
122
+ # Recalculate hash (what a tamperer would do)
123
+ tampered_hash = hashlib.sha256(
124
+ str({k: v for k, v in reward_tx.items() if k != 'hash'}).encode()
125
+ ).hexdigest()
126
+
127
+ # Hashes should not match
128
+ self.assertNotEqual(tampered_hash, original_hash,
129
+ "Tampering should change transaction hash")
130
+
131
+ def test_reward_from_zero_difficulty_invalid(self):
132
+ """Difficulty 0 or negative should produce no reward"""
133
+ for invalid_difficulty in [0, -1, -5]:
134
+ block = self._create_test_block(difficulty=invalid_difficulty)
135
+ reward_txs = [tx for tx in block.get('transactions', [])
136
+ if tx.get('type') == 'reward']
137
+
138
+ self.assertEqual(len(reward_txs), 0,
139
+ f"Difficulty {invalid_difficulty} should produce no reward")
140
+
141
+ def _create_test_block(self, difficulty: int = 1) -> Dict:
142
+ """Create a test block with specified difficulty"""
143
+ return {
144
+ 'index': 1,
145
+ 'previous_hash': '0' * 64,
146
+ 'timestamp': int(time.time()),
147
+ 'transactions': [
148
+ {
149
+ 'hash': hashlib.sha256(b'reward').hexdigest(),
150
+ 'type': 'reward',
151
+ 'from': 'network',
152
+ 'to': 'LUN_MINER_ADDRESS',
153
+ 'amount': float(difficulty),
154
+ 'fee': 0.0,
155
+ 'signature': 'network_signed',
156
+ 'timestamp': int(time.time())
157
+ }
158
+ ] if difficulty > 0 else [],
159
+ 'difficulty': difficulty,
160
+ 'nonce': 12345,
161
+ 'hash': 'a' * difficulty + '0' * (64 - difficulty)
162
+ }
163
+
164
+ def _extract_reward_transaction(self, block: Dict) -> Dict:
165
+ """Extract reward transaction from block"""
166
+ for tx in block.get('transactions', []):
167
+ if tx.get('type') == 'reward':
168
+ return tx
169
+ return {}
170
+
171
+
172
+ # ============================================================================
173
+ # TEST SUITE 2: TRANSACTION SIGNATURE VERIFICATION
174
+ # ============================================================================
175
+
176
+ class TestTransactionSignatureVerification(unittest.TestCase):
177
+ """
178
+ Ensure transactions cannot be forged - require valid cryptographic signatures.
179
+ """
180
+
181
+ def setUp(self):
182
+ """Setup test fixtures"""
183
+ self.wallet = LunaWallet()
184
+ self.tx_manager = TransactionManager()
185
+
186
+ # Create test wallet
187
+ self.wallet.create_wallet("Test", "password123")
188
+ self.wallet.unlock_wallet(self.wallet.current_wallet_address, "password123")
189
+
190
+ def test_transaction_requires_valid_signature(self):
191
+ """Transaction without valid signature should be rejected"""
192
+ invalid_tx = {
193
+ 'hash': 'abc123',
194
+ 'type': 'transfer',
195
+ 'from': 'LUN_SENDER',
196
+ 'to': 'LUN_RECEIVER',
197
+ 'amount': 100.0,
198
+ 'fee': 0.001,
199
+ 'signature': 'invalid_signature', # Invalid!
200
+ 'timestamp': int(time.time())
201
+ }
202
+
203
+ # Try to validate
204
+ is_valid, message = self.tx_manager.validate_transaction(invalid_tx)
205
+
206
+ self.assertFalse(is_valid or 'invalid' in message.lower(),
207
+ "Invalid signature should cause validation to fail")
208
+
209
+ def test_signed_transaction_has_valid_signature(self):
210
+ """Properly signed transaction should have valid signature"""
211
+ tx = self.tx_manager.create_transaction(
212
+ from_address=self.wallet.address,
213
+ to_address='LUN_RECEIVER',
214
+ amount=100.0,
215
+ private_key=self.wallet.private_key,
216
+ transaction_type='transfer'
217
+ )
218
+
219
+ # Should have signature
220
+ self.assertIn('signature', tx)
221
+ self.assertNotEqual(tx['signature'], 'unsigned')
222
+ self.assertGreater(len(tx['signature']), 10)
223
+
224
+ def test_tampering_with_transaction_detects_invalid_signature(self):
225
+ """Modifying transaction data should invalidate signature"""
226
+ tx = self.tx_manager.create_transaction(
227
+ from_address=self.wallet.address,
228
+ to_address='LUN_RECEIVER',
229
+ amount=100.0,
230
+ private_key=self.wallet.private_key,
231
+ transaction_type='transfer'
232
+ )
233
+
234
+ original_hash = tx['hash']
235
+ original_amount = tx['amount']
236
+
237
+ # Tamper with amount
238
+ tx['amount'] = 999.0
239
+
240
+ # Hash should change
241
+ new_hash = self.tx_manager._calculate_transaction_hash(tx)
242
+
243
+ self.assertNotEqual(new_hash, original_hash,
244
+ "Tampering with amount should change hash")
245
+
246
+ def test_wrong_private_key_produces_invalid_signature(self):
247
+ """Using wrong private key should produce invalid signature"""
248
+ tx1 = self.tx_manager.create_transaction(
249
+ from_address=self.wallet.address,
250
+ to_address='LUN_RECEIVER',
251
+ amount=100.0,
252
+ private_key=self.wallet.private_key,
253
+ transaction_type='transfer'
254
+ )
255
+
256
+ # Create another wallet
257
+ self.wallet.create_new_wallet("Other", "pass")
258
+ other_address = self.wallet.current_wallet_address
259
+
260
+ # Create transaction with other key
261
+ tx2 = self.tx_manager.create_transaction(
262
+ from_address=other_address,
263
+ to_address='LUN_RECEIVER',
264
+ amount=100.0,
265
+ private_key=self.wallet.private_key,
266
+ transaction_type='transfer'
267
+ )
268
+
269
+ # Signatures should be different
270
+ self.assertNotEqual(tx1['signature'], tx2['signature'],
271
+ "Different private keys should produce different signatures")
272
+
273
+ def test_public_key_matches_private_key(self):
274
+ """Public key derived from private key should match"""
275
+ from lunalib.core.crypto import KeyManager
276
+
277
+ key_manager = KeyManager()
278
+ private_key, public_key, address = key_manager.generate_keypair()
279
+
280
+ # Derived public key should match
281
+ derived_public = key_manager.derive_public_key(private_key)
282
+
283
+ self.assertEqual(derived_public, public_key,
284
+ "Derived public key must match generated public key")
285
+
286
+
287
+ # ============================================================================
288
+ # TEST SUITE 3: ADDRESS SPOOFING PREVENTION
289
+ # ============================================================================
290
+
291
+ class TestAddressSpoofingPrevention(unittest.TestCase):
292
+ """
293
+ Ensure addresses cannot be spoofed - from address must be verified.
294
+ """
295
+
296
+ def setUp(self):
297
+ """Setup test fixtures"""
298
+ self.wallet = LunaWallet()
299
+ self.blockchain = BlockchainManager()
300
+
301
+ def test_address_format_validation(self):
302
+ """Only properly formatted addresses should be accepted"""
303
+ valid_addresses = [
304
+ 'LUN_abc123def456',
305
+ 'LUN_' + 'a' * 30,
306
+ ]
307
+
308
+ invalid_addresses = [
309
+ 'INVALID_abc123',
310
+ 'abc123', # Missing LUN_ prefix
311
+ 'LUN', # Too short
312
+ '', # Empty
313
+ ]
314
+
315
+ for addr in valid_addresses:
316
+ # Should normalize without error
317
+ normalized = self.blockchain._normalize_address(addr)
318
+ self.assertIsNotNone(normalized)
319
+
320
+ for addr in invalid_addresses:
321
+ # Should fail or normalize to empty
322
+ normalized = self.blockchain._normalize_address(addr)
323
+ # Either empty or clearly invalid
324
+ self.assertTrue(len(normalized) == 0 or 'invalid' in normalized.lower() or len(normalized) < 20)
325
+
326
+ def test_from_address_cannot_be_faked(self):
327
+ """From address in transaction must match signing key"""
328
+ wallet1 = LunaWallet()
329
+ wallet1.create_wallet("Wallet1", "pass1")
330
+ wallet1.unlock_wallet(wallet1.current_wallet_address, "pass1")
331
+
332
+ wallet2 = LunaWallet()
333
+ wallet2.create_wallet("Wallet2", "pass2")
334
+
335
+ tx_manager = TransactionManager()
336
+
337
+ # Create transaction from wallet1
338
+ tx = tx_manager.create_transaction(
339
+ from_address=wallet1.address,
340
+ to_address='LUN_RECEIVER',
341
+ amount=100.0,
342
+ private_key=wallet1.private_key,
343
+ transaction_type='transfer'
344
+ )
345
+
346
+ # The from_address should match wallet1
347
+ self.assertEqual(tx['from'], wallet1.address,
348
+ "From address must match the wallet creating it")
349
+
350
+ # Trying to change it should be detectable (hash mismatch)
351
+ original_hash = tx['hash']
352
+ tx['from'] = wallet2.address
353
+ new_hash = tx_manager._calculate_transaction_hash(tx)
354
+
355
+ self.assertNotEqual(original_hash, new_hash,
356
+ "Changing from address should invalidate transaction")
357
+
358
+ def test_address_case_sensitivity(self):
359
+ """Addresses should be case-insensitive for comparison"""
360
+ addr1 = 'LUN_ABC123'
361
+ addr2 = 'lun_abc123'
362
+
363
+ norm1 = self.blockchain._normalize_address(addr1)
364
+ norm2 = self.blockchain._normalize_address(addr2)
365
+
366
+ self.assertEqual(norm1, norm2,
367
+ "Addresses should normalize to same value (case-insensitive)")
368
+
369
+ def test_address_prefix_cannot_be_omitted(self):
370
+ """Addresses must have LUN_ prefix, cannot be spoofed without it"""
371
+ wallet = LunaWallet()
372
+ wallet.create_wallet("Test", "pass")
373
+
374
+ # Address should have prefix
375
+ self.assertTrue(wallet.address.startswith('LUN_') or
376
+ wallet.address.startswith('lun_'),
377
+ "Generated address must have LUN_ prefix")
378
+
379
+ def test_transaction_from_unregistered_address_rejected(self):
380
+ """Transactions from unknown addresses should be tracked/rejected"""
381
+ blockchain = BlockchainManager()
382
+ mempool = MempoolManager()
383
+
384
+ # Transaction from unknown address
385
+ suspicious_tx = {
386
+ 'hash': 'xyz789',
387
+ 'type': 'transfer',
388
+ 'from': 'LUN_UNKNOWN_SPOOFER',
389
+ 'to': 'LUN_TARGET',
390
+ 'amount': 1000000.0,
391
+ 'fee': 0.0,
392
+ 'signature': 'fake_signature',
393
+ 'timestamp': int(time.time())
394
+ }
395
+
396
+ # Validation should fail
397
+ is_valid, msg = mempool._validate_transaction_basic(suspicious_tx)
398
+
399
+ # May pass basic validation but signature won't verify
400
+ # The key is that the blockchain won't credit the unregistered address
401
+ self.assertIn('from', suspicious_tx)
402
+
403
+
404
+ # ============================================================================
405
+ # TEST SUITE 4: DDOS/SPAM PROTECTION
406
+ # ============================================================================
407
+
408
+ class TestDDoSSpamProtection(unittest.TestCase):
409
+ """
410
+ Prevent DDoS, spam, and overwhelming requests.
411
+ Implement rate limiting, transaction throttling, block size limits.
412
+ """
413
+
414
+ def setUp(self):
415
+ """Setup test fixtures"""
416
+ self.mempool = MempoolManager()
417
+ self.blockchain = BlockchainManager()
418
+
419
+ def test_mempool_size_limit(self):
420
+ """Mempool should have maximum size limit"""
421
+ # Check mempool has max size
422
+ self.assertGreater(self.mempool.max_mempool_size, 0,
423
+ "Mempool must have size limit")
424
+ self.assertEqual(self.mempool.max_mempool_size, 10000,
425
+ "Mempool max size should be reasonable (10000)")
426
+
427
+ def test_duplicate_transactions_rejected(self):
428
+ """Duplicate transactions should not increase mempool"""
429
+ tx = {
430
+ 'hash': 'duplicate_test_123',
431
+ 'type': 'transfer',
432
+ 'from': 'LUN_SENDER',
433
+ 'to': 'LUN_RECEIVER',
434
+ 'amount': 100.0,
435
+ 'fee': 0.001,
436
+ 'signature': 'sig',
437
+ 'timestamp': int(time.time())
438
+ }
439
+
440
+ initial_size = len(self.mempool.local_mempool)
441
+
442
+ # Add same transaction twice
443
+ self.mempool.add_transaction(tx)
444
+ size_after_first = len(self.mempool.local_mempool)
445
+
446
+ self.mempool.add_transaction(tx)
447
+ size_after_second = len(self.mempool.local_mempool)
448
+
449
+ # Should only be added once
450
+ self.assertEqual(size_after_first, size_after_second,
451
+ "Duplicate transactions should not be added again")
452
+
453
+ def test_transaction_rate_limiting(self):
454
+ """Single sender should be rate-limited"""
455
+ sender = 'LUN_SPAMMER'
456
+
457
+ # Try to add many transactions from same sender
458
+ transactions_added = 0
459
+
460
+ for i in range(100):
461
+ tx = {
462
+ 'hash': f'spam_tx_{i}',
463
+ 'type': 'transfer',
464
+ 'from': sender,
465
+ 'to': f'LUN_RECEIVER_{i}',
466
+ 'amount': 1.0,
467
+ 'fee': 0.001,
468
+ 'signature': 'sig',
469
+ 'timestamp': int(time.time())
470
+ }
471
+
472
+ success = self.mempool.add_transaction(tx)
473
+ if success:
474
+ transactions_added += 1
475
+
476
+ # Should accept some but not all (rate limiting)
477
+ # At minimum, should have reasonable limit
478
+ self.assertGreater(transactions_added, 0,
479
+ "Should accept some transactions")
480
+ self.assertLess(transactions_added, 100,
481
+ "Should rate-limit spam (not accept all 100)")
482
+
483
+ def test_minimum_fee_requirement(self):
484
+ """Transactions should require minimum fee to prevent spam"""
485
+ tx_no_fee = {
486
+ 'hash': 'no_fee_tx',
487
+ 'type': 'transfer',
488
+ 'from': 'LUN_SENDER',
489
+ 'to': 'LUN_RECEIVER',
490
+ 'amount': 100.0,
491
+ 'fee': 0.0, # No fee!
492
+ 'signature': 'sig',
493
+ 'timestamp': int(time.time())
494
+ }
495
+
496
+ tx_with_fee = {
497
+ 'hash': 'fee_tx',
498
+ 'type': 'transfer',
499
+ 'from': 'LUN_SENDER',
500
+ 'to': 'LUN_RECEIVER',
501
+ 'amount': 100.0,
502
+ 'fee': 0.001, # Has fee
503
+ 'signature': 'sig',
504
+ 'timestamp': int(time.time())
505
+ }
506
+
507
+ # Fee transaction should be preferred
508
+ # (This is a design recommendation)
509
+ # Both may be added, but fee transaction should be prioritized
510
+
511
+ def test_block_size_limit(self):
512
+ """Blocks should have maximum transaction size"""
513
+ # A reasonable block size might be 1MB or 10000 transactions
514
+ # This prevents blocks from being too large
515
+
516
+ # This is more relevant when submitting blocks
517
+ # A block with 1 million transactions should be rejected
518
+ pass
519
+
520
+ def test_timestamp_validation(self):
521
+ """Transactions too far in past/future should be rejected"""
522
+ now = int(time.time())
523
+
524
+ # Transaction from 24 hours in past
525
+ old_tx = {
526
+ 'hash': 'old_tx',
527
+ 'type': 'transfer',
528
+ 'from': 'LUN_SENDER',
529
+ 'to': 'LUN_RECEIVER',
530
+ 'amount': 100.0,
531
+ 'fee': 0.001,
532
+ 'signature': 'sig',
533
+ 'timestamp': now - 86400 # 24 hours ago
534
+ }
535
+
536
+ # Transaction from 5 minutes in future
537
+ future_tx = {
538
+ 'hash': 'future_tx',
539
+ 'type': 'transfer',
540
+ 'from': 'LUN_SENDER',
541
+ 'to': 'LUN_RECEIVER',
542
+ 'amount': 100.0,
543
+ 'fee': 0.001,
544
+ 'signature': 'sig',
545
+ 'timestamp': now + 300 # 5 minutes in future
546
+ }
547
+
548
+ # Validate old transaction
549
+ is_valid_old, msg_old = self.mempool._validate_transaction_basic(old_tx)
550
+ # This may be rejected or accepted depending on design
551
+
552
+ # Validate future transaction
553
+ is_valid_future, msg_future = self.mempool._validate_transaction_basic(future_tx)
554
+
555
+ # Future transactions should be rejected
556
+ self.assertFalse(is_valid_future,
557
+ "Transactions too far in future should be rejected")
558
+
559
+ def test_concurrent_transaction_handling(self):
560
+ """System should handle concurrent transaction submissions safely"""
561
+ results = []
562
+
563
+ def submit_tx(tx_id):
564
+ tx = {
565
+ 'hash': f'concurrent_tx_{tx_id}',
566
+ 'type': 'transfer',
567
+ 'from': 'LUN_SENDER',
568
+ 'to': f'LUN_RECEIVER_{tx_id}',
569
+ 'amount': 1.0,
570
+ 'fee': 0.001,
571
+ 'signature': 'sig',
572
+ 'timestamp': int(time.time())
573
+ }
574
+ success = self.mempool.add_transaction(tx)
575
+ results.append(success)
576
+
577
+ # Submit 50 transactions concurrently
578
+ threads = [threading.Thread(target=submit_tx, args=(i,))
579
+ for i in range(50)]
580
+
581
+ for t in threads:
582
+ t.start()
583
+
584
+ for t in threads:
585
+ t.join()
586
+
587
+ # Should complete without deadlock
588
+ self.assertEqual(len(results), 50,
589
+ "All concurrent submissions should complete")
590
+
591
+ # Should have added most/all (depending on limits)
592
+ successful = sum(results)
593
+ self.assertGreater(successful, 0,
594
+ "Should successfully add some concurrent transactions")
595
+
596
+
597
+ # ============================================================================
598
+ # TEST SUITE 5: MULTI-WALLET STATE MANAGEMENT
599
+ # ============================================================================
600
+
601
+ class TestMultiWalletStateManagement(unittest.TestCase):
602
+ """
603
+ Ensure wallet state is correctly maintained for multiple wallets.
604
+ """
605
+
606
+ def setUp(self):
607
+ """Setup test fixtures"""
608
+ self.wallet = LunaWallet()
609
+ self.state_manager = WalletStateManager()
610
+
611
+ def test_multiple_wallets_register_correctly(self):
612
+ """Multiple wallets should register and track separately"""
613
+ addresses = []
614
+
615
+ for i in range(5):
616
+ self.wallet.create_new_wallet(f"Wallet{i}", f"pass{i}")
617
+ addresses.append(self.wallet.current_wallet_address)
618
+
619
+ # Register with state manager
620
+ self.state_manager.register_wallets(addresses)
621
+
622
+ # Should have all 5 registered
623
+ self.assertEqual(len(self.state_manager.wallet_states), 5,
624
+ "Should register all 5 wallets")
625
+
626
+ def test_wallet_isolation(self):
627
+ """One wallet's transactions should not affect another"""
628
+ wallet1 = LunaWallet()
629
+ wallet2 = LunaWallet()
630
+
631
+ wallet1.create_wallet("W1", "pass1")
632
+ wallet2.create_wallet("W2", "pass2")
633
+
634
+ state_manager = WalletStateManager()
635
+ state_manager.register_wallets([wallet1.address, wallet2.address])
636
+
637
+ # Create transactions for wallet1
638
+ blockchain_txs = {
639
+ wallet1.address: [
640
+ {
641
+ 'hash': 'tx1',
642
+ 'type': 'transfer',
643
+ 'from': wallet1.address,
644
+ 'to': 'LUN_OTHER',
645
+ 'amount': 100.0,
646
+ 'fee': 0.001,
647
+ 'timestamp': int(time.time())
648
+ }
649
+ ],
650
+ wallet2.address: [] # Wallet2 has no transactions
651
+ }
652
+
653
+ mempool_txs = {wallet1.address: [], wallet2.address: []}
654
+
655
+ # Sync
656
+ state_manager.sync_wallets_from_sources(blockchain_txs, mempool_txs)
657
+
658
+ # Wallet1 should have transaction
659
+ w1_state = state_manager.get_wallet_state(wallet1.address)
660
+ self.assertGreater(len(w1_state.confirmed_transactions), 0,
661
+ "Wallet1 should have transactions")
662
+
663
+ # Wallet2 should have none
664
+ w2_state = state_manager.get_wallet_state(wallet2.address)
665
+ self.assertEqual(len(w2_state.confirmed_transactions), 0,
666
+ "Wallet2 should have no transactions")
667
+
668
+ def test_balance_calculation_isolation(self):
669
+ """Balances should be calculated independently per wallet"""
670
+ state_manager = WalletStateManager()
671
+
672
+ addr1 = 'LUN_ADDR1'
673
+ addr2 = 'LUN_ADDR2'
674
+
675
+ state_manager.register_wallets([addr1, addr2])
676
+
677
+ # Give different balances
678
+ blockchain_txs = {
679
+ addr1: [
680
+ {
681
+ 'hash': 'tx1',
682
+ 'type': 'reward',
683
+ 'from': 'network',
684
+ 'to': addr1,
685
+ 'amount': 100.0,
686
+ 'fee': 0.0,
687
+ 'timestamp': int(time.time())
688
+ }
689
+ ],
690
+ addr2: [
691
+ {
692
+ 'hash': 'tx2',
693
+ 'type': 'reward',
694
+ 'from': 'network',
695
+ 'to': addr2,
696
+ 'amount': 50.0,
697
+ 'fee': 0.0,
698
+ 'timestamp': int(time.time())
699
+ }
700
+ ]
701
+ }
702
+
703
+ state_manager.sync_wallets_from_sources(blockchain_txs, {})
704
+
705
+ # Check balances
706
+ bal1 = state_manager.get_balance(addr1)
707
+ bal2 = state_manager.get_balance(addr2)
708
+
709
+ self.assertEqual(bal1['confirmed_balance'], 100.0)
710
+ self.assertEqual(bal2['confirmed_balance'], 50.0,
711
+ "Different wallets should have independent balances")
712
+
713
+
714
+ # ============================================================================
715
+ # TEST SUITE 6: BLOCKCHAIN INTEGRITY
716
+ # ============================================================================
717
+
718
+ class TestBlockchainIntegrity(unittest.TestCase):
719
+ """
720
+ Ensure blockchain cannot be modified or corrupted.
721
+ """
722
+
723
+ def setUp(self):
724
+ """Setup test fixtures"""
725
+ self.blockchain = BlockchainManager()
726
+
727
+ def test_block_hash_immutable(self):
728
+ """Block hash should not change if block is valid"""
729
+ block_data = {
730
+ 'index': 1,
731
+ 'previous_hash': '0' * 64,
732
+ 'timestamp': int(time.time()),
733
+ 'transactions': [],
734
+ 'miner': 'LUN_MINER',
735
+ 'difficulty': 1,
736
+ 'nonce': 12345,
737
+ 'hash': 'a' + '0' * 63
738
+ }
739
+
740
+ original_hash = block_data['hash']
741
+
742
+ # Hash should not change
743
+ self.assertEqual(block_data['hash'], original_hash,
744
+ "Valid block hash should not change")
745
+
746
+ def test_block_modification_detectable(self):
747
+ """Modifying block data should invalidate hash"""
748
+ block_data = {
749
+ 'index': 1,
750
+ 'previous_hash': '0' * 64,
751
+ 'timestamp': int(time.time()),
752
+ 'transactions': [
753
+ {
754
+ 'hash': 'tx1',
755
+ 'type': 'reward',
756
+ 'amount': 5.0,
757
+ 'to': 'LUN_MINER'
758
+ }
759
+ ],
760
+ 'miner': 'LUN_MINER',
761
+ 'difficulty': 5,
762
+ 'nonce': 12345,
763
+ 'hash': 'aaaaa' + '0' * 59
764
+ }
765
+
766
+ # Calculate hash based on contents (excluding hash field)
767
+ block_contents = {k: v for k, v in block_data.items() if k != 'hash'}
768
+ content_str = str(block_contents)
769
+ calculated_hash = hashlib.sha256(content_str.encode()).hexdigest()
770
+
771
+ # Now tamper with transaction
772
+ block_data['transactions'][0]['amount'] = 999.0
773
+
774
+ # Recalculate
775
+ new_contents = {k: v for k, v in block_data.items() if k != 'hash'}
776
+ new_hash = hashlib.sha256(str(new_contents).encode()).hexdigest()
777
+
778
+ # Hashes should not match
779
+ self.assertNotEqual(calculated_hash, new_hash,
780
+ "Block modification should change hash")
781
+
782
+ def test_previous_block_reference_immutable(self):
783
+ """Previous block hash reference should not change"""
784
+ block = {
785
+ 'index': 2,
786
+ 'previous_hash': 'abc123...' + '0' * 55,
787
+ 'timestamp': int(time.time()),
788
+ 'transactions': [],
789
+ 'miner': 'LUN_MINER',
790
+ 'difficulty': 1,
791
+ 'nonce': 12345,
792
+ 'hash': '0' * 64
793
+ }
794
+
795
+ # Previous hash should be immutable
796
+ self.assertEqual(
797
+ block['previous_hash'],
798
+ 'abc123...' + '0' * 55,
799
+ "Previous block reference must match"
800
+ )
801
+
802
+
803
+ # ============================================================================
804
+ # Run All Tests
805
+ # ============================================================================
806
+
807
+ if __name__ == '__main__':
808
+ # Create test suite
809
+ loader = unittest.TestLoader()
810
+ suite = unittest.TestSuite()
811
+
812
+ # Add all test suites
813
+ suite.addTests(loader.loadTestsFromTestCase(TestRewardDifficultyCorrelation))
814
+ suite.addTests(loader.loadTestsFromTestCase(TestTransactionSignatureVerification))
815
+ suite.addTests(loader.loadTestsFromTestCase(TestAddressSpoofingPrevention))
816
+ suite.addTests(loader.loadTestsFromTestCase(TestDDoSSpamProtection))
817
+ suite.addTests(loader.loadTestsFromTestCase(TestMultiWalletStateManagement))
818
+ suite.addTests(loader.loadTestsFromTestCase(TestBlockchainIntegrity))
819
+
820
+ # Run with verbose output
821
+ runner = unittest.TextTestRunner(verbosity=2)
822
+ result = runner.run(suite)
823
+
824
+ # Print summary
825
+ safe_print("\n" + "="*70)
826
+ safe_print("COMPREHENSIVE TEST SUMMARY")
827
+ safe_print("="*70)
828
+ safe_print(f"Tests Run: {result.testsRun}")
829
+ safe_print(f"Failures: {len(result.failures)}")
830
+ safe_print(f"Errors: {len(result.errors)}")
831
+ safe_print(f"Success Rate: {((result.testsRun - len(result.failures) - len(result.errors)) / result.testsRun * 100):.1f}%")
832
+ safe_print("="*70)