/ src / Mining / Mining.php
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  }