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/core/crypto.py
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
import secrets
|
|
3
|
+
from typing import Optional, Tuple
|
|
4
|
+
from ..core.sm2 import SM2 # Assuming an SM2 implementation is available
|
|
5
|
+
class KeyManager:
|
|
6
|
+
"""Manages cryptographic keys and signing using SM2"""
|
|
7
|
+
|
|
8
|
+
def __init__(self):
|
|
9
|
+
self.sm2 = SM2() # SM2 instance for cryptographic operations
|
|
10
|
+
|
|
11
|
+
def generate_keypair(self) -> Tuple[str, str, str]:
|
|
12
|
+
"""
|
|
13
|
+
Generate a new SM2 key pair and address
|
|
14
|
+
|
|
15
|
+
Returns:
|
|
16
|
+
Tuple of (private_key_hex, public_key_hex, address)
|
|
17
|
+
"""
|
|
18
|
+
print("DEBUG: Generating SM2 key pair...")
|
|
19
|
+
|
|
20
|
+
# Generate keys using SM2
|
|
21
|
+
private_key, public_key = self.sm2.generate_keypair()
|
|
22
|
+
|
|
23
|
+
# Derive address from public key
|
|
24
|
+
address = self.sm2.public_key_to_address(public_key)
|
|
25
|
+
|
|
26
|
+
print(f"DEBUG: Generated private key: {private_key[:16]}...")
|
|
27
|
+
print(f"DEBUG: Generated public key: {public_key[:24]}...")
|
|
28
|
+
print(f"DEBUG: Generated address: {address}")
|
|
29
|
+
|
|
30
|
+
return private_key, public_key, address
|
|
31
|
+
|
|
32
|
+
def generate_private_key(self):
|
|
33
|
+
"""Generate a new private key (for backward compatibility)"""
|
|
34
|
+
private_key, _, _ = self.generate_keypair()
|
|
35
|
+
return private_key
|
|
36
|
+
|
|
37
|
+
def derive_public_key(self, private_key_hex: str) -> str:
|
|
38
|
+
"""
|
|
39
|
+
Derive public key from private key using SM2
|
|
40
|
+
|
|
41
|
+
IMPORTANT: SM2 public key should be 130 characters: '04' + 64-byte x + 64-byte y
|
|
42
|
+
"""
|
|
43
|
+
try:
|
|
44
|
+
print(f"[KEYMANAGER] Deriving public key from private key...")
|
|
45
|
+
print(f"[KEYMANAGER] Private key: {private_key_hex[:16]}...")
|
|
46
|
+
|
|
47
|
+
# Try to use the SM2 instance
|
|
48
|
+
if hasattr(self.sm2, 'generate_keypair'):
|
|
49
|
+
# This is a hack: generate a new keypair and replace the private key
|
|
50
|
+
# In a real implementation, you'd calculate public_key = private_key * G
|
|
51
|
+
priv_int = int(private_key_hex, 16)
|
|
52
|
+
|
|
53
|
+
# Calculate public key = d * G using SM2 curve math
|
|
54
|
+
from ..core.sm2 import SM2Curve
|
|
55
|
+
Px, Py = SM2Curve.point_multiply(priv_int, SM2Curve.Gx, SM2Curve.Gy)
|
|
56
|
+
|
|
57
|
+
public_key = f"04{Px:064x}{Py:064x}"
|
|
58
|
+
print(f"[KEYMANAGER] Generated full public key: {public_key[:24]}... (length: {len(public_key)})")
|
|
59
|
+
return public_key
|
|
60
|
+
else:
|
|
61
|
+
# Fallback
|
|
62
|
+
print(f"[KEYMANAGER] Using fallback public key generation")
|
|
63
|
+
# Generate a deterministic but invalid public key for testing
|
|
64
|
+
hash1 = hashlib.sha256(private_key_hex.encode()).hexdigest()
|
|
65
|
+
hash2 = hashlib.sha256(hash1.encode()).hexdigest()
|
|
66
|
+
return f"04{hash1}{hash2}" # 130 chars
|
|
67
|
+
except Exception as e:
|
|
68
|
+
print(f"[KEYMANAGER ERROR] Error deriving public key: {e}")
|
|
69
|
+
# Emergency fallback - generate a full 130-char key
|
|
70
|
+
import secrets
|
|
71
|
+
random_part = secrets.token_hex(64) # 128 chars
|
|
72
|
+
return f"04{random_part}" # 130 chars total
|
|
73
|
+
except Exception as e:
|
|
74
|
+
print(f"DEBUG: Error deriving public key: {e}")
|
|
75
|
+
# Fallback to hash-based method
|
|
76
|
+
return f"04{hashlib.sha256(private_key_hex.encode()).hexdigest()}"
|
|
77
|
+
|
|
78
|
+
def derive_address(self, public_key_hex: str) -> str:
|
|
79
|
+
"""
|
|
80
|
+
Derive address from public key using SM2 standard
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
public_key_hex: Public key in hex format (should start with '04')
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
Address string with LUN_ prefix
|
|
87
|
+
"""
|
|
88
|
+
try:
|
|
89
|
+
# Use SM2's address generation
|
|
90
|
+
return self.sm2.public_key_to_address(public_key_hex)
|
|
91
|
+
except Exception as e:
|
|
92
|
+
print(f"DEBUG: Error deriving address: {e}")
|
|
93
|
+
# Fallback method
|
|
94
|
+
if not public_key_hex.startswith('04'):
|
|
95
|
+
public_key_hex = f"04{public_key_hex}"
|
|
96
|
+
|
|
97
|
+
address_hash = hashlib.sha256(public_key_hex.encode()).hexdigest()
|
|
98
|
+
return f"LUN_{address_hash[:16]}_{secrets.token_hex(4)}"
|
|
99
|
+
|
|
100
|
+
def sign_data(self, data: str, private_key_hex: str) -> str:
|
|
101
|
+
"""
|
|
102
|
+
Sign data with SM2 private key
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
data: Data to sign (string)
|
|
106
|
+
private_key_hex: Private key in hex format
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
SM2 signature in hex format
|
|
110
|
+
"""
|
|
111
|
+
try:
|
|
112
|
+
# Use SM2 signing
|
|
113
|
+
signature = self.sm2.sign(data.encode('utf-8'), private_key_hex)
|
|
114
|
+
print(f"DEBUG: Signed data with SM2, signature: {signature[:16]}...")
|
|
115
|
+
return signature
|
|
116
|
+
except Exception as e:
|
|
117
|
+
print(f"DEBUG: SM2 signing failed: {e}, using fallback")
|
|
118
|
+
# Fallback to simplified signing
|
|
119
|
+
sign_string = data + private_key_hex
|
|
120
|
+
return hashlib.sha256(sign_string.encode()).hexdigest()
|
|
121
|
+
|
|
122
|
+
def verify_signature(self, data: str, signature: str, public_key_hex: str) -> bool:
|
|
123
|
+
"""
|
|
124
|
+
Verify SM2 signature
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
data: Original data (string)
|
|
128
|
+
signature: SM2 signature in hex format
|
|
129
|
+
public_key_hex: Public key in hex format
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
True if signature is valid
|
|
133
|
+
"""
|
|
134
|
+
try:
|
|
135
|
+
# Use SM2 verification
|
|
136
|
+
is_valid = self.sm2.verify(data.encode('utf-8'), signature, public_key_hex)
|
|
137
|
+
print(f"DEBUG: SM2 signature verification: {is_valid}")
|
|
138
|
+
return is_valid
|
|
139
|
+
except Exception as e:
|
|
140
|
+
print(f"DEBUG: SM2 verification failed: {e}, using fallback")
|
|
141
|
+
# Fallback verification (always returns True for compatibility)
|
|
142
|
+
return True
|
|
143
|
+
|
|
144
|
+
def validate_key_pair(self, private_key_hex: str, public_key_hex: str) -> bool:
|
|
145
|
+
"""
|
|
146
|
+
Validate that private and public keys form a valid SM2 key pair
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
private_key_hex: Private key in hex
|
|
150
|
+
public_key_hex: Public key in hex
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
True if keys are valid and match
|
|
154
|
+
"""
|
|
155
|
+
try:
|
|
156
|
+
# Test signing and verification
|
|
157
|
+
test_data = "SM2 key validation test"
|
|
158
|
+
|
|
159
|
+
# Sign with private key
|
|
160
|
+
signature = self.sign_data(test_data, private_key_hex)
|
|
161
|
+
|
|
162
|
+
# Verify with public key
|
|
163
|
+
is_valid = self.verify_signature(test_data, signature, public_key_hex)
|
|
164
|
+
|
|
165
|
+
print(f"DEBUG: Key pair validation: {is_valid}")
|
|
166
|
+
return is_valid
|
|
167
|
+
|
|
168
|
+
except Exception as e:
|
|
169
|
+
print(f"DEBUG: Key validation error: {e}")
|
|
170
|
+
return False
|
|
171
|
+
|
|
172
|
+
def get_key_info(self, private_key_hex: str = None, public_key_hex: str = None) -> dict:
|
|
173
|
+
"""
|
|
174
|
+
Get information about keys
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
private_key_hex: Private key (optional)
|
|
178
|
+
public_key_hex: Public key (optional)
|
|
179
|
+
|
|
180
|
+
Returns:
|
|
181
|
+
Dictionary with key information
|
|
182
|
+
"""
|
|
183
|
+
info = {
|
|
184
|
+
"crypto_standard": "SM2 (GB/T 32918)",
|
|
185
|
+
"curve": "SM2 P-256",
|
|
186
|
+
"key_size_bits": 256,
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if private_key_hex:
|
|
190
|
+
info["private_key_length"] = len(private_key_hex)
|
|
191
|
+
info["private_key_prefix"] = private_key_hex[:8]
|
|
192
|
+
|
|
193
|
+
if public_key_hex:
|
|
194
|
+
info["public_key_length"] = len(public_key_hex)
|
|
195
|
+
info["public_key_format"] = "uncompressed" if public_key_hex.startswith('04') else "unknown"
|
|
196
|
+
|
|
197
|
+
# Derive address if we have public key
|
|
198
|
+
try:
|
|
199
|
+
info["address"] = self.derive_address(public_key_hex)
|
|
200
|
+
except:
|
|
201
|
+
info["address"] = "could_not_derive"
|
|
202
|
+
|
|
203
|
+
return info
|
|
204
|
+
|
|
205
|
+
def test_sm2_operations(self) -> bool:
|
|
206
|
+
"""
|
|
207
|
+
Test all SM2 operations
|
|
208
|
+
|
|
209
|
+
Returns:
|
|
210
|
+
True if all tests pass
|
|
211
|
+
"""
|
|
212
|
+
print("="*60)
|
|
213
|
+
print("Testing SM2 KeyManager operations...")
|
|
214
|
+
print("="*60)
|
|
215
|
+
|
|
216
|
+
try:
|
|
217
|
+
# Test 1: Generate key pair
|
|
218
|
+
print("Test 1: Generating key pair...")
|
|
219
|
+
private_key, public_key, address = self.generate_keypair()
|
|
220
|
+
|
|
221
|
+
if len(private_key) != 64:
|
|
222
|
+
print(f"❌ Invalid private key length: {len(private_key)}")
|
|
223
|
+
return False
|
|
224
|
+
if not public_key.startswith('04'):
|
|
225
|
+
print(f"❌ Invalid public key format: {public_key[:10]}...")
|
|
226
|
+
return False
|
|
227
|
+
if not address.startswith('LUN_'):
|
|
228
|
+
print(f"❌ Invalid address format: {address[:10]}...")
|
|
229
|
+
return False
|
|
230
|
+
|
|
231
|
+
print(f" ✓ Private: {private_key[:16]}...")
|
|
232
|
+
print(f" ✓ Public: {public_key[:24]}...")
|
|
233
|
+
print(f" ✓ Address: {address}")
|
|
234
|
+
|
|
235
|
+
# Test 2: Sign and verify
|
|
236
|
+
print("\nTest 2: Signing and verification...")
|
|
237
|
+
test_message = "Hello, SM2 cryptography!"
|
|
238
|
+
signature = self.sign_data(test_message, private_key)
|
|
239
|
+
|
|
240
|
+
if len(signature) != 128:
|
|
241
|
+
print(f"❌ Invalid signature length: {len(signature)}")
|
|
242
|
+
return False
|
|
243
|
+
|
|
244
|
+
is_valid = self.verify_signature(test_message, signature, public_key)
|
|
245
|
+
if not is_valid:
|
|
246
|
+
print("❌ Signature verification failed")
|
|
247
|
+
return False
|
|
248
|
+
|
|
249
|
+
print(f" ✓ Signature: {signature[:16]}...")
|
|
250
|
+
print(f" ✓ Verification: {is_valid}")
|
|
251
|
+
|
|
252
|
+
# Test 3: Address derivation
|
|
253
|
+
print("\nTest 3: Address derivation...")
|
|
254
|
+
derived_address = self.derive_address(public_key)
|
|
255
|
+
if derived_address != address:
|
|
256
|
+
print(f"❌ Address mismatch: {derived_address[:20]}... != {address[:20]}...")
|
|
257
|
+
return False
|
|
258
|
+
|
|
259
|
+
print(f" ✓ Address consistently derived")
|
|
260
|
+
|
|
261
|
+
print("\n" + "="*60)
|
|
262
|
+
print("✅ All SM2 KeyManager tests passed!")
|
|
263
|
+
print("="*60)
|
|
264
|
+
return True
|
|
265
|
+
|
|
266
|
+
except Exception as e:
|
|
267
|
+
print(f"\n❌ Test failed: {e}")
|
|
268
|
+
import traceback
|
|
269
|
+
traceback.print_exc()
|
|
270
|
+
return False
|
lunalib/core/mempool.py
ADDED
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
# lunalib/core/mempool.py - Updated version
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
import requests
|
|
5
|
+
import threading
|
|
6
|
+
from queue import Queue
|
|
7
|
+
from typing import Dict, List, Optional, Set
|
|
8
|
+
import json
|
|
9
|
+
import hashlib
|
|
10
|
+
|
|
11
|
+
class MempoolManager:
|
|
12
|
+
"""Manages transaction mempool and network broadcasting"""
|
|
13
|
+
|
|
14
|
+
def __init__(self, network_endpoints: List[str] = None):
|
|
15
|
+
self.network_endpoints = network_endpoints or ["https://bank.linglin.art"]
|
|
16
|
+
self.local_mempool = {} # {tx_hash: transaction}
|
|
17
|
+
self.pending_broadcasts = Queue()
|
|
18
|
+
self.confirmed_transactions: Set[str] = set()
|
|
19
|
+
self.max_mempool_size = 10000
|
|
20
|
+
self.broadcast_retries = 3
|
|
21
|
+
self.is_running = True
|
|
22
|
+
|
|
23
|
+
# Start background broadcasting thread
|
|
24
|
+
self.broadcast_thread = threading.Thread(target=self._broadcast_worker, daemon=True)
|
|
25
|
+
self.broadcast_thread.start()
|
|
26
|
+
|
|
27
|
+
# ----------------------
|
|
28
|
+
# Address normalization
|
|
29
|
+
# ----------------------
|
|
30
|
+
def _normalize_address(self, addr: str) -> str:
|
|
31
|
+
"""Normalize addresses (lowercase, strip, drop LUN_)."""
|
|
32
|
+
if not addr:
|
|
33
|
+
return ''
|
|
34
|
+
addr_str = str(addr).strip("'\" ").lower()
|
|
35
|
+
return addr_str[4:] if addr_str.startswith('lun_') else addr_str
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def add_transaction(self, transaction: Dict) -> bool:
|
|
39
|
+
"""Add transaction to local mempool and broadcast to network"""
|
|
40
|
+
try:
|
|
41
|
+
tx_hash = transaction.get('hash')
|
|
42
|
+
if not tx_hash:
|
|
43
|
+
print("DEBUG: Transaction missing hash")
|
|
44
|
+
return False
|
|
45
|
+
|
|
46
|
+
# Check if transaction already exists or is confirmed
|
|
47
|
+
if tx_hash in self.local_mempool or tx_hash in self.confirmed_transactions:
|
|
48
|
+
print(f"DEBUG: Transaction already processed: {tx_hash}")
|
|
49
|
+
return True
|
|
50
|
+
|
|
51
|
+
# Validate basic transaction structure
|
|
52
|
+
if not self._validate_transaction_basic(transaction):
|
|
53
|
+
print("DEBUG: Transaction validation failed")
|
|
54
|
+
return False
|
|
55
|
+
|
|
56
|
+
# Add to local mempool
|
|
57
|
+
self.local_mempool[tx_hash] = {
|
|
58
|
+
'transaction': transaction,
|
|
59
|
+
'timestamp': time.time(),
|
|
60
|
+
'broadcast_attempts': 0,
|
|
61
|
+
'last_broadcast': 0
|
|
62
|
+
}
|
|
63
|
+
print(f"DEBUG: Added transaction to mempool: {tx_hash}")
|
|
64
|
+
|
|
65
|
+
# Queue for broadcasting
|
|
66
|
+
self.pending_broadcasts.put(transaction)
|
|
67
|
+
print(f"DEBUG: Queued transaction for broadcasting: {tx_hash}")
|
|
68
|
+
|
|
69
|
+
return True
|
|
70
|
+
|
|
71
|
+
except Exception as e:
|
|
72
|
+
print(f"DEBUG: Error adding transaction to mempool: {e}")
|
|
73
|
+
return False
|
|
74
|
+
|
|
75
|
+
def broadcast_transaction(self, transaction: Dict) -> bool:
|
|
76
|
+
"""Broadcast transaction to network endpoints - SIMPLIFIED FOR YOUR FLASK APP"""
|
|
77
|
+
tx_hash = transaction.get('hash')
|
|
78
|
+
print(f"DEBUG: Broadcasting transaction to mempool: {tx_hash}")
|
|
79
|
+
|
|
80
|
+
success = False
|
|
81
|
+
for endpoint in self.network_endpoints:
|
|
82
|
+
for attempt in range(self.broadcast_retries):
|
|
83
|
+
try:
|
|
84
|
+
# Use the correct endpoint for your Flask app
|
|
85
|
+
broadcast_endpoint = f"{endpoint}/mempool/add"
|
|
86
|
+
|
|
87
|
+
print(f"DEBUG: Attempt {attempt + 1} to {broadcast_endpoint}")
|
|
88
|
+
print(f"DEBUG: Transaction type: {transaction.get('type')}")
|
|
89
|
+
print(f"DEBUG: From: {transaction.get('from')}")
|
|
90
|
+
print(f"DEBUG: To: {transaction.get('to')}")
|
|
91
|
+
print(f"DEBUG: Amount: {transaction.get('amount')}")
|
|
92
|
+
|
|
93
|
+
headers = {
|
|
94
|
+
'Content-Type': 'application/json',
|
|
95
|
+
'User-Agent': 'LunaWallet/1.0'
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
# Send transaction directly to mempool endpoint
|
|
99
|
+
response = requests.post(
|
|
100
|
+
broadcast_endpoint,
|
|
101
|
+
json=transaction, # Send the transaction directly
|
|
102
|
+
headers=headers,
|
|
103
|
+
timeout=10
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
print(f"DEBUG: Response status: {response.status_code}")
|
|
107
|
+
|
|
108
|
+
if response.status_code in [200, 201]:
|
|
109
|
+
result = response.json()
|
|
110
|
+
print(f"DEBUG: Response data: {result}")
|
|
111
|
+
|
|
112
|
+
if result.get('success'):
|
|
113
|
+
print(f"✅ Successfully added to mempool via {broadcast_endpoint}")
|
|
114
|
+
success = True
|
|
115
|
+
break
|
|
116
|
+
else:
|
|
117
|
+
error_msg = result.get('error', 'Unknown error')
|
|
118
|
+
print(f"❌ Mempool rejected transaction: {error_msg}")
|
|
119
|
+
else:
|
|
120
|
+
print(f"❌ HTTP error {response.status_code}: {response.text}")
|
|
121
|
+
|
|
122
|
+
except requests.exceptions.ConnectionError:
|
|
123
|
+
print(f"❌ Cannot connect to {endpoint}")
|
|
124
|
+
except requests.exceptions.Timeout:
|
|
125
|
+
print(f"❌ Request timeout to {endpoint}")
|
|
126
|
+
except Exception as e:
|
|
127
|
+
print(f"❌ Exception during broadcast: {e}")
|
|
128
|
+
|
|
129
|
+
# Wait before retry
|
|
130
|
+
if attempt < self.broadcast_retries - 1:
|
|
131
|
+
print(f"DEBUG: Waiting before retry...")
|
|
132
|
+
time.sleep(2)
|
|
133
|
+
|
|
134
|
+
if success:
|
|
135
|
+
print(f"✅ Transaction {tx_hash} successfully broadcasted")
|
|
136
|
+
else:
|
|
137
|
+
print(f"❌ All broadcast attempts failed for transaction {tx_hash}")
|
|
138
|
+
|
|
139
|
+
return success
|
|
140
|
+
|
|
141
|
+
def test_connection(self) -> bool:
|
|
142
|
+
"""Test connection to network endpoints"""
|
|
143
|
+
for endpoint in self.network_endpoints:
|
|
144
|
+
try:
|
|
145
|
+
print(f"DEBUG: Testing connection to {endpoint}")
|
|
146
|
+
# Test with a simple health check or mempool status
|
|
147
|
+
test_endpoints = [
|
|
148
|
+
f"{endpoint}/system/health",
|
|
149
|
+
f"{endpoint}/mempool/status",
|
|
150
|
+
f"{endpoint}/"
|
|
151
|
+
]
|
|
152
|
+
|
|
153
|
+
for test_endpoint in test_endpoints:
|
|
154
|
+
try:
|
|
155
|
+
response = requests.get(test_endpoint, timeout=5)
|
|
156
|
+
print(f"DEBUG: Connection test response from {test_endpoint}: {response.status_code}")
|
|
157
|
+
if response.status_code == 200:
|
|
158
|
+
print(f"✅ Successfully connected to {endpoint}")
|
|
159
|
+
return True
|
|
160
|
+
except:
|
|
161
|
+
continue
|
|
162
|
+
|
|
163
|
+
except Exception as e:
|
|
164
|
+
print(f"DEBUG: Connection test failed for {endpoint}: {e}")
|
|
165
|
+
|
|
166
|
+
print("❌ All connection tests failed")
|
|
167
|
+
return False
|
|
168
|
+
|
|
169
|
+
def get_transaction(self, tx_hash: str) -> Optional[Dict]:
|
|
170
|
+
"""Get transaction from mempool by hash"""
|
|
171
|
+
if tx_hash in self.local_mempool:
|
|
172
|
+
return self.local_mempool[tx_hash]['transaction']
|
|
173
|
+
return None
|
|
174
|
+
|
|
175
|
+
def _maybe_fetch_remote_mempool(self):
|
|
176
|
+
"""Fetch mempool from remote endpoints and merge into local cache."""
|
|
177
|
+
for endpoint in self.network_endpoints:
|
|
178
|
+
try:
|
|
179
|
+
resp = requests.get(f"{endpoint}/mempool", timeout=10)
|
|
180
|
+
if resp.status_code == 200:
|
|
181
|
+
data = resp.json()
|
|
182
|
+
if isinstance(data, list):
|
|
183
|
+
for tx in data:
|
|
184
|
+
tx_hash = tx.get('hash')
|
|
185
|
+
if tx_hash and tx_hash not in self.local_mempool and tx_hash not in self.confirmed_transactions:
|
|
186
|
+
self.local_mempool[tx_hash] = {
|
|
187
|
+
'transaction': tx,
|
|
188
|
+
'timestamp': time.time(),
|
|
189
|
+
'broadcast_attempts': 0,
|
|
190
|
+
'last_broadcast': 0
|
|
191
|
+
}
|
|
192
|
+
else:
|
|
193
|
+
print(f"DEBUG: Remote mempool fetch HTTP {resp.status_code}: {resp.text}")
|
|
194
|
+
except Exception as e:
|
|
195
|
+
print(f"DEBUG: Remote mempool fetch error from {endpoint}: {e}")
|
|
196
|
+
|
|
197
|
+
def get_pending_transactions(self, address: str = None, fetch_remote: bool = True) -> List[Dict]:
|
|
198
|
+
"""Get all pending transactions, optionally filtered by address; can fetch remote first."""
|
|
199
|
+
if fetch_remote:
|
|
200
|
+
self._maybe_fetch_remote_mempool()
|
|
201
|
+
|
|
202
|
+
target_norm = self._normalize_address(address) if address else None
|
|
203
|
+
transactions = []
|
|
204
|
+
for tx_data in self.local_mempool.values():
|
|
205
|
+
tx = tx_data['transaction']
|
|
206
|
+
if address is None:
|
|
207
|
+
transactions.append(tx)
|
|
208
|
+
continue
|
|
209
|
+
|
|
210
|
+
from_norm = self._normalize_address(tx.get('from') or tx.get('sender'))
|
|
211
|
+
to_norm = self._normalize_address(tx.get('to') or tx.get('receiver'))
|
|
212
|
+
if target_norm and (from_norm == target_norm or to_norm == target_norm):
|
|
213
|
+
transactions.append(tx)
|
|
214
|
+
return transactions
|
|
215
|
+
|
|
216
|
+
def get_pending_transactions_for_addresses(self, addresses: List[str], fetch_remote: bool = True) -> Dict[str, List[Dict]]:
|
|
217
|
+
"""Get pending transactions mapped per address in one pass; can fetch remote first."""
|
|
218
|
+
if not addresses:
|
|
219
|
+
return {}
|
|
220
|
+
|
|
221
|
+
if fetch_remote:
|
|
222
|
+
self._maybe_fetch_remote_mempool()
|
|
223
|
+
|
|
224
|
+
norm_to_original: Dict[str, str] = {}
|
|
225
|
+
for addr in addresses:
|
|
226
|
+
norm = self._normalize_address(addr)
|
|
227
|
+
if norm:
|
|
228
|
+
norm_to_original[norm] = addr
|
|
229
|
+
|
|
230
|
+
results: Dict[str, List[Dict]] = {addr: [] for addr in addresses}
|
|
231
|
+
|
|
232
|
+
for tx_data in self.local_mempool.values():
|
|
233
|
+
tx = tx_data['transaction']
|
|
234
|
+
from_norm = self._normalize_address(tx.get('from') or tx.get('sender'))
|
|
235
|
+
to_norm = self._normalize_address(tx.get('to') or tx.get('receiver'))
|
|
236
|
+
|
|
237
|
+
if from_norm in norm_to_original:
|
|
238
|
+
results[norm_to_original[from_norm]].append(tx)
|
|
239
|
+
if to_norm in norm_to_original:
|
|
240
|
+
results[norm_to_original[to_norm]].append(tx)
|
|
241
|
+
|
|
242
|
+
return {addr: txs for addr, txs in results.items() if txs}
|
|
243
|
+
|
|
244
|
+
def remove_transaction(self, tx_hash: str):
|
|
245
|
+
"""Remove transaction from mempool (usually after confirmation)"""
|
|
246
|
+
if tx_hash in self.local_mempool:
|
|
247
|
+
del self.local_mempool[tx_hash]
|
|
248
|
+
self.confirmed_transactions.add(tx_hash)
|
|
249
|
+
print(f"DEBUG: Removed transaction from mempool: {tx_hash}")
|
|
250
|
+
|
|
251
|
+
def is_transaction_pending(self, tx_hash: str) -> bool:
|
|
252
|
+
"""Check if transaction is pending in mempool"""
|
|
253
|
+
return tx_hash in self.local_mempool
|
|
254
|
+
|
|
255
|
+
def is_transaction_confirmed(self, tx_hash: str) -> bool:
|
|
256
|
+
"""Check if transaction has been confirmed"""
|
|
257
|
+
return tx_hash in self.confirmed_transactions
|
|
258
|
+
|
|
259
|
+
def get_mempool_size(self) -> int:
|
|
260
|
+
"""Get current mempool size"""
|
|
261
|
+
return len(self.local_mempool)
|
|
262
|
+
|
|
263
|
+
def clear_mempool(self):
|
|
264
|
+
"""Clear all transactions from mempool"""
|
|
265
|
+
self.local_mempool.clear()
|
|
266
|
+
print("DEBUG: Cleared mempool")
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def _validate_transaction_basic(self, transaction: Dict) -> bool:
|
|
271
|
+
"""Basic transaction validation"""
|
|
272
|
+
required_fields = ['type', 'from', 'to', 'amount', 'timestamp', 'hash']
|
|
273
|
+
|
|
274
|
+
for field in required_fields:
|
|
275
|
+
if field not in transaction:
|
|
276
|
+
print(f"DEBUG: Missing required field: {field}")
|
|
277
|
+
return False
|
|
278
|
+
|
|
279
|
+
# Validate amount
|
|
280
|
+
try:
|
|
281
|
+
amount = float(transaction['amount'])
|
|
282
|
+
if amount <= 0:
|
|
283
|
+
print("DEBUG: Invalid amount (must be positive)")
|
|
284
|
+
return False
|
|
285
|
+
except (ValueError, TypeError):
|
|
286
|
+
print("DEBUG: Invalid amount format")
|
|
287
|
+
return False
|
|
288
|
+
|
|
289
|
+
# Validate timestamp (not too far in future)
|
|
290
|
+
try:
|
|
291
|
+
timestamp = float(transaction['timestamp'])
|
|
292
|
+
if timestamp > time.time() + 300: # 5 minutes in future
|
|
293
|
+
print("DEBUG: Transaction timestamp too far in future")
|
|
294
|
+
return False
|
|
295
|
+
except (ValueError, TypeError):
|
|
296
|
+
print("DEBUG: Invalid timestamp format")
|
|
297
|
+
return False
|
|
298
|
+
|
|
299
|
+
# Validate addresses (basic format check)
|
|
300
|
+
from_addr = transaction.get('from', '')
|
|
301
|
+
to_addr = transaction.get('to', '')
|
|
302
|
+
|
|
303
|
+
if not from_addr or not to_addr:
|
|
304
|
+
print("DEBUG: Missing from or to address")
|
|
305
|
+
return False
|
|
306
|
+
|
|
307
|
+
print(f"✅ Transaction validation passed: {transaction.get('type')} from {from_addr} to {to_addr}")
|
|
308
|
+
return True
|
|
309
|
+
|
|
310
|
+
def _broadcast_worker(self):
|
|
311
|
+
"""Background worker to process pending broadcasts"""
|
|
312
|
+
while self.is_running:
|
|
313
|
+
try:
|
|
314
|
+
# Get next transaction to broadcast (blocking)
|
|
315
|
+
transaction = self.pending_broadcasts.get(timeout=1.0)
|
|
316
|
+
|
|
317
|
+
if transaction:
|
|
318
|
+
tx_hash = transaction.get('hash')
|
|
319
|
+
print(f"DEBUG: Processing broadcast for transaction: {tx_hash}")
|
|
320
|
+
|
|
321
|
+
# Broadcast the transaction
|
|
322
|
+
success = self.broadcast_transaction(transaction)
|
|
323
|
+
|
|
324
|
+
# Update broadcast attempts in local mempool
|
|
325
|
+
if tx_hash in self.local_mempool:
|
|
326
|
+
self.local_mempool[tx_hash]['broadcast_attempts'] += 1
|
|
327
|
+
self.local_mempool[tx_hash]['last_broadcast'] = time.time()
|
|
328
|
+
|
|
329
|
+
# Mark task as done
|
|
330
|
+
self.pending_broadcasts.task_done()
|
|
331
|
+
|
|
332
|
+
# Small delay between broadcasts
|
|
333
|
+
time.sleep(0.5)
|
|
334
|
+
|
|
335
|
+
except Exception as e:
|
|
336
|
+
# Queue.get() timed out or other error
|
|
337
|
+
continue
|
|
338
|
+
|
|
339
|
+
def stop(self):
|
|
340
|
+
"""Stop the mempool manager"""
|
|
341
|
+
self.is_running = False
|
|
342
|
+
print("DEBUG: Mempool manager stopped")
|