Mining.php
1 <?php 2 3 namespace BitcoindRPC\Mining; 4 5 use BitcoindRPC\RpcClient; 6 use BitcoindRPC\RpcResponse; 7 8 class Mining 9 { 10 private RpcClient $client; 11 12 public function __construct(RpcClient $client) 13 { 14 $this->client = $client; 15 } 16 17 public function getBlockTemplate( 18 ?array $templateRequest = null, 19 ?string $mode = null, 20 ?array $capabilities = null, 21 ?array $rules = null 22 ): MiningResult { 23 $params = []; 24 25 if ($templateRequest !== null) { 26 $params[] = $templateRequest; 27 } elseif ($mode !== null || $capabilities !== null || $rules !== null) { 28 // Handle deprecated parameter style 29 $request = []; 30 if ($mode !== null) { 31 $request['mode'] = $mode; 32 } 33 if ($capabilities !== null) { 34 $request['capabilities'] = $capabilities; 35 } 36 if ($rules !== null) { 37 $request['rules'] = $rules; 38 } 39 $params[] = $request; 40 } 41 42 $response = $this->client->call('getblocktemplate', $params); 43 return new MiningResult($response); 44 } 45 46 public function getMiningInfo(): MiningResult 47 { 48 $response = $this->client->call('getmininginfo'); 49 return new MiningResult($response); 50 } 51 52 public function getNetworkHashPS(?int $nblocks = null, ?int $height = null): MiningResult 53 { 54 $params = []; 55 if ($nblocks !== null) { 56 $params[] = $nblocks; 57 if ($height !== null) { 58 $params[] = $height; 59 } 60 } elseif ($height !== null) { 61 $params[] = 120; // Default nblocks 62 $params[] = $height; 63 } 64 65 $response = $this->client->call('getnetworkhashps', $params); 66 return new MiningResult($response); 67 } 68 69 public function prioritiseTransaction(string $txid, int $feeDelta, $dummy = null): MiningResult 70 { 71 $params = [$txid, $dummy ?? 0, $feeDelta]; 72 $response = $this->client->call('prioritisetransaction', $params); 73 return new MiningResult($response); 74 } 75 76 public function submitBlock(string $hexData, ?array $parameters = null): MiningResult 77 { 78 $params = [$hexData]; 79 if ($parameters !== null) { 80 $params[] = $parameters; 81 } 82 83 $response = $this->client->call('submitblock', $params); 84 85 // submitblock returns null on success, error string on failure 86 if ($response->isSuccess() && $response->getData() !== null) { 87 return new MiningResult(new RpcResponse( 88 data: null, 89 error: "Block submission rejected: " . $response->getData(), 90 statusCode: 422 91 )); 92 } 93 94 return new MiningResult($response); 95 } 96 97 public function submitHeader(string $hexHeader): MiningResult 98 { 99 $response = $this->client->call('submitheader', [$hexHeader]); 100 return new MiningResult($response); 101 } 102 103 public function estimateSmartFee(int $confTarget, string $estimateMode = "CONSERVATIVE"): MiningResult 104 { 105 if ($confTarget < 1 || $confTarget > 1008) { 106 return new MiningResult(new RpcResponse( 107 data: null, 108 error: "conf_target must be between 1 and 1008", 109 statusCode: 400 110 )); 111 } 112 113 if (!in_array($estimateMode, ["UNSET", "ECONOMICAL", "CONSERVATIVE"])) { 114 return new MiningResult(new RpcResponse( 115 data: null, 116 error: "estimate_mode must be 'UNSET', 'ECONOMICAL', or 'CONSERVATIVE'", 117 statusCode: 400 118 )); 119 } 120 121 $response = $this->client->call('estimatesmartfee', [$confTarget, $estimateMode]); 122 return new MiningResult($response); 123 } 124 125 public function validateAddress(string $address): MiningResult 126 { 127 $response = $this->client->call('validateaddress', [$address]); 128 return new MiningResult($response); 129 } 130 131 // Utility methods 132 public function getBlockSubmissionResult(string $hexData): array 133 { 134 $result = $this->submitBlock($hexData); 135 136 if ($result->isSuccess()) { 137 return [ 138 'success' => true, 139 'result' => 'Block accepted', 140 'error' => null 141 ]; 142 } else { 143 return [ 144 'success' => false, 145 'result' => null, 146 'error' => $result->getError() 147 ]; 148 } 149 } 150 151 public function getMiningStatistics(): ?array 152 { 153 $miningInfo = $this->getMiningInfo(); 154 if (!$miningInfo->isSuccess()) { 155 return null; 156 } 157 158 $data = $miningInfo->getData(); 159 160 return [ 161 'blocks' => $data['blocks'] ?? null, 162 'current_block_size' => $data['currentblocksize'] ?? null, 163 'current_block_weight' => $data['currentblockweight'] ?? null, 164 'difficulty' => $data['difficulty'] ?? null, 165 'network_hash_ps' => $data['networkhashps'] ?? null, 166 'pooled_tx' => $data['pooledtx'] ?? null, 167 'chain' => $data['chain'] ?? null, 168 'warnings' => $data['warnings'] ?? null 169 ]; 170 } 171 172 public function getFeeEstimation(int $confTarget = 6): ?float 173 { 174 $result = $this->estimateSmartFee($confTarget); 175 if (!$result->isSuccess()) { 176 return null; 177 } 178 179 $data = $result->getData(); 180 return $data['feerate'] ?? null; 181 } 182 183 public function isAddressValidForMining(string $address): bool 184 { 185 $result = $this->validateAddress($address); 186 if (!$result->isSuccess()) { 187 return false; 188 } 189 190 $data = $result->getData(); 191 return $data['isvalid'] ?? false; 192 } 193 194 public function getNetworkDifficulty(): ?float 195 { 196 $result = $this->getMiningInfo(); 197 if (!$result->isSuccess()) { 198 return null; 199 } 200 201 $data = $result->getData(); 202 return $data['difficulty'] ?? null; 203 } 204 205 public function getMempoolSize(): ?int 206 { 207 $result = $this->getMiningInfo(); 208 if (!$result->isSuccess()) { 209 return null; 210 } 211 212 $data = $result->getData(); 213 return $data['pooledtx'] ?? null; 214 } 215 216 public function calculateExpectedBlockTime(?float $networkHashRate = null): ?float 217 { 218 $difficulty = $this->getNetworkDifficulty(); 219 if ($difficulty === null) { 220 return null; 221 } 222 223 if ($networkHashRate === null) { 224 $hashResult = $this->getNetworkHashPS(); 225 if (!$hashResult->isSuccess()) { 226 return null; 227 } 228 $networkHashRate = $hashResult->getData(); 229 } 230 231 if ($networkHashRate <= 0) { 232 return null; 233 } 234 235 // Expected time = difficulty * 2^32 / network hashrate 236 return ($difficulty * 2**32) / $networkHashRate; 237 } 238 239 public function getMiningSummary(): array 240 { 241 $miningInfo = $this->getMiningInfo(); 242 $networkHash = $this->getNetworkHashPS(); 243 $difficulty = $this->getNetworkDifficulty(); 244 245 $summary = [ 246 'mining_info_success' => $miningInfo->isSuccess(), 247 'network_hash_success' => $networkHash->isSuccess(), 248 'current_block' => null, 249 'difficulty' => null, 250 'network_hash_rate' => null, 251 'mempool_size' => null, 252 'chain' => null, 253 'errors' => [] 254 ]; 255 256 if ($miningInfo->isSuccess()) { 257 $data = $miningInfo->getData(); 258 $summary['current_block'] = $data['blocks'] ?? null; 259 $summary['difficulty'] = $data['difficulty'] ?? null; 260 $summary['mempool_size'] = $data['pooledtx'] ?? null; 261 $summary['chain'] = $data['chain'] ?? null; 262 } else { 263 $summary['errors'][] = "Failed to get mining info: " . $miningInfo->getError(); 264 } 265 266 if ($networkHash->isSuccess()) { 267 $summary['network_hash_rate'] = $networkHash->getData(); 268 } else { 269 $summary['errors'][] = "Failed to get network hash rate: " . $networkHash->getError(); 270 } 271 272 // Calculate expected block time 273 if ($summary['difficulty'] !== null && $summary['network_hash_rate'] !== null) { 274 $summary['expected_block_time'] = $this->calculateExpectedBlockTime($summary['network_hash_rate']); 275 } 276 277 return $summary; 278 } 279 }