lunalib 1.6.7__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 (54) 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/blockchain.py +182 -2
  9. lunalib/core/daemon.py +108 -5
  10. lunalib/core/mempool.py +1 -0
  11. lunalib/core/wallet.py +856 -492
  12. lunalib/core/wallet_db.py +68 -0
  13. lunalib/gtx/__pycache__/__init__.cpython-310.pyc +0 -0
  14. lunalib/gtx/__pycache__/bill_registry.cpython-310.pyc +0 -0
  15. lunalib/gtx/__pycache__/digital_bill.cpython-310.pyc +0 -0
  16. lunalib/gtx/__pycache__/genesis.cpython-310.pyc +0 -0
  17. lunalib/mining/__pycache__/__init__.cpython-310.pyc +0 -0
  18. lunalib/mining/__pycache__/cuda_manager.cpython-310.pyc +0 -0
  19. lunalib/mining/__pycache__/difficulty.cpython-310.pyc +0 -0
  20. lunalib/mining/__pycache__/miner.cpython-310.pyc +0 -0
  21. lunalib/mining/miner.py +33 -14
  22. lunalib/storage/__pycache__/__init__.cpython-310.pyc +0 -0
  23. lunalib/storage/__pycache__/cache.cpython-310.pyc +0 -0
  24. lunalib/storage/__pycache__/database.cpython-310.pyc +0 -0
  25. lunalib/storage/__pycache__/encryption.cpython-310.pyc +0 -0
  26. lunalib/tests/__pycache__/conftest.cpython-310-pytest-9.0.1.pyc +0 -0
  27. lunalib/tests/__pycache__/test_blockchain.cpython-310-pytest-9.0.1.pyc +0 -0
  28. lunalib/tests/__pycache__/test_crypto.cpython-310-pytest-9.0.1.pyc +0 -0
  29. lunalib/tests/__pycache__/test_gtx.cpython-310-pytest-9.0.1.pyc +0 -0
  30. lunalib/tests/__pycache__/test_mining.cpython-310-pytest-9.0.1.pyc +0 -0
  31. lunalib/tests/__pycache__/test_storage.cpython-310-pytest-9.0.1.pyc +0 -0
  32. lunalib/tests/__pycache__/test_transactions.cpython-310-pytest-9.0.1.pyc +0 -0
  33. lunalib/tests/__pycache__/test_wallet.cpython-310-pytest-9.0.1.pyc +0 -0
  34. lunalib/tests/conftest.py +41 -0
  35. lunalib/tests/init.py +0 -0
  36. lunalib/tests/integration/__pycache__/test_integration.cpython-310-pytest-9.0.1.pyc +0 -0
  37. lunalib/tests/integration/test_integration.py +62 -0
  38. lunalib/tests/test_blockchain.py +34 -0
  39. lunalib/tests/test_crypto.py +42 -0
  40. lunalib/tests/test_gtx.py +135 -0
  41. lunalib/tests/test_mining.py +244 -0
  42. lunalib/tests/test_security_suite.py +832 -0
  43. lunalib/tests/test_storage.py +84 -0
  44. lunalib/tests/test_transactions.py +103 -0
  45. lunalib/tests/test_wallet.py +91 -0
  46. lunalib/transactions/__pycache__/__init__.cpython-310.pyc +0 -0
  47. lunalib/transactions/__pycache__/security.cpython-310.pyc +0 -0
  48. lunalib/transactions/__pycache__/transactions.cpython-310.pyc +0 -0
  49. lunalib/transactions/__pycache__/validator.cpython-310.pyc +0 -0
  50. {lunalib-1.6.7.dist-info → lunalib-1.7.9.dist-info}/METADATA +2 -1
  51. lunalib-1.7.9.dist-info/RECORD +77 -0
  52. lunalib-1.6.7.dist-info/RECORD +0 -33
  53. {lunalib-1.6.7.dist-info → lunalib-1.7.9.dist-info}/WHEEL +0 -0
  54. {lunalib-1.6.7.dist-info → lunalib-1.7.9.dist-info}/top_level.txt +0 -0
