Bit-Wasp /
bitcoind-server
| 1 | <?php |
||
| 2 | |||
| 3 | declare(strict_types=1); |
||
| 4 | |||
| 5 | namespace BitWasp\Bitcoind; |
||
| 6 | |||
| 7 | use BitWasp\Bitcoind\Config; |
||
| 8 | use BitWasp\Bitcoind\Exception\UnitTestException; |
||
| 9 | use BitWasp\Bitcoind\Node\NodeOptions; |
||
| 10 | use BitWasp\Bitcoind\Node\Server; |
||
| 11 | use BitWasp\Bitcoind\Utils\File; |
||
| 12 | use Matomo\Ini\IniReader; |
||
| 13 | |||
| 14 | class UnitTestNodeService |
||
| 15 | { |
||
| 16 | /** |
||
| 17 | * @var string |
||
| 18 | */ |
||
| 19 | private $tmpDir; |
||
| 20 | |||
| 21 | /** |
||
| 22 | * @var string |
||
| 23 | */ |
||
| 24 | private $bitcoindPath; |
||
| 25 | |||
| 26 | /** |
||
| 27 | * @var NodeService |
||
| 28 | */ |
||
| 29 | private $service; |
||
| 30 | |||
| 31 | /** |
||
| 32 | * @var Config\FilesystemWriter |
||
| 33 | */ |
||
| 34 | private $writer; |
||
| 35 | |||
| 36 | /** |
||
| 37 | * @var Config\FilesystemLoader |
||
| 38 | */ |
||
| 39 | private $reader; |
||
| 40 | |||
| 41 | /** |
||
| 42 | * @var array |
||
| 43 | */ |
||
| 44 | private $nodeDirs = []; |
||
| 45 | |||
| 46 | /** |
||
| 47 | * @var Server[] |
||
| 48 | */ |
||
| 49 | private $servers = []; |
||
| 50 | |||
| 51 | /** |
||
| 52 | * @var DataDirGeneratorInterface |
||
| 53 | */ |
||
| 54 | private $dataDirGenerator; |
||
| 55 | |||
| 56 | private $cleanupWhenFinished = true; |
||
| 57 | |||
| 58 | public function __construct(string $bitcoindPath, NodeService $nodeService, DataDirGeneratorInterface $dataDirGenerator = null) |
||
| 59 | { |
||
| 60 | $this->tmpDir = sys_get_temp_dir(); |
||
| 61 | $this->bitcoindPath = $bitcoindPath; |
||
| 62 | $this->dataDirGenerator = $dataDirGenerator ?: new IncrementalDataDirGenerator($this->tmpDir); |
||
| 63 | $this->service = $nodeService; |
||
| 64 | $this->writer = new Config\FilesystemWriter(); |
||
| 65 | $this->reader = new Config\FilesystemLoader(new IniReader()); |
||
| 66 | } |
||
| 67 | |||
| 68 | public function setCleanup(bool $doCleanup) |
||
| 69 | { |
||
| 70 | $this->cleanupWhenFinished = $doCleanup; |
||
| 71 | return $this; |
||
| 72 | } |
||
| 73 | |||
| 74 | /** |
||
| 75 | * @param int $testId |
||
| 76 | * @return int |
||
| 77 | * @throws UnitTestException |
||
| 78 | */ |
||
| 79 | protected function findAvailablePort(int $testId): int |
||
| 80 | { |
||
| 81 | $port = $testId; |
||
| 82 | do { |
||
| 83 | if ($port > 65535) { |
||
| 84 | throw new UnitTestException("Invalid port for node"); |
||
| 85 | } |
||
| 86 | $connection = @fsockopen('localhost', $port); |
||
| 87 | $isUsed = is_resource($connection); |
||
| 88 | if (!$isUsed) { |
||
| 89 | return $port; |
||
| 90 | } |
||
| 91 | $port++; |
||
| 92 | } while ($isUsed); |
||
| 93 | } |
||
| 94 | |||
| 95 | /** |
||
| 96 | * @param int $testId |
||
| 97 | * @return Config\Config |
||
| 98 | * @throws UnitTestException |
||
| 99 | * @throws \Exception |
||
| 100 | */ |
||
| 101 | protected function createRandomConfig(int $testId): Config\Config |
||
| 102 | { |
||
| 103 | $this->findAvailablePort($testId); |
||
| 104 | $password = preg_replace("/[^A-Za-z0-9 ]/", '', base64_encode(random_bytes(16))); |
||
| 105 | $nonce = preg_replace("/[^A-Za-z0-9 ]/", '', base64_encode(random_bytes(4))); |
||
| 106 | return new Config\Config([ |
||
| 107 | 'rpcuser' => "user-{$testId}-{$nonce}", |
||
| 108 | 'rpcpassword' => $password, |
||
| 109 | 'rpcallowip' => '127.0.0.1', |
||
| 110 | 'regtest' => '1', |
||
| 111 | 'rpcport' => '18443', |
||
| 112 | 'server' => '1', |
||
| 113 | 'daemon' => '1', |
||
| 114 | ]); |
||
| 115 | } |
||
| 116 | |||
| 117 | /** |
||
| 118 | * @return Server |
||
| 119 | * @throws Exception\ServerException |
||
| 120 | * @throws UnitTestException |
||
| 121 | * @throws \Exception |
||
| 122 | */ |
||
| 123 | public function create(bool $wait = null): Server |
||
| 124 | { |
||
| 125 | if ($wait === null) { |
||
| 126 | $wait = true; |
||
| 127 | } |
||
| 128 | |||
| 129 | $testId = count($this->nodeDirs); |
||
| 130 | $dataDir = $this->dataDirGenerator->createNextDir(); |
||
| 131 | $config = $this->createRandomConfig($testId); |
||
| 132 | $options = new NodeOptions($this->bitcoindPath, $dataDir); |
||
| 133 | |||
| 134 | $node = $this->service->createNewNode($options, $config, $this->writer); |
||
| 135 | $node->start($this->reader); |
||
| 136 | if ($wait) { |
||
| 137 | $node->waitForStartup(); |
||
| 138 | $node->waitForRpc(); |
||
| 139 | } |
||
| 140 | |||
| 141 | $this->servers[] = $node; |
||
| 142 | |||
| 143 | return $node; |
||
| 144 | } |
||
| 145 | |||
| 146 | /** |
||
| 147 | * |
||
| 148 | */ |
||
| 149 | protected function cleanup() |
||
| 150 | { |
||
| 151 | $servers = 0; |
||
| 152 | foreach ($this->servers as $server) { |
||
| 153 | $servers++; |
||
| 154 | if ($server->isRunning()) { |
||
| 155 | $server->shutdown(); |
||
| 156 | |||
| 157 | $dataDir = $server->getNodeOptions()->getDataDir(); |
||
| 158 | if (file_exists($dataDir)) { |
||
| 159 | File::recursiveDelete($dataDir); |
||
| 160 | } |
||
| 161 | } |
||
| 162 | } |
||
| 163 | |||
| 164 | foreach ($this->dataDirGenerator->getDirs() as $dir) { |
||
| 165 | if (is_dir($dir)) { |
||
| 166 | File::recursiveDelete($dataDir); |
||
|
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
Loading history...
|
|||
| 167 | } |
||
| 168 | } |
||
| 169 | } |
||
| 170 | |||
| 171 | public function __destruct() |
||
| 172 | { |
||
| 173 | $this->cleanup(); |
||
| 174 | } |
||
| 175 | } |
||
| 176 |