/ src / Generating / Generating.php
Generating.php
  1  <?php
  2  
  3  namespace BitcoindRPC\Generating;
  4  
  5  use BitcoindRPC\RpcClient;
  6  use BitcoindRPC\RpcResponse;
  7  
  8  class Generating
  9  {
 10      private RpcClient $client;
 11  
 12      public function __construct(RpcClient $client)
 13      {
 14          $this->client = $client;
 15      }
 16  
 17      public function generateBlock(string $output, array $transactions, int $maxTries = 1000000): GeneratingResult
 18      {
 19          if (empty($transactions)) {
 20              return new GeneratingResult(new RpcResponse(
 21                  data: null,
 22                  error: "transactions list cannot be empty",
 23                  statusCode: 400
 24              ));
 25          }
 26  
 27          $params = [$output, $transactions, $maxTries];
 28          $response = $this->client->call('generateblock', $params);
 29  
 30          return new GeneratingResult($response);
 31      }
 32  
 33      public function generateToAddress(int $nblocks, string $address, int $maxTries = 1000000): GeneratingResult
 34      {
 35          if ($nblocks <= 0) {
 36              return new GeneratingResult(new RpcResponse(
 37                  data: null,
 38                  error: "nblocks must be greater than 0",
 39                  statusCode: 400
 40              ));
 41          }
 42  
 43          $params = [$nblocks, $address];
 44          if ($maxTries !== 1000000) {
 45              $params[] = $maxTries;
 46          }
 47  
 48          $response = $this->client->call('generatetoaddress', $params);
 49          return new GeneratingResult($response);
 50      }
 51  
 52      public function generateToDescriptor(int $numBlocks, string $descriptor, int $maxTries = 1000000): GeneratingResult
 53      {
 54          if ($numBlocks <= 0) {
 55              return new GeneratingResult(new RpcResponse(
 56                  data: null,
 57                  error: "num_blocks must be greater than 0",
 58                  statusCode: 400
 59              ));
 60          }
 61  
 62          $params = [$numBlocks, $descriptor, $maxTries];
 63          $response = $this->client->call('generatetodescriptor', $params);
 64          return new GeneratingResult($response);
 65      }
 66  
 67      public function generate(int $nblocks, int $maxTries = 1000000): GeneratingResult
 68      {
 69          if ($nblocks <= 0) {
 70              return new GeneratingResult(new RpcResponse(
 71                  data: null,
 72                  error: "nblocks must be greater than 0",
 73                  statusCode: 400
 74              ));
 75          }
 76  
 77          $params = [$nblocks];
 78          if ($maxTries !== 1000000) {
 79              $params[] = $maxTries;
 80          }
 81  
 82          $response = $this->client->call('generate', $params);
 83          return new GeneratingResult($response);
 84      }
 85  
 86      // Utility methods
 87      public function generateBlocksWithStats(
 88          int $numBlocks,
 89          string $output,
 90          string $outputType = "address",
 91          int $maxTries = 1000000
 92      ): array {
 93          $startTime = microtime(true);
 94  
 95          if ($outputType === "address") {
 96              $result = $this->generateToAddress($numBlocks, $output, $maxTries);
 97          } elseif ($outputType === "descriptor") {
 98              $result = $this->generateToDescriptor($numBlocks, $output, $maxTries);
 99          } else {
100              return [
101                  'result' => new GeneratingResult(new RpcResponse(
102                      data: null,
103                      error: "output_type must be 'address' or 'descriptor'",
104                      statusCode: 400
105                  )),
106                  'stats' => null
107              ];
108          }
109  
110          $endTime = microtime(true);
111          $totalTime = $endTime - $startTime;
112  
113          if ($result->isSuccess() && $result->getBlockHashes()) {
114              $averageTime = $numBlocks > 0 ? $totalTime / $numBlocks : 0;
115              $hashesPerSecond = $totalTime > 0 ? $numBlocks / $totalTime : 0;
116  
117              $stats = [
118                  'total_blocks' => $numBlocks,
119                  'average_time' => $averageTime,
120                  'total_time' => $totalTime,
121                  'hashes_per_second' => $hashesPerSecond,
122                  'first_block_hash' => $result->getBlockHashes()[0] ?? null,
123                  'last_block_hash' => $result->getBlockHashes()[count($result->getBlockHashes()) - 1] ?? null
124              ];
125  
126              return [
127                  'result' => $result,
128                  'stats' => $stats
129              ];
130          } else {
131              return [
132                  'result' => $result,
133                  'stats' => null
134              ];
135          }
136      }
137  
138      public function validateOutput(string $output, string $outputType = "address"): GeneratingResult
139      {
140          if ($outputType === "address") {
141              $response = $this->client->call('validateaddress', [$output]);
142          } elseif ($outputType === "descriptor") {
143              $response = $this->client->call('getdescriptorinfo', [$output]);
144          } else {
145              return new GeneratingResult(new RpcResponse(
146                  data: null,
147                  error: "output_type must be 'address' or 'descriptor'",
148                  statusCode: 400
149              ));
150          }
151  
152          return new GeneratingResult($response);
153      }
154  
155      public function estimateGenerationTime(int $numBlocks, ?float $networkHashRate = null): GeneratingResult
156      {
157          if ($numBlocks <= 0) {
158              return new GeneratingResult(new RpcResponse(
159                  data: null,
160                  error: "num_blocks must be greater than 0",
161                  statusCode: 400
162              ));
163          }
164  
165          // Get current network hashrate if not provided
166          if ($networkHashRate === null) {
167              $hashResponse = $this->client->call('getnetworkhashps', [120]);
168              if ($hashResponse->error) {
169                  return new GeneratingResult(new RpcResponse(
170                      data: null,
171                      error: "Failed to get network hashrate: " . $hashResponse->error,
172                      statusCode: 500
173                  ));
174              }
175              $networkHashRate = (float) $hashResponse->data;
176          }
177  
178          // Get current difficulty
179          $miningInfoResponse = $this->client->call('getmininginfo');
180          if ($miningInfoResponse->error) {
181              return new GeneratingResult(new RpcResponse(
182                  data: null,
183                  error: "Failed to get mining info: " . $miningInfoResponse->error,
184                  statusCode: 500
185              ));
186          }
187  
188          $miningInfo = $miningInfoResponse->data;
189          $difficulty = (float) ($miningInfo['difficulty'] ?? 1.0);
190  
191          // Simple estimation: time = (difficulty * 2^32) / hashrate * num_blocks
192          $singleBlockTime = ($difficulty * 2**32) / $networkHashRate;
193          $totalTime = $singleBlockTime * $numBlocks;
194  
195          $estimationData = [
196              'num_blocks' => $numBlocks,
197              'network_hash_rate' => $networkHashRate,
198              'difficulty' => $difficulty,
199              'estimated_time_per_block' => $singleBlockTime,
200              'estimated_total_time' => $totalTime,
201              'estimated_time_per_block_human' => number_format($singleBlockTime, 2) . " seconds",
202              'estimated_total_time_human' => number_format($totalTime, 2) . " seconds"
203          ];
204  
205          return new GeneratingResult(new RpcResponse(data: $estimationData));
206      }
207  
208      public function createAndGenerate(
209          string $output,
210          string $outputType = "address",
211          int $numBlocks = 1,
212          int $maxTries = 1000000
213      ): GeneratingResult {
214          // First validate the output
215          $validation = $this->validateOutput($output, $outputType);
216          if (!$validation->isSuccess()) {
217              return $validation;
218          }
219  
220          // Then generate blocks
221          if ($outputType === "address") {
222              return $this->generateToAddress($numBlocks, $output, $maxTries);
223          } else {
224              return $this->generateToDescriptor($numBlocks, $output, $maxTries);
225          }
226      }
227  
228      public function getGenerationStatus(): GeneratingResult
229      {
230          $response = $this->client->call('getmininginfo');
231          return new GeneratingResult($response);
232      }
233  }