lunalib 1.5.1__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.
Potentially problematic release.
This version of lunalib might be problematic. Click here for more details.
- core/__init__.py +0 -0
- core/blockchain.py +172 -0
- core/crypto.py +32 -0
- core/wallet.py +408 -0
- gtx/__init__.py +0 -0
- gtx/bill_registry.py +122 -0
- gtx/digital_bill.py +273 -0
- gtx/genesis.py +338 -0
- lunalib/__init__.py +21 -0
- lunalib/cli.py +18 -0
- lunalib/core/__init__.py +0 -0
- lunalib/core/blockchain.py +803 -0
- lunalib/core/crypto.py +270 -0
- lunalib/core/mempool.py +342 -0
- lunalib/core/sm2.py +723 -0
- lunalib/core/wallet.py +1342 -0
- lunalib/core/wallet_manager.py +638 -0
- lunalib/core/wallet_sync_helper.py +163 -0
- lunalib/gtx/__init__.py +0 -0
- lunalib/gtx/bill_registry.py +122 -0
- lunalib/gtx/digital_bill.py +273 -0
- lunalib/gtx/genesis.py +349 -0
- lunalib/luna_lib.py +87 -0
- lunalib/mining/__init__.py +0 -0
- lunalib/mining/cuda_manager.py +137 -0
- lunalib/mining/difficulty.py +106 -0
- lunalib/mining/miner.py +617 -0
- lunalib/requirements.txt +44 -0
- lunalib/storage/__init__.py +0 -0
- lunalib/storage/cache.py +148 -0
- lunalib/storage/database.py +222 -0
- lunalib/storage/encryption.py +105 -0
- lunalib/transactions/__init__.py +0 -0
- lunalib/transactions/security.py +234 -0
- lunalib/transactions/transactions.py +399 -0
- lunalib/transactions/validator.py +71 -0
- lunalib-1.5.1.dist-info/METADATA +283 -0
- lunalib-1.5.1.dist-info/RECORD +53 -0
- lunalib-1.5.1.dist-info/WHEEL +5 -0
- lunalib-1.5.1.dist-info/entry_points.txt +2 -0
- lunalib-1.5.1.dist-info/top_level.txt +6 -0
- mining/__init__.py +0 -0
- mining/cuda_manager.py +137 -0
- mining/difficulty.py +106 -0
- mining/miner.py +107 -0
- storage/__init__.py +0 -0
- storage/cache.py +148 -0
- storage/database.py +222 -0
- storage/encryption.py +105 -0
- transactions/__init__.py +0 -0
- transactions/security.py +172 -0
- transactions/transactions.py +424 -0
- transactions/validator.py +71 -0
lunalib/mining/miner.py
ADDED
|
@@ -0,0 +1,617 @@
|
|
|
1
|
+
# lunalib/mining/miner.py
|
|
2
|
+
import time
|
|
3
|
+
import hashlib
|
|
4
|
+
import json
|
|
5
|
+
import threading
|
|
6
|
+
from typing import Dict, Optional, List, Union, Callable
|
|
7
|
+
from ..mining.difficulty import DifficultySystem
|
|
8
|
+
from ..gtx.digital_bill import DigitalBill
|
|
9
|
+
from ..transactions.transactions import TransactionManager
|
|
10
|
+
from ..core.blockchain import BlockchainManager
|
|
11
|
+
from ..core.mempool import MempoolManager
|
|
12
|
+
|
|
13
|
+
class GenesisMiner:
|
|
14
|
+
"""Mines GTX Genesis bills AND regular transfer transactions with configurable difficulty"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, network_endpoints: List[str] = None):
|
|
17
|
+
self.difficulty_system = DifficultySystem()
|
|
18
|
+
self.transaction_manager = TransactionManager(network_endpoints)
|
|
19
|
+
self.blockchain_manager = BlockchainManager(network_endpoints[0] if network_endpoints else "https://bank.linglin.art")
|
|
20
|
+
self.mempool_manager = MempoolManager(network_endpoints)
|
|
21
|
+
|
|
22
|
+
self.mining_active = False
|
|
23
|
+
self.current_thread = None
|
|
24
|
+
self.mining_stats = {
|
|
25
|
+
"bills_mined": 0,
|
|
26
|
+
"blocks_mined": 0,
|
|
27
|
+
"total_mining_time": 0,
|
|
28
|
+
"total_hash_attempts": 0
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
print("🔧 GenesisMiner initialized with integrated lunalib components")
|
|
32
|
+
|
|
33
|
+
def mine_bill(self, denomination: float, user_address: str, bill_data: Dict = None) -> Dict:
|
|
34
|
+
"""Mine a GTX Genesis bill using DigitalBill system"""
|
|
35
|
+
try:
|
|
36
|
+
difficulty = self.difficulty_system.get_bill_difficulty(denomination)
|
|
37
|
+
|
|
38
|
+
# Create digital bill using GTX system
|
|
39
|
+
digital_bill = DigitalBill(
|
|
40
|
+
denomination=denomination,
|
|
41
|
+
user_address=user_address,
|
|
42
|
+
difficulty=difficulty,
|
|
43
|
+
bill_data=bill_data or {}
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
print(f"⛏️ Mining GTX ${denomination:,} Bill - Difficulty: {difficulty} zeros")
|
|
47
|
+
|
|
48
|
+
start_time = time.time()
|
|
49
|
+
mining_result = self._perform_bill_mining(digital_bill, difficulty)
|
|
50
|
+
|
|
51
|
+
if mining_result["success"]:
|
|
52
|
+
mining_time = time.time() - start_time
|
|
53
|
+
|
|
54
|
+
# Finalize the bill
|
|
55
|
+
bill = digital_bill.finalize(
|
|
56
|
+
hash=mining_result["hash"],
|
|
57
|
+
nonce=mining_result["nonce"],
|
|
58
|
+
mining_time=mining_time
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
# Update mining statistics
|
|
62
|
+
self.mining_stats["bills_mined"] += 1
|
|
63
|
+
self.mining_stats["total_mining_time"] += mining_time
|
|
64
|
+
self.mining_stats["total_hash_attempts"] += mining_result["nonce"]
|
|
65
|
+
|
|
66
|
+
print(f"✅ Successfully mined GTX ${denomination:,} bill!")
|
|
67
|
+
print(f"⏱️ Mining time: {mining_time:.2f}s")
|
|
68
|
+
print(f"📊 Hash attempts: {mining_result['nonce']:,}")
|
|
69
|
+
print(f"🔗 Bill hash: {mining_result['hash'][:32]}...")
|
|
70
|
+
|
|
71
|
+
# Convert to GTX Genesis transaction
|
|
72
|
+
gtx_transaction = self._create_gtx_genesis_transaction(bill)
|
|
73
|
+
return {
|
|
74
|
+
"success": True,
|
|
75
|
+
"type": "bill",
|
|
76
|
+
"bill": bill,
|
|
77
|
+
"transaction": gtx_transaction,
|
|
78
|
+
"mining_time": mining_time,
|
|
79
|
+
"hash_attempts": mining_result["nonce"]
|
|
80
|
+
}
|
|
81
|
+
else:
|
|
82
|
+
return {"success": False, "error": "Bill mining failed"}
|
|
83
|
+
|
|
84
|
+
except Exception as e:
|
|
85
|
+
print(f"[X]Error mining bill: {e}")
|
|
86
|
+
return {"success": False, "error": str(e)}
|
|
87
|
+
|
|
88
|
+
def mine_transaction_block(self, miner_address: str, previous_hash: str = None, block_height: int = None) -> Dict:
|
|
89
|
+
"""Mine a block containing transactions from mempool"""
|
|
90
|
+
try:
|
|
91
|
+
# Get current blockchain state if not provided
|
|
92
|
+
if previous_hash is None or block_height is None:
|
|
93
|
+
current_height = self.blockchain_manager.get_blockchain_height()
|
|
94
|
+
latest_block = self.blockchain_manager.get_latest_block()
|
|
95
|
+
block_height = current_height + 1
|
|
96
|
+
previous_hash = latest_block.get('hash', '0' * 64) if latest_block else '0' * 64
|
|
97
|
+
|
|
98
|
+
# Get transactions from mempool
|
|
99
|
+
pending_txs = self.mempool_manager.get_pending_transactions()
|
|
100
|
+
transactions = pending_txs[:10] # Limit block size
|
|
101
|
+
|
|
102
|
+
if not transactions:
|
|
103
|
+
return {"success": False, "error": "No transactions in mempool"}
|
|
104
|
+
|
|
105
|
+
# Calculate block difficulty
|
|
106
|
+
difficulty = self.difficulty_system.get_transaction_block_difficulty(transactions)
|
|
107
|
+
|
|
108
|
+
print(f"⛏️ Mining Transaction Block #{block_height} - Difficulty: {difficulty} zeros")
|
|
109
|
+
print(f"📦 Transactions: {len(transactions)} | Previous Hash: {previous_hash[:16]}...")
|
|
110
|
+
|
|
111
|
+
# Create block structure for mining
|
|
112
|
+
block_data = {
|
|
113
|
+
"index": block_height,
|
|
114
|
+
"previous_hash": previous_hash,
|
|
115
|
+
"timestamp": time.time(),
|
|
116
|
+
"transactions": transactions,
|
|
117
|
+
"miner": miner_address,
|
|
118
|
+
"difficulty": difficulty,
|
|
119
|
+
"nonce": 0,
|
|
120
|
+
"version": "1.0"
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
start_time = time.time()
|
|
124
|
+
mining_result = self._perform_block_mining(block_data, difficulty)
|
|
125
|
+
|
|
126
|
+
if mining_result["success"]:
|
|
127
|
+
mining_time = time.time() - start_time
|
|
128
|
+
|
|
129
|
+
# Add hash and nonce to block_data for validation
|
|
130
|
+
block_data["hash"] = mining_result["hash"]
|
|
131
|
+
block_data["nonce"] = mining_result["nonce"]
|
|
132
|
+
|
|
133
|
+
# Create reward transaction WITH VALIDATION
|
|
134
|
+
reward_tx = self._create_mining_reward_transaction(
|
|
135
|
+
miner_address=miner_address,
|
|
136
|
+
block_height=block_height,
|
|
137
|
+
transactions=transactions,
|
|
138
|
+
block_data=block_data # Pass block data for validation
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
# Add reward transaction
|
|
142
|
+
block_data["transactions"].append(reward_tx)
|
|
143
|
+
|
|
144
|
+
# Calculate merkleroot for submission
|
|
145
|
+
merkleroot = self._calculate_merkleroot(transactions) # Without reward
|
|
146
|
+
|
|
147
|
+
# Finalize block
|
|
148
|
+
block = {
|
|
149
|
+
**block_data,
|
|
150
|
+
"hash": mining_result["hash"],
|
|
151
|
+
"nonce": mining_result["nonce"],
|
|
152
|
+
"merkleroot": merkleroot,
|
|
153
|
+
"transactions_hash": merkleroot,
|
|
154
|
+
"mining_time": mining_time,
|
|
155
|
+
"reward": reward_tx["amount"],
|
|
156
|
+
"transaction_count": len(block_data["transactions"]),
|
|
157
|
+
"timestamp": block_data["timestamp"] # Ensure timestamp is included
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
# Update mining statistics
|
|
161
|
+
self.mining_stats["blocks_mined"] += 1
|
|
162
|
+
self.mining_stats["total_mining_time"] += mining_time
|
|
163
|
+
self.mining_stats["total_hash_attempts"] += mining_result["nonce"]
|
|
164
|
+
|
|
165
|
+
print(f"✅ Successfully mined and validated Transaction Block #{block_height}!")
|
|
166
|
+
print(f"⏱️ Mining time: {mining_time:.2f}s")
|
|
167
|
+
print(f"💰 Block reward: {block['reward']:.6f} LUN")
|
|
168
|
+
print(f"📊 Transactions: {block['transaction_count']}")
|
|
169
|
+
print(f"🔗 Block hash: {mining_result['hash'][:32]}...")
|
|
170
|
+
|
|
171
|
+
# Submit block to blockchain
|
|
172
|
+
submission_success = self.blockchain_manager.submit_mined_block(block)
|
|
173
|
+
if submission_success:
|
|
174
|
+
print("✅ Block successfully submitted to blockchain!")
|
|
175
|
+
# Clear mined transactions from local mempool
|
|
176
|
+
self._clear_mined_transactions(transactions)
|
|
177
|
+
else:
|
|
178
|
+
print("⚠️ Block mined but submission failed")
|
|
179
|
+
|
|
180
|
+
return {
|
|
181
|
+
"success": True,
|
|
182
|
+
"type": "block",
|
|
183
|
+
"block": block,
|
|
184
|
+
"submitted": submission_success,
|
|
185
|
+
"mining_time": mining_time,
|
|
186
|
+
"hash_attempts": mining_result["nonce"]
|
|
187
|
+
}
|
|
188
|
+
else:
|
|
189
|
+
return {"success": False, "error": "Block mining failed"}
|
|
190
|
+
|
|
191
|
+
except Exception as e:
|
|
192
|
+
print(f"❌ Error mining block: {e}")
|
|
193
|
+
import traceback
|
|
194
|
+
traceback.print_exc()
|
|
195
|
+
return {"success": False, "error": str(e)}
|
|
196
|
+
|
|
197
|
+
def _perform_bill_mining(self, digital_bill: DigitalBill, difficulty: int) -> Dict:
|
|
198
|
+
"""Perform proof-of-work mining for GTX bills"""
|
|
199
|
+
target = "0" * difficulty
|
|
200
|
+
nonce = 0
|
|
201
|
+
start_time = time.time()
|
|
202
|
+
last_update = start_time
|
|
203
|
+
|
|
204
|
+
while self.mining_active:
|
|
205
|
+
mining_data = digital_bill.get_mining_data(nonce)
|
|
206
|
+
data_string = json.dumps(mining_data, sort_keys=True)
|
|
207
|
+
bill_hash = hashlib.sha256(data_string.encode()).hexdigest()
|
|
208
|
+
|
|
209
|
+
if bill_hash.startswith(target):
|
|
210
|
+
mining_time = time.time() - start_time
|
|
211
|
+
return {
|
|
212
|
+
"success": True,
|
|
213
|
+
"hash": bill_hash,
|
|
214
|
+
"nonce": nonce,
|
|
215
|
+
"mining_time": mining_time
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
nonce += 1
|
|
219
|
+
|
|
220
|
+
# Progress updates every 5 seconds
|
|
221
|
+
current_time = time.time()
|
|
222
|
+
if current_time - last_update >= 5:
|
|
223
|
+
hashrate = nonce / (current_time - start_time)
|
|
224
|
+
print(f"⏳ Bill mining: {nonce:,} attempts | Rate: {hashrate:,.0f} H/s")
|
|
225
|
+
last_update = current_time
|
|
226
|
+
|
|
227
|
+
return {"success": False, "error": "Mining stopped"}
|
|
228
|
+
|
|
229
|
+
def _perform_block_mining(self, block_data: Dict, difficulty: int) -> Dict:
|
|
230
|
+
"""Perform proof-of-work mining for transaction blocks"""
|
|
231
|
+
target = "0" * difficulty
|
|
232
|
+
nonce = 0
|
|
233
|
+
start_time = time.time()
|
|
234
|
+
last_update = start_time
|
|
235
|
+
|
|
236
|
+
while self.mining_active:
|
|
237
|
+
# Update nonce for this attempt
|
|
238
|
+
block_data["nonce"] = nonce
|
|
239
|
+
|
|
240
|
+
# Create block hash
|
|
241
|
+
block_string = json.dumps(block_data, sort_keys=True)
|
|
242
|
+
block_hash = hashlib.sha256(block_string.encode()).hexdigest()
|
|
243
|
+
|
|
244
|
+
if block_hash.startswith(target):
|
|
245
|
+
mining_time = time.time() - start_time
|
|
246
|
+
return {
|
|
247
|
+
"success": True,
|
|
248
|
+
"hash": block_hash,
|
|
249
|
+
"nonce": nonce,
|
|
250
|
+
"mining_time": mining_time
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
nonce += 1
|
|
254
|
+
|
|
255
|
+
# Progress updates every 5 seconds
|
|
256
|
+
current_time = time.time()
|
|
257
|
+
if current_time - last_update >= 5:
|
|
258
|
+
hashrate = nonce / (current_time - start_time)
|
|
259
|
+
print(f"Block mining: {nonce:,} attempts | Rate: {hashrate:,.0f} H/s")
|
|
260
|
+
last_update = current_time
|
|
261
|
+
|
|
262
|
+
return {"success": False, "error": "Mining stopped"}
|
|
263
|
+
|
|
264
|
+
def _create_gtx_genesis_transaction(self, bill: Dict) -> Dict:
|
|
265
|
+
"""Create GTX Genesis transaction from mined bill"""
|
|
266
|
+
return self.transaction_manager.create_gtx_transaction(bill)
|
|
267
|
+
|
|
268
|
+
def _create_mining_reward_transaction(self, miner_address: str, block_height: int,
|
|
269
|
+
transactions: List[Dict], block_data: Dict = None) -> Dict:
|
|
270
|
+
"""Create mining reward transaction with validation of the mining proof"""
|
|
271
|
+
|
|
272
|
+
# Calculate reward
|
|
273
|
+
base_reward = 1.0 # Base block reward
|
|
274
|
+
total_fees = sum(tx.get('fee', 0) for tx in transactions)
|
|
275
|
+
total_reward = base_reward + total_fees
|
|
276
|
+
|
|
277
|
+
# If block_data is provided, validate the mining proof
|
|
278
|
+
if block_data:
|
|
279
|
+
print("🔍 Validating mining proof before creating reward...")
|
|
280
|
+
|
|
281
|
+
# Extract mining proof components
|
|
282
|
+
block_hash = block_data.get('hash', '')
|
|
283
|
+
difficulty = block_data.get('difficulty', 0)
|
|
284
|
+
nonce = block_data.get('nonce', 0)
|
|
285
|
+
timestamp = block_data.get('timestamp', time.time())
|
|
286
|
+
previous_hash = block_data.get('previous_hash', '0' * 64)
|
|
287
|
+
miner = block_data.get('miner', miner_address)
|
|
288
|
+
|
|
289
|
+
# Calculate merkleroot from transactions
|
|
290
|
+
merkleroot = self._calculate_merkleroot(transactions)
|
|
291
|
+
|
|
292
|
+
print(f"📊 Mining proof components:")
|
|
293
|
+
print(f" Block hash: {block_hash[:16]}...")
|
|
294
|
+
print(f" Difficulty: {difficulty}")
|
|
295
|
+
print(f" Nonce: {nonce}")
|
|
296
|
+
print(f" Timestamp: {timestamp}")
|
|
297
|
+
print(f" Previous hash: {previous_hash[:16]}...")
|
|
298
|
+
print(f" Miner: {miner}")
|
|
299
|
+
print(f" Merkleroot: {merkleroot[:16]}...")
|
|
300
|
+
|
|
301
|
+
# Validate difficulty requirement
|
|
302
|
+
if not block_hash.startswith('0' * difficulty):
|
|
303
|
+
print(f"❌ FAIL: Hash doesn't start with {difficulty} zeros")
|
|
304
|
+
raise ValueError(f"Invalid mining proof: Hash doesn't meet difficulty requirement")
|
|
305
|
+
|
|
306
|
+
# Try multiple validation methods
|
|
307
|
+
validation_passed = False
|
|
308
|
+
|
|
309
|
+
# Method 1: Original format (what server likely expects)
|
|
310
|
+
original_string = f"{previous_hash}{timestamp}{merkleroot}{miner}{nonce}"
|
|
311
|
+
original_hash = hashlib.sha256(original_string.encode()).hexdigest()
|
|
312
|
+
|
|
313
|
+
print(f"🔍 Original format validation:")
|
|
314
|
+
print(f" String: {original_string[:80]}...")
|
|
315
|
+
print(f" Calculated: {original_hash[:16]}...")
|
|
316
|
+
|
|
317
|
+
if original_hash == block_hash:
|
|
318
|
+
validation_passed = True
|
|
319
|
+
print("✅ Original format validation passed")
|
|
320
|
+
|
|
321
|
+
# Method 2: JSON format (what miner might be using)
|
|
322
|
+
if not validation_passed:
|
|
323
|
+
mining_json = {
|
|
324
|
+
"index": block_height,
|
|
325
|
+
"previous_hash": previous_hash,
|
|
326
|
+
"timestamp": timestamp,
|
|
327
|
+
"transactions": transactions,
|
|
328
|
+
"miner": miner,
|
|
329
|
+
"difficulty": difficulty,
|
|
330
|
+
"nonce": nonce,
|
|
331
|
+
"version": "1.0"
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
json_string = json.dumps(mining_json, sort_keys=True)
|
|
335
|
+
json_hash = hashlib.sha256(json_string.encode()).hexdigest()
|
|
336
|
+
|
|
337
|
+
print(f"🔍 JSON format validation:")
|
|
338
|
+
print(f" String: {json_string[:100]}...")
|
|
339
|
+
print(f" Calculated: {json_hash[:16]}...")
|
|
340
|
+
|
|
341
|
+
if json_hash == block_hash:
|
|
342
|
+
validation_passed = True
|
|
343
|
+
print("✅ JSON format validation passed")
|
|
344
|
+
|
|
345
|
+
# Method 3: JSON without transactions (for empty blocks)
|
|
346
|
+
if not validation_passed and len(transactions) == 0:
|
|
347
|
+
mining_json_empty = {
|
|
348
|
+
"index": block_height,
|
|
349
|
+
"previous_hash": previous_hash,
|
|
350
|
+
"timestamp": timestamp,
|
|
351
|
+
"transactions": [],
|
|
352
|
+
"miner": miner,
|
|
353
|
+
"difficulty": difficulty,
|
|
354
|
+
"nonce": nonce,
|
|
355
|
+
"version": "1.0"
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
json_string_empty = json.dumps(mining_json_empty, sort_keys=True)
|
|
359
|
+
json_hash_empty = hashlib.sha256(json_string_empty.encode()).hexdigest()
|
|
360
|
+
|
|
361
|
+
print(f"🔍 JSON empty format validation:")
|
|
362
|
+
print(f" Calculated: {json_hash_empty[:16]}...")
|
|
363
|
+
|
|
364
|
+
if json_hash_empty == block_hash:
|
|
365
|
+
validation_passed = True
|
|
366
|
+
print("✅ JSON empty format validation passed")
|
|
367
|
+
|
|
368
|
+
if not validation_passed:
|
|
369
|
+
print("❌ All validation methods failed")
|
|
370
|
+
raise ValueError("Invalid mining proof: Hash verification failed")
|
|
371
|
+
|
|
372
|
+
print("✅ Mining proof validation successful!")
|
|
373
|
+
|
|
374
|
+
# Create the reward transaction
|
|
375
|
+
return self.transaction_manager.create_reward_transaction(
|
|
376
|
+
to_address=miner_address,
|
|
377
|
+
amount=total_reward,
|
|
378
|
+
block_height=block_height,
|
|
379
|
+
reward_type="block"
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
def _calculate_merkleroot(self, transactions: List[Dict]) -> str:
|
|
383
|
+
"""Calculate merkle root from transactions"""
|
|
384
|
+
if not transactions:
|
|
385
|
+
return "0" * 64
|
|
386
|
+
|
|
387
|
+
tx_hashes = []
|
|
388
|
+
for tx in transactions:
|
|
389
|
+
if 'hash' in tx:
|
|
390
|
+
tx_hashes.append(tx['hash'])
|
|
391
|
+
else:
|
|
392
|
+
tx_string = json.dumps(tx, sort_keys=True)
|
|
393
|
+
tx_hashes.append(hashlib.sha256(tx_string.encode()).hexdigest())
|
|
394
|
+
|
|
395
|
+
# Simple merkle root calculation
|
|
396
|
+
while len(tx_hashes) > 1:
|
|
397
|
+
new_hashes = []
|
|
398
|
+
for i in range(0, len(tx_hashes), 2):
|
|
399
|
+
if i + 1 < len(tx_hashes):
|
|
400
|
+
combined = tx_hashes[i] + tx_hashes[i + 1]
|
|
401
|
+
else:
|
|
402
|
+
combined = tx_hashes[i] + tx_hashes[i]
|
|
403
|
+
new_hash = hashlib.sha256(combined.encode()).hexdigest()
|
|
404
|
+
new_hashes.append(new_hash)
|
|
405
|
+
tx_hashes = new_hashes
|
|
406
|
+
|
|
407
|
+
return tx_hashes[0] if tx_hashes else "0" * 64
|
|
408
|
+
|
|
409
|
+
def _clear_mined_transactions(self, mined_transactions: List[Dict]):
|
|
410
|
+
"""Remove mined transactions from local mempool"""
|
|
411
|
+
for tx in mined_transactions:
|
|
412
|
+
tx_hash = tx.get('hash')
|
|
413
|
+
if tx_hash:
|
|
414
|
+
self.mempool_manager.remove_transaction(tx_hash)
|
|
415
|
+
|
|
416
|
+
print(f"Cleared {len(mined_transactions)} mined transactions from mempool")
|
|
417
|
+
|
|
418
|
+
def start_auto_bill_mining(self, denominations: List[float], user_address: str,
|
|
419
|
+
callback: Callable = None) -> bool:
|
|
420
|
+
"""Start auto-mining multiple GTX bills"""
|
|
421
|
+
if self.mining_active:
|
|
422
|
+
print("Mining already active")
|
|
423
|
+
return False
|
|
424
|
+
|
|
425
|
+
self.mining_active = True
|
|
426
|
+
|
|
427
|
+
def auto_mine():
|
|
428
|
+
results = []
|
|
429
|
+
for denomination in denominations:
|
|
430
|
+
if not self.mining_active:
|
|
431
|
+
break
|
|
432
|
+
|
|
433
|
+
print(f"Starting auto-mining for ${denomination:,} bill...")
|
|
434
|
+
result = self.mine_bill(denomination, user_address)
|
|
435
|
+
results.append(result)
|
|
436
|
+
|
|
437
|
+
if callback:
|
|
438
|
+
callback(result)
|
|
439
|
+
|
|
440
|
+
# Brief pause between bills
|
|
441
|
+
time.sleep(1)
|
|
442
|
+
|
|
443
|
+
print("Auto bill mining completed")
|
|
444
|
+
return results
|
|
445
|
+
|
|
446
|
+
self.current_thread = threading.Thread(target=auto_mine, daemon=True)
|
|
447
|
+
self.current_thread.start()
|
|
448
|
+
print(f"Started auto-mining {len(denominations)} bills")
|
|
449
|
+
return True
|
|
450
|
+
|
|
451
|
+
def start_continuous_block_mining(self, miner_address: str, callback: Callable = None) -> bool:
|
|
452
|
+
"""Start continuous transaction block mining"""
|
|
453
|
+
if self.mining_active:
|
|
454
|
+
print("⚠️ Mining already active")
|
|
455
|
+
return False
|
|
456
|
+
|
|
457
|
+
self.mining_active = True
|
|
458
|
+
|
|
459
|
+
def continuous_mine():
|
|
460
|
+
block_height = self.blockchain_manager.get_blockchain_height() + 1
|
|
461
|
+
latest_block = self.blockchain_manager.get_latest_block()
|
|
462
|
+
previous_hash = latest_block.get('hash', '0' * 64) if latest_block else '0' * 64
|
|
463
|
+
|
|
464
|
+
while self.mining_active:
|
|
465
|
+
# Check mempool for transactions
|
|
466
|
+
pending_count = len(self.mempool_manager.get_pending_transactions())
|
|
467
|
+
|
|
468
|
+
if pending_count > 0:
|
|
469
|
+
print(f"🔄 Mining block #{block_height} with {pending_count} pending transactions...")
|
|
470
|
+
|
|
471
|
+
result = self.mine_transaction_block(miner_address, previous_hash, block_height)
|
|
472
|
+
|
|
473
|
+
if result.get("success"):
|
|
474
|
+
if callback:
|
|
475
|
+
callback(result)
|
|
476
|
+
|
|
477
|
+
# Update for next block
|
|
478
|
+
block_height += 1
|
|
479
|
+
previous_hash = result["block"]["hash"]
|
|
480
|
+
|
|
481
|
+
# Brief pause between blocks
|
|
482
|
+
time.sleep(2)
|
|
483
|
+
else:
|
|
484
|
+
print("⏳ No transactions in mempool, waiting...")
|
|
485
|
+
time.sleep(10) # Wait longer if no transactions
|
|
486
|
+
|
|
487
|
+
self.current_thread = threading.Thread(target=continuous_mine, daemon=True)
|
|
488
|
+
self.current_thread.start()
|
|
489
|
+
print("Started continuous block mining")
|
|
490
|
+
return True
|
|
491
|
+
|
|
492
|
+
def start_hybrid_mining(self, miner_address: str, bill_denominations: List[float] = None,
|
|
493
|
+
callback: Callable = None) -> bool:
|
|
494
|
+
"""Start hybrid mining - both GTX bills and transaction blocks"""
|
|
495
|
+
if self.mining_active:
|
|
496
|
+
print("Mining already active")
|
|
497
|
+
return False
|
|
498
|
+
|
|
499
|
+
self.mining_active = True
|
|
500
|
+
|
|
501
|
+
def hybrid_mine():
|
|
502
|
+
# Mine GTX bills first if denominations provided
|
|
503
|
+
if bill_denominations:
|
|
504
|
+
for denomination in bill_denominations:
|
|
505
|
+
if not self.mining_active:
|
|
506
|
+
break
|
|
507
|
+
|
|
508
|
+
print(f"Mining GTX ${denomination:,} bill...")
|
|
509
|
+
bill_result = self.mine_bill(denomination, miner_address)
|
|
510
|
+
|
|
511
|
+
if callback:
|
|
512
|
+
callback({"type": "bill", "data": bill_result})
|
|
513
|
+
|
|
514
|
+
time.sleep(1)
|
|
515
|
+
|
|
516
|
+
# Switch to continuous block mining
|
|
517
|
+
block_height = self.blockchain_manager.get_blockchain_height() + 1
|
|
518
|
+
latest_block = self.blockchain_manager.get_latest_block()
|
|
519
|
+
previous_hash = latest_block.get('hash', '0' * 64) if latest_block else '0' * 64
|
|
520
|
+
|
|
521
|
+
while self.mining_active:
|
|
522
|
+
pending_count = len(self.mempool_manager.get_pending_transactions())
|
|
523
|
+
|
|
524
|
+
if pending_count > 0:
|
|
525
|
+
print(f"🔄 Mining transaction block #{block_height}...")
|
|
526
|
+
|
|
527
|
+
block_result = self.mine_transaction_block(miner_address, previous_hash, block_height)
|
|
528
|
+
|
|
529
|
+
if block_result.get("success"):
|
|
530
|
+
if callback:
|
|
531
|
+
callback({"type": "block", "data": block_result})
|
|
532
|
+
|
|
533
|
+
block_height += 1
|
|
534
|
+
previous_hash = block_result["block"]["hash"]
|
|
535
|
+
|
|
536
|
+
time.sleep(2)
|
|
537
|
+
else:
|
|
538
|
+
print("⏳ No transactions, checking again in 10s...")
|
|
539
|
+
time.sleep(10)
|
|
540
|
+
|
|
541
|
+
self.current_thread = threading.Thread(target=hybrid_mine, daemon=True)
|
|
542
|
+
self.current_thread.start()
|
|
543
|
+
print("Started hybrid mining (bills + blocks)")
|
|
544
|
+
return True
|
|
545
|
+
|
|
546
|
+
def get_mining_stats(self) -> Dict:
|
|
547
|
+
"""Get comprehensive mining statistics"""
|
|
548
|
+
pending_txs = self.mempool_manager.get_pending_transactions()
|
|
549
|
+
|
|
550
|
+
return {
|
|
551
|
+
"mining_active": self.mining_active,
|
|
552
|
+
"bills_mined": self.mining_stats["bills_mined"],
|
|
553
|
+
"blocks_mined": self.mining_stats["blocks_mined"],
|
|
554
|
+
"total_mining_time": self.mining_stats["total_mining_time"],
|
|
555
|
+
"total_hash_attempts": self.mining_stats["total_hash_attempts"],
|
|
556
|
+
"mempool_size": len(pending_txs),
|
|
557
|
+
"pending_transactions": [
|
|
558
|
+
{
|
|
559
|
+
"hash": tx.get('hash', '')[:16] + '...',
|
|
560
|
+
"from": tx.get('from', ''),
|
|
561
|
+
"to": tx.get('to', ''),
|
|
562
|
+
"amount": tx.get('amount', 0),
|
|
563
|
+
"fee": tx.get('fee', 0),
|
|
564
|
+
"type": tx.get('type', 'unknown')
|
|
565
|
+
}
|
|
566
|
+
for tx in pending_txs[:5] # Show first 5
|
|
567
|
+
],
|
|
568
|
+
"average_hashrate": (
|
|
569
|
+
self.mining_stats["total_hash_attempts"] / self.mining_stats["total_mining_time"]
|
|
570
|
+
if self.mining_stats["total_mining_time"] > 0 else 0
|
|
571
|
+
)
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
def stop_mining(self):
|
|
575
|
+
"""Stop all mining activities"""
|
|
576
|
+
self.mining_active = False
|
|
577
|
+
if self.current_thread and self.current_thread.is_alive():
|
|
578
|
+
self.current_thread.join(timeout=5)
|
|
579
|
+
print("Mining stopped")
|
|
580
|
+
|
|
581
|
+
stats = self.get_mining_stats()
|
|
582
|
+
print(f"Final statistics:")
|
|
583
|
+
print(f"Bills mined: {stats['bills_mined']}")
|
|
584
|
+
print(f"Blocks mined: {stats['blocks_mined']}")
|
|
585
|
+
print(f"Total mining time: {stats['total_mining_time']:.2f}s")
|
|
586
|
+
print(f"Average hashrate: {stats['average_hashrate']:,.0f} H/s")
|
|
587
|
+
print(f"Mempool size: {stats['mempool_size']} transactions")
|
|
588
|
+
|
|
589
|
+
def submit_transaction(self, transaction: Dict) -> bool:
|
|
590
|
+
"""Submit transaction to mempool for mining"""
|
|
591
|
+
try:
|
|
592
|
+
success = self.mempool_manager.add_transaction(transaction)
|
|
593
|
+
if success:
|
|
594
|
+
print(f"📨 Added transaction to mining mempool: {transaction.get('hash', '')[:16]}...")
|
|
595
|
+
return success
|
|
596
|
+
except Exception as e:
|
|
597
|
+
print(f"Error submitting transaction: {e}")
|
|
598
|
+
return False
|
|
599
|
+
|
|
600
|
+
def get_network_status(self) -> Dict:
|
|
601
|
+
"""Get current network and blockchain status"""
|
|
602
|
+
try:
|
|
603
|
+
height = self.blockchain_manager.get_blockchain_height()
|
|
604
|
+
connected = self.blockchain_manager.check_network_connection()
|
|
605
|
+
mempool_size = len(self.mempool_manager.get_pending_transactions())
|
|
606
|
+
|
|
607
|
+
return {
|
|
608
|
+
"network_connected": connected,
|
|
609
|
+
"blockchain_height": height,
|
|
610
|
+
"mempool_size": mempool_size,
|
|
611
|
+
"mining_active": self.mining_active
|
|
612
|
+
}
|
|
613
|
+
except Exception as e:
|
|
614
|
+
return {
|
|
615
|
+
"network_connected": False,
|
|
616
|
+
"error": str(e)
|
|
617
|
+
}
|
lunalib/requirements.txt
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Core dependencies
|
|
2
|
+
requests>=2.31.0 # HTTP requests for blockchain API calls
|
|
3
|
+
cryptography>=41.0.0 # Encryption for wallet security
|
|
4
|
+
qrcode[pil]>=7.4.0 # QR code generation for addresses
|
|
5
|
+
Pillow>=10.0.0 # Image processing for QR codes
|
|
6
|
+
|
|
7
|
+
# Development and testing
|
|
8
|
+
pytest>=7.4.0 # Test framework
|
|
9
|
+
pytest-cov>=4.1.0 # Test coverage
|
|
10
|
+
pytest-mock>=3.11.0 # Mocking for tests
|
|
11
|
+
coverage>=7.0.0 # Coverage reporting
|
|
12
|
+
|
|
13
|
+
# Code quality and formatting
|
|
14
|
+
black>=23.0.0 # Code formatting
|
|
15
|
+
flake8>=6.0.0 # Linting
|
|
16
|
+
mypy>=1.5.0 # Type checking
|
|
17
|
+
isort>=5.12.0 # Import sorting
|
|
18
|
+
bandit>=1.7.0 # Security scanning
|
|
19
|
+
|
|
20
|
+
# Packaging and distribution
|
|
21
|
+
build>=0.10.0 # Package building
|
|
22
|
+
twine>=4.0.0 # Package uploading
|
|
23
|
+
|
|
24
|
+
# Documentation
|
|
25
|
+
sphinx>=7.0.0 # Documentation generation
|
|
26
|
+
sphinx-rtd-theme>=1.3.0 # ReadTheDocs theme
|
|
27
|
+
|
|
28
|
+
# Type stubs for better IDE support
|
|
29
|
+
types-requests>=2.31.0
|
|
30
|
+
typing-extensions>=4.8.0 # Additional type hints
|
|
31
|
+
|
|
32
|
+
# Optional GPU acceleration (choose one based on your hardware)
|
|
33
|
+
# Uncomment the appropriate line for your setup:
|
|
34
|
+
|
|
35
|
+
# NVIDIA CUDA 12.x (latest)
|
|
36
|
+
#cupy-cuda12x>=12.0.0
|
|
37
|
+
|
|
38
|
+
# NVIDIA CUDA 11.x
|
|
39
|
+
# cupy-cuda11x>=11.0.0
|
|
40
|
+
|
|
41
|
+
# AMD ROCm
|
|
42
|
+
# cupy-rocm-5-0>=12.0.0
|
|
43
|
+
|
|
44
|
+
# CPU-only fallback (slower but works everywhere)
|
|
File without changes
|