├── .gitattributes ├── LICENSE ├── Makefile ├── composer.json └── src ├── DnsSeeds ├── DnsSeedList.php ├── MainNetDnsSeeds.php └── TestNetDnsSeeds.php ├── Factory.php ├── GetDataType.php ├── Ip ├── IpInterface.php ├── Ipv4.php ├── Ipv6.php └── Onion.php ├── Message.php ├── Messages ├── AbstractBlockLocator.php ├── AbstractInventory.php ├── Addr.php ├── Alert.php ├── Block.php ├── Factory.php ├── FeeFilter.php ├── FilterAdd.php ├── FilterClear.php ├── FilterLoad.php ├── GetAddr.php ├── GetBlocks.php ├── GetData.php ├── GetHeaders.php ├── Headers.php ├── Inv.php ├── MemPool.php ├── MerkleBlock.php ├── NotFound.php ├── Ping.php ├── Pong.php ├── Reject.php ├── SendHeaders.php ├── Tx.php ├── VerAck.php └── Version.php ├── NetworkMessage.php ├── NetworkSerializable.php ├── NetworkSerializableInterface.php ├── Peer ├── ConnectionParams.php ├── Connector.php ├── Listener.php ├── Locator.php ├── Manager.php └── Peer.php ├── Protocol.php ├── Serializer ├── Ip │ └── IpSerializer.php ├── Message │ ├── AddrSerializer.php │ ├── AlertSerializer.php │ ├── FeeFilterSerializer.php │ ├── FilterAddSerializer.php │ ├── FilterLoadSerializer.php │ ├── GetBlocksSerializer.php │ ├── GetDataSerializer.php │ ├── GetHeadersSerializer.php │ ├── HeadersSerializer.php │ ├── InvSerializer.php │ ├── MerkleBlockSerializer.php │ ├── NotFoundSerializer.php │ ├── PingSerializer.php │ ├── PongSerializer.php │ ├── RejectSerializer.php │ └── VersionSerializer.php ├── NetworkMessageSerializer.php └── Structure │ ├── AlertDetailSerializer.php │ ├── HeaderSerializer.php │ ├── InventorySerializer.php │ ├── NetworkAddressSerializer.php │ └── NetworkAddressTimestampSerializer.php ├── Services.php ├── Settings ├── MainnetSettings.php ├── MutableNetworkSettingsInterface.php ├── NetworkSettings.php ├── NetworkSettingsInterface.php └── Testnet3Settings.php └── Structure ├── AlertDetail.php ├── Header.php ├── Inventory.php ├── NetworkAddress.php ├── NetworkAddressInterface.php └── NetworkAddressTimestamp.php /.gitattributes: -------------------------------------------------------------------------------- 1 | /tests export-ignore 2 | /.gitignore export-ignore 3 | /.scrutinizer.yml export-ignore 4 | /.travis.yml export-ignore 5 | /phpunit.xml export-ignore 6 | /README.md export-ignore 7 | /tool export-ignore 8 | /examples export-ignore 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | 26 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | test: phpunit phpcs 2 | 3 | .PHONY: test phpunit phpcs 4 | 5 | pretest: 6 | if [ ! -d vendor ] || [ ! -f composer.lock ]; then composer install; else echo "Already have dependencies"; fi 7 | 8 | phpunit: pretest 9 | mkdir -p build 10 | /usr/bin/php vendor/bin/phpunit --coverage-text --coverage-clover=build/coverage.clover --coverage-html=build 11 | 12 | phpunit-ci: pretest 13 | mkdir -p build 14 | vendor/bin/phpunit --coverage-text --coverage-clover=build/coverage.clover 15 | 16 | ifndef STRICT 17 | STRICT = 0 18 | endif 19 | 20 | phpcs: pretest 21 | vendor/bin/phpcs --standard=PSR1,PSR2 -n src tests/ examples/ 22 | 23 | phpcbf: pretest 24 | vendor/bin/phpcbf --standard=PSR1,PSR2 -n src tests/ examples/ 25 | 26 | ocular: 27 | wget https://scrutinizer-ci.com/ocular.phar 28 | 29 | ifdef OCULAR_TOKEN 30 | scrutinizer: ocular 31 | @php ocular.phar code-coverage:upload --format=php-clover build/coverage.clover --access-token=$(OCULAR_TOKEN); 32 | else 33 | scrutinizer: ocular 34 | php ocular.phar code-coverage:upload --format=php-clover build/coverage.clover; 35 | endif 36 | 37 | clean: clean-env clean-deps 38 | 39 | clean-env: 40 | rm -rf ocular.phar 41 | rm -rf build 42 | 43 | clean-deps: 44 | rm -rf vendor/ 45 | 46 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bitwasp/bitcoin-p2p", 3 | "description": "Implementation of the bitcoin protocol using ReactPHP", 4 | "type": "library", 5 | "homepage": "https://github.com/bit-wasp/node-php", 6 | "license": "Unlicense", 7 | "authors": [ 8 | { 9 | "name": "Thomas Kerin", 10 | "homepage": "https://thomaskerin.io", 11 | "role": "Author" 12 | } 13 | ], 14 | "autoload": { 15 | "psr-4": { 16 | "BitWasp\\Bitcoin\\Networking\\": "src/" 17 | } 18 | }, 19 | "autoload-dev": { 20 | "psr-4": { 21 | "BitWasp\\Bitcoin\\Tests\\Networking\\": "tests/unit/" 22 | } 23 | }, 24 | "require": { 25 | "bitwasp/bitcoin": "^1.0.0", 26 | "react/socket": "^0.8.0", 27 | "react/dns": "^0.4.0", 28 | "christian-riesen/base32": "^1.3.0" 29 | }, 30 | "require-dev": { 31 | "squizlabs/php_codesniffer": "^3.0", 32 | "phpunit/phpunit": "^6.0", 33 | "symfony/yaml": "~2.6", 34 | "phpstan/phpstan": "^0.9.2" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/DnsSeeds/DnsSeedList.php: -------------------------------------------------------------------------------- 1 | seeds = $seeds; 21 | } 22 | 23 | /** 24 | * @param string $host 25 | * @return $this 26 | */ 27 | public function addHost(string $host) 28 | { 29 | $this->seeds[] = $host; 30 | return $this; 31 | } 32 | 33 | /** 34 | * @return array 35 | */ 36 | public function getHosts(): array 37 | { 38 | return $this->seeds; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/DnsSeeds/MainNetDnsSeeds.php: -------------------------------------------------------------------------------- 1 | loop = $loop; 46 | $this->network = $network ?: Bitcoin::getNetwork(); 47 | $this->settings = new Settings\MainnetSettings(); 48 | } 49 | 50 | /** 51 | * @return Resolver 52 | */ 53 | public function getDns(): Resolver 54 | { 55 | return (new \React\Dns\Resolver\Factory())->create($this->settings->getDnsServer(), $this->loop); 56 | } 57 | 58 | /** 59 | * @param Random|null $random 60 | * @return Messages\Factory 61 | */ 62 | public function getMessages(Random $random = null): Messages\Factory 63 | { 64 | if (null === $this->messages) { 65 | $this->messages = new Messages\Factory( 66 | $this->network, 67 | $random ?: new Random() 68 | ); 69 | } 70 | return $this->messages; 71 | } 72 | 73 | /** 74 | * @param Settings\NetworkSettings $settings 75 | */ 76 | public function setSettings(Settings\NetworkSettings $settings) 77 | { 78 | $this->settings = $settings; 79 | } 80 | 81 | /** 82 | * @return Settings\NetworkSettings 83 | */ 84 | public function getSettings(): Settings\NetworkSettings 85 | { 86 | return $this->settings; 87 | } 88 | 89 | /** 90 | * @param Peer\ConnectionParams $params 91 | * @return Peer\Connector 92 | */ 93 | public function getConnector( 94 | Peer\ConnectionParams $params 95 | ): Peer\Connector { 96 | return new Peer\Connector( 97 | $this->getMessages(), 98 | $params, 99 | $this->loop, 100 | $this->settings->getSocketParams() 101 | ); 102 | } 103 | 104 | /** 105 | * @param Peer\Connector $connector 106 | * @return Peer\Manager 107 | */ 108 | public function getManager(Peer\Connector $connector): Peer\Manager 109 | { 110 | return new Peer\Manager($connector, $this->settings); 111 | } 112 | 113 | /** 114 | * @return Peer\Locator 115 | */ 116 | public function getLocator(): Peer\Locator 117 | { 118 | return new Peer\Locator($this->getDns(), $this->settings); 119 | } 120 | 121 | /** 122 | * @param Peer\ConnectionParams $params 123 | * @param Structure\NetworkAddressInterface $serverAddress 124 | * @return Peer\Listener 125 | */ 126 | public function getListener( 127 | Peer\ConnectionParams $params, 128 | Structure\NetworkAddressInterface $serverAddress 129 | ): Peer\Listener { 130 | return new Peer\Listener( 131 | $params, 132 | $this->getMessages(), 133 | $serverAddress, 134 | $this->loop 135 | ); 136 | } 137 | 138 | /** 139 | * @param IpInterface $ipAddress 140 | * @param int $port 141 | * @param int $services 142 | * @return Structure\NetworkAddress 143 | */ 144 | public function getAddress( 145 | IpInterface $ipAddress, 146 | int $port = null, 147 | int $services = Services::NONE 148 | ): Structure\NetworkAddress { 149 | if (null === $port) { 150 | $port = $this->settings->getDefaultP2PPort(); 151 | } 152 | 153 | return new Structure\NetworkAddress($services, $ipAddress, $port); 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/GetDataType.php: -------------------------------------------------------------------------------- 1 | ip = $ip; 30 | } 31 | 32 | /** 33 | * @return string 34 | */ 35 | public function getHost(): string 36 | { 37 | return $this->ip; 38 | } 39 | 40 | /** 41 | * @return BufferInterface 42 | */ 43 | public function getBuffer(): BufferInterface 44 | { 45 | return new Buffer( 46 | sprintf("%s%s", self::MAGIC, pack("N", ip2long($this->ip))) 47 | ); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Ip/Ipv6.php: -------------------------------------------------------------------------------- 1 | ip = $ip; 29 | } 30 | 31 | /** 32 | * @return string 33 | */ 34 | public function getHost(): string 35 | { 36 | return $this->ip; 37 | } 38 | 39 | /** 40 | * @return BufferInterface 41 | */ 42 | public function getBuffer(): BufferInterface 43 | { 44 | return new Buffer(inet_pton($this->ip)); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Ip/Onion.php: -------------------------------------------------------------------------------- 1 | identifier = $decoded; 47 | $this->host = $onionHost; 48 | } 49 | 50 | /** 51 | * @return string 52 | */ 53 | public function getHost(): string 54 | { 55 | return $this->host; 56 | } 57 | 58 | /** 59 | * @return BufferInterface 60 | */ 61 | public function getBuffer() 62 | { 63 | return new Buffer(self::MAGIC . $this->identifier); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Message.php: -------------------------------------------------------------------------------- 1 | version = $version; 34 | $this->locator = $locator; 35 | } 36 | 37 | /** 38 | * @return int 39 | */ 40 | public function getVersion(): int 41 | { 42 | return $this->version; 43 | } 44 | 45 | /** 46 | * @return BlockLocator 47 | */ 48 | public function getLocator(): BlockLocator 49 | { 50 | return $this->locator; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Messages/AbstractInventory.php: -------------------------------------------------------------------------------- 1 | addItem($item); 24 | } 25 | } 26 | 27 | /** 28 | * @param Inventory $item 29 | */ 30 | private function addItem(Inventory $item) 31 | { 32 | $this->items[] = $item; 33 | } 34 | 35 | /** 36 | * @return int 37 | */ 38 | public function count(): int 39 | { 40 | return count($this->items); 41 | } 42 | 43 | /** 44 | * @return Inventory[] 45 | */ 46 | public function getItems(): array 47 | { 48 | return $this->items; 49 | } 50 | 51 | /** 52 | * @param int $index 53 | * @return Inventory 54 | */ 55 | public function getItem(int $index): Inventory 56 | { 57 | if (false === isset($this->items[$index])) { 58 | throw new \InvalidArgumentException('No item found at that index'); 59 | } 60 | 61 | return $this->items[$index]; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/Messages/Addr.php: -------------------------------------------------------------------------------- 1 | addAddress($addr); 31 | } 32 | } 33 | 34 | /** 35 | * @param NetworkAddressTimestamp $address 36 | * @return $this 37 | */ 38 | private function addAddress(NetworkAddressTimestamp $address) 39 | { 40 | $this->addresses[] = $address; 41 | return $this; 42 | } 43 | 44 | /** 45 | * @see https://en.bitcoin.it/wiki/Protocol_documentation#addr 46 | * @return string 47 | */ 48 | public function getNetworkCommand(): string 49 | { 50 | return Message::ADDR; 51 | } 52 | 53 | /** 54 | * @return int 55 | */ 56 | public function count(): int 57 | { 58 | return count($this->addresses); 59 | } 60 | 61 | /** 62 | * @return NetworkAddressTimestamp[] 63 | */ 64 | public function getAddresses(): array 65 | { 66 | return $this->addresses; 67 | } 68 | 69 | /** 70 | * @param int $index 71 | * @return NetworkAddressTimestamp 72 | * @throws \InvalidArgumentException 73 | */ 74 | public function getAddress(int $index): NetworkAddressTimestamp 75 | { 76 | if (false === isset($this->addresses[$index])) { 77 | throw new \InvalidArgumentException('No address exists at this index'); 78 | } 79 | 80 | return $this->addresses[$index]; 81 | } 82 | 83 | /** 84 | * @see \BitWasp\Bitcoin\SerializableInterface::getBuffer() 85 | */ 86 | public function getBuffer(): BufferInterface 87 | { 88 | return (new AddrSerializer(new NetworkAddressTimestampSerializer()))->serialize($this); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/Messages/Alert.php: -------------------------------------------------------------------------------- 1 | alert = $alert; 34 | $this->signature = $signature; 35 | } 36 | 37 | /** 38 | * @return string 39 | * @see https://en.bitcoin.it/wiki/Protocol_documentation#alert 40 | */ 41 | public function getNetworkCommand(): string 42 | { 43 | return Message::ALERT; 44 | } 45 | 46 | /** 47 | * @return AlertDetail 48 | */ 49 | public function getDetail(): AlertDetail 50 | { 51 | return $this->alert; 52 | } 53 | 54 | /** 55 | * @return SignatureInterface 56 | */ 57 | public function getSignature(): SignatureInterface 58 | { 59 | return $this->signature; 60 | } 61 | 62 | /** 63 | * @see \BitWasp\Bitcoin\SerializableInterface::getBuffer() 64 | * @return BufferInterface 65 | */ 66 | public function getBuffer(): BufferInterface 67 | { 68 | return (new AlertSerializer(new AlertDetailSerializer()))->serialize($this); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/Messages/Block.php: -------------------------------------------------------------------------------- 1 | blockData = $blockData; 21 | } 22 | 23 | /** 24 | * {@inheritdoc} 25 | * @see https://en.bitcoin.it/wiki/Protocol_documentation#block 26 | * @see \BitWasp\Bitcoin\Network\NetworkSerializableInterface::getNetworkCommand() 27 | */ 28 | public function getNetworkCommand(): string 29 | { 30 | return Message::BLOCK; 31 | } 32 | 33 | /** 34 | * @return BufferInterface 35 | */ 36 | public function getBlock(): BufferInterface 37 | { 38 | return $this->blockData; 39 | } 40 | 41 | /** 42 | * {@inheritdoc} 43 | * @see \BitWasp\Bitcoin\SerializableInterface::getBuffer() 44 | */ 45 | public function getBuffer(): BufferInterface 46 | { 47 | return $this->blockData; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Messages/Factory.php: -------------------------------------------------------------------------------- 1 | network = $network; 47 | $this->random = $random; 48 | $this->serializer = new NetworkMessageSerializer($this->network); 49 | } 50 | 51 | /** 52 | * @param int $version 53 | * @param int $services 54 | * @param int $timestamp 55 | * @param NetworkAddress $addrRecv 56 | * @param NetworkAddress $addrFrom 57 | * @param BufferInterface $userAgent 58 | * @param int $startHeight 59 | * @param bool $relay 60 | * @return Version 61 | */ 62 | public function version( 63 | int $version, 64 | $services, 65 | int $timestamp, 66 | NetworkAddress $addrRecv, 67 | NetworkAddress $addrFrom, 68 | BufferInterface $userAgent, 69 | $startHeight, 70 | $relay 71 | ): Version { 72 | return new Version( 73 | $version, 74 | $services, 75 | $timestamp, 76 | $addrRecv, 77 | $addrFrom, 78 | (int) $this->random->bytes(8)->getInt(), 79 | $userAgent, 80 | $startHeight, 81 | $relay 82 | ); 83 | } 84 | 85 | /** 86 | * @return VerAck 87 | */ 88 | public function verack(): VerAck 89 | { 90 | return new VerAck(); 91 | } 92 | 93 | /** 94 | * @return SendHeaders 95 | */ 96 | public function sendheaders(): SendHeaders 97 | { 98 | return new SendHeaders(); 99 | } 100 | 101 | /** 102 | * @param NetworkAddressTimestamp[] $addrs 103 | * @return Addr 104 | */ 105 | public function addr(array $addrs): Addr 106 | { 107 | return new Addr($addrs); 108 | } 109 | 110 | /** 111 | * @param Inventory[] $vectors 112 | * @return Inv 113 | */ 114 | public function inv(array $vectors): Inv 115 | { 116 | return new Inv($vectors); 117 | } 118 | 119 | /** 120 | * @param Inventory[] $vectors 121 | * @return GetData 122 | */ 123 | public function getdata(array $vectors): GetData 124 | { 125 | return new GetData($vectors); 126 | } 127 | 128 | /** 129 | * @param Inventory[] $vectors 130 | * @return NotFound 131 | */ 132 | public function notfound(array $vectors): NotFound 133 | { 134 | return new NotFound($vectors); 135 | } 136 | 137 | /** 138 | * @param int $version 139 | * @param BlockLocator $blockLocator 140 | * @return GetBlocks 141 | */ 142 | public function getblocks(int $version, BlockLocator $blockLocator): GetBlocks 143 | { 144 | return new GetBlocks($version, $blockLocator); 145 | } 146 | 147 | /** 148 | * @param int $version 149 | * @param BlockLocator $blockLocator 150 | * @return GetHeaders 151 | */ 152 | public function getheaders(int $version, BlockLocator $blockLocator): GetHeaders 153 | { 154 | return new GetHeaders($version, $blockLocator); 155 | } 156 | 157 | /** 158 | * @param BufferInterface $txData 159 | * @return Tx 160 | */ 161 | public function tx(BufferInterface $txData): Tx 162 | { 163 | return new Tx($txData); 164 | } 165 | 166 | /** 167 | * @param BufferInterface $blockData 168 | * @return Block 169 | */ 170 | public function block(BufferInterface $blockData): Block 171 | { 172 | return new Block($blockData); 173 | } 174 | 175 | /** 176 | * @param BufferInterface ...$headers 177 | * @return Headers 178 | */ 179 | public function headers(BufferInterface ...$headers): Headers 180 | { 181 | return new Headers(...$headers); 182 | } 183 | 184 | /** 185 | * @return GetAddr 186 | */ 187 | public function getaddr(): GetAddr 188 | { 189 | return new GetAddr(); 190 | } 191 | 192 | /** 193 | * @return MemPool 194 | */ 195 | public function mempool(): MemPool 196 | { 197 | return new MemPool(); 198 | } 199 | 200 | /** 201 | * @param int $feeRate 202 | * @return FeeFilter 203 | */ 204 | public function feefilter(int $feeRate): FeeFilter 205 | { 206 | return new FeeFilter($feeRate); 207 | } 208 | 209 | /** 210 | * @param BufferInterface $data 211 | * @return FilterAdd 212 | */ 213 | public function filteradd(BufferInterface $data): FilterAdd 214 | { 215 | return new FilterAdd($data); 216 | } 217 | 218 | /** 219 | * @param BloomFilter $filter 220 | * @return FilterLoad 221 | */ 222 | public function filterload(BloomFilter $filter): FilterLoad 223 | { 224 | return new FilterLoad($filter); 225 | } 226 | 227 | /** 228 | * @return FilterClear 229 | */ 230 | public function filterclear(): FilterClear 231 | { 232 | return new FilterClear(); 233 | } 234 | 235 | /** 236 | * @param FilteredBlock $filtered 237 | * @return MerkleBlock 238 | */ 239 | public function merkleblock(FilteredBlock $filtered): MerkleBlock 240 | { 241 | return new MerkleBlock($filtered); 242 | } 243 | /** 244 | * @return Ping 245 | * @throws \BitWasp\Bitcoin\Exceptions\RandomBytesFailure 246 | */ 247 | public function ping(): Ping 248 | { 249 | return Ping::generate($this->random); 250 | } 251 | 252 | /** 253 | * @param Ping $ping 254 | * @return Pong 255 | */ 256 | public function pong(Ping $ping): Pong 257 | { 258 | return new Pong($ping->getNonce()); 259 | } 260 | 261 | /** 262 | * @param BufferInterface $message 263 | * @param int $code 264 | * @param BufferInterface $reason 265 | * @param BufferInterface|null $data 266 | * @return Reject 267 | */ 268 | public function reject( 269 | BufferInterface $message, 270 | $code, 271 | BufferInterface $reason, 272 | BufferInterface $data = null 273 | ): Reject { 274 | if (null === $data) { 275 | $data = new Buffer(); 276 | } 277 | 278 | return new Reject( 279 | $message, 280 | $code, 281 | $reason, 282 | $data 283 | ); 284 | } 285 | 286 | /** 287 | * @param AlertDetail $detail 288 | * @param SignatureInterface $sig 289 | * @return Alert 290 | */ 291 | public function alert(AlertDetail $detail, SignatureInterface $sig): Alert 292 | { 293 | return new Alert( 294 | $detail, 295 | $sig 296 | ); 297 | } 298 | 299 | /** 300 | * @param Parser $parser 301 | * @return NetworkMessage 302 | */ 303 | public function parse(Parser $parser): NetworkMessage 304 | { 305 | return $this->serializer->fromParser($parser); 306 | } 307 | 308 | /** 309 | * @return NetworkMessageSerializer 310 | */ 311 | public function getSerializer(): NetworkMessageSerializer 312 | { 313 | return $this->serializer; 314 | } 315 | 316 | /** 317 | * @return NetworkInterface 318 | */ 319 | public function getNetwork(): NetworkInterface 320 | { 321 | return $this->network; 322 | } 323 | } 324 | -------------------------------------------------------------------------------- /src/Messages/FeeFilter.php: -------------------------------------------------------------------------------- 1 | feeRate = $feeRate; 27 | } 28 | 29 | /** 30 | * @return int 31 | */ 32 | public function getFeeRate(): int 33 | { 34 | return $this->feeRate; 35 | } 36 | 37 | /** 38 | * @return string 39 | * @see https://en.bitcoin.it/wiki/Protocol_documentation#filterload.2C_filteradd.2C_filterclear.2C_merkleblock 40 | */ 41 | public function getNetworkCommand(): string 42 | { 43 | return Message::FEEFILTER; 44 | } 45 | 46 | /** 47 | * @return \BitWasp\Buffertools\BufferInterface 48 | */ 49 | public function getBuffer(): BufferInterface 50 | { 51 | return (new FeeFilterSerializer())->serialize($this); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Messages/FilterAdd.php: -------------------------------------------------------------------------------- 1 | data = $data; 25 | } 26 | 27 | /** 28 | * @return string 29 | * @see https://en.bitcoin.it/wiki/Protocol_documentation#filterload.2C_filteradd.2C_filterclear.2C_merkleblock 30 | */ 31 | public function getNetworkCommand(): string 32 | { 33 | return Message::FILTERADD; 34 | } 35 | 36 | /** 37 | * @return BufferInterface 38 | */ 39 | public function getData(): BufferInterface 40 | { 41 | return $this->data; 42 | } 43 | 44 | /** 45 | * @return BufferInterface 46 | */ 47 | public function getBuffer(): BufferInterface 48 | { 49 | return (new FilterAddSerializer())->serialize($this); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Messages/FilterClear.php: -------------------------------------------------------------------------------- 1 | filter = $filter; 27 | } 28 | 29 | /** 30 | * @return string 31 | * @see https://en.bitcoin.it/wiki/Protocol_documentation#filterload.2C_filteradd.2C_filterclear.2C_merkleblock 32 | */ 33 | public function getNetworkCommand(): string 34 | { 35 | return Message::FILTERLOAD; 36 | } 37 | 38 | /** 39 | * @return BloomFilter 40 | */ 41 | public function getFilter(): BloomFilter 42 | { 43 | return $this->filter; 44 | } 45 | /** 46 | * @return BufferInterface 47 | */ 48 | public function getBuffer(): BufferInterface 49 | { 50 | return (new FilterLoadSerializer(new BloomFilterSerializer()))->serialize($this); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Messages/GetAddr.php: -------------------------------------------------------------------------------- 1 | serialize($this); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Messages/GetData.php: -------------------------------------------------------------------------------- 1 | serialize($this); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Messages/GetHeaders.php: -------------------------------------------------------------------------------- 1 | serialize($this); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Messages/Headers.php: -------------------------------------------------------------------------------- 1 | headers = $headers; 22 | } 23 | 24 | /** 25 | * @return string 26 | * @see https://en.bitcoin.it/wiki/Protocol_documentation#headers 27 | */ 28 | public function getNetworkCommand(): string 29 | { 30 | return Message::HEADERS; 31 | } 32 | 33 | /** 34 | * @return BufferInterface[] 35 | */ 36 | public function getHeaders(): array 37 | { 38 | return $this->headers; 39 | } 40 | 41 | /** 42 | * @return int 43 | */ 44 | public function count(): int 45 | { 46 | return count($this->headers); 47 | } 48 | 49 | /** 50 | * @param int $index 51 | * @return BufferInterface 52 | */ 53 | public function getHeader(int $index): BufferInterface 54 | { 55 | if (!array_key_exists($index, $this->headers)) { 56 | throw new \InvalidArgumentException('No header exists at this index'); 57 | } 58 | 59 | return $this->headers[$index]; 60 | } 61 | 62 | /** 63 | * {@inheritdoc} 64 | * @see \BitWasp\Bitcoin\SerializableInterface::getBuffer() 65 | */ 66 | public function getBuffer(): BufferInterface 67 | { 68 | return (new HeadersSerializer())->serialize($this); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/Messages/Inv.php: -------------------------------------------------------------------------------- 1 | serialize($this); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Messages/MemPool.php: -------------------------------------------------------------------------------- 1 | merkle = $merkleBlock; 29 | } 30 | 31 | /** 32 | * @return string 33 | * @@see https://en.bitcoin.it/wiki/Protocol_documentation#filterload.2C_filteradd.2C_filterclear.2C_merkleblock 34 | */ 35 | public function getNetworkCommand(): string 36 | { 37 | return Message::MERKLEBLOCK; 38 | } 39 | 40 | /** 41 | * @return FilteredBlock 42 | */ 43 | public function getFilteredBlock(): FilteredBlock 44 | { 45 | return $this->merkle; 46 | } 47 | 48 | /** 49 | * @return BufferInterface 50 | */ 51 | public function getBuffer(): BufferInterface 52 | { 53 | return (new MerkleBlockSerializer(new FilteredBlockSerializer(new BlockHeaderSerializer(), new PartialMerkleTreeSerializer())))->serialize($this); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Messages/NotFound.php: -------------------------------------------------------------------------------- 1 | serialize($this); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Messages/Ping.php: -------------------------------------------------------------------------------- 1 | getSize() !== 8) { 27 | throw new \RuntimeException("Invalid nonce size"); 28 | } 29 | $this->nonce = $nonce; 30 | } 31 | 32 | /** 33 | * @param Random $random 34 | * @return Ping 35 | * @throws \BitWasp\Bitcoin\Exceptions\RandomBytesFailure 36 | */ 37 | public static function generate(Random $random): Ping 38 | { 39 | return new Ping($random->bytes(8)); 40 | } 41 | 42 | /** 43 | * @return string 44 | * @see https://en.bitcoin.it/wiki/Protocol_documentation#ping 45 | */ 46 | public function getNetworkCommand(): string 47 | { 48 | return Message::PING; 49 | } 50 | 51 | /** 52 | * @return BufferInterface 53 | */ 54 | public function getNonce(): BufferInterface 55 | { 56 | return $this->nonce; 57 | } 58 | 59 | /** 60 | * @return BufferInterface 61 | */ 62 | public function getBuffer(): BufferInterface 63 | { 64 | return (new PingSerializer())->serialize($this); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Messages/Pong.php: -------------------------------------------------------------------------------- 1 | getSize() !== 8) { 25 | throw new \RuntimeException("Invalid nonce size"); 26 | } 27 | $this->nonce = $nonce; 28 | } 29 | 30 | /** 31 | * @return string 32 | * @see https://en.bitcoin.it/wiki/Protocol_documentation#pong 33 | */ 34 | public function getNetworkCommand(): string 35 | { 36 | return Message::PONG; 37 | } 38 | 39 | /** 40 | * @return BufferInterface 41 | */ 42 | public function getNonce(): BufferInterface 43 | { 44 | return $this->nonce; 45 | } 46 | 47 | /** 48 | * @return BufferInterface 49 | */ 50 | public function getBuffer(): BufferInterface 51 | { 52 | return (new PongSerializer())->serialize($this); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Messages/Reject.php: -------------------------------------------------------------------------------- 1 | checkCCode($ccode)) { 57 | throw new \InvalidArgumentException('Invalid code provided to reject message'); 58 | } 59 | 60 | $this->message = $message; 61 | $this->ccode = $ccode; 62 | $this->reason = $reason; 63 | $this->data = $data ?: new Buffer(); 64 | } 65 | 66 | /** 67 | * @return string 68 | * @see https://en.bitcoin.it/wiki/Protocol_documentation#reject 69 | */ 70 | public function getNetworkCommand(): string 71 | { 72 | return Message::REJECT; 73 | } 74 | 75 | /** 76 | * @param int $code 77 | * @return bool 78 | */ 79 | private function checkCCode(int $code): bool 80 | { 81 | return in_array($code, [ 82 | self::REJECT_MALFORMED, self::REJECT_INVALID, 83 | self::REJECT_OBSOLETE, self::REJECT_DUPLICATE, 84 | self::REJECT_NONSTANDARD, self::REJECT_DUST, 85 | self::REJECT_INSUFFICIENTFEE, self::REJECT_CHECKPOINT 86 | ], true); 87 | } 88 | 89 | /** 90 | * @return BufferInterface 91 | */ 92 | public function getMessage(): BufferInterface 93 | { 94 | return $this->message; 95 | } 96 | 97 | /** 98 | * @return int 99 | */ 100 | public function getCode(): int 101 | { 102 | return $this->ccode; 103 | } 104 | 105 | /** 106 | * @return BufferInterface 107 | */ 108 | public function getReason(): BufferInterface 109 | { 110 | return $this->reason; 111 | } 112 | 113 | /** 114 | * @return BufferInterface 115 | */ 116 | public function getData(): BufferInterface 117 | { 118 | return $this->data; 119 | } 120 | 121 | /** 122 | * @return BufferInterface 123 | */ 124 | public function getBuffer(): BufferInterface 125 | { 126 | return (new RejectSerializer())->serialize($this); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/Messages/SendHeaders.php: -------------------------------------------------------------------------------- 1 | transaction = $tx; 23 | } 24 | 25 | /** 26 | * {@inheritdoc} 27 | * @see https://en.bitcoin.it/wiki/Protocol_documentation#tx 28 | * @see \BitWasp\Bitcoin\Network\NetworkSerializableInterface::getNetworkCommand() 29 | */ 30 | public function getNetworkCommand(): string 31 | { 32 | return Message::TX; 33 | } 34 | 35 | /** 36 | * @return BufferInterface 37 | */ 38 | public function getTransaction(): BufferInterface 39 | { 40 | return $this->transaction; 41 | } 42 | 43 | /** 44 | * {@inheritdoc} 45 | * @see \BitWasp\Bitcoin\SerializableInterface::getBuffer() 46 | */ 47 | public function getBuffer(): BufferInterface 48 | { 49 | return $this->transaction; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Messages/VerAck.php: -------------------------------------------------------------------------------- 1 | = 106 43 | 44 | /** 45 | * The network address of the node emitting this message 46 | * @var NetworkAddress 47 | */ 48 | private $addrFrom; 49 | 50 | /** 51 | * Node random nonce, randomly generated every time a 52 | * version packet is sent. This nonce is used to detect 53 | * connections to self. 54 | * @var int 55 | */ 56 | private $nonce; 57 | 58 | /** 59 | * User agent 60 | * @var BufferInterface 61 | */ 62 | private $userAgent; 63 | 64 | /** 65 | * The last block received by the emitting node 66 | * @var int 67 | */ 68 | private $startHeight; 69 | 70 | // Fields below require version >= 70001 71 | 72 | /** 73 | * Whether the remote peer should announce relayed transactions or not. 74 | * @var bool 75 | */ 76 | private $relay; 77 | 78 | /** 79 | * Version constructor. 80 | * @param int $version 81 | * @param int $services 82 | * @param int $timestamp 83 | * @param NetworkAddress $addrRecv 84 | * @param NetworkAddress $addrFrom 85 | * @param int $nonce 86 | * @param BufferInterface $userAgent 87 | * @param int $startHeight 88 | * @param bool $relay 89 | * @throws \BitWasp\Bitcoin\Exceptions\RandomBytesFailure 90 | */ 91 | public function __construct( 92 | int $version, 93 | int $services, 94 | int $timestamp, 95 | NetworkAddress $addrRecv, 96 | NetworkAddress $addrFrom, 97 | int $nonce, 98 | BufferInterface $userAgent, 99 | int $startHeight, 100 | bool $relay 101 | ) { 102 | 103 | if ($addrRecv instanceof NetworkAddressTimestamp) { 104 | $addrRecv = $addrRecv->withoutTimestamp(); 105 | } 106 | if ($addrFrom instanceof NetworkAddressTimestamp) { 107 | $addrFrom = $addrFrom->withoutTimestamp(); 108 | } 109 | 110 | $random = new Random(); 111 | $this->nonce = (int) $random->bytes(8)->getInt(); 112 | $this->version = $version; 113 | $this->services = $services; 114 | $this->timestamp = $timestamp; 115 | $this->addrRecv = $addrRecv; 116 | $this->nonce = $nonce; 117 | $this->addrFrom = $addrFrom; 118 | $this->userAgent = $userAgent; 119 | $this->startHeight = $startHeight; 120 | $this->relay = $relay; 121 | } 122 | 123 | /** 124 | * {@inheritdoc} 125 | * @see https://en.bitcoin.it/wiki/Protocol_documentation#version 126 | * @see \BitWasp\Bitcoin\Network\NetworkSerializableInterface::getNetworkCommand() 127 | */ 128 | public function getNetworkCommand(): string 129 | { 130 | return Message::VERSION; 131 | } 132 | 133 | /** 134 | * @return int 135 | */ 136 | public function getNonce(): int 137 | { 138 | return $this->nonce; 139 | } 140 | 141 | /** 142 | * @return int 143 | */ 144 | public function getVersion(): int 145 | { 146 | return $this->version; 147 | } 148 | 149 | /** 150 | * @return int 151 | */ 152 | public function getServices(): int 153 | { 154 | return $this->services; 155 | } 156 | 157 | /** 158 | * @return int 159 | */ 160 | public function getTimestamp(): int 161 | { 162 | return $this->timestamp; 163 | } 164 | 165 | /** 166 | * @return NetworkAddress 167 | */ 168 | public function getRecipientAddress(): NetworkAddress 169 | { 170 | return $this->addrRecv; 171 | } 172 | 173 | /** 174 | * @return NetworkAddress 175 | */ 176 | public function getSenderAddress(): NetworkAddress 177 | { 178 | return $this->addrFrom; 179 | } 180 | 181 | /** 182 | * @return BufferInterface 183 | */ 184 | public function getUserAgent(): BufferInterface 185 | { 186 | return $this->userAgent; 187 | } 188 | 189 | /** 190 | * @return int 191 | */ 192 | public function getStartHeight(): int 193 | { 194 | return $this->startHeight; 195 | } 196 | 197 | /** 198 | * @return bool 199 | */ 200 | public function getRelay(): bool 201 | { 202 | return $this->relay; 203 | } 204 | 205 | /** 206 | * @return BufferInterface 207 | */ 208 | public function getBuffer(): BufferInterface 209 | { 210 | return (new VersionSerializer(new NetworkAddressSerializer()))->serialize($this); 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /src/NetworkMessage.php: -------------------------------------------------------------------------------- 1 | network = $network; 40 | $this->payload = $message; 41 | 42 | $buffer = $message->getBuffer(); 43 | 44 | $this->header = new Header( 45 | $message->getNetworkCommand(), 46 | $buffer->getSize(), 47 | Hash::sha256d($buffer)->slice(0, 4) 48 | ); 49 | } 50 | 51 | /** 52 | * @return NetworkSerializableInterface 53 | */ 54 | public function getPayload(): NetworkSerializableInterface 55 | { 56 | return $this->payload; 57 | } 58 | 59 | /** 60 | * @return string 61 | */ 62 | public function getCommand(): string 63 | { 64 | return $this->payload->getNetworkCommand(); 65 | } 66 | 67 | /** 68 | * @return BufferInterface 69 | */ 70 | public function getChecksum(): BufferInterface 71 | { 72 | return $this->header->getChecksum(); 73 | } 74 | 75 | /** 76 | * @return Header 77 | */ 78 | public function getHeader(): Header 79 | { 80 | return $this->header; 81 | } 82 | 83 | /** 84 | * @return BufferInterface 85 | */ 86 | public function getBuffer(): BufferInterface 87 | { 88 | return (new NetworkMessageSerializer($this->network))->serialize($this); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/NetworkSerializable.php: -------------------------------------------------------------------------------- 1 | txRelay = $optRelay; 83 | return $this; 84 | } 85 | 86 | /** 87 | * @param int $blockHeight 88 | * @return $this 89 | */ 90 | public function setBestBlockHeight(int $blockHeight) 91 | { 92 | $this->bestBlockHeight = $blockHeight; 93 | return $this; 94 | } 95 | 96 | /** 97 | * @param callable $callable 98 | * @return $this 99 | */ 100 | public function setBestBlockHeightCallback(callable $callable) 101 | { 102 | $this->bestBlockHeightCallback = $callable; 103 | return $this; 104 | } 105 | 106 | /** 107 | * @param int $version 108 | * @return $this 109 | */ 110 | public function setProtocolVersion(int $version) 111 | { 112 | $this->protocolVersion = $version; 113 | return $this; 114 | } 115 | 116 | /** 117 | * @param IpInterface $ip 118 | * @return $this 119 | */ 120 | public function setLocalIp(IpInterface $ip) 121 | { 122 | $this->localIp = $ip; 123 | return $this; 124 | } 125 | 126 | /** 127 | * @param int $port 128 | * @return $this 129 | */ 130 | public function setLocalPort(int $port) 131 | { 132 | $this->localPort = $port; 133 | return $this; 134 | } 135 | 136 | /** 137 | * @param int $services 138 | * @return $this 139 | */ 140 | public function setLocalServices(int $services) 141 | { 142 | $this->localServices = $services; 143 | return $this; 144 | } 145 | 146 | /** 147 | * @param NetworkAddressInterface $networkAddress 148 | * @return $this 149 | */ 150 | public function setLocalNetAddr(NetworkAddressInterface $networkAddress) 151 | { 152 | // @todo: just set net addr? 153 | $this->setLocalIp($networkAddress->getIp()); 154 | $this->setLocalPort($networkAddress->getPort()); 155 | $this->setLocalServices($networkAddress->getServices()); 156 | return $this; 157 | } 158 | 159 | /** 160 | * @param int $timestamp 161 | * @return $this 162 | */ 163 | public function setTimestamp(int $timestamp) 164 | { 165 | $this->timestamp = $timestamp; 166 | return $this; 167 | } 168 | 169 | /** 170 | * @param int $services 171 | * @return $this 172 | */ 173 | public function setRequiredServices(int $services) 174 | { 175 | $this->requiredServices = $services; 176 | return $this; 177 | } 178 | 179 | /** 180 | * @return int 181 | */ 182 | public function getRequiredServices(): int 183 | { 184 | return $this->requiredServices; 185 | } 186 | 187 | /** 188 | * @param string $string 189 | * @return $this 190 | */ 191 | public function setUserAgent(string $string) 192 | { 193 | $this->userAgent = new Buffer($string); 194 | return $this; 195 | } 196 | 197 | /** 198 | * @param MsgFactory $messageFactory 199 | * @param NetworkAddress $remoteAddress 200 | * @return Version 201 | */ 202 | public function produceVersion(MsgFactory $messageFactory, NetworkAddress $remoteAddress): Version 203 | { 204 | $protocolVersion = is_null($this->protocolVersion) ? $this->defaultProtocolVersion : $this->protocolVersion; 205 | $localServices = is_null($this->localServices) ? Services::NONE : $this->localServices; 206 | $timestamp = is_null($this->timestamp) ? time() : $this->timestamp; 207 | $localAddr = new NetworkAddress( 208 | $localServices, 209 | is_null($this->localIp) ? new Ipv4($this->defaultLocalIp) : $this->localIp, 210 | is_null($this->localPort) ? $this->defaultLocalPort : $this->localPort 211 | ); 212 | 213 | $userAgent = is_null($this->userAgent) ? new Buffer($this->defaultUserAgent) : $this->userAgent; 214 | 215 | if (is_callable($this->bestBlockHeightCallback)) { 216 | $cb = $this->bestBlockHeightCallback; 217 | $bestHeight = $cb(); 218 | } elseif (!is_null($this->bestBlockHeight)) { 219 | $bestHeight = $this->bestBlockHeight; 220 | } else { 221 | $bestHeight = $this->defaultBlockHeight; 222 | } 223 | 224 | $relay = is_null($this->txRelay) ? $this->defaultTxRelay : $this->txRelay; 225 | 226 | return $messageFactory->version($protocolVersion, $localServices, $timestamp, $remoteAddress, $localAddr, $userAgent, $bestHeight, $relay); 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /src/Peer/Connector.php: -------------------------------------------------------------------------------- 1 | params = $params; 46 | $this->msgs = $msgs; 47 | $this->eventLoop = $loop; 48 | $this->socketConnector = new \React\Socket\Connector($loop, $settings); 49 | } 50 | 51 | /** 52 | * @param NetworkAddressInterface $remotePeer 53 | * @return \React\Promise\PromiseInterface 54 | */ 55 | public function rawConnect(NetworkAddressInterface $remotePeer) 56 | { 57 | return $this->socketConnector 58 | ->connect("tcp://{$remotePeer->getIp()->getHost()}:{$remotePeer->getPort()}") 59 | ->then(function (ConnectionInterface $stream) { 60 | $peer = new Peer($this->msgs, $this->eventLoop); 61 | $peer->setupStream($stream); 62 | return $peer; 63 | }); 64 | } 65 | 66 | /** 67 | * @param NetworkAddress $remotePeer 68 | * @return \React\Promise\PromiseInterface 69 | */ 70 | public function connect(NetworkAddress $remotePeer) 71 | { 72 | return $this 73 | ->rawConnect($remotePeer) 74 | ->then(function (Peer $peer) use ($remotePeer) { 75 | return $peer->outboundHandshake($remotePeer, $this->params); 76 | })->then(function (Peer $peer) { 77 | $reqService = $this->params->getRequiredServices(); 78 | if ($reqService != 0) { 79 | if ($reqService != ($peer->getRemoteVersion()->getServices() & $reqService)) { 80 | return new RejectedPromise(new \RuntimeException('peer does not satisfy required services')); 81 | } 82 | } 83 | 84 | return $peer; 85 | }); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/Peer/Listener.php: -------------------------------------------------------------------------------- 1 | params = $params; 50 | $this->messageFactory = $messageFactory; 51 | $this->server = $server = new Server("tcp://{$addr->getIp()->getHost()}:{$addr->getPort()}", $loop); 52 | $this->loop = $loop; 53 | 54 | $server->on('connection', [$this, 'handleIncomingPeer']); 55 | } 56 | 57 | /** 58 | * @param ConnectionInterface $connection 59 | * @return \React\Promise\Promise|\React\Promise\PromiseInterface 60 | */ 61 | public function handleIncomingPeer(ConnectionInterface $connection) 62 | { 63 | return (new Peer($this->messageFactory, $this->loop)) 64 | ->setupStream($connection) 65 | ->inboundHandshake($connection, $this->params) 66 | ->then( 67 | function (Peer $peer) { 68 | $this->emit('connection', [$peer]); 69 | } 70 | ); 71 | } 72 | 73 | /** 74 | * Shut down the server 75 | */ 76 | public function close() 77 | { 78 | $this->server->close(); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/Peer/Locator.php: -------------------------------------------------------------------------------- 1 | seeds = $settings->getDnsSeedList(); 46 | $this->dns = $dns; 47 | $this->settings = $settings; 48 | } 49 | 50 | /** 51 | * Takes an arbitrary list of dns seed hostnames, and attempts 52 | * to return a list from each. Request fails if any hosts are 53 | * offline or cause an error. 54 | * 55 | * @param array $seeds 56 | * @return \React\Promise\Promise|\React\Promise\PromiseInterface 57 | */ 58 | public function querySeeds(array $seeds) 59 | { 60 | $peerList = new Deferred(); 61 | 62 | // Connect to $numSeeds peers 63 | /** @var Peer[] $vNetAddr */ 64 | foreach ($seeds as $seed) { 65 | $this->dns 66 | ->resolveAll($seed, \DNS_A) 67 | ->then(function (array $ipList) use ($peerList) { 68 | $peerList->resolve($ipList); 69 | }, function ($error) use ($peerList) { 70 | $peerList->reject($error); 71 | }) 72 | ; 73 | } 74 | 75 | // Compile the list of lists of peers into $this->knownAddresses 76 | return $peerList->promise(); 77 | } 78 | 79 | /** 80 | * Given a number of DNS seeds to query, select a random few and 81 | * return their peers. 82 | * 83 | * @param int $numSeeds 84 | * @return \React\Promise\Promise|\React\Promise\PromiseInterface 85 | */ 86 | private function getPeerList(int $numSeeds = 1) 87 | { 88 | // Take $numSeeds 89 | $seedHosts = $this->seeds->getHosts(); 90 | shuffle($seedHosts); 91 | $seeds = array_slice($seedHosts, 0, min($numSeeds, count($seedHosts))); 92 | 93 | return $this->querySeeds($seeds); 94 | } 95 | 96 | /** 97 | * Query $numSeeds DNS seeds, returning the NetworkAddress[] result. 98 | * Is rejected if any of the seeds fail. 99 | * 100 | * @param int $numSeeds 101 | * @return \React\Promise\Promise|\React\Promise\PromiseInterface 102 | */ 103 | public function queryDnsSeeds(int $numSeeds = 1) 104 | { 105 | $deferred = new Deferred(); 106 | $this 107 | ->getPeerList($numSeeds) 108 | ->then( 109 | function (array $vPeerVAddrs) use ($deferred) { 110 | shuffle($vPeerVAddrs); 111 | 112 | /** @var NetworkAddressInterface[] $addresses */ 113 | $addresses = []; 114 | foreach ($vPeerVAddrs as $ip) { 115 | $addresses[] = new NetworkAddress( 116 | Services::NETWORK, 117 | new Ipv4($ip), 118 | $this->settings->getDefaultP2PPort() 119 | ); 120 | } 121 | 122 | $this->knownAddresses = array_merge( 123 | $this->knownAddresses, 124 | $addresses 125 | ); 126 | $deferred->resolve($this); 127 | }, 128 | function (\Exception $error) use ($deferred) { 129 | $deferred->reject($error); 130 | } 131 | ) 132 | ; 133 | 134 | return $deferred->promise(); 135 | } 136 | 137 | /** 138 | * @return NetworkAddressInterface[] 139 | */ 140 | public function getKnownAddresses(): array 141 | { 142 | return $this->knownAddresses; 143 | } 144 | 145 | /** 146 | * Pop an address from the discovered peers 147 | * 148 | * @return NetworkAddressInterface 149 | * @throws \Exception 150 | */ 151 | public function popAddress(): NetworkAddressInterface 152 | { 153 | if (count($this->knownAddresses) < 1) { 154 | throw new \Exception('No peers'); 155 | } 156 | 157 | return array_pop($this->knownAddresses); 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/Peer/Manager.php: -------------------------------------------------------------------------------- 1 | connector = $connector; 55 | $this->settings = $settings; 56 | } 57 | 58 | /** 59 | * Store the newly connected peer, and trigger a new connection if they go away. 60 | * 61 | * @param Peer $peer 62 | * @return Peer 63 | */ 64 | public function registerOutboundPeer(Peer $peer): Peer 65 | { 66 | $next = $this->nOutPeers++; 67 | $peer->on('close', function ($peer) use ($next) { 68 | $this->emit('disconnect', [$peer]); 69 | unset($this->outPeers[$next]); 70 | }); 71 | 72 | $this->outPeers[$next] = $peer; 73 | $this->emit('outbound', [$peer]); 74 | return $peer; 75 | } 76 | 77 | /** 78 | * @param Peer $peer 79 | */ 80 | public function registerInboundPeer(Peer $peer) 81 | { 82 | $next = $this->nInPeers++; 83 | $this->inPeers[$next] = $peer; 84 | $peer->on('close', function () use ($next) { 85 | unset($this->inPeers[$next]); 86 | }); 87 | $this->emit('inbound', [$peer]); 88 | } 89 | 90 | /** 91 | * @param Listener $listener 92 | * @return $this 93 | */ 94 | public function registerListener(Listener $listener) 95 | { 96 | $listener->on('connection', function (Peer $peer) { 97 | $this->registerInboundPeer($peer); 98 | }); 99 | 100 | return $this; 101 | } 102 | 103 | /** 104 | * @param NetworkAddress $address 105 | * @return \React\Promise\PromiseInterface 106 | * @throws \Exception 107 | */ 108 | public function connect(NetworkAddressInterface $address) 109 | { 110 | return $this->connector->connect($address); 111 | } 112 | 113 | /** 114 | * @param Locator $locator 115 | * @return \React\Promise\Promise|\React\Promise\PromiseInterface 116 | */ 117 | public function getAnotherPeer(Locator $locator) 118 | { 119 | $deferred = new Deferred(); 120 | 121 | // Otherwise, rely on the Locator. 122 | try { 123 | $deferred->resolve($locator->popAddress()); 124 | } catch (\Exception $e) { 125 | $locator->queryDnsSeeds()->then( 126 | function () use ($deferred, $locator) { 127 | $deferred->resolve($locator->popAddress()); 128 | }, 129 | function ($error): RejectedPromise { 130 | return new RejectedPromise($error); 131 | } 132 | ); 133 | } 134 | 135 | return $deferred->promise(); 136 | } 137 | 138 | /** 139 | * @param Locator $locator 140 | * @return \React\Promise\Promise|\React\Promise\PromiseInterface 141 | */ 142 | public function attemptNextPeer(Locator $locator) 143 | { 144 | $attempt = new Deferred(); 145 | 146 | $this 147 | ->getAnotherPeer($locator) 148 | ->then(function (NetworkAddress $address) use ($attempt) { 149 | return $this->connect($address)->then( 150 | function (Peer $peer) use ($attempt) { 151 | $this->registerOutboundPeer($peer); 152 | $attempt->resolve($peer); 153 | return $peer; 154 | }, 155 | function (\Exception $error) use ($attempt) { 156 | $attempt->reject($error); 157 | } 158 | ); 159 | }, function ($error) use ($attempt) { 160 | $attempt->reject($error); 161 | }); 162 | 163 | return $attempt->promise(); 164 | } 165 | 166 | /** 167 | * @param Locator $locator 168 | * @param int $retries 169 | * @return \React\Promise\PromiseInterface 170 | */ 171 | public function connectNextPeer(Locator $locator, int $retries = null) 172 | { 173 | if ($retries === null) { 174 | $retries = $this->settings->getMaxConnectRetries(); 175 | } 176 | 177 | if (!(is_integer($retries) && $retries >= 0)) { 178 | throw new \InvalidArgumentException("Invalid retry count, must be an integer greater than zero"); 179 | } 180 | 181 | $errorBack = function ($error) use ($locator, $retries) { 182 | $allowContinue = false; 183 | if ($error instanceof \RuntimeException) { 184 | if ($error->getMessage() === "Connection refused") { 185 | $allowContinue = true; 186 | } 187 | } 188 | 189 | if ($error instanceof TimeoutException) { 190 | $allowContinue = true; 191 | } 192 | 193 | if (!$allowContinue) { 194 | throw $error; 195 | } 196 | 197 | if (0 >= $retries) { 198 | throw new \RuntimeException("Connection to peers failed: too many attempts"); 199 | } 200 | 201 | return $this->connectNextPeer($locator, $retries - 1); 202 | }; 203 | 204 | return $this 205 | ->attemptNextPeer($locator) 206 | ->then(null, $errorBack); 207 | } 208 | 209 | /** 210 | * @param Locator $locator 211 | * @param int $n 212 | * @return \React\Promise\Promise 213 | */ 214 | public function connectToPeers(Locator $locator, int $n) 215 | { 216 | $peers = []; 217 | for ($i = 0; $i < $n; $i++) { 218 | $peers[$i] = $this->connectNextPeer($locator); 219 | } 220 | 221 | return \React\Promise\all($peers); 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /src/Peer/Peer.php: -------------------------------------------------------------------------------- 1 | msgs = $msgs; 88 | $this->loop = $loop; 89 | } 90 | 91 | /** 92 | * @return Version 93 | */ 94 | public function getLocalVersion(): Version 95 | { 96 | return $this->localVersion; 97 | } 98 | 99 | /** 100 | * @return Version 101 | */ 102 | public function getRemoteVersion(): Version 103 | { 104 | return $this->remoteVersion; 105 | } 106 | 107 | /** 108 | * Reliably returns the remote peers NetAddr when known through 109 | * the connection process. Often better than the data contained 110 | * in a Version message. 111 | * 112 | * @return NetworkAddress 113 | */ 114 | public function getRemoteAddress(): NetworkAddress 115 | { 116 | return $this->peerAddress; 117 | } 118 | 119 | /** 120 | * @return ConnectionParams 121 | */ 122 | public function getConnectionParams(): ConnectionParams 123 | { 124 | return $this->connectionParams; 125 | } 126 | 127 | /** 128 | * @param NetworkSerializable $msg 129 | */ 130 | public function send(NetworkSerializable $msg) 131 | { 132 | $netMsg = $msg->getNetworkMessage($this->msgs->getNetwork()); 133 | $serialized = $this->msgs->getSerializer()->serialize($netMsg); 134 | $this->stream->write($serialized->getBinary()); 135 | $this->emit('send', [$netMsg]); 136 | } 137 | 138 | /** 139 | * @param ConnectionInterface $stream 140 | * @return $this 141 | */ 142 | public function setupStream(ConnectionInterface $stream) 143 | { 144 | $this->stream = $stream; 145 | $this->stream->on('data', function ($data) { 146 | $this->buffer .= $data; 147 | 148 | $data = new Buffer($this->buffer); 149 | $parser = new Parser($data); 150 | 151 | $pos = $parser->getPosition(); 152 | $sz = $data->getSize(); 153 | 154 | while ($pos < $sz) { 155 | if (null === $this->incomingMsgHeader) { 156 | if ($sz - $pos < 24) { 157 | break; 158 | } 159 | $this->incomingMsgHeader = $this->msgs->getSerializer()->parseHeader($parser); 160 | $pos = $parser->getPosition(); 161 | } 162 | 163 | if ($sz - $pos < $this->incomingMsgHeader->getLength()) { 164 | break; 165 | } 166 | 167 | $message = $this->msgs->getSerializer()->parsePacket($this->incomingMsgHeader, $parser); 168 | $this->incomingMsgHeader = null; 169 | $this->loop->futureTick(function () use ($message) { 170 | $this->emit('msg', [$this, $message]); 171 | }); 172 | $pos = $parser->getPosition(); 173 | } 174 | 175 | $this->buffer = $parser->getBuffer()->slice($pos)->getBinary(); 176 | }); 177 | 178 | $this->stream->once('close', function () { 179 | $this->close(); 180 | }); 181 | 182 | $this->on('msg', function (Peer $peer, NetworkMessage $msg) { 183 | $this->emit($msg->getCommand(), [$peer, $msg->getPayload()]); 184 | }); 185 | 186 | return $this; 187 | } 188 | 189 | /** 190 | * @param ConnectionInterface $connection 191 | * @param ConnectionParams $params 192 | * @return \React\Promise\Promise|\React\Promise\PromiseInterface 193 | */ 194 | public function inboundHandshake(ConnectionInterface $connection, ConnectionParams $params) 195 | { 196 | $this->connectionParams = $params; 197 | 198 | $deferred = new Deferred(); 199 | $this->on(Message::VERSION, function (Peer $peer, Version $version) use ($params) { 200 | $this->peerAddress = $version->getSenderAddress(); 201 | $this->remoteVersion = $version; 202 | $this->localVersion = $localVersion = $params->produceVersion($this->msgs, $version->getSenderAddress()); 203 | $this->send($localVersion); 204 | }); 205 | 206 | $this->on(Message::VERACK, function () use ($deferred) { 207 | if (false === $this->exchangedVersion) { 208 | $this->exchangedVersion = true; 209 | $this->verack(); 210 | $this->emit('ready', [$this]); 211 | $deferred->resolve($this); 212 | } 213 | }); 214 | 215 | return $deferred->promise(); 216 | } 217 | 218 | /** 219 | * @param NetworkAddress $remotePeer 220 | * @param ConnectionParams $params 221 | * @return \React\Promise\Promise|\React\Promise\PromiseInterface 222 | */ 223 | public function outboundHandshake(NetworkAddress $remotePeer, ConnectionParams $params) 224 | { 225 | $deferred = new Deferred(); 226 | 227 | $awaitVersion = true; 228 | $this->stream->once('close', function () use (&$awaitVersion, $deferred) { 229 | if ($awaitVersion) { 230 | $awaitVersion = false; 231 | $deferred->reject(new \Exception('peer disconnected')); 232 | } 233 | }); 234 | 235 | $this->on(Message::VERSION, function (Peer $peer, Version $version) { 236 | $this->remoteVersion = $version; 237 | $this->verack(); 238 | }); 239 | 240 | $this->on(Message::VERACK, function () use ($deferred) { 241 | if (false === $this->exchangedVersion) { 242 | $this->exchangedVersion = true; 243 | $this->emit('ready', [$this]); 244 | $deferred->resolve($this); 245 | } 246 | }); 247 | 248 | $this->peerAddress = $remotePeer; 249 | $this->localVersion = $version = $params->produceVersion($this->msgs, $remotePeer); 250 | $this->connectionParams = $params; 251 | 252 | $this->send($version); 253 | 254 | return $deferred->promise(); 255 | } 256 | 257 | /** 258 | * 259 | */ 260 | public function intentionalClose() 261 | { 262 | $this->emit('intentionaldisconnect', [$this]); 263 | $this->close(); 264 | } 265 | 266 | /** 267 | * 268 | */ 269 | public function close() 270 | { 271 | $this->emit('close', [$this]); 272 | $this->stream->end(); 273 | $this->removeAllListeners(); 274 | } 275 | 276 | /** 277 | * @param int $protocolVersion 278 | * @param int $services 279 | * @param int $timestamp 280 | * @param NetworkAddress $remoteAddr 281 | * @param NetworkAddress $localAddr 282 | * @param string $userAgent 283 | * @param int $blockHeight 284 | * @param bool $relayToUs 285 | */ 286 | public function version( 287 | int $protocolVersion, 288 | int $services, 289 | int $timestamp, 290 | NetworkAddress $remoteAddr, 291 | NetworkAddress $localAddr, 292 | string $userAgent, 293 | int $blockHeight, 294 | bool $relayToUs 295 | ) { 296 | $this->send($this->msgs->version( 297 | $protocolVersion, 298 | $services, 299 | $timestamp, 300 | $remoteAddr, 301 | $localAddr, 302 | new Buffer($userAgent), 303 | $blockHeight, 304 | $relayToUs 305 | )); 306 | } 307 | 308 | /** 309 | * 310 | */ 311 | public function verack() 312 | { 313 | $this->send($this->msgs->verack()); 314 | } 315 | 316 | /** 317 | * 318 | */ 319 | public function sendheaders() 320 | { 321 | $this->send($this->msgs->sendheaders()); 322 | } 323 | 324 | /** 325 | * @param Inventory[] $vInv 326 | */ 327 | public function inv(array $vInv) 328 | { 329 | $this->send($this->msgs->inv($vInv)); 330 | } 331 | 332 | /** 333 | * @param Inventory[] $vInv 334 | */ 335 | public function getdata(array $vInv) 336 | { 337 | $this->send($this->msgs->getdata($vInv)); 338 | } 339 | 340 | /** 341 | * @param Inventory[] $vInv 342 | */ 343 | public function notfound(array $vInv) 344 | { 345 | $this->send($this->msgs->notfound($vInv)); 346 | } 347 | 348 | /** 349 | * @param NetworkAddressTimestamp[] $vNetAddr 350 | */ 351 | public function addr(array $vNetAddr) 352 | { 353 | $this->send($this->msgs->addr($vNetAddr)); 354 | } 355 | 356 | /** 357 | * 358 | */ 359 | public function getaddr() 360 | { 361 | $this->send($this->msgs->getaddr()); 362 | } 363 | 364 | /** 365 | * 366 | */ 367 | public function ping() 368 | { 369 | $this->send($this->msgs->ping()); 370 | } 371 | 372 | /** 373 | * @param Ping $ping 374 | */ 375 | public function pong(Ping $ping) 376 | { 377 | $this->send($this->msgs->pong($ping)); 378 | } 379 | 380 | /** 381 | * @param BufferInterface $txData 382 | */ 383 | public function tx(BufferInterface $txData) 384 | { 385 | $this->send($this->msgs->tx($txData)); 386 | } 387 | 388 | /** 389 | * @param BlockLocator $locator 390 | */ 391 | public function getblocks(BlockLocator $locator) 392 | { 393 | $this->send($this->msgs->getblocks( 394 | $this->localVersion->getVersion(), 395 | $locator 396 | )); 397 | } 398 | 399 | /** 400 | * @param BlockLocator $locator 401 | */ 402 | public function getheaders(BlockLocator $locator) 403 | { 404 | $this->send($this->msgs->getheaders( 405 | $this->localVersion->getVersion(), 406 | $locator 407 | )); 408 | } 409 | 410 | /** 411 | * @param BufferInterface $blockData 412 | */ 413 | public function block(BufferInterface $blockData) 414 | { 415 | $this->send($this->msgs->block($blockData)); 416 | } 417 | 418 | /** 419 | * @param BufferInterface ...$vHeaders 420 | */ 421 | public function headers(BufferInterface ...$vHeaders) 422 | { 423 | $this->send($this->msgs->headers(...$vHeaders)); 424 | } 425 | 426 | /** 427 | * @param AlertDetail $detail 428 | * @param SignatureInterface $signature 429 | */ 430 | public function alert(AlertDetail $detail, SignatureInterface $signature) 431 | { 432 | $this->send($this->msgs->alert($detail, $signature)); 433 | } 434 | 435 | /** 436 | * @param int $feeRate 437 | */ 438 | public function feefilter($feeRate) 439 | { 440 | $this->send($this->msgs->feefilter($feeRate)); 441 | } 442 | 443 | /** 444 | * @param BufferInterface $data 445 | */ 446 | public function filteradd(BufferInterface $data) 447 | { 448 | $this->send($this->msgs->filteradd($data)); 449 | } 450 | 451 | /** 452 | * @param BloomFilter $filter 453 | */ 454 | public function filterload(BloomFilter $filter) 455 | { 456 | $this->send($this->msgs->filterload($filter)); 457 | } 458 | 459 | /** 460 | * 461 | */ 462 | public function filterclear() 463 | { 464 | $this->send($this->msgs->filterclear()); 465 | } 466 | 467 | /** 468 | * @param FilteredBlock $filtered 469 | */ 470 | public function merkleblock(FilteredBlock $filtered) 471 | { 472 | $this->send($this->msgs->merkleblock($filtered)); 473 | } 474 | 475 | /** 476 | * 477 | */ 478 | public function mempool() 479 | { 480 | $this->send($this->msgs->mempool()); 481 | } 482 | 483 | /** 484 | * Issue a Reject message, with a required $msg, $code, and $reason 485 | * 486 | * @param BufferInterface $msg 487 | * @param int $code 488 | * @param BufferInterface $reason 489 | * @param BufferInterface $data 490 | */ 491 | public function reject(BufferInterface $msg, $code, BufferInterface $reason, BufferInterface $data = null) 492 | { 493 | $this->send($this->msgs->reject($msg, $code, $reason, $data)); 494 | } 495 | } 496 | -------------------------------------------------------------------------------- /src/Protocol.php: -------------------------------------------------------------------------------- 1 | > 2; 19 | } 20 | -------------------------------------------------------------------------------- /src/Serializer/Ip/IpSerializer.php: -------------------------------------------------------------------------------- 1 | readBytes(16); 26 | $binary = $buffer->getBinary(); 27 | 28 | if (Onion::MAGIC === substr($binary, 0, strlen(Onion::MAGIC))) { 29 | $addr = strtolower(Base32::encode($buffer->slice(strlen(Onion::MAGIC))->getBinary())) . '.onion'; 30 | $ip = new Onion($addr); 31 | } elseif (Ipv4::MAGIC === substr($binary, 0, strlen(Ipv4::MAGIC))) { 32 | $end = $buffer->slice(strlen(Ipv4::MAGIC), 4); 33 | $ip = new Ipv4(inet_ntop($end->getBinary())); 34 | } else { 35 | $addr = []; 36 | foreach (str_split($binary, 2) as $segment) { 37 | $addr[] = bin2hex($segment); 38 | } 39 | 40 | $addr = implode(":", $addr); 41 | $ip = new Ipv6($addr); 42 | } 43 | 44 | return $ip; 45 | } 46 | 47 | /** 48 | * @param BufferInterface $data 49 | * @return IpInterface 50 | */ 51 | public function parse(BufferInterface $data): IpInterface 52 | { 53 | return $this->fromParser(new Parser($data)); 54 | } 55 | 56 | /** 57 | * @param IpInterface $address 58 | * @return BufferInterface 59 | */ 60 | public function serialize(IpInterface $address): BufferInterface 61 | { 62 | return $address->getBuffer(); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/Serializer/Message/AddrSerializer.php: -------------------------------------------------------------------------------- 1 | vectorNetAddr = Types::vector([$serializer, 'fromParser']); 27 | } 28 | 29 | /** 30 | * @param Parser $parser 31 | * @return Addr 32 | */ 33 | public function fromParser(Parser $parser): Addr 34 | { 35 | $addresses = $this->vectorNetAddr->read($parser); 36 | return new Addr($addresses); 37 | } 38 | 39 | /** 40 | * @param BufferInterface $data 41 | * @return Addr 42 | */ 43 | public function parse(BufferInterface $data): Addr 44 | { 45 | return $this->fromParser(new Parser($data)); 46 | } 47 | 48 | /** 49 | * @param Addr $addr 50 | * @return BufferInterface 51 | */ 52 | public function serialize(Addr $addr): BufferInterface 53 | { 54 | return new Buffer($this->vectorNetAddr->write($addr->getAddresses())); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Serializer/Message/AlertSerializer.php: -------------------------------------------------------------------------------- 1 | detail = $detail; 34 | $this->varstring = Types::varstring(); 35 | } 36 | 37 | /** 38 | * @param Parser $parser 39 | * @return Alert 40 | */ 41 | public function fromParser(Parser $parser): Alert 42 | { 43 | $detailBuffer = $this->varstring->read($parser); 44 | $detail = $this->detail->fromParser(new Parser($detailBuffer)); 45 | 46 | $sigBuffer = $this->varstring->read($parser); 47 | $adapter = Bitcoin::getEcAdapter(); 48 | $serializer = EcSerializer::getSerializer('BitWasp\Bitcoin\Crypto\EcAdapter\Serializer\Signature\DerSignatureSerializerInterface', true, $adapter); 49 | $sig = $serializer->parse($sigBuffer); 50 | 51 | return new Alert($detail, $sig); 52 | } 53 | 54 | /** 55 | * @param BufferInterface $data 56 | * @return Alert 57 | */ 58 | public function parse(BufferInterface $data): Alert 59 | { 60 | return $this->fromParser(new Parser($data)); 61 | } 62 | 63 | /** 64 | * @param Alert $alert 65 | * @return BufferInterface 66 | */ 67 | public function serialize(Alert $alert): BufferInterface 68 | { 69 | return new Buffer("{$this->varstring->write($alert->getDetail()->getBuffer())}{$this->varstring->write($alert->getSignature()->getBuffer())}"); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/Serializer/Message/FeeFilterSerializer.php: -------------------------------------------------------------------------------- 1 | uint64 = Types::uint64(); 23 | } 24 | 25 | /** 26 | * @param Parser $parser 27 | * @return FeeFilter 28 | */ 29 | public function fromParser(Parser $parser): FeeFilter 30 | { 31 | return new FeeFilter((int) $this->uint64->read($parser)); 32 | } 33 | 34 | /** 35 | * @param BufferInterface $data 36 | * @return FeeFilter 37 | */ 38 | public function parse(BufferInterface $data): FeeFilter 39 | { 40 | return $this->fromParser(new Parser($data)); 41 | } 42 | 43 | /** 44 | * @param FeeFilter $feeFilter 45 | * @return BufferInterface 46 | */ 47 | public function serialize(FeeFilter $feeFilter): BufferInterface 48 | { 49 | return new Buffer($this->uint64->write($feeFilter->getFeeRate())); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Serializer/Message/FilterAddSerializer.php: -------------------------------------------------------------------------------- 1 | varString = Types::varstring(); 23 | } 24 | 25 | /** 26 | * @param Parser $parser 27 | * @return FilterAdd 28 | */ 29 | public function fromParser(Parser $parser): FilterAdd 30 | { 31 | $data = $this->varString->read($parser); 32 | 33 | return new FilterAdd($data); 34 | } 35 | 36 | /** 37 | * @param BufferInterface $data 38 | * @return FilterAdd 39 | */ 40 | public function parse(BufferInterface $data): FilterAdd 41 | { 42 | return $this->fromParser(new Parser($data)); 43 | } 44 | 45 | /** 46 | * @param FilterAdd $filteradd 47 | * @return BufferInterface 48 | */ 49 | public function serialize(FilterAdd $filteradd): BufferInterface 50 | { 51 | return new Buffer($this->varString->write($filteradd->getData())); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Serializer/Message/FilterLoadSerializer.php: -------------------------------------------------------------------------------- 1 | filterSerializer = $filterSerializer; 25 | } 26 | 27 | /** 28 | * @param Parser $parser 29 | * @return FilterLoad 30 | */ 31 | public function fromParser(Parser $parser): FilterLoad 32 | { 33 | return new FilterLoad($this->filterSerializer->fromParser($parser)); 34 | } 35 | 36 | /** 37 | * @param BufferInterface $data 38 | * @return FilterLoad 39 | */ 40 | public function parse(BufferInterface $data): FilterLoad 41 | { 42 | return $this->fromParser(new Parser($data)); 43 | } 44 | 45 | /** 46 | * @param FilterLoad $filterload 47 | * @return BufferInterface 48 | */ 49 | public function serialize(FilterLoad $filterload): BufferInterface 50 | { 51 | return $this->filterSerializer->serialize($filterload->getFilter()); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Serializer/Message/GetBlocksSerializer.php: -------------------------------------------------------------------------------- 1 | uint32le = Types::uint32le(); 33 | $this->locator = $locatorSerializer; 34 | } 35 | 36 | /** 37 | * @param Parser $parser 38 | * @return GetBlocks 39 | */ 40 | public function fromParser(Parser $parser): GetBlocks 41 | { 42 | return new GetBlocks( 43 | (int) $this->uint32le->read($parser), 44 | $this->locator->fromParser($parser) 45 | ); 46 | } 47 | 48 | /** 49 | * @param BufferInterface $data 50 | * @return GetBlocks 51 | */ 52 | public function parse(BufferInterface $data): GetBlocks 53 | { 54 | return $this->fromParser(new Parser($data)); 55 | } 56 | 57 | /** 58 | * @param GetBlocks $msg 59 | * @return BufferInterface 60 | */ 61 | public function serialize(GetBlocks $msg): BufferInterface 62 | { 63 | return Buffertools::concat( 64 | new Buffer($this->uint32le->write($msg->getVersion())), 65 | $this->locator->serialize($msg->getLocator()) 66 | ); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Serializer/Message/GetDataSerializer.php: -------------------------------------------------------------------------------- 1 | vectorInt = Types::vector([$inv, 'fromParser']); 27 | } 28 | 29 | /** 30 | * @param Parser $parser 31 | * @return GetData 32 | */ 33 | public function fromParser(Parser $parser): GetData 34 | { 35 | $addrs = $this->vectorInt->read($parser); 36 | return new GetData($addrs); 37 | } 38 | 39 | /** 40 | * @param BufferInterface $data 41 | * @return GetData 42 | */ 43 | public function parse(BufferInterface $data): GetData 44 | { 45 | return $this->fromParser(new Parser($data)); 46 | } 47 | 48 | /** 49 | * @param GetData $getData 50 | * @return BufferInterface 51 | */ 52 | public function serialize(GetData $getData): BufferInterface 53 | { 54 | return new Buffer($this->vectorInt->write($getData->getItems())); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Serializer/Message/GetHeadersSerializer.php: -------------------------------------------------------------------------------- 1 | uint32le = Types::uint32le(); 32 | $this->locator = $locatorSerializer; 33 | } 34 | 35 | /** 36 | * @param Parser $parser 37 | * @return GetHeaders 38 | */ 39 | public function fromParser(Parser $parser): GetHeaders 40 | { 41 | return new GetHeaders( 42 | (int) $this->uint32le->read($parser), 43 | $this->locator->fromParser($parser) 44 | ); 45 | } 46 | 47 | /** 48 | * @param BufferInterface $data 49 | * @return GetHeaders 50 | */ 51 | public function parse(BufferInterface $data): GetHeaders 52 | { 53 | return $this->fromParser(new Parser($data)); 54 | } 55 | 56 | /** 57 | * @param GetHeaders $msg 58 | * @return BufferInterface 59 | */ 60 | public function serialize(GetHeaders $msg): BufferInterface 61 | { 62 | return new Buffer( 63 | $this->uint32le->write($msg->getVersion()) . 64 | $this->locator->serialize($msg->getLocator())->getBinary() 65 | ); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/Serializer/Message/HeadersSerializer.php: -------------------------------------------------------------------------------- 1 | varint = Types::varint(); 24 | } 25 | 26 | /** 27 | * @param Parser $parser 28 | * @return Headers 29 | */ 30 | public function fromParser(Parser $parser): Headers 31 | { 32 | $numHeaders = $this->varint->read($parser); 33 | $headers = []; 34 | for ($i = 0; $i < $numHeaders; $i++) { 35 | $headers[] = $parser->readBytes(80); 36 | $parser->readBytes(1); 37 | } 38 | return new Headers(...$headers); 39 | } 40 | 41 | /** 42 | * @param BufferInterface $data 43 | * @return Headers 44 | */ 45 | public function parse(BufferInterface $data): Headers 46 | { 47 | return $this->fromParser(new Parser($data)); 48 | } 49 | 50 | /** 51 | * @param Headers $msg 52 | * @return BufferInterface 53 | */ 54 | public function serialize(Headers $msg): BufferInterface 55 | { 56 | $numHeaders = $msg->count(); 57 | $encoded = Buffertools::numToVarIntBin($numHeaders); 58 | for ($i = 0; $i < $numHeaders; $i++) { 59 | $encoded .= "{$msg->getHeader($i)->getBinary()}\x00"; 60 | } 61 | return new Buffer($encoded); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/Serializer/Message/InvSerializer.php: -------------------------------------------------------------------------------- 1 | vectorInventory = Types::vector([$invVector, 'fromParser']); 28 | } 29 | 30 | /** 31 | * @param Parser $parser 32 | * @return Inv 33 | */ 34 | public function fromParser(Parser $parser): Inv 35 | { 36 | $items = $this->vectorInventory->read($parser); 37 | return new Inv($items); 38 | } 39 | 40 | /** 41 | * @param BufferInterface $data 42 | * @return Inv 43 | */ 44 | public function parse(BufferInterface $data): Inv 45 | { 46 | return $this->fromParser(new Parser($data)); 47 | } 48 | 49 | /** 50 | * @param Inv $inv 51 | * @return BufferInterface 52 | */ 53 | public function serialize(Inv $inv): BufferInterface 54 | { 55 | return new Buffer($this->vectorInventory->write($inv->getItems())); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Serializer/Message/MerkleBlockSerializer.php: -------------------------------------------------------------------------------- 1 | filteredSerializer = $filtered; 25 | } 26 | 27 | /** 28 | * @param Parser $parser 29 | * @return MerkleBlock 30 | */ 31 | public function fromParser(Parser $parser): MerkleBlock 32 | { 33 | return new MerkleBlock($this->filteredSerializer->fromParser($parser)); 34 | } 35 | 36 | /** 37 | * @param BufferInterface $data 38 | * @return MerkleBlock 39 | */ 40 | public function parse(BufferInterface $data): MerkleBlock 41 | { 42 | return $this->fromParser(new Parser($data)); 43 | } 44 | 45 | /** 46 | * @param MerkleBlock $merkle 47 | * @return BufferInterface 48 | */ 49 | public function serialize(MerkleBlock $merkle): BufferInterface 50 | { 51 | return $this->filteredSerializer->serialize($merkle->getFilteredBlock()); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Serializer/Message/NotFoundSerializer.php: -------------------------------------------------------------------------------- 1 | invSerializer = $inv; 34 | $this->vectorInvSer = Types::vector(function (Parser $parser): Inventory { 35 | return $this->invSerializer->fromParser($parser); 36 | }); 37 | } 38 | 39 | /** 40 | * @param Parser $parser 41 | * @return NotFound 42 | */ 43 | public function fromParser(Parser $parser): NotFound 44 | { 45 | $items = $this->vectorInvSer->read($parser); 46 | return new NotFound($items); 47 | } 48 | 49 | /** 50 | * @param BufferInterface $data 51 | * @return NotFound 52 | */ 53 | public function parse(BufferInterface $data): NotFound 54 | { 55 | return $this->fromParser(new Parser($data)); 56 | } 57 | 58 | /** 59 | * @param NotFound $notFound 60 | * @return BufferInterface 61 | */ 62 | public function serialize(NotFound $notFound): BufferInterface 63 | { 64 | return new Buffer($this->vectorInvSer->write($notFound->getItems())); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Serializer/Message/PingSerializer.php: -------------------------------------------------------------------------------- 1 | getNonce(); 20 | } 21 | 22 | /** 23 | * @param Parser $parser 24 | * @return Ping 25 | */ 26 | public function fromParser(Parser $parser): Ping 27 | { 28 | return new Ping($parser->readBytes(8)); 29 | } 30 | 31 | /** 32 | * @param BufferInterface $data 33 | * @return Ping 34 | */ 35 | public function parse(BufferInterface $data): Ping 36 | { 37 | return $this->fromParser(new Parser($data)); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Serializer/Message/PongSerializer.php: -------------------------------------------------------------------------------- 1 | getNonce(); 20 | } 21 | 22 | /** 23 | * @param Parser $parser 24 | * @return Pong 25 | */ 26 | public function fromParser(Parser $parser): Pong 27 | { 28 | return new Pong($parser->readBytes(8)); 29 | } 30 | 31 | /** 32 | * @param BufferInterface $data 33 | * @return Pong 34 | */ 35 | public function parse(BufferInterface $data): Pong 36 | { 37 | return $this->fromParser(new Parser($data)); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Serializer/Message/RejectSerializer.php: -------------------------------------------------------------------------------- 1 | varString = Types::varstring(); 28 | $this->uint8 = Types::uint8(); 29 | } 30 | 31 | /** 32 | * @param Reject $reject 33 | * @return BufferInterface 34 | */ 35 | public function serialize(Reject $reject): BufferInterface 36 | { 37 | return new Buffer(sprintf( 38 | "%s%s%s%s", 39 | $this->varString->write($reject->getMessage()), 40 | $this->uint8->write($reject->getCode()), 41 | $this->varString->write($reject->getReason()), 42 | $this->varString->write($reject->getData()) 43 | )); 44 | } 45 | 46 | /** 47 | * @param Parser $parser 48 | * @return Reject 49 | */ 50 | public function fromParser(Parser $parser): Reject 51 | { 52 | return new Reject( 53 | $this->varString->read($parser), 54 | (int) $this->uint8->read($parser), 55 | $this->varString->read($parser), 56 | $this->varString->read($parser) 57 | ); 58 | } 59 | 60 | /** 61 | * @param BufferInterface $data 62 | * @return Reject 63 | */ 64 | public function parse(BufferInterface $data): Reject 65 | { 66 | return $this->fromParser(new Parser($data)); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Serializer/Message/VersionSerializer.php: -------------------------------------------------------------------------------- 1 | netAddr = $netAddr; 47 | $this->uint32le = Types::uint32le(); 48 | $this->uint64le = Types::uint64le(); 49 | $this->bs26le = Types::bytestringle(26); 50 | $this->varstring = Types::varstring(); 51 | $this->uint8le = Types::uint8le(); 52 | } 53 | 54 | /** 55 | * @param Parser $parser 56 | * @return Version 57 | */ 58 | public function fromParser(Parser $parser) 59 | { 60 | return new Version( 61 | (int) $this->uint32le->read($parser), 62 | (int) $this->uint64le->read($parser), 63 | (int) $this->uint64le->read($parser), 64 | $this->netAddr->fromParser($parser), 65 | $this->netAddr->fromParser($parser), 66 | (int) $this->uint64le->read($parser), 67 | $this->varstring->read($parser), 68 | (int) $this->uint32le->read($parser), 69 | (bool) $this->uint8le->read($parser) 70 | ); 71 | } 72 | 73 | /** 74 | * @param BufferInterface $string 75 | * @return Version 76 | */ 77 | public function parse(BufferInterface $string): Version 78 | { 79 | return $this->fromParser(new Parser($string)); 80 | } 81 | 82 | /** 83 | * @param Version $version 84 | * @return BufferInterface 85 | */ 86 | public function serialize(Version $version): BufferInterface 87 | { 88 | return new Buffer( 89 | sprintf( 90 | "%s%s%s%s%s%s%s%s%s", 91 | $this->uint32le->write($version->getVersion()), 92 | $this->uint64le->write($version->getServices()), 93 | $this->uint64le->write($version->getTimestamp()), 94 | $this->netAddr->serialize($version->getRecipientAddress())->getBinary(), 95 | $this->netAddr->serialize($version->getSenderAddress())->getBinary(), 96 | $this->uint64le->write($version->getNonce()), 97 | $this->varstring->write($version->getUserAgent()), 98 | $this->uint32le->write($version->getStartHeight()), 99 | $this->uint8le->write((int) $version->getRelay()) 100 | ) 101 | ); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/Serializer/NetworkMessageSerializer.php: -------------------------------------------------------------------------------- 1 | math = Bitcoin::getMath(); 191 | $this->network = $network; 192 | $this->bs4le = Types::bytestringle(4); 193 | $this->txSerializer = new TransactionSerializer(); 194 | $this->headerSerializer = new BlockHeaderSerializer(); 195 | $this->blockSerializer = new BlockSerializer($this->math, $this->headerSerializer, $this->txSerializer); 196 | $this->filteredBlockSerializer = new FilteredBlockSerializer($this->headerSerializer, new PartialMerkleTreeSerializer()); 197 | $this->headersSerializer = new HeadersSerializer(); 198 | $this->filterAddSerializer = new FilterAddSerializer(); 199 | $this->filterLoadSerializer = new FilterLoadSerializer(new BloomFilterSerializer()); 200 | $this->merkleBlockSerializer = new MerkleBlockSerializer($this->filteredBlockSerializer); 201 | $this->pingSerializer = new PingSerializer(); 202 | $this->pongSerializer = new PongSerializer(); 203 | $this->alertSerializer = new AlertSerializer(new AlertDetailSerializer()); 204 | $this->inventorySerializer = new InventorySerializer(); 205 | $this->getDataSerializer = new GetDataSerializer($this->inventorySerializer); 206 | $this->invSerializer = new InvSerializer($this->inventorySerializer); 207 | $this->notFoundSerializer = new NotFoundSerializer($this->inventorySerializer); 208 | $this->feeFilterSerializer = new FeeFilterSerializer(); 209 | $this->rejectSerializer = new RejectSerializer(); 210 | $this->blockLocatorSerializer = new BlockLocatorSerializer(); 211 | $this->getBlocksSerializer = new GetBlocksSerializer($this->blockLocatorSerializer); 212 | $this->getHeadersSerializer = new GetHeadersSerializer($this->blockLocatorSerializer); 213 | $this->versionSerializer = new VersionSerializer(new NetworkAddressSerializer()); 214 | $this->addrSerializer = new AddrSerializer(new NetworkAddressTimestampSerializer()); 215 | $this->packetHeaderSerializer = new HeaderSerializer(); 216 | } 217 | 218 | /** 219 | * @param Parser $parser 220 | * @return Header 221 | */ 222 | public function parseHeader(Parser $parser): Header 223 | { 224 | $prefix = $this->bs4le->read($parser); 225 | if ($prefix->getHex() !== $this->network->getNetMagicBytes()) { 226 | throw new \RuntimeException('Invalid magic bytes for network'); 227 | } 228 | 229 | return $this->packetHeaderSerializer->fromParser($parser); 230 | } 231 | 232 | /** 233 | * @param Header $header 234 | * @param Parser $parser 235 | * @return NetworkMessage 236 | */ 237 | public function parsePacket(Header $header, Parser $parser) 238 | { 239 | $buffer = $header->getLength() > 0 240 | ? $parser->readBytes($header->getLength()) 241 | : new Buffer('', 0); 242 | 243 | // Compare payload checksum against header value 244 | if (!Hash::sha256d($buffer)->slice(0, 4)->equals($header->getChecksum())) { 245 | throw new \RuntimeException('Invalid packet checksum'); 246 | } 247 | 248 | $cmd = trim($header->getCommand()); 249 | switch ($cmd) { 250 | case Message::VERSION: 251 | $payload = $this->versionSerializer->parse($buffer); 252 | break; 253 | case Message::VERACK: 254 | $payload = new VerAck(); 255 | break; 256 | case Message::SENDHEADERS: 257 | $payload = new SendHeaders(); 258 | break; 259 | case Message::ADDR: 260 | $payload = $this->addrSerializer->parse($buffer); 261 | break; 262 | case Message::INV: 263 | $payload = $this->invSerializer->parse($buffer); 264 | break; 265 | case Message::GETDATA: 266 | $payload = $this->getDataSerializer->parse($buffer); 267 | break; 268 | case Message::NOTFOUND: 269 | $payload = $this->notFoundSerializer->parse($buffer); 270 | break; 271 | case Message::GETBLOCKS: 272 | $payload = $this->getBlocksSerializer->parse($buffer); 273 | break; 274 | case Message::GETHEADERS: 275 | $payload = $this->getHeadersSerializer->parse($buffer); 276 | break; 277 | case Message::TX: 278 | $payload = new Tx($buffer); 279 | break; 280 | case Message::BLOCK: 281 | $payload = new Block($buffer); 282 | break; 283 | case Message::HEADERS: 284 | $payload = $this->headersSerializer->parse($buffer); 285 | break; 286 | case Message::GETADDR: 287 | $payload = new GetAddr(); 288 | break; 289 | case Message::MEMPOOL: 290 | $payload = new MemPool(); 291 | break; 292 | case Message::FEEFILTER: 293 | $payload = $this->feeFilterSerializer->parse($buffer); 294 | break; 295 | case Message::FILTERLOAD: 296 | $payload = $this->filterLoadSerializer->parse($buffer); 297 | break; 298 | case Message::FILTERADD: 299 | $payload = $this->filterAddSerializer->parse($buffer); 300 | break; 301 | case Message::FILTERCLEAR: 302 | $payload = new FilterClear(); 303 | break; 304 | case Message::MERKLEBLOCK: 305 | $payload = $this->merkleBlockSerializer->parse($buffer); 306 | break; 307 | case Message::PING: 308 | $payload = $this->pingSerializer->parse($buffer); 309 | break; 310 | case Message::PONG: 311 | $payload = $this->pongSerializer->parse($buffer); 312 | break; 313 | case Message::REJECT: 314 | $payload = $this->rejectSerializer->parse($buffer); 315 | break; 316 | case Message::ALERT: 317 | $payload = $this->alertSerializer->parse($buffer); 318 | break; 319 | default: 320 | throw new \RuntimeException('Unsupported message type'); 321 | } 322 | 323 | return new NetworkMessage($this->network, $payload); 324 | } 325 | 326 | /** 327 | * @param Parser $parser 328 | * @return NetworkMessage 329 | * @throws \BitWasp\Buffertools\Exceptions\ParserOutOfRange 330 | * @throws \Exception 331 | */ 332 | public function fromParser(Parser $parser): NetworkMessage 333 | { 334 | $header = $this->parseHeader($parser); 335 | return $this->parsePacket($header, $parser); 336 | } 337 | 338 | /** 339 | * @param NetworkMessage $object 340 | * @return BufferInterface 341 | */ 342 | public function serialize(NetworkMessage $object): BufferInterface 343 | { 344 | $prefix = $this->bs4le->write(Buffer::hex($this->network->getNetMagicBytes())); 345 | $header = $this->packetHeaderSerializer->serialize($object->getHeader()); 346 | $payload = $object->getPayload()->getBuffer(); 347 | 348 | return new Buffer("{$prefix}{$header->getBinary()}{$payload->getBinary()}"); 349 | } 350 | 351 | /** 352 | * @param BufferInterface $data 353 | * @return NetworkMessage 354 | * @throws \Exception 355 | */ 356 | public function parse(BufferInterface $data): NetworkMessage 357 | { 358 | return $this->fromParser(new Parser($data)); 359 | } 360 | } 361 | -------------------------------------------------------------------------------- /src/Serializer/Structure/AlertDetailSerializer.php: -------------------------------------------------------------------------------- 1 | uint32le = Types::uint32le(); 35 | $this->uint64le = Types::uint64le(); 36 | $this->vectorUint32le = Types::vector(function (Parser $parser): int { 37 | return (int) $this->uint32le->read($parser); 38 | }); 39 | $this->varstring = Types::varstring(); 40 | } 41 | 42 | /** 43 | * @param Parser $parser 44 | * @return AlertDetail 45 | * @throws \BitWasp\Buffertools\Exceptions\ParserOutOfRange 46 | */ 47 | public function fromParser(Parser $parser): AlertDetail 48 | { 49 | return new AlertDetail( 50 | (int) $this->uint32le->read($parser), 51 | (int) $this->uint64le->read($parser), 52 | (int) $this->uint64le->read($parser), 53 | (int) $this->uint32le->read($parser), 54 | (int) $this->uint32le->read($parser), 55 | $this->vectorUint32le->read($parser), 56 | (int) $this->uint32le->read($parser), 57 | (int) $this->uint32le->read($parser), 58 | $this->vectorUint32le->read($parser), 59 | (int) $this->uint32le->read($parser), 60 | $this->varstring->read($parser), 61 | $this->varstring->read($parser) 62 | ); 63 | } 64 | 65 | /** 66 | * @param BufferInterface $data 67 | * @return AlertDetail 68 | */ 69 | public function parse(BufferInterface $data): AlertDetail 70 | { 71 | return $this->fromParser(new Parser($data)); 72 | } 73 | 74 | /** 75 | * @param AlertDetail $detail 76 | * @return BufferInterface 77 | */ 78 | public function serialize(AlertDetail $detail): BufferInterface 79 | { 80 | $setCancels = []; 81 | foreach ($detail->getSetCancel() as $toCancel) { 82 | $setCancels[] = new Buffer(pack('V', $toCancel)); 83 | } 84 | 85 | $setSubVers = []; 86 | foreach ($detail->getSetSubVer() as $subVer) { 87 | $setSubVers[] = new Buffer(pack('V', $subVer)); 88 | } 89 | 90 | return new Buffer( 91 | sprintf( 92 | "%s%s%s%s%s%s%s%s%s%s%s%s", 93 | $this->uint32le->write($detail->getVersion()), 94 | $this->uint64le->write($detail->getRelayUntil()), 95 | $this->uint64le->write($detail->getExpiration()), 96 | $this->uint32le->write($detail->getId()), 97 | $this->uint32le->write($detail->getCancel()), 98 | $this->vectorUint32le->write($setCancels), 99 | $this->uint32le->write($detail->getMinVer()), 100 | $this->uint32le->write($detail->getMaxVer()), 101 | $this->vectorUint32le->write($setSubVers), 102 | $this->uint32le->write($detail->getPriority()), 103 | $this->varstring->write($detail->getComment()), 104 | $this->varstring->write($detail->getStatusBar()) 105 | ) 106 | ); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/Serializer/Structure/HeaderSerializer.php: -------------------------------------------------------------------------------- 1 | bytestring12 = Types::bytestring(12); 33 | $this->bytestring4 = Types::bytestring(4); 34 | $this->uint32 = Types::uint32le(); 35 | } 36 | 37 | /** 38 | * @param Header $header 39 | * @return Buffer 40 | */ 41 | public function serialize(Header $header): BufferInterface 42 | { 43 | $command = new Buffer(str_pad($header->getCommand(), 12, "\x00", STR_PAD_RIGHT)); 44 | 45 | return new Buffer( 46 | $this->bytestring12->write($command). 47 | $this->uint32->write($header->getLength()). 48 | $this->bytestring4->write($header->getChecksum()) 49 | ); 50 | } 51 | 52 | /** 53 | * @param BufferInterface $data 54 | * @return Header 55 | */ 56 | public function parse(BufferInterface $data): Header 57 | { 58 | return $this->fromParser(new Parser($data)); 59 | } 60 | 61 | /** 62 | * @param Parser $parser 63 | * @return Header 64 | */ 65 | public function fromParser(Parser $parser): Header 66 | { 67 | return new Header( 68 | trim($this->bytestring12->read($parser)->getBinary()), 69 | (int) $this->uint32->read($parser), 70 | $this->bytestring4->read($parser) 71 | ); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/Serializer/Structure/InventorySerializer.php: -------------------------------------------------------------------------------- 1 | uint32le = Types::uint32le(); 28 | $this->bytestring32le = Types::bytestringle(32); 29 | } 30 | 31 | /** 32 | * @param Inventory $inv 33 | * @return BufferInterface 34 | */ 35 | public function serialize(Inventory $inv): BufferInterface 36 | { 37 | $flags = $this->uint32le->write($inv->getType()); 38 | $hash = $this->bytestring32le->write($inv->getHash()); 39 | return new Buffer("{$flags}{$hash}"); 40 | } 41 | 42 | /** 43 | * @param Parser $parser 44 | * @return Inventory 45 | */ 46 | public function fromParser(Parser $parser): Inventory 47 | { 48 | $type = (int) $this->uint32le->read($parser); 49 | $hash = $this->bytestring32le->read($parser); 50 | return new Inventory($type, $hash); 51 | } 52 | 53 | /** 54 | * @param BufferInterface $data 55 | * @return Inventory 56 | */ 57 | public function parse(BufferInterface $data): Inventory 58 | { 59 | return $this->fromParser(new Parser($data)); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Serializer/Structure/NetworkAddressSerializer.php: -------------------------------------------------------------------------------- 1 | uint16 = Types::uint16(); 34 | $this->uint64le = Types::uint64le(); 35 | $this->bytestring16 = Types::bytestring(16); 36 | } 37 | 38 | /** 39 | * @param NetworkAddress $addr 40 | * @return BufferInterface 41 | */ 42 | public function serialize(NetworkAddress $addr): BufferInterface 43 | { 44 | $services = $this->uint64le->write($addr->getServices()); 45 | $ip = $addr->getIp()->getBuffer()->getBinary(); 46 | $port = $this->uint16->write($addr->getPort()); 47 | return new Buffer("{$services}{$ip}{$port}"); 48 | } 49 | 50 | /** 51 | * @param Parser $parser 52 | * @return NetworkAddress 53 | */ 54 | public function fromParser(Parser $parser): NetworkAddress 55 | { 56 | // @todo: move this into constructor param? 57 | $ipSerializer = new IpSerializer(); 58 | return new NetworkAddress( 59 | (int) $this->uint64le->read($parser), 60 | $ipSerializer->fromParser($parser), 61 | (int) $this->uint16->read($parser) 62 | ); 63 | } 64 | 65 | /** 66 | * @param BufferInterface $data 67 | * @return NetworkAddress 68 | */ 69 | public function parse(BufferInterface $data): NetworkAddress 70 | { 71 | return $this->fromParser(new Parser($data)); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/Serializer/Structure/NetworkAddressTimestampSerializer.php: -------------------------------------------------------------------------------- 1 | uint16 = Types::uint16(); 39 | $this->uint32 = Types::uint32(); 40 | $this->uint64le = Types::uint64le(); 41 | $this->bytestring16 = Types::bytestring(16); 42 | } 43 | 44 | /** 45 | * @param NetworkAddressTimestamp $addr 46 | * @return BufferInterface 47 | */ 48 | public function serialize(NetworkAddressTimestamp $addr): BufferInterface 49 | { 50 | return new Buffer( 51 | sprintf( 52 | "%s%s%s%s", 53 | $this->uint32->write($addr->getTimestamp()), 54 | $this->uint64le->write($addr->getServices()), 55 | $addr->getIp()->getBuffer()->getBinary(), 56 | $this->uint16->write($addr->getPort()) 57 | ) 58 | ); 59 | } 60 | 61 | /** 62 | * @param Parser $parser 63 | * @return NetworkAddressTimestamp 64 | */ 65 | public function fromParser(Parser $parser): NetworkAddressTimestamp 66 | { 67 | $ipSerializer = new IpSerializer(); 68 | return new NetworkAddressTimestamp( 69 | (int) $this->uint32->read($parser), 70 | (int) $this->uint64le->read($parser), 71 | $ipSerializer->fromParser($parser), 72 | (int) $this->uint16->read($parser) 73 | ); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/Services.php: -------------------------------------------------------------------------------- 1 | dnsSeeds = new MainNetDnsSeeds(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Settings/MutableNetworkSettingsInterface.php: -------------------------------------------------------------------------------- 1 | dnsSeeds) { 42 | throw new \RuntimeException("Missing DNS seed list"); 43 | } 44 | return $this->dnsSeeds; 45 | } 46 | 47 | /** 48 | * @return string 49 | */ 50 | public function getDnsServer(): string 51 | { 52 | if (null === $this->dnsServer) { 53 | throw new \RuntimeException("Missing DNS server"); 54 | } 55 | return $this->dnsServer; 56 | } 57 | 58 | /** 59 | * @return int 60 | */ 61 | public function getDefaultP2PPort(): int 62 | { 63 | return $this->defaultP2PPort; 64 | } 65 | 66 | /** 67 | * @return int 68 | */ 69 | public function getConnectionTimeout(): int 70 | { 71 | return $this->connectionTimeout; 72 | } 73 | 74 | /** 75 | * @return int 76 | */ 77 | public function getMaxConnectRetries(): int 78 | { 79 | return $this->maxRetries; 80 | } 81 | 82 | /** 83 | * @return array 84 | */ 85 | public function getSocketParams(): array 86 | { 87 | return [ 88 | 'timeout' => $this->getConnectionTimeout(), 89 | ]; 90 | } 91 | 92 | /** 93 | * @param string $server 94 | * @return $this 95 | */ 96 | public function withDnsServer(string $server = null): NetworkSettings 97 | { 98 | $clone = clone $this; 99 | $clone->dnsServer = $server; 100 | return $clone; 101 | } 102 | 103 | /** 104 | * @param DnsSeedList $list 105 | * @return $this 106 | */ 107 | public function withDnsSeeds(DnsSeedList $list = null): NetworkSettings 108 | { 109 | $clone = clone $this; 110 | $clone->dnsSeeds = $list; 111 | return $clone; 112 | } 113 | 114 | /** 115 | * @param int $p2pPort 116 | * @return $this 117 | */ 118 | public function withDefaultP2PPort(int $p2pPort): NetworkSettings 119 | { 120 | $clone = clone $this; 121 | $clone->defaultP2PPort = $p2pPort; 122 | return $clone; 123 | } 124 | 125 | /** 126 | * @param int $timeout 127 | * @return $this 128 | */ 129 | public function withConnectionTimeout(int $timeout): NetworkSettings 130 | { 131 | $clone = clone $this; 132 | $clone->connectionTimeout = $timeout; 133 | return $clone; 134 | } 135 | 136 | /** 137 | * @param int $maxRetries 138 | * @return $this 139 | */ 140 | public function withMaxConnectRetries(int $maxRetries): NetworkSettings 141 | { 142 | $clone = clone $this; 143 | $clone->maxRetries = $maxRetries; 144 | return $clone; 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/Settings/NetworkSettingsInterface.php: -------------------------------------------------------------------------------- 1 | dnsSeeds = new TestNetDnsSeeds(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Structure/AlertDetail.php: -------------------------------------------------------------------------------- 1 | version = $version; 122 | $this->relayUntil = $relayUntil; 123 | $this->expiration = $expiration; 124 | $this->id = $id; 125 | $this->cancel = $cancel; 126 | $this->setCancel = $setCancel; 127 | $this->minVer = $minVer; 128 | $this->maxVer = $maxVer; 129 | $this->setSubVer = $setSubVer; 130 | $this->priority = $priority; 131 | $this->comment = $comment; 132 | $this->statusBar = $statusBar; 133 | } 134 | 135 | /** 136 | * @return int 137 | */ 138 | public function getVersion(): int 139 | { 140 | return $this->version; 141 | } 142 | 143 | /** 144 | * @return int 145 | */ 146 | public function getRelayUntil(): int 147 | { 148 | return $this->relayUntil; 149 | } 150 | 151 | /** 152 | * @return int 153 | */ 154 | public function getExpiration(): int 155 | { 156 | return $this->expiration; 157 | } 158 | 159 | /** 160 | * @return int 161 | */ 162 | public function getId(): int 163 | { 164 | return $this->id; 165 | } 166 | 167 | /** 168 | * @return int 169 | */ 170 | public function getCancel(): int 171 | { 172 | return $this->cancel; 173 | } 174 | 175 | /** 176 | * @return integer[] 177 | */ 178 | public function getSetCancel(): array 179 | { 180 | return $this->setCancel; 181 | } 182 | 183 | /** 184 | * @return int 185 | */ 186 | public function getMinVer(): int 187 | { 188 | return $this->minVer; 189 | } 190 | 191 | /** 192 | * @return int 193 | */ 194 | public function getMaxVer(): int 195 | { 196 | return $this->maxVer; 197 | } 198 | 199 | /** 200 | * @return integer[] 201 | */ 202 | public function getSetSubVer(): array 203 | { 204 | return $this->setSubVer; 205 | } 206 | 207 | /** 208 | * @return int 209 | */ 210 | public function getPriority(): int 211 | { 212 | return $this->priority; 213 | } 214 | 215 | /** 216 | * @return BufferInterface 217 | */ 218 | public function getComment(): BufferInterface 219 | { 220 | return $this->comment; 221 | } 222 | 223 | /** 224 | * @return BufferInterface 225 | */ 226 | public function getStatusBar(): BufferInterface 227 | { 228 | return $this->statusBar; 229 | } 230 | 231 | /** 232 | * @see \BitWasp\Bitcoin\SerializableInterface::getBuffer() 233 | * @return BufferInterface 234 | */ 235 | public function getBuffer(): BufferInterface 236 | { 237 | return (new AlertDetailSerializer())->serialize($this); 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /src/Structure/Header.php: -------------------------------------------------------------------------------- 1 | getSize() != 4) { 35 | throw new \InvalidArgumentException("Checksum has invalid length"); 36 | } 37 | 38 | $this->command = $command; 39 | $this->length = $length; 40 | $this->checksum = $checksum; 41 | } 42 | 43 | /** 44 | * @return string 45 | */ 46 | public function getCommand(): string 47 | { 48 | return $this->command; 49 | } 50 | 51 | /** 52 | * @return BufferInterface 53 | */ 54 | public function getChecksum(): BufferInterface 55 | { 56 | return $this->checksum; 57 | } 58 | 59 | /** 60 | * @return int 61 | */ 62 | public function getLength(): int 63 | { 64 | return $this->length; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Structure/Inventory.php: -------------------------------------------------------------------------------- 1 | checkType($type)) { 37 | throw new \InvalidArgumentException('Invalid type in InventoryVector'); 38 | } 39 | 40 | if (32 !== $hash->getSize()) { 41 | throw new \InvalidArgumentException('Hash size must be 32 bytes'); 42 | } 43 | 44 | $this->type = $type; 45 | $this->hash = $hash; 46 | } 47 | 48 | /** 49 | * @param BufferInterface $hash 50 | * @return Inventory 51 | */ 52 | public static function tx(BufferInterface $hash): Inventory 53 | { 54 | return new self(self::MSG_TX, $hash); 55 | } 56 | 57 | /** 58 | * @param BufferInterface $hash 59 | * @return Inventory 60 | */ 61 | public static function witnessTx(BufferInterface $hash): Inventory 62 | { 63 | return new self(self::MSG_WITNESS_TX, $hash); 64 | } 65 | 66 | /** 67 | * @param BufferInterface $hash 68 | * @return Inventory 69 | */ 70 | public static function block(BufferInterface $hash): Inventory 71 | { 72 | return new self(self::MSG_BLOCK, $hash); 73 | } 74 | 75 | /** 76 | * @param BufferInterface $hash 77 | * @return Inventory 78 | */ 79 | public static function witnessBlock(BufferInterface $hash): Inventory 80 | { 81 | return new self(self::MSG_WITNESS_BLOCK, $hash); 82 | } 83 | 84 | /** 85 | * @param BufferInterface $hash 86 | * @return Inventory 87 | */ 88 | public static function filteredBlock(BufferInterface $hash): Inventory 89 | { 90 | return new self(self::MSG_FILTERED_BLOCK, $hash); 91 | } 92 | 93 | /** 94 | * @return int 95 | */ 96 | public function getType(): int 97 | { 98 | return $this->type; 99 | } 100 | 101 | /** 102 | * @return bool 103 | */ 104 | public function isError(): bool 105 | { 106 | return $this->type === self::ERROR; 107 | } 108 | 109 | /** 110 | * @return bool 111 | */ 112 | public function isTx(): bool 113 | { 114 | return $this->type === self::MSG_TX; 115 | } 116 | 117 | /** 118 | * @return bool 119 | */ 120 | public function isWitnessTx(): bool 121 | { 122 | return $this->type === self::MSG_WITNESS_TX; 123 | } 124 | 125 | /** 126 | * @return bool 127 | */ 128 | public function isBlock(): bool 129 | { 130 | return $this->type === self::MSG_BLOCK; 131 | } 132 | 133 | /** 134 | * @return bool 135 | */ 136 | public function isWitnessBlock(): bool 137 | { 138 | return $this->type === self::MSG_WITNESS_BLOCK; 139 | } 140 | 141 | /** 142 | * @return bool 143 | */ 144 | public function isFilteredBlock(): bool 145 | { 146 | return $this->type === self::MSG_FILTERED_BLOCK; 147 | } 148 | 149 | /** 150 | * @return BufferInterface 151 | */ 152 | public function getHash(): BufferInterface 153 | { 154 | return $this->hash; 155 | } 156 | 157 | /** 158 | * @param int $type 159 | * @return bool 160 | */ 161 | private function checkType(int $type): bool 162 | { 163 | return in_array($type, [self::ERROR, self::MSG_TX, self::MSG_BLOCK, self::MSG_FILTERED_BLOCK, self::MSG_WITNESS_TX, self::MSG_WITNESS_BLOCK]); 164 | } 165 | 166 | /** 167 | * @return BufferInterface 168 | */ 169 | public function getBuffer(): BufferInterface 170 | { 171 | return (new InventorySerializer())->serialize($this); 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /src/Structure/NetworkAddress.php: -------------------------------------------------------------------------------- 1 | services = $services; 37 | $this->ip = $ip; 38 | $this->port = $port; 39 | } 40 | 41 | /** 42 | * @return int 43 | */ 44 | public function getServices(): int 45 | { 46 | return $this->services; 47 | } 48 | 49 | /** 50 | * @return IpInterface 51 | */ 52 | public function getIp(): IpInterface 53 | { 54 | return $this->ip; 55 | } 56 | 57 | /** 58 | * @return int 59 | */ 60 | public function getPort(): int 61 | { 62 | return $this->port; 63 | } 64 | 65 | /** 66 | * @return BufferInterface 67 | */ 68 | public function getBuffer(): BufferInterface 69 | { 70 | return (new NetworkAddressSerializer())->serialize($this); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/Structure/NetworkAddressInterface.php: -------------------------------------------------------------------------------- 1 | time = $time; 31 | parent::__construct($services, $ip, $port); 32 | } 33 | 34 | /** 35 | * @return int 36 | */ 37 | public function getTimestamp(): int 38 | { 39 | return $this->time; 40 | } 41 | 42 | /** 43 | * @return NetworkAddress 44 | */ 45 | public function withoutTimestamp(): NetworkAddress 46 | { 47 | return new NetworkAddress( 48 | $this->getServices(), 49 | $this->getIp(), 50 | $this->getPort() 51 | ); 52 | } 53 | 54 | /** 55 | * @return BufferInterface 56 | */ 57 | public function getBuffer(): BufferInterface 58 | { 59 | return (new NetworkAddressTimestampSerializer())->serialize($this); 60 | } 61 | } 62 | --------------------------------------------------------------------------------