lunalib 1.5.2__py3-none-any.whl → 1.6.6__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.
lunalib/core/__init__.py CHANGED
@@ -0,0 +1,14 @@
1
+ from .blockchain import BlockchainManager
2
+ from .wallet import LunaWallet
3
+ from .mempool import MempoolManager
4
+ from .p2p import P2PClient, HybridBlockchainClient
5
+ from .daemon import BlockchainDaemon
6
+
7
+ __all__ = [
8
+ 'BlockchainManager',
9
+ 'LunaWallet',
10
+ 'MempoolManager',
11
+ 'P2PClient',
12
+ 'HybridBlockchainClient',
13
+ 'BlockchainDaemon'
14
+ ]
@@ -736,7 +736,7 @@ class BlockchainManager:
736
736
  transactions.append(enhanced_tx)
737
737
  print(f"⬇️ Found outgoing transaction: {amount} LUN + {fee} fee")
738
738
 
739
- print(f"📊 Scan complete for block #{block.get('index')}: {len(transactions)} transactions found")
739
+ print(f" Scan complete for block #{block.get('index')}: {len(transactions)} transactions found")
740
740
  return transactions
741
741
  def _handle_regular_transfers(self, tx: Dict, address_lower: str) -> Dict:
742
742
  """Handle regular transfer transactions that might be in different formats"""
lunalib/core/daemon.py ADDED
@@ -0,0 +1,391 @@
1
+ # lunalib/core/daemon.py
2
+ import time
3
+ import threading
4
+ from typing import Dict, List, Optional
5
+ import json
6
+ from datetime import datetime
7
+
8
+
9
+ class BlockchainDaemon:
10
+ """
11
+ Primary blockchain daemon that manages the authoritative blockchain state.
12
+ Validates all transactions, manages peer registry, and serves as source of truth.
13
+ """
14
+
15
+ def __init__(self, blockchain_manager, mempool_manager, security_manager=None):
16
+ self.blockchain = blockchain_manager
17
+ self.mempool = mempool_manager
18
+ self.security = security_manager
19
+
20
+ # Initialize difficulty system for validation
21
+ from ..mining.difficulty import DifficultySystem
22
+ self.difficulty_system = DifficultySystem()
23
+
24
+ # Peer registry
25
+ self.peers = {} # {node_id: peer_info}
26
+ self.peer_lock = threading.Lock()
27
+
28
+ # Daemon state
29
+ self.is_running = False
30
+ self.validation_thread = None
31
+ self.cleanup_thread = None
32
+
33
+ # Statistics
34
+ self.stats = {
35
+ 'blocks_validated': 0,
36
+ 'transactions_validated': 0,
37
+ 'peers_registered': 0,
38
+ 'start_time': time.time()
39
+ }
40
+
41
+ def start(self):
42
+ """Start the blockchain daemon"""
43
+ if self.is_running:
44
+ return
45
+
46
+ self.is_running = True
47
+ print("🏛️ Blockchain Daemon starting...")
48
+
49
+ # Start background threads
50
+ self.validation_thread = threading.Thread(target=self._validation_loop, daemon=True)
51
+ self.validation_thread.start()
52
+
53
+ self.cleanup_thread = threading.Thread(target=self._cleanup_loop, daemon=True)
54
+ self.cleanup_thread.start()
55
+
56
+ print("✅ Blockchain Daemon started")
57
+
58
+ def stop(self):
59
+ """Stop the blockchain daemon"""
60
+ self.is_running = False
61
+
62
+ if self.validation_thread:
63
+ self.validation_thread.join(timeout=5)
64
+ if self.cleanup_thread:
65
+ self.cleanup_thread.join(timeout=5)
66
+
67
+ print("🛑 Blockchain Daemon stopped")
68
+
69
+ def register_peer(self, peer_info: Dict) -> Dict:
70
+ """
71
+ Register a new peer node.
72
+ Returns: {'success': bool, 'node_id': str, 'message': str}
73
+ """
74
+ try:
75
+ node_id = peer_info.get('node_id')
76
+ if not node_id:
77
+ return {'success': False, 'message': 'Missing node_id'}
78
+
79
+ with self.peer_lock:
80
+ self.peers[node_id] = {
81
+ 'node_id': node_id,
82
+ 'registered_at': time.time(),
83
+ 'last_seen': time.time(),
84
+ 'capabilities': peer_info.get('capabilities', []),
85
+ 'url': peer_info.get('url'),
86
+ 'version': peer_info.get('version', 'unknown')
87
+ }
88
+
89
+ self.stats['peers_registered'] += 1
90
+
91
+ print(f"👤 New peer registered: {node_id}")
92
+ return {
93
+ 'success': True,
94
+ 'node_id': node_id,
95
+ 'message': 'Peer registered successfully'
96
+ }
97
+
98
+ except Exception as e:
99
+ return {'success': False, 'message': str(e)}
100
+
101
+ def unregister_peer(self, node_id: str) -> Dict:
102
+ """
103
+ Unregister a peer node.
104
+ Returns: {'success': bool, 'message': str}
105
+ """
106
+ try:
107
+ with self.peer_lock:
108
+ if node_id in self.peers:
109
+ del self.peers[node_id]
110
+ print(f"👋 Peer unregistered: {node_id}")
111
+ return {'success': True, 'message': 'Peer unregistered'}
112
+ else:
113
+ return {'success': False, 'message': 'Peer not found'}
114
+ except Exception as e:
115
+ return {'success': False, 'message': str(e)}
116
+
117
+ def get_peer_list(self, exclude_node_id: Optional[str] = None) -> List[Dict]:
118
+ """
119
+ Get list of registered peers.
120
+ exclude_node_id: Optional node ID to exclude from results
121
+ """
122
+ with self.peer_lock:
123
+ peer_list = []
124
+ for node_id, peer_info in self.peers.items():
125
+ if exclude_node_id and node_id == exclude_node_id:
126
+ continue
127
+
128
+ peer_list.append({
129
+ 'node_id': peer_info['node_id'],
130
+ 'url': peer_info.get('url'),
131
+ 'last_seen': peer_info['last_seen'],
132
+ 'capabilities': peer_info.get('capabilities', [])
133
+ })
134
+
135
+ return peer_list
136
+
137
+ def update_peer_heartbeat(self, node_id: str):
138
+ """Update peer's last seen timestamp"""
139
+ with self.peer_lock:
140
+ if node_id in self.peers:
141
+ self.peers[node_id]['last_seen'] = time.time()
142
+
143
+ def validate_block(self, block: Dict) -> Dict:
144
+ """
145
+ Validate a block submitted by a peer or miner.
146
+ Returns: {'valid': bool, 'message': str, 'errors': List[str]}
147
+ """
148
+ errors = []
149
+
150
+ try:
151
+ # Basic structure validation using difficulty system
152
+ is_valid, error_msg = self.difficulty_system.validate_block_structure(block)
153
+ if not is_valid:
154
+ errors.append(error_msg)
155
+ return {'valid': False, 'message': 'Invalid block structure', 'errors': errors}
156
+
157
+ # Validate block index
158
+ latest_block = self.blockchain.get_latest_block()
159
+ if latest_block:
160
+ expected_index = latest_block.get('index', 0) + 1
161
+ if block['index'] != expected_index:
162
+ errors.append(f"Invalid block index: expected {expected_index}, got {block['index']}")
163
+
164
+ # Validate previous hash
165
+ if latest_block and block['previous_hash'] != latest_block.get('hash'):
166
+ expected_hash = latest_block.get('hash', '')
167
+ actual_hash = block.get('previous_hash', '')
168
+ errors.append(f"Previous hash mismatch: expected {expected_hash[:16]}..., got {actual_hash[:16]}...")
169
+
170
+ # Validate hash meets difficulty requirement using difficulty system
171
+ difficulty = block.get('difficulty', 0)
172
+ block_hash = block.get('hash', '')
173
+
174
+ if not self.difficulty_system.validate_block_hash(block_hash, difficulty):
175
+ errors.append(f"Hash doesn't meet difficulty {difficulty} requirement (needs {difficulty} leading zeros)")
176
+
177
+ # Validate exponential reward matches difficulty
178
+ expected_reward = self.difficulty_system.calculate_block_reward(difficulty)
179
+ actual_reward = block.get('reward', 0)
180
+
181
+ # Allow small tolerance for floating point comparison
182
+ 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")
184
+
185
+ # Validate transactions
186
+ for tx in block.get('transactions', []):
187
+ tx_validation = self.validate_transaction(tx)
188
+ if not tx_validation['valid']:
189
+ errors.append(f"Invalid transaction: {tx_validation['message']}")
190
+
191
+ if errors:
192
+ return {'valid': False, 'message': 'Block validation failed', 'errors': errors}
193
+
194
+ self.stats['blocks_validated'] += 1
195
+ print(f"✅ Block #{block['index']} validated: Difficulty {difficulty}, Reward {actual_reward} LKC, Hash {block_hash[:16]}...")
196
+ return {'valid': True, 'message': 'Block is valid', 'errors': []}
197
+
198
+ except Exception as e:
199
+ return {'valid': False, 'message': f'Validation error: {str(e)}', 'errors': [str(e)]}
200
+
201
+ def validate_transaction(self, transaction: Dict) -> Dict:
202
+ """
203
+ Validate a transaction submitted by a peer.
204
+ Returns: {'valid': bool, 'message': str, 'errors': List[str]}
205
+ """
206
+ errors = []
207
+
208
+ try:
209
+ # Basic validation
210
+ tx_type = transaction.get('type')
211
+ if not tx_type:
212
+ errors.append("Missing transaction type")
213
+
214
+ # Type-specific validation
215
+ if tx_type == 'transaction':
216
+ required = ['from', 'to', 'amount', 'timestamp']
217
+ for field in required:
218
+ if field not in transaction:
219
+ errors.append(f"Missing field: {field}")
220
+
221
+ # Validate amount
222
+ amount = transaction.get('amount', 0)
223
+ if amount <= 0:
224
+ errors.append("Invalid amount: must be positive")
225
+
226
+ elif tx_type == 'genesis_bill':
227
+ if 'denomination' not in transaction:
228
+ errors.append("Missing denomination for genesis bill")
229
+
230
+ elif tx_type == 'reward':
231
+ required = ['to', 'amount']
232
+ for field in required:
233
+ if field not in transaction:
234
+ errors.append(f"Missing field: {field}")
235
+
236
+ # Security validation if available
237
+ if self.security and not errors:
238
+ try:
239
+ # Use security manager for advanced validation
240
+ # security_result = self.security.validate_transaction(transaction)
241
+ pass
242
+ except Exception as e:
243
+ errors.append(f"Security validation error: {e}")
244
+
245
+ if errors:
246
+ return {'valid': False, 'message': 'Transaction validation failed', 'errors': errors}
247
+
248
+ self.stats['transactions_validated'] += 1
249
+ return {'valid': True, 'message': 'Transaction is valid', 'errors': []}
250
+
251
+ except Exception as e:
252
+ return {'valid': False, 'message': f'Validation error: {str(e)}', 'errors': [str(e)]}
253
+
254
+ def process_incoming_block(self, block: Dict, from_peer: Optional[str] = None) -> Dict:
255
+ """
256
+ Process an incoming block from P2P network.
257
+ Validates and adds to blockchain if valid.
258
+ """
259
+ try:
260
+ # Validate block
261
+ validation = self.validate_block(block)
262
+
263
+ if not validation['valid']:
264
+ print(f"❌ Invalid block from peer {from_peer}: {validation['message']}")
265
+ return validation
266
+
267
+ # Add to blockchain
268
+ success = self.blockchain.submit_mined_block(block)
269
+
270
+ if success:
271
+ print(f"✅ Block #{block['index']} accepted from peer {from_peer}")
272
+
273
+ # Broadcast to other peers
274
+ self._broadcast_block_to_peers(block, exclude=from_peer)
275
+
276
+ return {'success': True, 'message': 'Block accepted and propagated'}
277
+ else:
278
+ return {'success': False, 'message': 'Block submission failed'}
279
+
280
+ except Exception as e:
281
+ return {'success': False, 'message': f'Processing error: {str(e)}'}
282
+
283
+ def process_incoming_transaction(self, transaction: Dict, from_peer: Optional[str] = None) -> Dict:
284
+ """
285
+ Process an incoming transaction from P2P network.
286
+ Validates and adds to mempool if valid.
287
+ """
288
+ try:
289
+ # Validate transaction
290
+ validation = self.validate_transaction(transaction)
291
+
292
+ if not validation['valid']:
293
+ print(f"❌ Invalid transaction from peer {from_peer}: {validation['message']}")
294
+ return validation
295
+
296
+ # Add to mempool
297
+ # self.mempool.add_transaction(transaction)
298
+ print(f"✅ Transaction accepted from peer {from_peer}")
299
+
300
+ # Broadcast to other peers
301
+ self._broadcast_transaction_to_peers(transaction, exclude=from_peer)
302
+
303
+ return {'success': True, 'message': 'Transaction accepted and propagated'}
304
+
305
+ except Exception as e:
306
+ return {'success': False, 'message': f'Processing error: {str(e)}'}
307
+
308
+ def _broadcast_block_to_peers(self, block: Dict, exclude: Optional[str] = None):
309
+ """Broadcast block to all registered peers except excluded one"""
310
+ with self.peer_lock:
311
+ for node_id, peer_info in self.peers.items():
312
+ if node_id == exclude:
313
+ continue
314
+
315
+ # Send block to peer (implementation depends on transport)
316
+ # This would typically use HTTP POST or WebSocket
317
+ pass
318
+
319
+ def _broadcast_transaction_to_peers(self, transaction: Dict, exclude: Optional[str] = None):
320
+ """Broadcast transaction to all registered peers except excluded one"""
321
+ with self.peer_lock:
322
+ for node_id, peer_info in self.peers.items():
323
+ if node_id == exclude:
324
+ continue
325
+
326
+ # Send transaction to peer
327
+ pass
328
+
329
+ def _validation_loop(self):
330
+ """Background thread for continuous validation"""
331
+ while self.is_running:
332
+ try:
333
+ # Periodic mempool validation
334
+ # Remove invalid transactions from mempool
335
+ time.sleep(30)
336
+
337
+ except Exception as e:
338
+ print(f"❌ Validation loop error: {e}")
339
+ time.sleep(10)
340
+
341
+ def _cleanup_loop(self):
342
+ """Background thread for peer cleanup"""
343
+ while self.is_running:
344
+ try:
345
+ current_time = time.time()
346
+ timeout = 300 # 5 minutes
347
+
348
+ with self.peer_lock:
349
+ inactive_peers = []
350
+ for node_id, peer_info in self.peers.items():
351
+ if current_time - peer_info['last_seen'] > timeout:
352
+ inactive_peers.append(node_id)
353
+
354
+ # Remove inactive peers
355
+ for node_id in inactive_peers:
356
+ del self.peers[node_id]
357
+ print(f"🧹 Removed inactive peer: {node_id}")
358
+
359
+ time.sleep(60) # Check every minute
360
+
361
+ except Exception as e:
362
+ print(f"❌ Cleanup loop error: {e}")
363
+ time.sleep(60)
364
+
365
+ def get_stats(self) -> Dict:
366
+ """Get daemon statistics"""
367
+ uptime = time.time() - self.stats['start_time']
368
+
369
+ with self.peer_lock:
370
+ peer_count = len(self.peers)
371
+
372
+ return {
373
+ 'uptime_seconds': uptime,
374
+ 'blocks_validated': self.stats['blocks_validated'],
375
+ 'transactions_validated': self.stats['transactions_validated'],
376
+ 'peers_registered': self.stats['peers_registered'],
377
+ 'active_peers': peer_count,
378
+ 'mempool_size': len(self.mempool.get_pending_transactions()) if self.mempool else 0
379
+ }
380
+
381
+ def get_blockchain_state(self) -> Dict:
382
+ """Get current blockchain state for peers"""
383
+ latest_block = self.blockchain.get_latest_block()
384
+ height = self.blockchain.get_blockchain_height()
385
+
386
+ return {
387
+ 'height': height,
388
+ 'latest_block': latest_block,
389
+ 'mempool_size': len(self.mempool.get_pending_transactions()) if self.mempool else 0,
390
+ 'timestamp': time.time()
391
+ }