lunalib 1.5.2__py3-none-any.whl → 1.6.6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
lunalib/mining/miner.py CHANGED
@@ -18,6 +18,7 @@ from ..gtx.digital_bill import DigitalBill
18
18
  from ..transactions.transactions import TransactionManager
19
19
  from ..core.blockchain import BlockchainManager
20
20
  from ..core.mempool import MempoolManager
21
+ from ..mining.cuda_manager import CUDAManager
21
22
 
22
23
  class GenesisMiner:
23
24
  """Mines GTX Genesis bills AND regular transfer transactions with configurable difficulty"""
@@ -74,7 +75,7 @@ class GenesisMiner:
74
75
 
75
76
  safe_print(f"✅ Successfully mined GTX ${denomination:,} bill!")
76
77
  safe_print(f"⏱️ Mining time: {mining_time:.2f}s")
77
- safe_print(f"📊 Hash attempts: {mining_result['nonce']:,}")
78
+ safe_print(f" Hash attempts: {mining_result['nonce']:,}")
78
79
  safe_print(f"🔗 Bill hash: {mining_result['hash'][:32]}...")
79
80
 
80
81
  # Convert to GTX Genesis transaction
@@ -174,7 +175,7 @@ class GenesisMiner:
174
175
  safe_print(f"✅ Successfully mined and validated Transaction Block #{block_height}!")
175
176
  safe_print(f"⏱️ Mining time: {mining_time:.2f}s")
176
177
  safe_print(f"💰 Block reward: {block['reward']:.6f} LUN")
177
- safe_print(f"📊 Transactions: {block['transaction_count']}")
178
+ safe_print(f" Transactions: {block['transaction_count']}")
178
179
  safe_print(f"🔗 Block hash: {mining_result['hash'][:32]}...")
179
180
 
180
181
  # Submit block to blockchain
@@ -298,7 +299,7 @@ class GenesisMiner:
298
299
  # Calculate merkleroot from transactions
299
300
  merkleroot = self._calculate_merkleroot(transactions)
300
301
 
301
- print(f"📊 Mining proof components:")
302
+ print(f" Mining proof components:")
302
303
  print(f" Block hash: {block_hash[:16]}...")
303
304
  print(f" Difficulty: {difficulty}")
304
305
  print(f" Nonce: {nonce}")