lunalib/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ .venv
2
+ build
3
+ .__pycache__
@@ -4,17 +4,23 @@ from ..storage.cache import BlockchainCache
4
4
  import requests
5
5
  import time
6
6
  import json
7
- from typing import Dict, List, Optional, Tuple
7
+ import asyncio
8
+ import threading
9
+ from concurrent.futures import ThreadPoolExecutor, Future
10
+ from typing import Dict, List, Optional, Tuple, Callable
8
11
 
9
12
 
10
13
  class BlockchainManager:
11
14
  """Manages blockchain interactions and scanning with transaction broadcasting"""
12
15
 
13
- def __init__(self, endpoint_url="https://bank.linglin.art"):
16
+ def __init__(self, endpoint_url="https://bank.linglin.art", max_workers=10):
14
17
  self.endpoint_url = endpoint_url.rstrip('/')
15
18
  self.cache = BlockchainCache()
16
19
  self.network_connected = False
17
20
  self._stop_events = [] # Track background monitors so they can be stopped
21
+ self.executor = ThreadPoolExecutor(max_workers=max_workers, thread_name_prefix="BlockchainWorker")
22
+ self._async_tasks = {} # Track async tasks by ID
23
+ self._task_callbacks = {} # Callbacks for task completion
18
24
 
19
25
  # ------------------------------------------------------------------
20
26
  # Address helpers
@@ -26,6 +32,30 @@ class BlockchainManager:
26
32
  addr_str = str(addr).strip("'\" ").lower()
27
33
  return addr_str[4:] if addr_str.startswith('lun_') else addr_str
28
34
 
35
+ def broadcast_transaction_async(self, transaction: Dict, callback: Callable = None) -> str:
36
+ """Async version: Broadcast transaction in background thread
37
+
38
+ Returns: task_id that can be used to check status
39
+ """
40
+ task_id = f"broadcast_{transaction.get('hash', 'unknown')}_{int(time.time())}"
41
+
42
+ def _broadcast_task():
43
+ try:
44
+ success, message = self.broadcast_transaction(transaction)
45
+ if callback:
46
+ callback(success=success, result=message, error=None if success else message)
47
+ return (success, message)
48
+ except Exception as e:
49
+ print(f"❌ Async broadcast error: {e}")
50
+ if callback:
51
+ callback(success=False, result=None, error=str(e))
52
+ return (False, str(e))
53
+
54
+ future = self.executor.submit(_broadcast_task)
55
+ self._async_tasks[task_id] = future
56
+ print(f"🔄 Started async broadcast: {task_id}")
57
+ return task_id
58
+
29
59
  def broadcast_transaction(self, transaction: Dict) -> Tuple[bool, str]:
30
60
  """Broadcast transaction to mempool with enhanced error handling"""
31
61
  try:
@@ -210,6 +240,30 @@ class BlockchainManager:
210
240
 
211
241
  return None
212
242
 
243
+ def get_blocks_range_async(self, start_height: int, end_height: int, callback: Callable = None) -> str:
244
+ """Async version: Get range of blocks in background thread
245
+
246
+ Returns: task_id that can be used to check status
247
+ """
248
+ task_id = f"blocks_range_{start_height}_{end_height}_{int(time.time())}"
249
+
250
+ def _fetch_task():
251
+ try:
252
+ result = self.get_blocks_range(start_height, end_height)
253
+ if callback:
254
+ callback(success=True, result=result, error=None)
255
+ return result
256
+ except Exception as e:
257
+ print(f"❌ Async blocks fetch error: {e}")
258
+ if callback:
259
+ callback(success=False, result=None, error=str(e))
260
+ return None
261
+
262
+ future = self.executor.submit(_fetch_task)
263
+ self._async_tasks[task_id] = future
264
+ print(f"🔄 Started async blocks fetch: {task_id}")
265
+ return task_id
266
+
213
267
  def get_blocks_range(self, start_height: int, end_height: int) -> List[Dict]:
214
268
  """Get range of blocks"""
215
269
  blocks = []
@@ -286,6 +340,31 @@ class BlockchainManager:
286
340
 
287
341
  print(f"[SCAN] Found {len(transactions)} total transactions for {address}")
288
342
  return transactions
343
+
344
+ def scan_transactions_for_address_async(self, address: str, callback: Callable = None,
345
+ start_height: int = 0, end_height: int = None) -> str:
346
+ """Async version: Scan blockchain in background thread, call callback when done
347
+
348
+ Returns: task_id that can be used to check status
349
+ """
350
+ task_id = f"scan_{address}_{int(time.time())}"
351
+
352
+ def _scan_task():
353
+ try:
354
+ result = self.scan_transactions_for_address(address, start_height, end_height)
355
+ if callback:
356
+ callback(success=True, result=result, error=None)
357
+ return result
358
+ except Exception as e:
359
+ print(f"❌ Async scan error: {e}")
360
+ if callback:
361
+ callback(success=False, result=None, error=str(e))
362
+ return None
363
+
364
+ future = self.executor.submit(_scan_task)
365
+ self._async_tasks[task_id] = future
366
+ print(f"🔄 Started async scan task: {task_id}")
367
+ return task_id
289
368
 
290
369
  def scan_transactions_for_addresses(self, addresses: List[str], start_height: int = 0, end_height: int = None) -> Dict[str, List[Dict]]:
291
370
  """Scan the blockchain once for multiple addresses (rewards and transfers)."""
@@ -328,7 +407,84 @@ class BlockchainManager:
328
407
  print(f" - {addr}: {len(results[addr])} transactions")
329
408
 
330
409
  return results
410
+
411
+ def scan_transactions_for_addresses_async(self, addresses: List[str], callback: Callable = None,
412
+ start_height: int = 0, end_height: int = None) -> str:
413
+ """Async version: Scan blockchain for multiple addresses in background thread
414
+
415
+ Returns: task_id that can be used to check status
416
+ """
417
+ task_id = f"multi_scan_{int(time.time())}"
418
+
419
+ def _scan_task():
420
+ try:
421
+ result = self.scan_transactions_for_addresses(addresses, start_height, end_height)
422
+ if callback:
423
+ callback(success=True, result=result, error=None)
424
+ return result
425
+ except Exception as e:
426
+ print(f"❌ Async multi-scan error: {e}")
427
+ if callback:
428
+ callback(success=False, result=None, error=str(e))
429
+ return None
430
+
431
+ future = self.executor.submit(_scan_task)
432
+ self._async_tasks[task_id] = future
433
+ print(f"🔄 Started async multi-scan task: {task_id}")
434
+ return task_id
331
435
 
436
+ def get_task_status(self, task_id: str) -> Dict:
437
+ """Check status of an async task
438
+
439
+ Returns: {'status': 'running'|'completed'|'failed'|'not_found', 'result': any, 'error': str}
440
+ """
441
+ if task_id not in self._async_tasks:
442
+ return {'status': 'not_found', 'result': None, 'error': 'Task not found'}
443
+
444
+ future = self._async_tasks[task_id]
445
+
446
+ if future.running():
447
+ return {'status': 'running', 'result': None, 'error': None}
448
+ elif future.done():
449
+ try:
450
+ result = future.result(timeout=0)
451
+ return {'status': 'completed', 'result': result, 'error': None}
452
+ except Exception as e:
453
+ return {'status': 'failed', 'result': None, 'error': str(e)}
454
+ else:
455
+ return {'status': 'pending', 'result': None, 'error': None}
456
+
457
+ def cancel_task(self, task_id: str) -> bool:
458
+ """Cancel a running async task
459
+
460
+ Returns: True if cancelled, False otherwise
461
+ """
462
+ if task_id in self._async_tasks:
463
+ future = self._async_tasks[task_id]
464
+ if future.cancel():
465
+ del self._async_tasks[task_id]
466
+ print(f"✅ Task cancelled: {task_id}")
467
+ return True
468
+ return False
469
+
470
+ def get_active_tasks(self) -> List[str]:
471
+ """Get list of active task IDs"""
472
+ return [task_id for task_id, future in self._async_tasks.items() if future.running()]
473
+
474
+ def cleanup_completed_tasks(self):
475
+ """Remove completed tasks from tracking"""
476
+ completed = [task_id for task_id, future in self._async_tasks.items() if future.done()]
477
+ for task_id in completed:
478
+ del self._async_tasks[task_id]
479
+ if completed:
480
+ print(f"🧹 Cleaned up {len(completed)} completed tasks")
481
+
482
+ def shutdown(self):
483
+ """Shutdown the thread pool executor"""
484
+ print("🛑 Shutting down BlockchainManager executor...")
485
+ self.executor.shutdown(wait=True)
486
+ print("✅ Executor shutdown complete")
487
+
332
488
  def monitor_addresses(self, addresses: List[str], on_update, poll_interval: int = 15):
333
489
  """Start background monitor for addresses; returns a stop event."""
334
490
  import threading
@@ -379,6 +535,30 @@ class BlockchainManager:
379
535
  thread.start()
380
536
  return stop_event
381
537
 
538
+ def submit_mined_block_async(self, block_data: Dict, callback: Callable = None) -> str:
539
+ """Async version: Submit mined block in background thread
540
+
541
+ Returns: task_id that can be used to check status
542
+ """
543
+ task_id = f"submit_block_{block_data.get('index', 'unknown')}_{int(time.time())}"
544
+
545
+ def _submit_task():
546
+ try:
547
+ success = self.submit_mined_block(block_data)
548
+ if callback:
549
+ callback(success=success, result=block_data if success else None, error=None if success else "Submission failed")
550
+ return success
551
+ except Exception as e:
552
+ print(f"❌ Async block submission error: {e}")
553
+ if callback:
554
+ callback(success=False, result=None, error=str(e))
555
+ return False
556
+
557
+ future = self.executor.submit(_submit_task)
558
+ self._async_tasks[task_id] = future
559
+ print(f"🔄 Started async block submission: {task_id}")
560
+ return task_id
561
+
382
562
  def submit_mined_block(self, block_data: Dict) -> bool:
383
563
  """Submit a mined block to the network with built-in validation"""
384
564
  try:
lunalib/core/daemon.py CHANGED
@@ -1,7 +1,8 @@
1
1
  # lunalib/core/daemon.py
2
2
  import time
3
3
  import threading
4
- from typing import Dict, List, Optional
4
+ from concurrent.futures import ThreadPoolExecutor
5
+ from typing import Dict, List, Optional, Callable
5
6
  import json
6
7
  from datetime import datetime
7
8
 
@@ -12,7 +13,7 @@ class BlockchainDaemon:
12
13
  Validates all transactions, manages peer registry, and serves as source of truth.
13
14
  """
14
15
 
15
- def __init__(self, blockchain_manager, mempool_manager, security_manager=None):
16
+ def __init__(self, blockchain_manager, mempool_manager, security_manager=None, max_workers=5):
16
17
  self.blockchain = blockchain_manager
17
18
  self.mempool = mempool_manager
18
19
  self.security = security_manager
@@ -21,6 +22,10 @@ class BlockchainDaemon:
21
22
  from ..mining.difficulty import DifficultySystem
22
23
  self.difficulty_system = DifficultySystem()
23
24
 
25
+ # Thread pool for async operations
26
+ self.executor = ThreadPoolExecutor(max_workers=max_workers, thread_name_prefix="DaemonWorker")
27
+ self._async_tasks = {} # Track async tasks
28
+
24
29
  # Peer registry
25
30
  self.peers = {} # {node_id: peer_info}
26
31
  self.peer_lock = threading.Lock()
@@ -64,6 +69,10 @@ class BlockchainDaemon:
64
69
  if self.cleanup_thread:
65
70
  self.cleanup_thread.join(timeout=5)
66
71
 
72
+ # Shutdown executor
73
+ print("🛑 Shutting down daemon executor...")
74
+ self.executor.shutdown(wait=True)
75
+
67
76
  print("🛑 Blockchain Daemon stopped")
68
77
 
69
78
  def register_peer(self, peer_info: Dict) -> Dict:
@@ -140,6 +149,30 @@ class BlockchainDaemon:
140
149
  if node_id in self.peers:
141
150
  self.peers[node_id]['last_seen'] = time.time()
142
151
 
152
+ def validate_block_async(self, block: Dict, callback: Callable = None) -> str:
153
+ """Async version: Validate block in background thread
154
+
155
+ Returns: task_id that can be used to check status
156
+ """
157
+ task_id = f"validate_block_{block.get('index', 'unknown')}_{int(time.time())}"
158
+
159
+ def _validate_task():
160
+ try:
161
+ result = self.validate_block(block)
162
+ if callback:
163
+ callback(success=result['valid'], result=result, error=None if result['valid'] else result['message'])
164
+ return result
165
+ except Exception as e:
166
+ print(f"❌ Async validation error: {e}")
167
+ if callback:
168
+ callback(success=False, result=None, error=str(e))
169
+ return {'valid': False, 'message': str(e), 'errors': [str(e)]}
170
+
171
+ future = self.executor.submit(_validate_task)
172
+ self._async_tasks[task_id] = future
173
+ print(f"🔄 Started async block validation: {task_id}")
174
+ return task_id
175
+
143
176
  def validate_block(self, block: Dict) -> Dict:
144
177
  """
145
178
  Validate a block submitted by a peer or miner.
@@ -174,13 +207,26 @@ class BlockchainDaemon:
174
207
  if not self.difficulty_system.validate_block_hash(block_hash, difficulty):
175
208
  errors.append(f"Hash doesn't meet difficulty {difficulty} requirement (needs {difficulty} leading zeros)")
176
209
 
177
- # Validate exponential reward matches difficulty
178
- expected_reward = self.difficulty_system.calculate_block_reward(difficulty)
210
+ # Validate reward matches difficulty
211
+ # Empty blocks use LINEAR system (difficulty = reward)
212
+ # Regular blocks use EXPONENTIAL system (10^(difficulty-1))
213
+ transactions = block.get('transactions', [])
214
+ is_empty_block = len(transactions) == 1 and transactions[0].get('is_empty_block', False)
215
+
216
+ if is_empty_block:
217
+ # Empty block: linear reward (difficulty 1 = 1 LKC, difficulty 2 = 2 LKC, etc.)
218
+ expected_reward = float(difficulty)
219
+ reward_type = "Linear"
220
+ else:
221
+ # Regular block: exponential reward (10^(difficulty-1))
222
+ expected_reward = self.difficulty_system.calculate_block_reward(difficulty)
223
+ reward_type = "Exponential"
224
+
179
225
  actual_reward = block.get('reward', 0)
180
226
 
181
227
  # Allow small tolerance for floating point comparison
182
228
  if abs(actual_reward - expected_reward) > 0.01:
183
- errors.append(f"Reward mismatch: expected {expected_reward} LKC for difficulty {difficulty}, got {actual_reward} LKC")
229
+ errors.append(f"Reward mismatch ({reward_type}): expected {expected_reward} LKC for difficulty {difficulty}, got {actual_reward} LKC")
184
230
 
185
231
  # Validate transactions
186
232
  for tx in block.get('transactions', []):
@@ -251,6 +297,30 @@ class BlockchainDaemon:
251
297
  except Exception as e:
252
298
  return {'valid': False, 'message': f'Validation error: {str(e)}', 'errors': [str(e)]}
253
299
 
300
+ def process_incoming_block_async(self, block: Dict, from_peer: Optional[str] = None, callback: Callable = None) -> str:
301
+ """Async version: Process incoming block in background thread
302
+
303
+ Returns: task_id that can be used to check status
304
+ """
305
+ task_id = f"process_block_{block.get('index', 'unknown')}_{int(time.time())}"
306
+
307
+ def _process_task():
308
+ try:
309
+ result = self.process_incoming_block(block, from_peer)
310
+ if callback:
311
+ callback(success=result.get('success', False), result=result, error=None if result.get('success') else result.get('message'))
312
+ return result
313
+ except Exception as e:
314
+ print(f"❌ Async block processing error: {e}")
315
+ if callback:
316
+ callback(success=False, result=None, error=str(e))
317
+ return {'success': False, 'message': str(e)}
318
+
319
+ future = self.executor.submit(_process_task)
320
+ self._async_tasks[task_id] = future
321
+ print(f"🔄 Started async block processing: {task_id}")
322
+ return task_id
323
+
254
324
  def process_incoming_block(self, block: Dict, from_peer: Optional[str] = None) -> Dict:
255
325
  """
256
326
  Process an incoming block from P2P network.
@@ -362,6 +432,39 @@ class BlockchainDaemon:
362
432
  print(f"❌ Cleanup loop error: {e}")
363
433
  time.sleep(60)
364
434
 
435
+ def get_task_status(self, task_id: str) -> Dict:
436
+ """Check status of an async task
437
+
438
+ Returns: {'status': 'running'|'completed'|'failed'|'not_found', 'result': any, 'error': str}
439
+ """
440
+ if task_id not in self._async_tasks:
441
+ return {'status': 'not_found', 'result': None, 'error': 'Task not found'}
442
+
443
+ future = self._async_tasks[task_id]
444
+
445
+ if future.running():
446
+ return {'status': 'running', 'result': None, 'error': None}
447
+ elif future.done():
448
+ try:
449
+ result = future.result(timeout=0)
450
+ return {'status': 'completed', 'result': result, 'error': None}
451
+ except Exception as e:
452
+ return {'status': 'failed', 'result': None, 'error': str(e)}
453
+ else:
454
+ return {'status': 'pending', 'result': None, 'error': None}
455
+
456
+ def get_active_tasks(self) -> List[str]:
457
+ """Get list of active task IDs"""
458
+ return [task_id for task_id, future in self._async_tasks.items() if future.running()]
459
+
460
+ def cleanup_completed_tasks(self):
461
+ """Remove completed tasks from tracking"""
462
+ completed = [task_id for task_id, future in self._async_tasks.items() if future.done()]
463
+ for task_id in completed:
464
+ del self._async_tasks[task_id]
465
+ if completed:
466
+ print(f"🧹 Cleaned up {len(completed)} completed tasks")
467
+
365
468
  def get_stats(self) -> Dict:
366
469
  """Get daemon statistics"""
367
470
  uptime = time.time() - self.stats['start_time']
lunalib/core/mempool.py CHANGED
@@ -211,6 +211,7 @@ class MempoolManager:
211
211
  to_norm = self._normalize_address(tx.get('to') or tx.get('receiver'))
212
212
  if target_norm and (from_norm == target_norm or to_norm == target_norm):
213
213
  transactions.append(tx)
214
+ print(f"[MEMPOOL] get_pending_transactions for {address}: {len(transactions)} txs returned")
214
215
  return transactions
215
216
 
216
217
  def get_pending_transactions_for_addresses(self, addresses: List[str], fetch_remote: bool = True) -> Dict[str, List[Dict]]: