lunalib 1.2.3__py3-none-any.whl → 1.5.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.
- core/wallet.py +407 -346
- lunalib/core/blockchain.py +315 -150
- lunalib/core/crypto.py +265 -27
- lunalib/core/mempool.py +104 -102
- lunalib/core/sm2.py +723 -0
- lunalib/core/wallet.py +850 -319
- lunalib/transactions/security.py +87 -25
- lunalib/transactions/transactions.py +247 -431
- {lunalib-1.2.3.dist-info → lunalib-1.5.0.dist-info}/METADATA +1 -1
- {lunalib-1.2.3.dist-info → lunalib-1.5.0.dist-info}/RECORD +14 -13
- transactions/transactions.py +423 -378
- {lunalib-1.2.3.dist-info → lunalib-1.5.0.dist-info}/WHEEL +0 -0
- {lunalib-1.2.3.dist-info → lunalib-1.5.0.dist-info}/entry_points.txt +0 -0
- {lunalib-1.2.3.dist-info → lunalib-1.5.0.dist-info}/top_level.txt +0 -0
|
@@ -5,272 +5,82 @@ import json
|
|
|
5
5
|
from typing import Dict, Optional, Tuple, List
|
|
6
6
|
from ..core.mempool import MempoolManager
|
|
7
7
|
|
|
8
|
+
# Import REAL SM2 KeyManager from crypto module
|
|
9
|
+
try:
|
|
10
|
+
from ..core.crypto import KeyManager as SM2KeyManager
|
|
11
|
+
SM2_AVAILABLE = True
|
|
12
|
+
print("DEBUG: Using SM2 KeyManager from crypto module")
|
|
13
|
+
except ImportError as e:
|
|
14
|
+
SM2_AVAILABLE = False
|
|
15
|
+
print(f"WARNING: SM2 KeyManager not available: {e}")
|
|
16
|
+
|
|
8
17
|
class TransactionSecurity:
|
|
9
18
|
"""Transaction security validation and risk assessment"""
|
|
10
19
|
|
|
11
20
|
def validate_transaction(self, transaction: Dict) -> Tuple[bool, str]:
|
|
12
21
|
"""Validate transaction structure"""
|
|
13
|
-
required_fields = ['type', 'from', 'to', 'amount', 'timestamp']
|
|
22
|
+
required_fields = ['type', 'from', 'to', 'amount', 'timestamp', 'hash']
|
|
14
23
|
for field in required_fields:
|
|
15
24
|
if field not in transaction:
|
|
16
25
|
return False, f'Missing required field: {field}'
|
|
17
26
|
|
|
18
27
|
# Validate amount
|
|
19
|
-
if transaction['amount'] <= 0:
|
|
28
|
+
if transaction['amount'] <= 0 and transaction['type'] != 'reward':
|
|
20
29
|
return False, 'Amount must be positive'
|
|
21
30
|
|
|
22
31
|
return True, 'Valid'
|
|
23
32
|
|
|
24
|
-
def validate_transaction_security(self, transaction: Dict) -> Tuple[bool, str]:
|
|
25
|
-
"""Enhanced security validation"""
|
|
26
|
-
required_fields = ['type', 'from', 'to', 'amount', 'timestamp', 'hash']
|
|
27
|
-
for field in required_fields:
|
|
28
|
-
if field not in transaction:
|
|
29
|
-
return False, f'Missing required field: {field}'
|
|
30
|
-
|
|
31
|
-
# Validate amount
|
|
32
|
-
if transaction['amount'] <= 0:
|
|
33
|
-
return False, 'Invalid amount'
|
|
34
|
-
|
|
35
|
-
# Validate transaction type
|
|
36
|
-
valid_types = ['transfer', 'stake', 'unstake', 'delegate', 'reward',
|
|
37
|
-
'fee_distribution', 'gtx_genesis', 'validator_join',
|
|
38
|
-
'validator_leave', 'governance_vote']
|
|
39
|
-
if transaction['type'] not in valid_types:
|
|
40
|
-
return False, f'Invalid transaction type: {transaction["type"]}'
|
|
41
|
-
|
|
42
|
-
return True, 'Secure'
|
|
43
|
-
|
|
44
33
|
def assess_risk(self, transaction: Dict) -> Tuple[str, str]:
|
|
45
34
|
"""Assess transaction risk level"""
|
|
46
35
|
amount = transaction.get('amount', 0)
|
|
47
36
|
tx_type = transaction.get('type', 'transfer')
|
|
48
37
|
|
|
49
|
-
if tx_type in ['gtx_genesis', 'reward'
|
|
38
|
+
if tx_type in ['gtx_genesis', 'reward']:
|
|
50
39
|
return 'very_low', 'System transaction'
|
|
51
40
|
|
|
52
41
|
if amount > 1000000:
|
|
53
|
-
return 'high', 'Very large transaction
|
|
42
|
+
return 'high', 'Very large transaction'
|
|
54
43
|
elif amount > 100000:
|
|
55
|
-
return '
|
|
56
|
-
elif amount > 10000:
|
|
57
|
-
return 'medium', 'Medium transaction amount'
|
|
58
|
-
elif amount > 1000:
|
|
59
|
-
return 'low', 'Small transaction amount'
|
|
44
|
+
return 'medium', 'Large transaction'
|
|
60
45
|
else:
|
|
61
|
-
return '
|
|
62
|
-
|
|
63
|
-
class KeyManager:
|
|
64
|
-
"""Key management for transaction signing"""
|
|
65
|
-
|
|
66
|
-
def derive_public_key(self, private_key: str) -> str:
|
|
67
|
-
"""Derive public key from private key"""
|
|
68
|
-
if not private_key:
|
|
69
|
-
return "unsigned_public_key"
|
|
70
|
-
return f"pub_{hashlib.sha256(private_key.encode()).hexdigest()[:32]}"
|
|
71
|
-
|
|
72
|
-
def sign_data(self, data: str, private_key: str) -> str:
|
|
73
|
-
"""Sign data with private key"""
|
|
74
|
-
if not private_key:
|
|
75
|
-
return "unsigned_signature"
|
|
76
|
-
return hashlib.sha256(f"{data}{private_key}".encode()).hexdigest()
|
|
77
|
-
|
|
78
|
-
def verify_signature(self, data: str, signature: str, public_key: str) -> bool:
|
|
79
|
-
"""Verify signature (simplified - in production use proper ECDSA)"""
|
|
80
|
-
if signature == "unsigned_signature" or public_key == "unsigned_public_key":
|
|
81
|
-
return True # Allow unsigned system transactions
|
|
82
|
-
|
|
83
|
-
# Simplified verification - in real implementation, use proper cryptographic verification
|
|
84
|
-
expected_signature = hashlib.sha256(f"{data}{public_key}".encode()).hexdigest()
|
|
85
|
-
return signature == expected_signature
|
|
86
|
-
|
|
87
|
-
class FeePoolManager:
|
|
88
|
-
"""Decentralized fee pool management"""
|
|
89
|
-
|
|
90
|
-
def __init__(self):
|
|
91
|
-
self.fee_pool_address = self._generate_fee_pool_address()
|
|
92
|
-
self.pending_fees = 0.0
|
|
93
|
-
self.distribution_blocks = 100 # Distribute fees every 100 blocks
|
|
94
|
-
self.last_distribution_block = 0
|
|
95
|
-
self.collected_fees = [] # Track fee collection history
|
|
96
|
-
|
|
97
|
-
def _generate_fee_pool_address(self) -> str:
|
|
98
|
-
"""Generate deterministic fee pool address"""
|
|
99
|
-
base_data = "LUNA_FEE_POOL_V2"
|
|
100
|
-
return hashlib.sha256(base_data.encode()).hexdigest()[:32]
|
|
101
|
-
|
|
102
|
-
def collect_fee(self, fee_amount: float, transaction_hash: str) -> bool:
|
|
103
|
-
"""Collect fee into the pool"""
|
|
104
|
-
if fee_amount > 0:
|
|
105
|
-
self.pending_fees += fee_amount
|
|
106
|
-
self.collected_fees.append({
|
|
107
|
-
'amount': fee_amount,
|
|
108
|
-
'tx_hash': transaction_hash,
|
|
109
|
-
'timestamp': time.time()
|
|
110
|
-
})
|
|
111
|
-
print(f"DEBUG: Collected fee {fee_amount} from transaction {transaction_hash}")
|
|
112
|
-
return True
|
|
113
|
-
return False
|
|
114
|
-
|
|
115
|
-
def should_distribute(self, current_block_height: int) -> bool:
|
|
116
|
-
"""Check if it's time to distribute fees"""
|
|
117
|
-
return (current_block_height - self.last_distribution_block) >= self.distribution_blocks
|
|
118
|
-
|
|
119
|
-
def calculate_rewards(self, stakers: List[Dict], total_stake: float) -> List[Dict]:
|
|
120
|
-
"""Calculate rewards for stakers based on their stake"""
|
|
121
|
-
if total_stake <= 0 or self.pending_fees <= 0:
|
|
122
|
-
return []
|
|
123
|
-
|
|
124
|
-
rewards = []
|
|
125
|
-
for staker in stakers:
|
|
126
|
-
stake_amount = staker.get('stake', 0)
|
|
127
|
-
if stake_amount > 0:
|
|
128
|
-
share = stake_amount / total_stake
|
|
129
|
-
reward = self.pending_fees * share
|
|
130
|
-
rewards.append({
|
|
131
|
-
'address': staker['address'],
|
|
132
|
-
'reward': reward,
|
|
133
|
-
'stake_share': share,
|
|
134
|
-
'stake_amount': stake_amount
|
|
135
|
-
})
|
|
136
|
-
|
|
137
|
-
return rewards
|
|
138
|
-
|
|
139
|
-
def create_distribution_transactions(self, current_block_height: int,
|
|
140
|
-
stakers: List[Dict], total_stake: float) -> List[Dict]:
|
|
141
|
-
"""Create fee distribution transactions to stakers"""
|
|
142
|
-
if not self.should_distribute(current_block_height) or self.pending_fees <= 0:
|
|
143
|
-
return []
|
|
144
|
-
|
|
145
|
-
rewards = self.calculate_rewards(stakers, total_stake)
|
|
146
|
-
distribution_txs = []
|
|
147
|
-
|
|
148
|
-
total_distributed = 0
|
|
149
|
-
for reward_info in rewards:
|
|
150
|
-
if reward_info['reward'] > 0:
|
|
151
|
-
distribution_tx = {
|
|
152
|
-
"type": "fee_distribution",
|
|
153
|
-
"from": self.fee_pool_address,
|
|
154
|
-
"to": reward_info['address'],
|
|
155
|
-
"amount": reward_info['reward'],
|
|
156
|
-
"fee": 0.0,
|
|
157
|
-
"block_height": current_block_height,
|
|
158
|
-
"distribution_cycle": current_block_height // self.distribution_blocks,
|
|
159
|
-
"stake_share": reward_info['stake_share'],
|
|
160
|
-
"stake_amount": reward_info['stake_amount'],
|
|
161
|
-
"timestamp": time.time(),
|
|
162
|
-
"hash": self._generate_distribution_hash(reward_info['address'], reward_info['reward'], current_block_height)
|
|
163
|
-
}
|
|
164
|
-
distribution_txs.append(distribution_tx)
|
|
165
|
-
total_distributed += reward_info['reward']
|
|
166
|
-
|
|
167
|
-
# Reset pending fees after distribution
|
|
168
|
-
self.pending_fees = 0.0
|
|
169
|
-
self.last_distribution_block = current_block_height
|
|
170
|
-
|
|
171
|
-
print(f"DEBUG: Distributed {total_distributed} fees to {len(distribution_txs)} stakers")
|
|
172
|
-
return distribution_txs
|
|
173
|
-
|
|
174
|
-
def _generate_distribution_hash(self, address: str, amount: float, block_height: int) -> str:
|
|
175
|
-
"""Generate unique hash for distribution transaction"""
|
|
176
|
-
data = f"fee_dist_{address}_{amount}_{block_height}_{time.time_ns()}"
|
|
177
|
-
return hashlib.sha256(data.encode()).hexdigest()
|
|
178
|
-
|
|
179
|
-
def get_fee_statistics(self) -> Dict:
|
|
180
|
-
"""Get fee pool statistics"""
|
|
181
|
-
total_collected = sum(fee['amount'] for fee in self.collected_fees)
|
|
182
|
-
return {
|
|
183
|
-
'pending_fees': self.pending_fees,
|
|
184
|
-
'total_collected': total_collected,
|
|
185
|
-
'distribution_count': len(self.collected_fees),
|
|
186
|
-
'last_distribution_block': self.last_distribution_block,
|
|
187
|
-
'pool_address': self.fee_pool_address
|
|
188
|
-
}
|
|
46
|
+
return 'low', 'Normal transaction'
|
|
189
47
|
|
|
190
48
|
class FeeCalculator:
|
|
191
|
-
"""
|
|
49
|
+
"""Simple fee calculation"""
|
|
192
50
|
|
|
193
|
-
def __init__(self
|
|
194
|
-
self.fee_pool_manager = fee_pool_manager
|
|
51
|
+
def __init__(self):
|
|
195
52
|
self.fee_config = {
|
|
196
|
-
'transfer': 0.
|
|
197
|
-
'
|
|
198
|
-
'
|
|
199
|
-
'delegate': 0.00005, # Delegation fee
|
|
200
|
-
'validator_join': 0.001, # Validator registration fee
|
|
201
|
-
'validator_leave': 0.0001, # Validator exit fee
|
|
202
|
-
'governance_vote': 0.00001, # Governance voting fee
|
|
203
|
-
'gtx_genesis': 0.0, # No fee for GTX genesis
|
|
204
|
-
'reward': 0.0, # No fee for rewards
|
|
205
|
-
'fee_distribution': 0.0, # No fee for fee distributions
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
# Dynamic fee tiers based on transaction amount
|
|
209
|
-
self.amount_tiers = {
|
|
210
|
-
'micro': (0, 1, 0.000001),
|
|
211
|
-
'small': (1, 100, 0.00001),
|
|
212
|
-
'medium': (100, 10000, 0.0001),
|
|
213
|
-
'large': (10000, 100000, 0.001),
|
|
214
|
-
'xlarge': (100000, float('inf'), 0.01)
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
def set_fee(self, transaction_type: str, fee_amount: float):
|
|
218
|
-
"""Set custom fee for a transaction type"""
|
|
219
|
-
self.fee_config[transaction_type] = max(0.0, fee_amount)
|
|
220
|
-
|
|
221
|
-
def get_fee(self, transaction_type: str, amount: float = 0.0) -> float:
|
|
222
|
-
"""Get fee for transaction type and amount"""
|
|
223
|
-
base_fee = self.fee_config.get(transaction_type, 0.00001)
|
|
224
|
-
|
|
225
|
-
# Apply amount-based fee scaling
|
|
226
|
-
for tier_name, (min_amt, max_amt, tier_fee) in self.amount_tiers.items():
|
|
227
|
-
if min_amt <= amount < max_amt:
|
|
228
|
-
return max(base_fee, tier_fee)
|
|
229
|
-
|
|
230
|
-
return base_fee
|
|
231
|
-
|
|
232
|
-
def calculate_network_fee(self, transaction_size: int, priority: str = 'normal') -> float:
|
|
233
|
-
"""Calculate fee based on transaction size and priority"""
|
|
234
|
-
base_fee_per_byte = 0.0000001 # Base fee per byte
|
|
235
|
-
|
|
236
|
-
priority_multipliers = {
|
|
237
|
-
'low': 0.5,
|
|
238
|
-
'normal': 1.0,
|
|
239
|
-
'high': 2.0,
|
|
240
|
-
'urgent': 5.0
|
|
53
|
+
'transfer': 0.001,
|
|
54
|
+
'reward': 0.0,
|
|
55
|
+
'gtx_genesis': 0.0
|
|
241
56
|
}
|
|
242
|
-
|
|
243
|
-
multiplier = priority_multipliers.get(priority, 1.0)
|
|
244
|
-
return transaction_size * base_fee_per_byte * multiplier
|
|
245
57
|
|
|
246
|
-
def
|
|
247
|
-
"""
|
|
248
|
-
|
|
249
|
-
if fee > 0:
|
|
250
|
-
return self.fee_pool_manager.collect_fee(fee, transaction.get('hash', ''))
|
|
251
|
-
return True
|
|
58
|
+
def get_fee(self, transaction_type: str) -> float:
|
|
59
|
+
"""Get fee for transaction type"""
|
|
60
|
+
return self.fee_config.get(transaction_type, 0.001)
|
|
252
61
|
|
|
253
62
|
class TransactionManager:
|
|
254
63
|
"""Handles transaction creation, signing, validation, and broadcasting"""
|
|
255
64
|
|
|
256
65
|
def __init__(self, network_endpoints: List[str] = None):
|
|
257
66
|
self.security = TransactionSecurity()
|
|
258
|
-
self.
|
|
259
|
-
self.fee_pool_manager = FeePoolManager()
|
|
260
|
-
self.fee_calculator = FeeCalculator(self.fee_pool_manager)
|
|
67
|
+
self.fee_calculator = FeeCalculator()
|
|
261
68
|
self.mempool_manager = MempoolManager(network_endpoints)
|
|
69
|
+
|
|
70
|
+
# Initialize SM2 KeyManager if available
|
|
71
|
+
if SM2_AVAILABLE:
|
|
72
|
+
self.key_manager = SM2KeyManager()
|
|
73
|
+
else:
|
|
74
|
+
print("ERROR: SM2 KeyManager not available - cannot sign transactions")
|
|
75
|
+
self.key_manager = None
|
|
262
76
|
|
|
263
77
|
def create_transaction(self, from_address: str, to_address: str, amount: float,
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
"""Create and sign a transaction with configurable fees"""
|
|
78
|
+
private_key: Optional[str] = None, memo: str = "",
|
|
79
|
+
transaction_type: str = "transfer") -> Dict:
|
|
80
|
+
"""Create and sign a transaction"""
|
|
268
81
|
|
|
269
82
|
# Calculate fee
|
|
270
|
-
|
|
271
|
-
fee = max(0.0, fee_override)
|
|
272
|
-
else:
|
|
273
|
-
fee = self.fee_calculator.get_fee(transaction_type, amount)
|
|
83
|
+
fee = self.fee_calculator.get_fee(transaction_type)
|
|
274
84
|
|
|
275
85
|
transaction = {
|
|
276
86
|
"type": transaction_type,
|
|
@@ -278,29 +88,56 @@ class TransactionManager:
|
|
|
278
88
|
"to": to_address,
|
|
279
89
|
"amount": float(amount),
|
|
280
90
|
"fee": fee,
|
|
281
|
-
"
|
|
282
|
-
"timestamp": time.time(),
|
|
91
|
+
"timestamp": int(time.time()),
|
|
283
92
|
"memo": memo,
|
|
284
|
-
"
|
|
285
|
-
"version": "1.0"
|
|
93
|
+
"version": "2.0" # Version 2.0 = SM2 signatures
|
|
286
94
|
}
|
|
287
95
|
|
|
288
|
-
#
|
|
289
|
-
if private_key:
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
96
|
+
# Sign transaction with SM2 if private key provided
|
|
97
|
+
if private_key and self.key_manager:
|
|
98
|
+
try:
|
|
99
|
+
# Sign the transaction data
|
|
100
|
+
tx_string = self._get_signing_data(transaction)
|
|
101
|
+
|
|
102
|
+
print(f"[TRANSACTIONS CREATE DEBUG] Signing data: {tx_string}")
|
|
103
|
+
print(f"[TRANSACTIONS CREATE DEBUG] Private key available: {bool(private_key)}")
|
|
104
|
+
print(f"[TRANSACTIONS CREATE DEBUG] Private key length: {len(private_key)}")
|
|
105
|
+
|
|
106
|
+
signature = self.key_manager.sign_data(tx_string, private_key)
|
|
107
|
+
|
|
108
|
+
# Get public key from private key
|
|
109
|
+
public_key = self.key_manager.derive_public_key(private_key)
|
|
110
|
+
|
|
111
|
+
print(f"[TRANSACTIONS CREATE DEBUG] Generated signature: {signature}")
|
|
112
|
+
print(f"[TRANSACTIONS CREATE DEBUG] Generated public key: {public_key}")
|
|
113
|
+
print(f"[TRANSACTIONS CREATE DEBUG] Signature length: {len(signature)}")
|
|
114
|
+
print(f"[TRANSACTIONS CREATE DEBUG] Public key length: {len(public_key)}")
|
|
115
|
+
|
|
116
|
+
transaction["signature"] = signature
|
|
117
|
+
transaction["public_key"] = public_key
|
|
118
|
+
|
|
119
|
+
# Immediately test verification
|
|
120
|
+
test_verify = self.key_manager.verify_signature(tx_string, signature, public_key)
|
|
121
|
+
print(f"[TRANSACTIONS CREATE DEBUG] Immediate self-verification: {test_verify}")
|
|
122
|
+
|
|
123
|
+
if not test_verify:
|
|
124
|
+
print(f"[TRANSACTIONS CREATE ERROR] Signature doesn't verify immediately!")
|
|
125
|
+
print(f"[TRANSACTIONS CREATE ERROR] This suggests an SM2 implementation issue")
|
|
126
|
+
|
|
127
|
+
except Exception as e:
|
|
128
|
+
print(f"[TRANSACTIONS CREATE ERROR] Signing failed: {e}")
|
|
129
|
+
import traceback
|
|
130
|
+
traceback.print_exc()
|
|
131
|
+
transaction["signature"] = "unsigned"
|
|
132
|
+
transaction["public_key"] = "unsigned"
|
|
294
133
|
else:
|
|
295
|
-
# For unsigned transactions (
|
|
296
|
-
transaction["public_key"] = "unsigned"
|
|
134
|
+
# For unsigned transactions (rewards, gtx_genesis)
|
|
297
135
|
transaction["signature"] = "unsigned"
|
|
136
|
+
transaction["public_key"] = "unsigned"
|
|
298
137
|
|
|
138
|
+
# Calculate transaction hash (must be last)
|
|
299
139
|
transaction["hash"] = self._calculate_transaction_hash(transaction)
|
|
300
140
|
|
|
301
|
-
# Automatically collect fee
|
|
302
|
-
self.fee_calculator.process_transaction_fee(transaction)
|
|
303
|
-
|
|
304
141
|
return transaction
|
|
305
142
|
|
|
306
143
|
def send_transaction(self, transaction: Dict) -> Tuple[bool, str]:
|
|
@@ -311,19 +148,24 @@ class TransactionManager:
|
|
|
311
148
|
if not is_valid:
|
|
312
149
|
return False, f"Validation failed: {message}"
|
|
313
150
|
|
|
151
|
+
# Verify signature for non-system transactions
|
|
152
|
+
if transaction["type"] == "transfer":
|
|
153
|
+
if not self.verify_transaction_signature(transaction):
|
|
154
|
+
return False, "Invalid transaction signature"
|
|
155
|
+
|
|
314
156
|
# Add to mempool
|
|
315
157
|
success = self.mempool_manager.add_transaction(transaction)
|
|
316
158
|
if success:
|
|
317
|
-
return True, f"Transaction added to mempool: {transaction.get('hash')}"
|
|
159
|
+
return True, f"Transaction added to mempool: {transaction.get('hash', '')[:16]}..."
|
|
318
160
|
else:
|
|
319
161
|
return False, "Failed to add transaction to mempool"
|
|
320
162
|
|
|
321
163
|
except Exception as e:
|
|
322
164
|
return False, f"Error sending transaction: {str(e)}"
|
|
323
165
|
|
|
324
|
-
# TRANSFER
|
|
166
|
+
# TRANSFER TRANSACTION
|
|
325
167
|
def create_transfer(self, from_address: str, to_address: str, amount: float,
|
|
326
|
-
private_key: str, memo: str = ""
|
|
168
|
+
private_key: str, memo: str = "") -> Dict:
|
|
327
169
|
"""Create a transfer transaction"""
|
|
328
170
|
return self.create_transaction(
|
|
329
171
|
from_address=from_address,
|
|
@@ -331,109 +173,30 @@ class TransactionManager:
|
|
|
331
173
|
amount=amount,
|
|
332
174
|
private_key=private_key,
|
|
333
175
|
memo=memo,
|
|
334
|
-
transaction_type="transfer"
|
|
335
|
-
fee_override=fee_override
|
|
176
|
+
transaction_type="transfer"
|
|
336
177
|
)
|
|
337
178
|
|
|
338
|
-
#
|
|
339
|
-
def create_stake(self, from_address: str, amount: float, private_key: str) -> Dict:
|
|
340
|
-
"""Create staking transaction"""
|
|
341
|
-
return self.create_transaction(
|
|
342
|
-
from_address=from_address,
|
|
343
|
-
to_address="staking_pool",
|
|
344
|
-
amount=amount,
|
|
345
|
-
private_key=private_key,
|
|
346
|
-
transaction_type="stake"
|
|
347
|
-
)
|
|
348
|
-
|
|
349
|
-
def create_unstake(self, from_address: str, amount: float, private_key: str) -> Dict:
|
|
350
|
-
"""Create unstaking transaction"""
|
|
351
|
-
return self.create_transaction(
|
|
352
|
-
from_address="staking_pool",
|
|
353
|
-
to_address=from_address,
|
|
354
|
-
amount=amount,
|
|
355
|
-
private_key=private_key,
|
|
356
|
-
transaction_type="unstake"
|
|
357
|
-
)
|
|
358
|
-
|
|
359
|
-
def create_delegate(self, from_address: str, to_validator: str, amount: float,
|
|
360
|
-
private_key: str) -> Dict:
|
|
361
|
-
"""Create delegation transaction"""
|
|
362
|
-
return self.create_transaction(
|
|
363
|
-
from_address=from_address,
|
|
364
|
-
to_address=to_validator,
|
|
365
|
-
amount=amount,
|
|
366
|
-
private_key=private_key,
|
|
367
|
-
transaction_type="delegate"
|
|
368
|
-
)
|
|
369
|
-
|
|
370
|
-
# VALIDATOR TRANSACTIONS
|
|
371
|
-
def create_validator_join(self, from_address: str, validator_info: Dict,
|
|
372
|
-
private_key: str) -> Dict:
|
|
373
|
-
"""Create validator registration transaction"""
|
|
374
|
-
transaction = self.create_transaction(
|
|
375
|
-
from_address=from_address,
|
|
376
|
-
to_address="validator_registry",
|
|
377
|
-
amount=validator_info.get('stake', 0),
|
|
378
|
-
private_key=private_key,
|
|
379
|
-
transaction_type="validator_join"
|
|
380
|
-
)
|
|
381
|
-
# Add validator-specific info
|
|
382
|
-
transaction.update({
|
|
383
|
-
"validator_name": validator_info.get('name', ''),
|
|
384
|
-
"validator_url": validator_info.get('url', ''),
|
|
385
|
-
"commission_rate": validator_info.get('commission_rate', 0.0)
|
|
386
|
-
})
|
|
387
|
-
return transaction
|
|
388
|
-
|
|
389
|
-
def create_validator_leave(self, from_address: str, private_key: str) -> Dict:
|
|
390
|
-
"""Create validator exit transaction"""
|
|
391
|
-
return self.create_transaction(
|
|
392
|
-
from_address=from_address,
|
|
393
|
-
to_address="validator_exit",
|
|
394
|
-
amount=0, # No amount for exit
|
|
395
|
-
private_key=private_key,
|
|
396
|
-
transaction_type="validator_leave"
|
|
397
|
-
)
|
|
398
|
-
|
|
399
|
-
# GOVERNANCE TRANSACTIONS
|
|
400
|
-
def create_governance_vote(self, from_address: str, proposal_id: str,
|
|
401
|
-
vote: str, private_key: str) -> Dict:
|
|
402
|
-
"""Create governance vote transaction"""
|
|
403
|
-
transaction = self.create_transaction(
|
|
404
|
-
from_address=from_address,
|
|
405
|
-
to_address="governance",
|
|
406
|
-
amount=0, # No amount for voting
|
|
407
|
-
private_key=private_key,
|
|
408
|
-
transaction_type="governance_vote"
|
|
409
|
-
)
|
|
410
|
-
# Add governance-specific info
|
|
411
|
-
transaction.update({
|
|
412
|
-
"proposal_id": proposal_id,
|
|
413
|
-
"vote": vote, # 'yes', 'no', 'abstain'
|
|
414
|
-
"voting_power": 1.0 # Could be based on stake
|
|
415
|
-
})
|
|
416
|
-
return transaction
|
|
417
|
-
|
|
418
|
-
# SYSTEM TRANSACTIONS
|
|
179
|
+
# SYSTEM TRANSACTIONS (unsigned)
|
|
419
180
|
def create_gtx_transaction(self, bill_info: Dict) -> Dict:
|
|
420
181
|
"""Create GTX Genesis transaction from mined bill"""
|
|
421
|
-
|
|
182
|
+
transaction = {
|
|
422
183
|
"type": "gtx_genesis",
|
|
423
184
|
"from": "mining",
|
|
424
185
|
"to": bill_info.get("owner_address", "unknown"),
|
|
425
|
-
"amount": bill_info.get("denomination", 0),
|
|
186
|
+
"amount": float(bill_info.get("denomination", 0)),
|
|
426
187
|
"fee": 0.0,
|
|
427
|
-
"timestamp": time.time(),
|
|
188
|
+
"timestamp": int(time.time()),
|
|
428
189
|
"bill_serial": bill_info.get("serial", ""),
|
|
429
190
|
"mining_difficulty": bill_info.get("difficulty", 0),
|
|
430
|
-
"
|
|
191
|
+
"signature": "system",
|
|
431
192
|
"public_key": "system",
|
|
432
|
-
"
|
|
193
|
+
"version": "2.0"
|
|
433
194
|
}
|
|
195
|
+
transaction["hash"] = self._calculate_transaction_hash(transaction)
|
|
196
|
+
return transaction
|
|
434
197
|
|
|
435
198
|
def create_reward_transaction(self, to_address: str, amount: float,
|
|
436
|
-
block_height: int
|
|
199
|
+
block_height: int) -> Dict:
|
|
437
200
|
"""Create reward transaction"""
|
|
438
201
|
transaction = {
|
|
439
202
|
"type": "reward",
|
|
@@ -442,61 +205,122 @@ class TransactionManager:
|
|
|
442
205
|
"amount": float(amount),
|
|
443
206
|
"fee": 0.0,
|
|
444
207
|
"block_height": block_height,
|
|
445
|
-
"
|
|
446
|
-
"
|
|
447
|
-
"hash": self._generate_reward_hash(to_address, amount, block_height, reward_type),
|
|
208
|
+
"timestamp": int(time.time()),
|
|
209
|
+
"signature": "system",
|
|
448
210
|
"public_key": "system",
|
|
449
|
-
"
|
|
211
|
+
"version": "2.0"
|
|
450
212
|
}
|
|
213
|
+
transaction["hash"] = self._generate_reward_hash(to_address, amount, block_height)
|
|
451
214
|
return transaction
|
|
452
215
|
|
|
453
|
-
def create_fee_distribution(self, to_address: str, amount: float,
|
|
454
|
-
distribution_cycle: int, stake_share: float) -> Dict:
|
|
455
|
-
"""Create fee distribution transaction"""
|
|
456
|
-
return {
|
|
457
|
-
"type": "fee_distribution",
|
|
458
|
-
"from": self.fee_pool_manager.fee_pool_address,
|
|
459
|
-
"to": to_address,
|
|
460
|
-
"amount": float(amount),
|
|
461
|
-
"fee": 0.0,
|
|
462
|
-
"distribution_cycle": distribution_cycle,
|
|
463
|
-
"stake_share": stake_share,
|
|
464
|
-
"timestamp": time.time(),
|
|
465
|
-
"hash": self.fee_pool_manager._generate_distribution_hash(to_address, amount, distribution_cycle),
|
|
466
|
-
"public_key": "system",
|
|
467
|
-
"signature": "system"
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
# FEE DISTRIBUTION
|
|
471
|
-
def distribute_fees(self, current_block_height: int, stakers: List[Dict],
|
|
472
|
-
total_stake: float) -> List[Dict]:
|
|
473
|
-
"""Distribute collected fees to stakers"""
|
|
474
|
-
return self.fee_pool_manager.create_distribution_transactions(
|
|
475
|
-
current_block_height, stakers, total_stake
|
|
476
|
-
)
|
|
477
|
-
|
|
478
216
|
# VALIDATION METHODS
|
|
479
217
|
def validate_transaction(self, transaction: Dict) -> Tuple[bool, str]:
|
|
480
218
|
"""Validate transaction using security module"""
|
|
481
219
|
return self.security.validate_transaction(transaction)
|
|
482
220
|
|
|
483
|
-
def validate_transaction_security(self, transaction: Dict) -> Tuple[bool, str]:
|
|
484
|
-
"""Validate transaction security"""
|
|
485
|
-
return self.security.validate_transaction_security(transaction)
|
|
486
|
-
|
|
487
|
-
def assess_transaction_risk(self, transaction: Dict) -> Tuple[str, str]:
|
|
488
|
-
"""Assess transaction risk level"""
|
|
489
|
-
return self.security.assess_risk(transaction)
|
|
490
|
-
|
|
491
221
|
def verify_transaction_signature(self, transaction: Dict) -> bool:
|
|
492
|
-
"""Verify transaction signature"""
|
|
222
|
+
"""Verify transaction signature with SM2"""
|
|
493
223
|
try:
|
|
494
|
-
sign_data = self._get_signing_data(transaction)
|
|
495
224
|
signature = transaction.get("signature", "")
|
|
225
|
+
tx_type = transaction.get("type", "").lower()
|
|
226
|
+
|
|
227
|
+
# System transactions are always valid
|
|
228
|
+
if signature in ["system", "unsigned", "test"]:
|
|
229
|
+
print(f"[TRANSACTIONS] Skipping signature check for {signature} transaction")
|
|
230
|
+
return True
|
|
231
|
+
|
|
232
|
+
if not self.key_manager:
|
|
233
|
+
print("[TRANSACTIONS] No key manager available for verification")
|
|
234
|
+
return False
|
|
235
|
+
|
|
236
|
+
# Check SM2 signature format
|
|
237
|
+
if len(signature) != 128:
|
|
238
|
+
print(f"[TRANSACTIONS] Invalid SM2 signature length: {len(signature)} (expected 128)")
|
|
239
|
+
return False
|
|
240
|
+
|
|
241
|
+
# Get signing data (without public_key!)
|
|
242
|
+
sign_data = self._get_signing_data(transaction)
|
|
496
243
|
public_key = transaction.get("public_key", "")
|
|
497
|
-
|
|
498
|
-
|
|
244
|
+
|
|
245
|
+
print(f"[TRANSACTIONS VERIFY] Signing data length: {len(sign_data)}")
|
|
246
|
+
print(f"[TRANSACTIONS VERIFY] Signing data (first 100 chars): {sign_data[:100]}")
|
|
247
|
+
|
|
248
|
+
# Try to verify with KeyManager
|
|
249
|
+
print(f"[TRANSACTIONS] Attempting verification...")
|
|
250
|
+
is_valid = self.key_manager.verify_signature(sign_data, signature, public_key)
|
|
251
|
+
|
|
252
|
+
print(f"[TRANSACTIONS] SM2 signature verification result: {is_valid}")
|
|
253
|
+
|
|
254
|
+
return is_valid
|
|
255
|
+
|
|
256
|
+
except Exception as e:
|
|
257
|
+
print(f"[TRANSACTIONS] Verification error: {e}")
|
|
258
|
+
import traceback
|
|
259
|
+
traceback.print_exc()
|
|
499
260
|
return False
|
|
261
|
+
def _debug_signature_issue(self, transaction: Dict, sign_data: str, signature: str, public_key: str):
|
|
262
|
+
"""Debug why signature verification is failing"""
|
|
263
|
+
print("\n" + "="*60)
|
|
264
|
+
print("DEBUGGING SIGNATURE ISSUE")
|
|
265
|
+
print("="*60)
|
|
266
|
+
|
|
267
|
+
# 1. Check if we can sign and verify a simple test
|
|
268
|
+
print("\n1. Testing SM2 with simple message...")
|
|
269
|
+
test_message = "Simple test message"
|
|
270
|
+
test_private = self.key_manager.generate_private_key()
|
|
271
|
+
test_public = self.key_manager.derive_public_key(test_private)
|
|
272
|
+
test_sig = self.key_manager.sign_data(test_message, test_private)
|
|
273
|
+
test_valid = self.key_manager.verify_signature(test_message, test_sig, test_public)
|
|
274
|
+
print(f" Simple test verification: {test_valid}")
|
|
275
|
+
|
|
276
|
+
# 2. Try to recreate what was signed during transaction creation
|
|
277
|
+
print("\n2. Reconstructing original transaction data...")
|
|
278
|
+
# Create the exact transaction data that should have been signed
|
|
279
|
+
reconstructed = {
|
|
280
|
+
"amount": float(transaction["amount"]),
|
|
281
|
+
"fee": float(transaction["fee"]),
|
|
282
|
+
"from": transaction["from"],
|
|
283
|
+
"memo": transaction.get("memo", ""),
|
|
284
|
+
"timestamp": int(transaction["timestamp"]),
|
|
285
|
+
"to": transaction["to"],
|
|
286
|
+
"type": transaction["type"],
|
|
287
|
+
"version": transaction.get("version", "2.0")
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
import json
|
|
291
|
+
reconstructed_json = json.dumps(reconstructed, sort_keys=True)
|
|
292
|
+
print(f" Reconstructed JSON: {reconstructed_json}")
|
|
293
|
+
print(f" Current signing data: {sign_data}")
|
|
294
|
+
print(f" Are they equal? {reconstructed_json == sign_data}")
|
|
295
|
+
print(f" Length difference: {len(reconstructed_json)} vs {len(sign_data)}")
|
|
296
|
+
|
|
297
|
+
# 3. Check for whitespace differences
|
|
298
|
+
print("\n3. Checking for whitespace differences...")
|
|
299
|
+
print(f" Reconstructed has spaces: {' ' in reconstructed_json}")
|
|
300
|
+
print(f" Sign data has spaces: {' ' in sign_data}")
|
|
301
|
+
|
|
302
|
+
# 4. Check float formatting
|
|
303
|
+
print("\n4. Checking float formatting...")
|
|
304
|
+
print(f" Amount in tx: {transaction['amount']} (type: {type(transaction['amount'])})")
|
|
305
|
+
print(f" Amount in reconstructed: {reconstructed['amount']} (type: {type(reconstructed['amount'])})")
|
|
306
|
+
|
|
307
|
+
# 5. Try different JSON serialization options
|
|
308
|
+
print("\n5. Trying different JSON formats...")
|
|
309
|
+
formats = [
|
|
310
|
+
("Compact", lambda x: json.dumps(x, sort_keys=True, separators=(',', ':'))),
|
|
311
|
+
("Default", lambda x: json.dumps(x, sort_keys=True)),
|
|
312
|
+
("Indented", lambda x: json.dumps(x, sort_keys=True, indent=2)),
|
|
313
|
+
]
|
|
314
|
+
|
|
315
|
+
for name, formatter in formats:
|
|
316
|
+
formatted = formatter(reconstructed)
|
|
317
|
+
is_valid_test = self.key_manager.verify_signature(formatted, signature, public_key)
|
|
318
|
+
print(f" {name} format: {is_valid_test} (length: {len(formatted)})")
|
|
319
|
+
|
|
320
|
+
print("="*60 + "\n")
|
|
321
|
+
def assess_transaction_risk(self, transaction: Dict) -> Tuple[str, str]:
|
|
322
|
+
"""Assess transaction risk level"""
|
|
323
|
+
return self.security.assess_risk(transaction)
|
|
500
324
|
|
|
501
325
|
# MEMPOOL MANAGEMENT
|
|
502
326
|
def get_pending_transactions(self, address: str = None) -> List[Dict]:
|
|
@@ -507,77 +331,69 @@ class TransactionManager:
|
|
|
507
331
|
"""Check if transaction is pending in mempool"""
|
|
508
332
|
return self.mempool_manager.is_transaction_pending(tx_hash)
|
|
509
333
|
|
|
510
|
-
def is_transaction_confirmed(self, tx_hash: str) -> bool:
|
|
511
|
-
"""Check if transaction has been confirmed"""
|
|
512
|
-
return self.mempool_manager.is_transaction_confirmed(tx_hash)
|
|
513
|
-
|
|
514
|
-
# FEE MANAGEMENT
|
|
515
|
-
def set_transaction_fee(self, transaction_type: str, fee_amount: float):
|
|
516
|
-
"""Set custom fee for transaction type"""
|
|
517
|
-
self.fee_calculator.set_fee(transaction_type, fee_amount)
|
|
518
|
-
|
|
519
|
-
def calculate_network_fee(self, transaction_size: int, priority: str = 'normal') -> float:
|
|
520
|
-
"""Calculate network fee based on size and priority"""
|
|
521
|
-
return self.fee_calculator.calculate_network_fee(transaction_size, priority)
|
|
522
|
-
|
|
523
|
-
def get_fee_pool_balance(self) -> float:
|
|
524
|
-
"""Get current fee pool balance"""
|
|
525
|
-
return self.fee_pool_manager.pending_fees
|
|
526
|
-
|
|
527
|
-
def get_fee_pool_address(self) -> str:
|
|
528
|
-
"""Get fee pool address"""
|
|
529
|
-
return self.fee_pool_manager.fee_pool_address
|
|
530
|
-
|
|
531
|
-
def get_fee_statistics(self) -> Dict:
|
|
532
|
-
"""Get fee pool statistics"""
|
|
533
|
-
return self.fee_pool_manager.get_fee_statistics()
|
|
534
|
-
|
|
535
|
-
# UTILITY METHODS
|
|
536
334
|
def _get_signing_data(self, transaction: Dict) -> str:
|
|
537
|
-
"""Create data string for signing"""
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
335
|
+
"""Create data string for signing - MUST match signing process exactly"""
|
|
336
|
+
# IMPORTANT: During signing, the transaction does NOT have 'public_key' yet!
|
|
337
|
+
# It's added AFTER signing. So we must exclude it during verification too.
|
|
338
|
+
|
|
339
|
+
# Fields that should NOT be included in signing data:
|
|
340
|
+
exclude_fields = ['signature', 'hash', 'public_key']
|
|
341
|
+
|
|
342
|
+
# Create copy without excluded fields
|
|
343
|
+
tx_copy = {k: v for k, v in transaction.items()
|
|
344
|
+
if k not in exclude_fields}
|
|
345
|
+
|
|
346
|
+
# Use the EXACT same format as during creation
|
|
347
|
+
import json
|
|
348
|
+
|
|
349
|
+
# Ensure consistent data types
|
|
350
|
+
for key in ['amount', 'fee']:
|
|
351
|
+
if key in tx_copy:
|
|
352
|
+
tx_copy[key] = float(tx_copy[key])
|
|
353
|
+
|
|
354
|
+
if 'timestamp' in tx_copy:
|
|
355
|
+
tx_copy['timestamp'] = int(tx_copy['timestamp'])
|
|
356
|
+
|
|
357
|
+
# Use COMPACT JSON (no extra spaces) - this is critical!
|
|
358
|
+
return json.dumps(tx_copy, sort_keys=True, separators=(',', ':'))
|
|
551
359
|
def _calculate_transaction_hash(self, transaction: Dict) -> str:
|
|
552
360
|
"""Calculate transaction hash"""
|
|
553
|
-
#
|
|
361
|
+
# Remove existing hash if present
|
|
554
362
|
tx_copy = transaction.copy()
|
|
555
|
-
tx_copy.pop("signature", None)
|
|
556
363
|
tx_copy.pop("hash", None)
|
|
364
|
+
|
|
365
|
+
# Convert to JSON and hash
|
|
557
366
|
data_string = json.dumps(tx_copy, sort_keys=True)
|
|
558
367
|
return hashlib.sha256(data_string.encode()).hexdigest()
|
|
559
368
|
|
|
560
369
|
def _generate_reward_hash(self, to_address: str, amount: float,
|
|
561
|
-
block_height: int
|
|
370
|
+
block_height: int) -> str:
|
|
562
371
|
"""Generate unique hash for reward transaction"""
|
|
563
|
-
data = f"reward_{
|
|
372
|
+
data = f"reward_{to_address}_{amount}_{block_height}_{time.time_ns()}"
|
|
564
373
|
return hashlib.sha256(data.encode()).hexdigest()
|
|
565
374
|
|
|
566
375
|
def stop(self):
|
|
567
376
|
"""Stop the transaction manager"""
|
|
568
377
|
self.mempool_manager.stop()
|
|
569
378
|
|
|
570
|
-
#
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
379
|
+
# Create a global instance for convenience
|
|
380
|
+
_transaction_manager = None
|
|
381
|
+
|
|
382
|
+
def get_transaction_manager() -> TransactionManager:
|
|
383
|
+
"""Get or create global transaction manager instance"""
|
|
384
|
+
global _transaction_manager
|
|
385
|
+
if _transaction_manager is None:
|
|
386
|
+
_transaction_manager = TransactionManager()
|
|
387
|
+
return _transaction_manager
|
|
388
|
+
|
|
389
|
+
# Convenience functions
|
|
390
|
+
def create_transfer(from_address: str, to_address: str, amount: float,
|
|
391
|
+
private_key: str, memo: str = "") -> Dict:
|
|
392
|
+
"""Create transfer transaction (convenience function)"""
|
|
393
|
+
mgr = get_transaction_manager()
|
|
394
|
+
return mgr.create_transfer(from_address, to_address, amount, private_key, memo)
|
|
395
|
+
|
|
396
|
+
def send_transaction(transaction: Dict) -> Tuple[bool, str]:
|
|
397
|
+
"""Send transaction (convenience function)"""
|
|
398
|
+
mgr = get_transaction_manager()
|
|
399
|
+
return mgr.send_transaction(transaction)
|