@@ -623,4 +624,484 @@ class GenesisMiner:
623
624
  return {
624
625
  "network_connected": False,
625
626
  "error": str(e)
626
- }
627
+ }
628
+
629
+ class Miner:
630
+ """
631
+ Robust miner class that integrates with BlockchainManager, MempoolManager,
632
+ DifficultySystem, and security validation to mine all transaction types.
633
+ """
634
+
635
+ def __init__(self, config, data_manager, mining_started_callback=None, mining_completed_callback=None, block_mined_callback=None):
636
+ self.config = config
637
+ self.data_manager = data_manager
638
+ self.is_mining = False
639
+ self.blocks_mined = 0
640
+ self.total_reward = 0.0
641
+ self.mining_started_callback = mining_started_callback
642
+ self.mining_completed_callback = mining_completed_callback
643
+ self.block_mined_callback = block_mined_callback
644
+
645
+ self.mining_history = self.data_manager.load_mining_history()
646
+
647
+ # Initialize lunalib components
648
+ self.blockchain_manager = BlockchainManager(endpoint_url=config.node_url)
649
+ self.mempool_manager = MempoolManager([config.node_url])
650
+ self.difficulty_system = DifficultySystem()
651
+ self.cuda_manager = CUDAManager()
652
+
653
+ # Import security components
654
+ try:
655
+ from ..transactions.security import SecurityManager
656
+ from ..transactions.validator import TransactionValidator
657
+ self.security_manager = SecurityManager()
658
+ self.transaction_validator = TransactionValidator()
659
+ except ImportError:
660
+ self.security_manager = None
661
+ self.transaction_validator = None
662
+
663
+ self.current_hash = ""
664
+ self.current_nonce = 0
665
+ self.hash_rate = 0
666
+ self.mining_thread = None
667
+ self.should_stop_mining = False
668
+
669
+ def mine_block(self) -> tuple[bool, str, Optional[Dict]]:
670
+ """
671
+ Mine a block from the mempool with proper validation and difficulty calculation.
672
+ Returns: (success, message, block_data)
673
+ """
674
+ try:
675
+ # Get the latest block from the blockchain
676
+ latest_block = self.blockchain_manager.get_latest_block()
677
+ if not latest_block:
678
+ return False, "Could not get latest block from server", None
679
+
680
+ current_index = latest_block.get('index', 0)
681
+ previous_hash = latest_block.get('hash', '0' * 64)
682
+ new_index = current_index + 1
683
+
684
+ # Get fresh transactions from mempool
685
+ mempool = self._get_fresh_mempool()
686
+
687
+ # Validate all transactions
688
+ valid_transactions = self._validate_transactions(mempool)
689
+
690
+ # If no valid transactions, create a reward transaction
691
+ if not valid_transactions:
692
+ reward_tx = self._create_empty_block_reward(new_index)
693
+ valid_transactions = [reward_tx]
694
+
695
+ # Calculate block difficulty based on transactions
696
+ block_difficulty = self._calculate_block_difficulty(valid_transactions)
697
+
698
+ # Calculate block reward using exponential difficulty system
699
+ total_reward = self._calculate_exponential_block_reward(block_difficulty)
700
+
701
+ # Create block data
702
+ block_data = {
703
+ 'index': new_index,
704
+ 'previous_hash': previous_hash,
705
+ 'timestamp': time.time(),
706
+ 'transactions': valid_transactions,
707
+ 'miner': self.config.miner_address,
708
+ 'difficulty': block_difficulty,
709
+ 'nonce': 0,
710
+ 'reward': total_reward,
711
+ 'hash': ''
712
+ }
713
+
714
+ # Try CUDA mining first if available
715
+ if self.cuda_manager and self.cuda_manager.cuda_available:
716
+ cuda_result = self._cuda_mine(block_data, block_difficulty)
717
+ if cuda_result:
718
+ return self._finalize_block(cuda_result, 'cuda', total_reward)
719
+
720
+ # Fallback to CPU mining
721
+ cpu_result = self._cpu_mine(block_data, block_difficulty)
722
+ if cpu_result:
723
+ return self._finalize_block(cpu_result, 'cpu', total_reward)
724
+
725
+ return False, "Mining timeout - no solution found", None
726
+
727
+ except Exception as e:
728
+ return False, f"Mining error: {str(e)}", None
729
+
730
+ def _get_fresh_mempool(self) -> List[Dict]:
731
+ """Get fresh mempool transactions with validation"""
732
+ try:
733
+ mempool = self.mempool_manager.get_pending_transactions()
734
+ if not mempool:
735
+ mempool = self.blockchain_manager.get_mempool()
736
+ return mempool if mempool else []
737
+ except Exception as e:
738
+ safe_print(f"Error fetching mempool: {e}")
739
+ return []
740
+
741
+ def _validate_transactions(self, transactions: List[Dict]) -> List[Dict]:
742
+ """Validate transactions using security manager"""
743
+ valid_transactions = []
744
+
745
+ for tx in transactions:
746
+ try:
747
+ # Basic validation
748
+ if not self._validate_transaction_structure(tx):
749
+ continue
750
+
751
+ # Security validation if available
752
+ if self.transaction_validator:
753
+ if not self.transaction_validator.validate_transaction(tx):
754
+ continue
755
+
756
+ valid_transactions.append(tx)
757
+ except Exception as e:
758
+ safe_print(f"Transaction validation error: {e}")
759
+ continue
760
+
761
+ return valid_transactions
762
+
763
+ def _validate_transaction_structure(self, tx: Dict) -> bool:
764
+ """Basic transaction structure validation"""
765
+ required_fields = ['type', 'timestamp']
766
+
767
+ for field in required_fields:
768
+ if field not in tx:
769
+ return False
770
+
771
+ tx_type = tx.get('type')
772
+
773
+ if tx_type == 'transaction':
774
+ if not all(k in tx for k in ['from', 'to', 'amount']):
775
+ return False
776
+ elif tx_type == 'genesis_bill':
777
+ if 'denomination' not in tx:
778
+ return False
779
+ elif tx_type == 'reward':
780
+ if not all(k in tx for k in ['to', 'amount']):
781
+ return False
782
+
783
+ return True
784
+
785
+ def _calculate_block_difficulty(self, transactions: List[Dict]) -> int:
786
+ """Calculate block difficulty based on transactions using DifficultySystem"""
787
+ if not transactions:
788
+ return self.config.difficulty
789
+
790
+ max_difficulty = self.config.difficulty
791
+
792
+ for tx in transactions:
793
+ tx_type = tx.get('type')
794
+
795
+ if tx_type == 'genesis_bill':
796
+ denomination = tx.get('denomination', 0)
797
+ tx_difficulty = self.difficulty_system.get_bill_difficulty(denomination)
798
+ max_difficulty = max(max_difficulty, tx_difficulty)
799
+ elif tx_type == 'transaction':
800
+ amount = tx.get('amount', 0)
801
+ tx_difficulty = self.difficulty_system.get_transaction_difficulty(amount)
802
+ max_difficulty = max(max_difficulty, tx_difficulty)
803
+ elif tx_type == 'reward':
804
+ max_difficulty = max(max_difficulty, 1)
805
+
806
+ return max(max_difficulty, self.config.difficulty)
807
+
808
+ def _calculate_block_reward(self, transactions: List[Dict]) -> float:
809
+ """Calculate total block reward based on transactions using DifficultySystem"""
810
+ total_reward = 0.0
811
+
812
+ for tx in transactions:
813
+ tx_type = tx.get('type')
814
+
815
+ if tx_type == 'genesis_bill':
816
+ denomination = tx.get('denomination', 0)
817
+ # Use difficulty system to calculate proper reward
818
+ bill_difficulty = self.difficulty_system.get_bill_difficulty(denomination)
819
+ avg_mining_time = 15.0 # Will be updated with actual time
820
+ bill_reward = self.difficulty_system.calculate_mining_reward(denomination, avg_mining_time)
821
+ total_reward += bill_reward
822
+ elif tx_type == 'transaction':
823
+ fee = tx.get('fee', 0)
824
+ total_reward += fee
825
+ elif tx_type == 'reward':
826
+ reward_amount = tx.get('amount', 0)
827
+ total_reward += reward_amount
828
+
829
+ # Minimum reward for empty blocks
830
+ if total_reward == 0:
831
+ total_reward = 1.0
832
+
833
+ return total_reward
834
+
835
+ def _calculate_exponential_block_reward(self, difficulty: int) -> float:
836
+ """Calculate block reward using exponential difficulty system
837
+
838
+ Uses the new exponential reward system:
839
+ difficulty 1 = 1 LKC
840
+ difficulty 2 = 10 LKC
841
+ difficulty 3 = 100 LKC
842
+ difficulty 9 = 100,000,000 LKC
843
+ """
844
+ return self.difficulty_system.calculate_block_reward(difficulty)
845
+
846
+ def _create_empty_block_reward(self, block_index: int) -> Dict:
847
+ """Create reward transaction for empty blocks"""
848
+ return {
849
+ 'type': 'reward',
850
+ 'from': 'network',
851
+ 'to': self.config.miner_address,
852
+ 'amount': 1.0,
853
+ 'timestamp': time.time(),
854
+ 'block_height': block_index,
855
+ 'hash': f"reward_{block_index}_{int(time.time())}",
856
+ 'description': 'Empty block mining reward'
857
+ }
858
+
859
+ def _cuda_mine(self, block_data: Dict, difficulty: int) -> Optional[Dict]:
860
+ """Mine using CUDA acceleration"""
861
+ try:
862
+ safe_print("Attempting CUDA mining...")
863
+ cuda_result = self.cuda_manager.cuda_mine_batch(
864
+ block_data, difficulty, batch_size=100000
865
+ )
866
+ if cuda_result and cuda_result.get('success'):
867
+ block_data['hash'] = cuda_result['hash']
868
+ block_data['nonce'] = cuda_result['nonce']
869
+ return block_data
870
+ except Exception as e:
871
+ safe_print(f"CUDA mining failed: {e}")
872
+ return None
873
+
874
+ def _cpu_mine(self, block_data: Dict, difficulty: int) -> Optional[Dict]:
875
+ """Mine using CPU"""
876
+ safe_print("Using CPU mining...")
877
+ start_time = time.time()
878
+ target = "0" * difficulty
879
+ nonce = 0
880
+ hash_count = 0
881
+ last_hash_update = start_time
882
+
883
+ while not self.should_stop_mining and nonce < 1000000:
884
+ # Calculate block hash
885
+ block_hash = self._calculate_block_hash(
886
+ block_data['index'],
887
+ block_data['previous_hash'],
888
+ block_data['timestamp'],
889
+ block_data['transactions'],
890
+ nonce,
891
+ block_data['miner'],
892
+ difficulty
893
+ )
894
+
895
+ if block_hash.startswith(target):
896
+ block_data['hash'] = block_hash
897
+ block_data['nonce'] = nonce
898
+ return block_data
899
+
900
+ nonce += 1
901
+ hash_count += 1
902
+ self.current_nonce = nonce
903
+ self.current_hash = block_hash
904
+
905
+ # Update hash rate
906
+ current_time = time.time()
907
+ if current_time - last_hash_update >= 1:
908
+ self.hash_rate = hash_count / (current_time - last_hash_update)
909
+ hash_count = 0
910
+ last_hash_update = current_time
911
+
912
+ if nonce % 1000 == 0 and not self.is_mining:
913
+ return None
914
+
915
+ return None
916
+
917
+ def _calculate_block_hash(self, index: int, previous_hash: str, timestamp: float,
918
+ transactions: List[Dict], nonce: int, miner: str, difficulty: int) -> str:
919
+ """Calculate SHA-256 hash of a block matching server validation"""
920
+ try:
921
+ block_data = {
922
+ "difficulty": int(difficulty),
923
+ "index": int(index),
924
+ "miner": str(miner),
925
+ "nonce": int(nonce),
926
+ "previous_hash": str(previous_hash),
927
+ "timestamp": float(timestamp),
928
+ "transactions": [], # Empty for mining proof
929
+ "version": "1.0"
930
+ }
931
+
932
+ block_string = json.dumps(block_data, sort_keys=True)
933
+ calculated_hash = hashlib.sha256(block_string.encode()).hexdigest()
934
+ return calculated_hash
935
+
936
+ except Exception as e:
937
+ safe_print(f"Hash calculation error: {e}")
938
+ return "0" * 64
939
+
940
+ def _finalize_block(self, block_data: Dict, method: str, total_reward: float) -> tuple[bool, str, Dict]:
941
+ """Finalize mined block with proper record keeping and blockchain submission"""
942
+ mining_time = time.time() - block_data.get('timestamp', time.time())
943
+
944
+ # Validate block before submission
945
+ validation_result = self._validate_mined_block(block_data)
946
+ if not validation_result[0]:
947
+ safe_print(f"❌ Block validation failed: {validation_result[1]}")
948
+ return False, f"Block validation failed: {validation_result[1]}", None
949
+
950
+ # Block reward is already calculated based on difficulty (exponential system)
951
+ final_reward = block_data['reward']
952
+
953
+ # Submit block to blockchain
954
+ try:
955
+ submission_success = self.blockchain_manager.submit_mined_block(block_data)
956
+
957
+ if not submission_success:
958
+ safe_print(f"⚠️ Block #{block_data['index']} submission failed")
959
+ return False, f"Block #{block_data['index']} mined but submission failed", None
960
+
961
+ safe_print(f"✅ Block #{block_data['index']} submitted successfully (Reward: {final_reward} LKC)")
962
+
963
+ # Clear mined transactions from mempool
964
+ self._clear_transactions_from_mempool(block_data['transactions'])
965
+
966
+ except Exception as e:
967
+ safe_print(f"❌ Block submission error: {e}")
968
+ return False, f"Block submission error: {str(e)}", None
969
+
970
+ # Record mining history
971
+ mining_record = {
972
+ 'block_index': block_data['index'],
973
+ 'timestamp': time.time(),
974
+ 'mining_time': mining_time,
975
+ 'difficulty': block_data['difficulty'],
976
+ 'nonce': block_data['nonce'],
977
+ 'hash': block_data['hash'],
978
+ 'method': method,
979
+ 'reward': final_reward,
980
+ 'status': 'success'
981
+ }
982
+ self.mining_history.append(mining_record)
983
+ self.save_mining_history()
984
+
985
+ self.blocks_mined += 1
986
+ self.total_reward += final_reward
987
+
988
+ if self.mining_completed_callback:
989
+ self.mining_completed_callback(True, f"Block #{block_data['index']} mined - Reward: {final_reward}")
990
+
991
+ if self.block_mined_callback:
992
+ self.block_mined_callback(block_data)
993
+
994
+ return True, f"Block #{block_data['index']} mined - Reward: {final_reward}", block_data
995
+
996
+ def _validate_mined_block(self, block: Dict) -> tuple:
997
+ """Validate mined block before submission
998
+
999
+ Returns: (is_valid, error_message)
1000
+ """
1001
+ # Validate structure
1002
+ is_valid, error = self.difficulty_system.validate_block_structure(block)
1003
+ if not is_valid:
1004
+ return False, error
1005
+
1006
+ # Validate hash meets difficulty
1007
+ block_hash = block.get('hash', '')
1008
+ difficulty = block.get('difficulty', 0)
1009
+
1010
+ if not self.difficulty_system.validate_block_hash(block_hash, difficulty):
1011
+ return False, f"Hash does not meet difficulty {difficulty} requirement"
1012
+
1013
+ # Validate previous hash (get from blockchain)
1014
+ try:
1015
+ latest_block = self.blockchain_manager.get_latest_block()
1016
+ if latest_block:
1017
+ expected_prev_hash = latest_block.get('hash', '')
1018
+ if block.get('previous_hash') != expected_prev_hash:
1019
+ return False, f"Previous hash mismatch: expected {expected_prev_hash[:16]}..., got {block.get('previous_hash', '')[:16]}..."
1020
+ except Exception as e:
1021
+ safe_print(f"⚠️ Could not validate previous hash: {e}")
1022
+
1023
+ # Validate reward matches difficulty
1024
+ expected_reward = self.difficulty_system.calculate_block_reward(difficulty)
1025
+ actual_reward = block.get('reward', 0)
1026
+
1027
+ # Allow some tolerance for floating point comparison
1028
+ if abs(actual_reward - expected_reward) > 0.01:
1029
+ return False, f"Reward mismatch: expected {expected_reward} LKC for difficulty {difficulty}, got {actual_reward} LKC"
1030
+
1031
+ safe_print(f"✅ Block validation passed: Hash meets difficulty {difficulty}, Reward: {actual_reward} LKC")
1032
+ return True, ""
1033
+
1034
+ def _clear_transactions_from_mempool(self, transactions: List[Dict]):
1035
+ """Remove mined transactions from mempool"""
1036
+ try:
1037
+ for tx in transactions:
1038
+ # Skip reward transactions (they were created during mining)
1039
+ if tx.get('type') == 'reward' and tx.get('from') == 'network':
1040
+ continue
1041
+
1042
+ tx_hash = tx.get('hash')
1043
+ if tx_hash:
1044
+ # Remove from mempool manager if available
1045
+ try:
1046
+ self.mempool_manager.remove_transaction(tx_hash)
1047
+ except:
1048
+ pass # Silent fail if method doesn't exist
1049
+
1050
+ safe_print(f"🧹 Cleared {len(transactions)} transactions from mempool")
1051
+
1052
+ except Exception as e:
1053
+ safe_print(f"⚠️ Error clearing mempool: {e}")
1054
+
1055
+ def _calculate_final_reward(self, transactions: List[Dict], actual_mining_time: float) -> float:
1056
+ """Calculate final reward using actual mining time"""
1057
+ total_reward = 0.0
1058
+
1059
+ for tx in transactions:
1060
+ tx_type = tx.get('type')
1061
+
1062
+ if tx_type == 'genesis_bill':
1063
+ denomination = tx.get('denomination', 0)
1064
+ bill_reward = self.difficulty_system.calculate_mining_reward(denomination, actual_mining_time)
1065
+ total_reward += bill_reward
1066
+ elif tx_type == 'transaction':
1067
+ total_reward += tx.get('fee', 0)
1068
+ elif tx_type == 'reward':
1069
+ total_reward += tx.get('amount', 0)
1070
+
1071
+ if total_reward == 0:
1072
+ total_reward = 1.0
1073
+
1074
+ return total_reward
1075
+
1076
+ def save_mining_history(self):
1077
+ """Save mining history to storage"""
1078
+ self.data_manager.save_mining_history(self.mining_history)
1079
+
1080
+ def start_mining(self):
1081
+ """Start the mining process"""
1082
+ if self.is_mining:
1083
+ return
1084
+
1085
+ self.is_mining = True
1086
+ self.should_stop_mining = False
1087
+
1088
+ if self.mining_started_callback:
1089
+ self.mining_started_callback()
1090
+
1091
+ def stop_mining(self):
1092
+ """Stop the mining process"""
1093
+ self.is_mining = False
1094
+ self.should_stop_mining = True
1095
+ if self.mining_thread and self.mining_thread.is_alive():
1096
+ self.mining_thread.join()
1097
+
1098
+ def get_mining_stats(self):
1099
+ """Return the current mining statistics"""
1100
+ return {
1101
+ "blocks_mined": self.blocks_mined,
1102
+ "total_reward": self.total_reward,
1103
+ "current_hash": self.current_hash,
1104
+ "current_nonce": self.current_nonce,
1105
+ "hash_rate": self.hash_rate,
1106
+ "mining_history": len(self.mining_history)
1107
+ }
@@ -123,22 +123,22 @@ class TransactionSecurity:
123
123
 
124
124
  # For unsigned test transactions
125
125
  if signature in ["system", "unsigned", "test"]:
126
- print(f"[SECURITY] Skipping signature check for system/unsigned transaction")
126
+ safe_print(f"[SECURITY] Skipping signature check for system/unsigned transaction")
127
127
  return True
128
128
 
129
129
  # Check SM2 signature length (should be 128 hex chars = 64 bytes)
130
130
  if len(signature) != 128:
131
- print(f"[SECURITY] Invalid SM2 signature length: {len(signature)} (expected 128)")
131
+ safe_print(f"[SECURITY] Invalid SM2 signature length: {len(signature)} (expected 128)")
132
132
  return False
133
133
 
134
134
  # Check if all characters are valid hex
135
135
  if not all(c in "0123456789abcdefABCDEF" for c in signature):
136
- print(f"[SECURITY] Signature contains non-hex characters")
136
+ safe_print(f"[SECURITY] Signature contains non-hex characters")
137
137
  return False
138
138
 
139
139
  # Check public key format (should start with '04' for uncompressed)
140
140
  if not public_key.startswith('04'):
141
- print(f"[SECURITY] Invalid public key format: {public_key[:20]}...")
141
+ safe_print(f"[SECURITY] Invalid public key format: {public_key[:20]}...")
142
142
  return False
143
143
 
144
144
  # Use KeyManager for verification if available
@@ -148,15 +148,15 @@ class TransactionSecurity:
148
148
 
149
149
  # Verify signature
150
150
  is_valid = self.key_manager.verify_signature(signing_data, signature, public_key)
151
- print(f"[SECURITY] SM2 signature verification: {is_valid}")
151
+ safe_print(f"[SECURITY] SM2 signature verification: {is_valid}")
152
152
  return is_valid
153
153
 
154
154
  # Fallback: Basic format check if SM2 not available
155
- print(f"[SECURITY] SM2 not available, using basic signature validation")
155
+ safe_print(f"[SECURITY] SM2 not available, using basic signature validation")
156
156
  return len(signature) == 128 and signature.startswith(('04', '03', '02'))
157
157
 
158
158
  except Exception as e:
159
- print(f"[SECURITY] Signature validation error: {e}")
159
+ safe_print(f"[SECURITY] Signature validation error: {e}")
160
160
  return False
161
161
 
162
162
  def _get_signing_data(self, transaction: Dict) -> str:
@@ -183,7 +183,7 @@ class TransactionSecurity:
183
183
 
184
184
  def _validate_signature(self, transaction: Dict) -> bool:
185
185
  """Legacy signature validation (for backward compatibility)"""
186
- print(f"[SECURITY] Using legacy signature validation")
186
+ safe_print(f"[SECURITY] Using legacy signature validation")
187
187
  return self._validate_signature_sm2(transaction)
188
188
 
189
189
  def _check_rate_limit(self, address: str) -> bool: