lunalib 1.2.3__py3-none-any.whl → 1.5.2__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.
@@ -1,276 +1,95 @@
1
1
  # lunalib/transactions/transactions.py
2
2
  import time
3
+ import sys
4
+
5
+ # --- Unicode-safe print for Windows console ---
6
+ def safe_print(*args, **kwargs):
7
+ try:
8
+ print(*args, **kwargs)
9
+ except UnicodeEncodeError:
10
+ encoding = getattr(sys.stdout, 'encoding', 'utf-8')
11
+ print(*(str(a).encode(encoding, errors='replace').decode(encoding) for a in args), **kwargs)
3
12
  import hashlib
4
13
  import json
5
14
  from typing import Dict, Optional, Tuple, List
6
15
  from ..core.mempool import MempoolManager
7
16
 
17
+ # Import REAL SM2 KeyManager from crypto module
18
+ try:
19
+ from ..core.crypto import KeyManager as SM2KeyManager
20
+ SM2_AVAILABLE = True
21
+ safe_print("DEBUG: Using SM2 KeyManager from crypto module")
22
+ except ImportError as e:
23
+ SM2_AVAILABLE = False
24
+ safe_print(f"WARNING: SM2 KeyManager not available: {e}")
25
+
8
26
  class TransactionSecurity:
9
27
  """Transaction security validation and risk assessment"""
10
28
 
11
29
  def validate_transaction(self, transaction: Dict) -> Tuple[bool, str]:
12
30
  """Validate transaction structure"""
13
- required_fields = ['type', 'from', 'to', 'amount', 'timestamp']
31
+ required_fields = ['type', 'from', 'to', 'amount', 'timestamp', 'hash']
14
32
  for field in required_fields:
15
33
  if field not in transaction:
16
34
  return False, f'Missing required field: {field}'
17
35
 
18
36
  # Validate amount
19
- if transaction['amount'] <= 0:
37
+ if transaction['amount'] <= 0 and transaction['type'] != 'reward':
20
38
  return False, 'Amount must be positive'
21
39
 
22
40
  return True, 'Valid'
23
41
 
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
42
  def assess_risk(self, transaction: Dict) -> Tuple[str, str]:
45
43
  """Assess transaction risk level"""
46
44
  amount = transaction.get('amount', 0)
47
45
  tx_type = transaction.get('type', 'transfer')
48
46
 
49
- if tx_type in ['gtx_genesis', 'reward', 'fee_distribution']:
47
+ if tx_type in ['gtx_genesis', 'reward']:
50
48
  return 'very_low', 'System transaction'
51
49
 
52
50
  if amount > 1000000:
53
- return 'high', 'Very large transaction amount'
51
+ return 'high', 'Very large transaction'
54
52
  elif amount > 100000:
55
- return 'high', 'Large transaction amount'
56
- elif amount > 10000:
57
- return 'medium', 'Medium transaction amount'
58
- elif amount > 1000:
59
- return 'low', 'Small transaction amount'
53
+ return 'medium', 'Large transaction'
60
54
  else:
61
- return 'very_low', 'Normal transaction'
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
- }
55
+ return 'low', 'Normal transaction'
189
56
 
190
57
  class FeeCalculator:
191
- """Configurable fee calculation system"""
58
+ """Simple fee calculation"""
192
59
 
193
- def __init__(self, fee_pool_manager: FeePoolManager):
194
- self.fee_pool_manager = fee_pool_manager
60
+ def __init__(self):
195
61
  self.fee_config = {
196
- 'transfer': 0.00001, # Default transfer fee
197
- 'stake': 0.0001, # Staking fee
198
- 'unstake': 0.0001, # Unstaking fee
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
62
+ 'transfer': 0.001,
63
+ 'reward': 0.0,
64
+ 'gtx_genesis': 0.0
241
65
  }
242
-
243
- multiplier = priority_multipliers.get(priority, 1.0)
244
- return transaction_size * base_fee_per_byte * multiplier
245
66
 
246
- def process_transaction_fee(self, transaction: Dict) -> bool:
247
- """Process and collect transaction fee"""
248
- fee = transaction.get('fee', 0)
249
- if fee > 0:
250
- return self.fee_pool_manager.collect_fee(fee, transaction.get('hash', ''))
251
- return True
67
+ def get_fee(self, transaction_type: str) -> float:
68
+ """Get fee for transaction type"""
69
+ return self.fee_config.get(transaction_type, 0.001)
252
70
 
253
71
  class TransactionManager:
254
72
  """Handles transaction creation, signing, validation, and broadcasting"""
255
73
 
256
74
  def __init__(self, network_endpoints: List[str] = None):
257
75
  self.security = TransactionSecurity()
258
- self.key_manager = KeyManager()
259
- self.fee_pool_manager = FeePoolManager()
260
- self.fee_calculator = FeeCalculator(self.fee_pool_manager)
76
+ self.fee_calculator = FeeCalculator()
261
77
  self.mempool_manager = MempoolManager(network_endpoints)
78
+
79
+ # Initialize SM2 KeyManager if available
80
+ if SM2_AVAILABLE:
81
+ self.key_manager = SM2KeyManager()
82
+ else:
83
+ safe_print("ERROR: SM2 KeyManager not available - cannot sign transactions")
84
+ self.key_manager = None
262
85
 
263
86
  def create_transaction(self, from_address: str, to_address: str, amount: float,
264
- private_key: Optional[str] = None, memo: str = "",
265
- transaction_type: str = "transfer", fee_override: Optional[float] = None,
266
- priority: str = 'normal') -> Dict:
267
- """Create and sign a transaction with configurable fees"""
87
+ private_key: Optional[str] = None, memo: str = "",
88
+ transaction_type: str = "transfer") -> Dict:
89
+ """Create and sign a transaction"""
268
90
 
269
91
  # Calculate fee
270
- if fee_override is not None:
271
- fee = max(0.0, fee_override)
272
- else:
273
- fee = self.fee_calculator.get_fee(transaction_type, amount)
92
+ fee = self.fee_calculator.get_fee(transaction_type)
274
93
 
275
94
  transaction = {
276
95
  "type": transaction_type,
@@ -278,29 +97,56 @@ class TransactionManager:
278
97
  "to": to_address,
279
98
  "amount": float(amount),
280
99
  "fee": fee,
281
- "nonce": int(time.time() * 1000),
282
- "timestamp": time.time(),
100
+ "timestamp": int(time.time()),
283
101
  "memo": memo,
284
- "priority": priority,
285
- "version": "1.0"
102
+ "version": "2.0" # Version 2.0 = SM2 signatures
286
103
  }
287
104
 
288
- # Only add cryptographic fields if private key is provided
289
- if private_key:
290
- transaction["public_key"] = self.key_manager.derive_public_key(private_key)
291
- sign_data = self._get_signing_data(transaction)
292
- signature = self.key_manager.sign_data(sign_data, private_key)
293
- transaction["signature"] = signature
105
+ # Sign transaction with SM2 if private key provided
106
+ if private_key and self.key_manager:
107
+ try:
108
+ # Sign the transaction data
109
+ tx_string = self._get_signing_data(transaction)
110
+
111
+ safe_print(f"[TRANSACTIONS CREATE DEBUG] Signing data: {tx_string}")
112
+ safe_print(f"[TRANSACTIONS CREATE DEBUG] Private key available: {bool(private_key)}")
113
+ safe_print(f"[TRANSACTIONS CREATE DEBUG] Private key length: {len(private_key)}")
114
+
115
+ signature = self.key_manager.sign_data(tx_string, private_key)
116
+
117
+ # Get public key from private key
118
+ public_key = self.key_manager.derive_public_key(private_key)
119
+
120
+ safe_print(f"[TRANSACTIONS CREATE DEBUG] Generated signature: {signature}")
121
+ safe_print(f"[TRANSACTIONS CREATE DEBUG] Generated public key: {public_key}")
122
+ safe_print(f"[TRANSACTIONS CREATE DEBUG] Signature length: {len(signature)}")
123
+ safe_print(f"[TRANSACTIONS CREATE DEBUG] Public key length: {len(public_key)}")
124
+
125
+ transaction["signature"] = signature
126
+ transaction["public_key"] = public_key
127
+
128
+ # Immediately test verification
129
+ test_verify = self.key_manager.verify_signature(tx_string, signature, public_key)
130
+ safe_print(f"[TRANSACTIONS CREATE DEBUG] Immediate self-verification: {test_verify}")
131
+
132
+ if not test_verify:
133
+ safe_print(f"[TRANSACTIONS CREATE ERROR] Signature doesn't verify immediately!")
134
+ safe_print(f"[TRANSACTIONS CREATE ERROR] This suggests an SM2 implementation issue")
135
+
136
+ except Exception as e:
137
+ safe_print(f"[TRANSACTIONS CREATE ERROR] Signing failed: {e}")
138
+ import traceback
139
+ traceback.print_exc()
140
+ transaction["signature"] = "unsigned"
141
+ transaction["public_key"] = "unsigned"
294
142
  else:
295
- # For unsigned transactions (like rewards or system transactions)
296
- transaction["public_key"] = "unsigned"
143
+ # For unsigned transactions (rewards, gtx_genesis)
297
144
  transaction["signature"] = "unsigned"
145
+ transaction["public_key"] = "unsigned"
298
146
 
147
+ # Calculate transaction hash (must be last)
299
148
  transaction["hash"] = self._calculate_transaction_hash(transaction)
300
149
 
301
- # Automatically collect fee
302
- self.fee_calculator.process_transaction_fee(transaction)
303
-
304
150
  return transaction
305
151
 
306
152
  def send_transaction(self, transaction: Dict) -> Tuple[bool, str]:
@@ -311,19 +157,24 @@ class TransactionManager:
311
157
  if not is_valid:
312
158
  return False, f"Validation failed: {message}"
313
159
 
160
+ # Verify signature for non-system transactions
161
+ if transaction["type"] == "transfer":
162
+ if not self.verify_transaction_signature(transaction):
163
+ return False, "Invalid transaction signature"
164
+
314
165
  # Add to mempool
315
166
  success = self.mempool_manager.add_transaction(transaction)
316
167
  if success:
317
- return True, f"Transaction added to mempool: {transaction.get('hash')}"
168
+ return True, f"Transaction added to mempool: {transaction.get('hash', '')[:16]}..."
318
169
  else:
319
170
  return False, "Failed to add transaction to mempool"
320
171
 
321
172
  except Exception as e:
322
173
  return False, f"Error sending transaction: {str(e)}"
323
174
 
324
- # TRANSFER TRANSACTIONS
175
+ # TRANSFER TRANSACTION
325
176
  def create_transfer(self, from_address: str, to_address: str, amount: float,
326
- private_key: str, memo: str = "", fee_override: Optional[float] = None) -> Dict:
177
+ private_key: str, memo: str = "") -> Dict:
327
178
  """Create a transfer transaction"""
328
179
  return self.create_transaction(
329
180
  from_address=from_address,
@@ -331,109 +182,30 @@ class TransactionManager:
331
182
  amount=amount,
332
183
  private_key=private_key,
333
184
  memo=memo,
334
- transaction_type="transfer",
335
- fee_override=fee_override
336
- )
337
-
338
- # STAKING TRANSACTIONS
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"
185
+ transaction_type="transfer"
409
186
  )
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
187
 
418
- # SYSTEM TRANSACTIONS
188
+ # SYSTEM TRANSACTIONS (unsigned)
419
189
  def create_gtx_transaction(self, bill_info: Dict) -> Dict:
420
190
  """Create GTX Genesis transaction from mined bill"""
421
- return {
191
+ transaction = {
422
192
  "type": "gtx_genesis",
423
193
  "from": "mining",
424
194
  "to": bill_info.get("owner_address", "unknown"),
425
- "amount": bill_info.get("denomination", 0),
195
+ "amount": float(bill_info.get("denomination", 0)),
426
196
  "fee": 0.0,
427
- "timestamp": time.time(),
197
+ "timestamp": int(time.time()),
428
198
  "bill_serial": bill_info.get("serial", ""),
429
199
  "mining_difficulty": bill_info.get("difficulty", 0),
430
- "hash": f"gtx_{hashlib.sha256(json.dumps(bill_info).encode()).hexdigest()[:16]}",
200
+ "signature": "system",
431
201
  "public_key": "system",
432
- "signature": "system"
202
+ "version": "2.0"
433
203
  }
204
+ transaction["hash"] = self._calculate_transaction_hash(transaction)
205
+ return transaction
434
206
 
435
207
  def create_reward_transaction(self, to_address: str, amount: float,
436
- block_height: int, reward_type: str = "block") -> Dict:
208
+ block_height: int) -> Dict:
437
209
  """Create reward transaction"""
438
210
  transaction = {
439
211
  "type": "reward",
@@ -442,61 +214,122 @@ class TransactionManager:
442
214
  "amount": float(amount),
443
215
  "fee": 0.0,
444
216
  "block_height": block_height,
445
- "reward_type": reward_type, # block, fee, staking
446
- "timestamp": time.time(),
447
- "hash": self._generate_reward_hash(to_address, amount, block_height, reward_type),
217
+ "timestamp": int(time.time()),
218
+ "signature": "system",
448
219
  "public_key": "system",
449
- "signature": "system"
220
+ "version": "2.0"
450
221
  }
222
+ transaction["hash"] = self._generate_reward_hash(to_address, amount, block_height)
451
223
  return transaction
452
224
 
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
225
  # VALIDATION METHODS
479
226
  def validate_transaction(self, transaction: Dict) -> Tuple[bool, str]:
480
227
  """Validate transaction using security module"""
481
228
  return self.security.validate_transaction(transaction)
482
229
 
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
230
  def verify_transaction_signature(self, transaction: Dict) -> bool:
492
- """Verify transaction signature"""
231
+ """Verify transaction signature with SM2"""
493
232
  try:
494
- sign_data = self._get_signing_data(transaction)
495
233
  signature = transaction.get("signature", "")
234
+ tx_type = transaction.get("type", "").lower()
235
+
236
+ # System transactions are always valid
237
+ if signature in ["system", "unsigned", "test"]:
238
+ print(f"[TRANSACTIONS] Skipping signature check for {signature} transaction")
239
+ return True
240
+
241
+ if not self.key_manager:
242
+ print("[TRANSACTIONS] No key manager available for verification")
243
+ return False
244
+
245
+ # Check SM2 signature format
246
+ if len(signature) != 128:
247
+ print(f"[TRANSACTIONS] Invalid SM2 signature length: {len(signature)} (expected 128)")
248
+ return False
249
+
250
+ # Get signing data (without public_key!)
251
+ sign_data = self._get_signing_data(transaction)
496
252
  public_key = transaction.get("public_key", "")
497
- return self.key_manager.verify_signature(sign_data, signature, public_key)
498
- except:
253
+
254
+ print(f"[TRANSACTIONS VERIFY] Signing data length: {len(sign_data)}")
255
+ print(f"[TRANSACTIONS VERIFY] Signing data (first 100 chars): {sign_data[:100]}")
256
+
257
+ # Try to verify with KeyManager
258
+ print(f"[TRANSACTIONS] Attempting verification...")
259
+ is_valid = self.key_manager.verify_signature(sign_data, signature, public_key)
260
+
261
+ print(f"[TRANSACTIONS] SM2 signature verification result: {is_valid}")
262
+
263
+ return is_valid
264
+
265
+ except Exception as e:
266
+ print(f"[TRANSACTIONS] Verification error: {e}")
267
+ import traceback
268
+ traceback.print_exc()
499
269
  return False
270
+ def _debug_signature_issue(self, transaction: Dict, sign_data: str, signature: str, public_key: str):
271
+ """Debug why signature verification is failing"""
272
+ print("\n" + "="*60)
273
+ print("DEBUGGING SIGNATURE ISSUE")
274
+ print("="*60)
275
+
276
+ # 1. Check if we can sign and verify a simple test
277
+ print("\n1. Testing SM2 with simple message...")
278
+ test_message = "Simple test message"
279
+ test_private = self.key_manager.generate_private_key()
280
+ test_public = self.key_manager.derive_public_key(test_private)
281
+ test_sig = self.key_manager.sign_data(test_message, test_private)
282
+ test_valid = self.key_manager.verify_signature(test_message, test_sig, test_public)
283
+ print(f" Simple test verification: {test_valid}")
284
+
285
+ # 2. Try to recreate what was signed during transaction creation
286
+ print("\n2. Reconstructing original transaction data...")
287
+ # Create the exact transaction data that should have been signed
288
+ reconstructed = {
289
+ "amount": float(transaction["amount"]),
290
+ "fee": float(transaction["fee"]),
291
+ "from": transaction["from"],
292
+ "memo": transaction.get("memo", ""),
293
+ "timestamp": int(transaction["timestamp"]),
294
+ "to": transaction["to"],
295
+ "type": transaction["type"],
296
+ "version": transaction.get("version", "2.0")
297
+ }
298
+
299
+ import json
300
+ reconstructed_json = json.dumps(reconstructed, sort_keys=True)
301
+ print(f" Reconstructed JSON: {reconstructed_json}")
302
+ print(f" Current signing data: {sign_data}")
303
+ print(f" Are they equal? {reconstructed_json == sign_data}")
304
+ print(f" Length difference: {len(reconstructed_json)} vs {len(sign_data)}")
305
+
306
+ # 3. Check for whitespace differences
307
+ print("\n3. Checking for whitespace differences...")
308
+ print(f" Reconstructed has spaces: {' ' in reconstructed_json}")
309
+ print(f" Sign data has spaces: {' ' in sign_data}")
310
+
311
+ # 4. Check float formatting
312
+ print("\n4. Checking float formatting...")
313
+ print(f" Amount in tx: {transaction['amount']} (type: {type(transaction['amount'])})")
314
+ print(f" Amount in reconstructed: {reconstructed['amount']} (type: {type(reconstructed['amount'])})")
315
+
316
+ # 5. Try different JSON serialization options
317
+ print("\n5. Trying different JSON formats...")
318
+ formats = [
319
+ ("Compact", lambda x: json.dumps(x, sort_keys=True, separators=(',', ':'))),
320
+ ("Default", lambda x: json.dumps(x, sort_keys=True)),
321
+ ("Indented", lambda x: json.dumps(x, sort_keys=True, indent=2)),
322
+ ]
323
+
324
+ for name, formatter in formats:
325
+ formatted = formatter(reconstructed)
326
+ is_valid_test = self.key_manager.verify_signature(formatted, signature, public_key)
327
+ print(f" {name} format: {is_valid_test} (length: {len(formatted)})")
328
+
329
+ print("="*60 + "\n")
330
+ def assess_transaction_risk(self, transaction: Dict) -> Tuple[str, str]:
331
+ """Assess transaction risk level"""
332
+ return self.security.assess_risk(transaction)
500
333
 
501
334
  # MEMPOOL MANAGEMENT
502
335
  def get_pending_transactions(self, address: str = None) -> List[Dict]:
@@ -507,77 +340,69 @@ class TransactionManager:
507
340
  """Check if transaction is pending in mempool"""
508
341
  return self.mempool_manager.is_transaction_pending(tx_hash)
509
342
 
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
343
  def _get_signing_data(self, transaction: Dict) -> str:
537
- """Create data string for signing"""
538
- parts = [
539
- transaction["type"],
540
- transaction["from"],
541
- transaction["to"],
542
- str(transaction["amount"]),
543
- str(transaction.get("nonce", 0)),
544
- str(transaction["timestamp"]),
545
- transaction.get("memo", ""),
546
- str(transaction.get("fee", 0)),
547
- transaction.get("version", "1.0")
548
- ]
549
- return "|".join(parts)
550
-
344
+ """Create data string for signing - MUST match signing process exactly"""
345
+ # IMPORTANT: During signing, the transaction does NOT have 'public_key' yet!
346
+ # It's added AFTER signing. So we must exclude it during verification too.
347
+
348
+ # Fields that should NOT be included in signing data:
349
+ exclude_fields = ['signature', 'hash', 'public_key']
350
+
351
+ # Create copy without excluded fields
352
+ tx_copy = {k: v for k, v in transaction.items()
353
+ if k not in exclude_fields}
354
+
355
+ # Use the EXACT same format as during creation
356
+ import json
357
+
358
+ # Ensure consistent data types
359
+ for key in ['amount', 'fee']:
360
+ if key in tx_copy:
361
+ tx_copy[key] = float(tx_copy[key])
362
+
363
+ if 'timestamp' in tx_copy:
364
+ tx_copy['timestamp'] = int(tx_copy['timestamp'])
365
+
366
+ # Use COMPACT JSON (no extra spaces) - this is critical!
367
+ return json.dumps(tx_copy, sort_keys=True, separators=(',', ':'))
551
368
  def _calculate_transaction_hash(self, transaction: Dict) -> str:
552
369
  """Calculate transaction hash"""
553
- # Create a copy without signature for consistent hashing
370
+ # Remove existing hash if present
554
371
  tx_copy = transaction.copy()
555
- tx_copy.pop("signature", None)
556
372
  tx_copy.pop("hash", None)
373
+
374
+ # Convert to JSON and hash
557
375
  data_string = json.dumps(tx_copy, sort_keys=True)
558
376
  return hashlib.sha256(data_string.encode()).hexdigest()
559
377
 
560
378
  def _generate_reward_hash(self, to_address: str, amount: float,
561
- block_height: int, reward_type: str) -> str:
379
+ block_height: int) -> str:
562
380
  """Generate unique hash for reward transaction"""
563
- data = f"reward_{reward_type}_{to_address}_{amount}_{block_height}_{time.time_ns()}"
381
+ data = f"reward_{to_address}_{amount}_{block_height}_{time.time_ns()}"
564
382
  return hashlib.sha256(data.encode()).hexdigest()
565
383
 
566
384
  def stop(self):
567
385
  """Stop the transaction manager"""
568
386
  self.mempool_manager.stop()
569
387
 
570
- # Additional validator class for backward compatibility
571
- class TransactionValidator:
572
- """Transaction validator for risk assessment and validation"""
573
-
574
- def __init__(self):
575
- self.security = TransactionSecurity()
576
-
577
- def assess_risk(self, transaction: Dict) -> Tuple[str, str]:
578
- """Assess transaction risk level"""
579
- return self.security.assess_risk(transaction)
580
-
581
- def validate(self, transaction: Dict) -> Tuple[bool, str]:
582
- """Validate transaction"""
583
- return self.security.validate_transaction(transaction)
388
+ # Create a global instance for convenience
389
+ _transaction_manager = None
390
+
391
+ def get_transaction_manager() -> TransactionManager:
392
+ """Get or create global transaction manager instance"""
393
+ global _transaction_manager
394
+ if _transaction_manager is None:
395
+ _transaction_manager = TransactionManager()
396
+ return _transaction_manager
397
+
398
+ # Convenience functions
399
+ def create_transfer(from_address: str, to_address: str, amount: float,
400
+ private_key: str, memo: str = "") -> Dict:
401
+ """Create transfer transaction (convenience function)"""
402
+ mgr = get_transaction_manager()
403
+ return mgr.create_transfer(from_address, to_address, amount, private_key, memo)
404
+
405
+ def send_transaction(transaction: Dict) -> Tuple[bool, str]:
406
+ """Send transaction (convenience function)"""
407
+ mgr = get_transaction_manager()
408
+ return mgr.send_transaction(transaction)