lunalib 1.1.0__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/__init__.py ADDED
@@ -0,0 +1,19 @@
1
+ """
2
+ Luna Library - Complete cryptocurrency wallet and mining system
3
+ """
4
+ import os
5
+
6
+ from .core.wallet import LunaWallet
7
+ from .mining.miner import GenesisMiner
8
+ from .gtx.genesis import GTXGenesis
9
+ from .transactions.transactions import TransactionManager
10
+ from .core.blockchain import BlockchainManager
11
+
12
+ __version__ = "1.0.0"
13
+ __all__ = [
14
+ 'LunaWallet',
15
+ 'GenesisMiner',
16
+ 'GTXGenesis',
17
+ 'TransactionManager',
18
+ 'BlockchainManager'
19
+ ]
lunalib/cli.py ADDED
@@ -0,0 +1,18 @@
1
+ # lunalib/cli.py
2
+ import argparse
3
+ from .luna_lib import LunaLib
4
+
5
+ def main():
6
+ """Command line interface for LunaLib"""
7
+ parser = argparse.ArgumentParser(description="LunaLib Cryptocurrency Wallet")
8
+ parser.add_argument('--version', action='store_true', help='Show version')
9
+
10
+ args = parser.parse_args()
11
+
12
+ if args.version:
13
+ print(f"LunaLib v{LunaLib.get_version()}")
14
+ else:
15
+ print("LunaLib - Use 'luna-wallet --help' for options")
16
+
17
+ if __name__ == "__main__":
18
+ main()
File without changes
@@ -0,0 +1,451 @@
1
+ # blockchain.py - Updated version
2
+
3
+ from lunalib.storage.cache import BlockchainCache
4
+ import requests
5
+ import time
6
+ import json
7
+ from typing import Dict, List, Optional, Tuple
8
+
9
+
10
+ class BlockchainManager:
11
+ """Manages blockchain interactions and scanning with transaction broadcasting"""
12
+
13
+ def __init__(self, endpoint_url="https://bank.linglin.art"):
14
+ self.endpoint_url = endpoint_url.rstrip('/')
15
+ self.cache = BlockchainCache()
16
+ self.network_connected = False
17
+
18
+ def broadcast_transaction(self, transaction: Dict) -> Tuple[bool, str]:
19
+ """Broadcast transaction to mempool with enhanced error handling"""
20
+ try:
21
+ print(f"🔄 Broadcasting transaction to {self.endpoint_url}/mempool/add")
22
+ print(f" Transaction type: {transaction.get('type', 'unknown')}")
23
+ print(f" From: {transaction.get('from', 'unknown')}")
24
+ print(f" To: {transaction.get('to', 'unknown')}")
25
+ print(f" Amount: {transaction.get('amount', 'unknown')}")
26
+
27
+ # Ensure transaction has required fields
28
+ if not self._validate_transaction_before_broadcast(transaction):
29
+ return False, "Transaction validation failed"
30
+
31
+ response = requests.post(
32
+ f'{self.endpoint_url}/mempool/add',
33
+ json=transaction,
34
+ headers={'Content-Type': 'application/json'},
35
+ timeout=30
36
+ )
37
+
38
+ print(f"📡 Broadcast response: HTTP {response.status_code}")
39
+
40
+ if response.status_code in [200, 201]:
41
+ result = response.json()
42
+ if result.get('success'):
43
+ tx_hash = result.get('transaction_hash', transaction.get('hash', 'unknown'))
44
+ print(f"✅ Transaction broadcast successful! Hash: {tx_hash}")
45
+ return True, f"Transaction broadcast successfully: {tx_hash}"
46
+ else:
47
+ error_msg = result.get('error', 'Unknown error from server')
48
+ print(f"❌ Broadcast failed: {error_msg}")
49
+ return False, f"Server rejected transaction: {error_msg}"
50
+ else:
51
+ error_msg = f"HTTP {response.status_code}: {response.text}"
52
+ print(f"❌ Network error: {error_msg}")
53
+ return False, error_msg
54
+
55
+ except requests.exceptions.ConnectionError:
56
+ error_msg = "Cannot connect to blockchain server"
57
+ print(f"❌ {error_msg}")
58
+ return False, error_msg
59
+ except requests.exceptions.Timeout:
60
+ error_msg = "Broadcast request timed out"
61
+ print(f"❌ {error_msg}")
62
+ return False, error_msg
63
+ except Exception as e:
64
+ error_msg = f"Unexpected error: {str(e)}"
65
+ print(f"❌ {error_msg}")
66
+ return False, error_msg
67
+
68
+ def _validate_transaction_before_broadcast(self, transaction: Dict) -> bool:
69
+ """Validate transaction before broadcasting"""
70
+ required_fields = ['type', 'from', 'to', 'amount', 'timestamp', 'hash', 'signature']
71
+
72
+ for field in required_fields:
73
+ if field not in transaction:
74
+ print(f"❌ Missing required field: {field}")
75
+ return False
76
+
77
+ # Validate addresses
78
+ if not transaction['from'].startswith('LUN_'):
79
+ print(f"❌ Invalid from address format: {transaction['from']}")
80
+ return False
81
+
82
+ if not transaction['to'].startswith('LUN_'):
83
+ print(f"❌ Invalid to address format: {transaction['to']}")
84
+ return False
85
+
86
+ # Validate amount
87
+ try:
88
+ amount = float(transaction['amount'])
89
+ if amount <= 0:
90
+ print(f"❌ Invalid amount: {amount}")
91
+ return False
92
+ except (ValueError, TypeError):
93
+ print(f"❌ Invalid amount format: {transaction['amount']}")
94
+ return False
95
+
96
+ # Validate signature
97
+ if not transaction.get('signature') or len(transaction['signature']) < 10:
98
+ print(f"❌ Invalid or missing signature")
99
+ return False
100
+
101
+ # Validate hash
102
+ if not transaction.get('hash') or len(transaction['hash']) < 10:
103
+ print(f"❌ Invalid or missing transaction hash")
104
+ return False
105
+
106
+ print("✅ Transaction validation passed")
107
+ return True
108
+
109
+ def get_transaction_status(self, tx_hash: str) -> Dict:
110
+ """Check transaction status (pending/confirmed)"""
111
+ try:
112
+ # First check mempool for pending transactions
113
+ mempool_txs = self.get_mempool()
114
+ for tx in mempool_txs:
115
+ if tx.get('hash') == tx_hash:
116
+ return {
117
+ 'status': 'pending',
118
+ 'message': 'Transaction is in mempool waiting to be mined',
119
+ 'confirmations': 0
120
+ }
121
+
122
+ # Then check blockchain for confirmed transactions
123
+ current_height = self.get_blockchain_height()
124
+ for height in range(max(0, current_height - 100), current_height + 1):
125
+ block = self.get_block(height)
126
+ if block:
127
+ for tx in block.get('transactions', []):
128
+ if tx.get('hash') == tx_hash:
129
+ confirmations = current_height - height + 1
130
+ return {
131
+ 'status': 'confirmed',
132
+ 'message': f'Transaction confirmed in block {height}',
133
+ 'confirmations': confirmations,
134
+ 'block_height': height
135
+ }
136
+
137
+ return {
138
+ 'status': 'unknown',
139
+ 'message': 'Transaction not found in mempool or recent blocks'
140
+ }
141
+
142
+ except Exception as e:
143
+ return {
144
+ 'status': 'error',
145
+ 'message': f'Error checking transaction status: {str(e)}'
146
+ }
147
+
148
+ def get_blockchain_height(self) -> int:
149
+ """Get current blockchain height - FIXED VERSION"""
150
+ try:
151
+ # Get the actual latest block to determine height
152
+ response = requests.get(f'{self.endpoint_url}/blockchain/blocks', timeout=10)
153
+ if response.status_code == 200:
154
+ data = response.json()
155
+ blocks = data.get('blocks', [])
156
+
157
+ if blocks:
158
+ # The height is the index of the latest block
159
+ latest_block = blocks[-1]
160
+ latest_index = latest_block.get('index', len(blocks) - 1)
161
+ print(f"🔍 Server has {len(blocks)} blocks, latest index: {latest_index}")
162
+ print(f"🔍 Latest block hash: {latest_block.get('hash', '')[:32]}...")
163
+ return latest_index
164
+ return 0
165
+
166
+ except Exception as e:
167
+ print(f"Blockchain height error: {e}")
168
+
169
+ return 0
170
+
171
+ def get_latest_block(self) -> Optional[Dict]:
172
+ """Get the actual latest block from server"""
173
+ try:
174
+ response = requests.get(f'{self.endpoint_url}/blockchain/blocks', timeout=10)
175
+ if response.status_code == 200:
176
+ data = response.json()
177
+ blocks = data.get('blocks', [])
178
+ if blocks:
179
+ return blocks[-1]
180
+ except Exception as e:
181
+ print(f"Get latest block error: {e}")
182
+ return None
183
+
184
+ def get_block(self, height: int) -> Optional[Dict]:
185
+ """Get block by height"""
186
+ # Check cache first
187
+ cached_block = self.cache.get_block(height)
188
+ if cached_block:
189
+ return cached_block
190
+
191
+ try:
192
+ response = requests.get(f'{self.endpoint_url}/blockchain/block/{height}', timeout=10)
193
+ if response.status_code == 200:
194
+ block = response.json()
195
+ self.cache.save_block(height, block.get('hash', ''), block)
196
+ return block
197
+ except Exception as e:
198
+ print(f"Get block error: {e}")
199
+
200
+ return None
201
+
202
+ def get_blocks_range(self, start_height: int, end_height: int) -> List[Dict]:
203
+ """Get range of blocks"""
204
+ blocks = []
205
+
206
+ # Check cache first
207
+ cached_blocks = self.cache.get_block_range(start_height, end_height)
208
+ if len(cached_blocks) == (end_height - start_height + 1):
209
+ return cached_blocks
210
+
211
+ try:
212
+ response = requests.get(
213
+ f'{self.endpoint_url}/blockchain/range?start={start_height}&end={end_height}',
214
+ timeout=30
215
+ )
216
+ if response.status_code == 200:
217
+ blocks = response.json().get('blocks', [])
218
+ # Cache the blocks
219
+ for block in blocks:
220
+ height = block.get('index', 0)
221
+ self.cache.save_block(height, block.get('hash', ''), block)
222
+ else:
223
+ # Fallback: get blocks individually
224
+ for height in range(start_height, end_height + 1):
225
+ block = self.get_block(height)
226
+ if block:
227
+ blocks.append(block)
228
+ time.sleep(0.01) # Be nice to the API
229
+
230
+ except Exception as e:
231
+ print(f"Get blocks range error: {e}")
232
+
233
+ return blocks
234
+
235
+ def get_mempool(self) -> List[Dict]:
236
+ """Get current mempool transactions"""
237
+ try:
238
+ response = requests.get(f'{self.endpoint_url}/mempool', timeout=10)
239
+ if response.status_code == 200:
240
+ return response.json()
241
+ except Exception as e:
242
+ print(f"Mempool error: {e}")
243
+
244
+ return []
245
+
246
+ def check_network_connection(self) -> bool:
247
+ """Check if network is accessible"""
248
+ try:
249
+ response = requests.get(f'{self.endpoint_url}/system/health', timeout=5)
250
+ self.network_connected = response.status_code == 200
251
+ return self.network_connected
252
+ except:
253
+ self.network_connected = False
254
+ return False
255
+
256
+ def scan_transactions_for_address(self, address: str, start_height: int = 0, end_height: int = None) -> List[Dict]:
257
+ """Scan blockchain for transactions involving an address"""
258
+ if end_height is None:
259
+ end_height = self.get_blockchain_height()
260
+
261
+ transactions = []
262
+
263
+ # Scan in batches for efficiency
264
+ batch_size = 100
265
+ for batch_start in range(start_height, end_height + 1, batch_size):
266
+ batch_end = min(batch_start + batch_size - 1, end_height)
267
+ blocks = self.get_blocks_range(batch_start, batch_end)
268
+
269
+ for block in blocks:
270
+ block_transactions = self._find_address_transactions(block, address)
271
+ transactions.extend(block_transactions)
272
+
273
+ return transactions
274
+
275
+ def submit_mined_block(self, block_data: Dict) -> bool:
276
+ """Submit a mined block to the network with built-in validation"""
277
+ try:
278
+ print(f"🔄 Preparing to submit block #{block_data.get('index')}...")
279
+
280
+ # Step 1: Validate block structure before submission
281
+ validation_result = self._validate_block_structure(block_data)
282
+ if not validation_result['valid']:
283
+ print(f"❌ Block validation failed:")
284
+ for issue in validation_result['issues']:
285
+ print(f" - {issue}")
286
+ return False
287
+
288
+ print(f"✅ Block structure validation passed")
289
+ print(f" Block #{block_data.get('index')} | Hash: {block_data.get('hash', '')[:16]}...")
290
+ print(f" Transactions: {len(block_data.get('transactions', []))} | Difficulty: {block_data.get('difficulty')}")
291
+
292
+ # Step 2: Submit to the correct endpoint
293
+ response = requests.post(
294
+ f'{self.endpoint_url}/blockchain/submit-block',
295
+ json=block_data,
296
+ headers={'Content-Type': 'application/json'},
297
+ timeout=30
298
+ )
299
+
300
+ # Step 3: Handle response
301
+ if response.status_code in [200, 201]:
302
+ result = response.json()
303
+ if result.get('success'):
304
+ print(f"🎉 Block #{block_data.get('index')} successfully added to blockchain!")
305
+ print(f" Block hash: {result.get('block_hash', '')[:16]}...")
306
+ print(f" Transactions count: {result.get('transactions_count', 0)}")
307
+ print(f" Miner: {result.get('miner', 'unknown')}")
308
+ return True
309
+ else:
310
+ error_msg = result.get('error', 'Unknown error')
311
+ print(f"❌ Block submission rejected: {error_msg}")
312
+ return False
313
+ else:
314
+ print(f"❌ HTTP error {response.status_code}: {response.text}")
315
+ return False
316
+
317
+ except requests.exceptions.RequestException as e:
318
+ print(f"❌ Network error submitting block: {e}")
319
+ return False
320
+ except Exception as e:
321
+ print(f"💥 Unexpected error submitting block: {e}")
322
+ return False
323
+
324
+ def _validate_block_structure(self, block_data: Dict) -> Dict:
325
+ """Internal: Validate block structure before submission"""
326
+ issues = []
327
+
328
+ # Check required fields
329
+ required_fields = ["index", "previous_hash", "timestamp", "transactions", "miner", "difficulty", "nonce", "hash"]
330
+ missing_fields = [field for field in required_fields if field not in block_data]
331
+ if missing_fields:
332
+ issues.append(f"Missing required fields: {missing_fields}")
333
+
334
+ # Check data types
335
+ if not isinstance(block_data.get('index'), int) or block_data.get('index') < 0:
336
+ issues.append("Index must be a non-negative integer")
337
+
338
+ if not isinstance(block_data.get('transactions'), list):
339
+ issues.append("Transactions must be a list")
340
+
341
+ if not isinstance(block_data.get('difficulty'), int) or block_data.get('difficulty') < 0:
342
+ issues.append("Difficulty must be a non-negative integer")
343
+
344
+ if not isinstance(block_data.get('nonce'), int) or block_data.get('nonce') < 0:
345
+ issues.append("Nonce must be a non-negative integer")
346
+
347
+ # Check hash meets difficulty requirement
348
+ block_hash = block_data.get('hash', '')
349
+ difficulty = block_data.get('difficulty', 0)
350
+ if difficulty > 0 and not block_hash.startswith('0' * difficulty):
351
+ issues.append(f"Hash doesn't meet difficulty {difficulty}: {block_hash[:16]}...")
352
+
353
+ # Check hash length (should be 64 chars for SHA-256)
354
+ if len(block_hash) != 64:
355
+ issues.append(f"Hash should be 64 characters, got {len(block_hash)}")
356
+
357
+ # Check previous hash format
358
+ previous_hash = block_data.get('previous_hash', '')
359
+ if len(previous_hash) != 64 and previous_hash != '0' * 64: # Allow genesis block
360
+ issues.append(f"Previous hash should be 64 characters, got {len(previous_hash)}")
361
+
362
+ # Check timestamp is reasonable
363
+ current_time = time.time()
364
+ block_time = block_data.get('timestamp', 0)
365
+ if block_time > current_time + 300: # 5 minutes in future
366
+ issues.append(f"Block timestamp is in the future")
367
+ if block_time < current_time - 86400: # 24 hours in past
368
+ issues.append(f"Block timestamp is too far in the past")
369
+
370
+ # Validate transactions structure
371
+ transactions = block_data.get('transactions', [])
372
+ for i, tx in enumerate(transactions):
373
+ if not isinstance(tx, dict):
374
+ issues.append(f"Transaction {i} is not a dictionary")
375
+ continue
376
+
377
+ tx_type = tx.get('type')
378
+ if not tx_type:
379
+ issues.append(f"Transaction {i} missing 'type' field")
380
+
381
+ # Basic transaction validation
382
+ if tx_type == 'GTX_Genesis':
383
+ required_tx_fields = ['serial_number', 'denomination', 'issued_to', 'timestamp', 'hash']
384
+ missing_tx_fields = [field for field in required_tx_fields if field not in tx]
385
+ if missing_tx_fields:
386
+ issues.append(f"GTX_Genesis transaction {i} missing fields: {missing_tx_fields}")
387
+
388
+ elif tx_type == 'reward':
389
+ required_tx_fields = ['to', 'amount', 'timestamp', 'hash']
390
+ missing_tx_fields = [field for field in required_tx_fields if field not in tx]
391
+ if missing_tx_fields:
392
+ issues.append(f"Reward transaction {i} missing fields: {missing_tx_fields}")
393
+
394
+ return {
395
+ 'valid': len(issues) == 0,
396
+ 'issues': issues,
397
+ 'block_info': {
398
+ 'index': block_data.get('index'),
399
+ 'hash_preview': block_data.get('hash', '')[:16] + '...',
400
+ 'transaction_count': len(transactions),
401
+ 'difficulty': block_data.get('difficulty'),
402
+ 'miner': block_data.get('miner'),
403
+ 'nonce': block_data.get('nonce')
404
+ }
405
+ }
406
+
407
+ def _find_address_transactions(self, block: Dict, address: str) -> List[Dict]:
408
+ """Find transactions in block that involve the address - ENHANCED FOR REWARDS"""
409
+ transactions = []
410
+ address_lower = address.lower()
411
+
412
+ # Check block reward (miner rewards)
413
+ miner = block.get('miner', '').lower()
414
+ if miner == address_lower:
415
+ reward_tx = {
416
+ 'type': 'reward',
417
+ 'from': 'network',
418
+ 'to': address,
419
+ 'amount': block.get('reward', 0),
420
+ 'block_height': block.get('index'),
421
+ 'timestamp': block.get('timestamp'),
422
+ 'hash': f"reward_{block.get('index')}_{address}",
423
+ 'status': 'confirmed',
424
+ 'description': f'Mining reward for block #{block.get("index")}'
425
+ }
426
+ transactions.append(reward_tx)
427
+ print(f"🎁 Found mining reward: {block.get('reward', 0)} LUN for block #{block.get('index')}")
428
+
429
+ # Check regular transactions in the block
430
+ for tx in block.get('transactions', []):
431
+ tx_type = tx.get('type', '').lower()
432
+ from_addr = (tx.get('from') or '').lower()
433
+ to_addr = (tx.get('to') or '').lower()
434
+
435
+ # Check if this transaction involves our address
436
+ if from_addr == address_lower or to_addr == address_lower:
437
+ enhanced_tx = tx.copy()
438
+ enhanced_tx['block_height'] = block.get('index')
439
+ enhanced_tx['status'] = 'confirmed'
440
+ transactions.append(enhanced_tx)
441
+
442
+ # SPECIFICALLY look for reward transactions sent to our address
443
+ elif tx_type == 'reward' and to_addr == address_lower:
444
+ enhanced_tx = tx.copy()
445
+ enhanced_tx['block_height'] = block.get('index')
446
+ enhanced_tx['status'] = 'confirmed'
447
+ enhanced_tx['type'] = 'reward' # Ensure type is set
448
+ transactions.append(enhanced_tx)
449
+ print(f"💰 Found reward transaction: {tx.get('amount', 0)} LUN")
450
+
451
+ return transactions
lunalib/core/crypto.py ADDED
@@ -0,0 +1,32 @@
1
+ import hashlib
2
+ import secrets
3
+ from typing import Optional
4
+
5
+ class KeyManager:
6
+ """Manages cryptographic keys and signing"""
7
+
8
+ def generate_private_key(self):
9
+ """Generate a new private key"""
10
+ return secrets.token_hex(32)
11
+
12
+ def derive_public_key(self, private_key):
13
+ """Derive public key from private key"""
14
+ return hashlib.sha256(private_key.encode()).hexdigest()
15
+
16
+ def derive_address(self, public_key):
17
+ """Derive address from public key"""
18
+ address_hash = hashlib.sha256(public_key.encode()).hexdigest()
19
+ return f"LUN_{address_hash[:16]}_{secrets.token_hex(4)}"
20
+
21
+ def sign_data(self, data, private_key):
22
+ """Sign data with private key"""
23
+ # In production, use proper ECDSA
24
+ # For now, simplified implementation
25
+ sign_string = data + private_key
26
+ return hashlib.sha256(sign_string.encode()).hexdigest()
27
+
28
+ def verify_signature(self, data, signature, public_key):
29
+ """Verify signature with public key"""
30
+ # Simplified verification
31
+ expected_public = self.derive_public_key(public_key)
32
+ return public_key == expected_public