├── README.md └── node.php /README.md: -------------------------------------------------------------------------------- 1 | A rudimentary bitcoin node, written in PHP. 2 | 3 | It's not actually a "node" because it doesn't relay any messages. 4 | 5 | It basically just shows you how to connect to a node on the bitcoin network and start receiving messages from them, so you can see how the "handshake" works and what the data looks like that gets sent between bitcoin nodes. 6 | 7 | ## Usage 8 | 9 | Go in to the folder and run: 10 | 11 | `php node.php` 12 | 13 | ## Tips 14 | 15 | * The only stuff you really need is four `socket_*` functions, and with those you can connect and send messages to bitcoin nodes. The rest of the script is just getting the right data in the right format (so that nodes can understand your messages correctly). 16 | * Don't rely on this for anything. It's just a quick script to show you how easy it is to use PHP to connect to the bitcoin network. 17 | -------------------------------------------------------------------------------- /node.php: -------------------------------------------------------------------------------- 1 | $version, // 4 bytes (60002) 130 | 'services' => $services, // 8 bytes 131 | 'timestamp' => $timestamp, // 8 bytes 132 | 'addr_recv' => $recv, // 26 bytes (8 + 16 + 2) 133 | 'addr_from' => $from, // 26 bytes (8 + 16 + 2) 134 | 'nonce' => $nonce, // 8 bytes 135 | 'user_agent' => $user_agent, // varint 136 | 'start_height' => $start_height // 4 bytes 137 | ]; 138 | 139 | $version_payload = str_replace(' ', '', implode($version_array)); 140 | 141 | return $version_payload; 142 | } 143 | 144 | function makeMessage($command, $payload, $testnet = false) { 145 | 146 | // Header 147 | $magicbytes = $testnet ? '0B 11 09 07' : 'F9 BE B4 D9'; 148 | $command = str_pad(ascii2hex($command), 24, '0', STR_PAD_RIGHT); // e.g. 76 65 72 73 69 6F 6E 00 00 00 00 00 149 | $payload_size = bytespaces(swapEndian(fieldSize1(dechex(strlen($payload) / 2), 4))); 150 | $checksum = checksum($payload); 151 | 152 | $header_array = [ 153 | 'magicbytes' => $magicbytes, 154 | 'command' => $command, 155 | 'payload_size' => $payload_size, 156 | 'checksum' => $checksum, 157 | ]; 158 | 159 | $header = str_replace(' ', '', implode($header_array)); 160 | // echo 'Header: '; print_r($header_array); 161 | 162 | return $header.$payload; 163 | 164 | } 165 | 166 | 167 | // ------------------ 168 | // 1. CONNECT TO NODE 169 | // ------------------ 170 | 171 | // 1. Create Version Message 172 | // ------------------------- 173 | // This is the initial "handshake" with the node we want to connect to. We send them a version message, and if it is valid they will send a version message back. 174 | 175 | $payload = makeVersionMessagePayload($version, $node_ip, $node_port, $local_ip, $local_port); 176 | $message = makeMessage('version', $payload, $testnet); 177 | $message_size = strlen($message) / 2; // the size of the message (in bytes) being sent 178 | 179 | // 2. Connect to socket and send version message 180 | // --------------------------------------------- 181 | echo "Connecting to $node_ip...\n"; 182 | $socket = socket_create(AF_INET, SOCK_STREAM, 6); socketerror(); // IPv4, TCP uses this type, TCP protocol 183 | socket_connect($socket, $node_ip, $node_port); 184 | echo "Sending version message...\n\n"; 185 | socket_send($socket, hex2bin($message), $message_size, 0); // don't forget to send message in binary 186 | 187 | // 3. Keep receiving data 188 | // ------------------------ 189 | while (true) { 190 | 191 | // Read what's written to the socket 1 byte at a time 192 | $buffer = ''; 193 | while (socket_recv($socket, $byte, 1, MSG_DONTWAIT)) { 194 | 195 | // Each byte is sent and received in binary, so convert to hex so we can look at it 196 | $byte = bin2hex($byte); 197 | 198 | // OPTION 1: Just print out every byte we recevie to the screen. 199 | //echo $byte; 200 | 201 | // OPTION 2: Decipher and print out indvidual messages (and respond to verack & ping messages to keep connection alive). 202 | $buffer .= $byte; 203 | 204 | // If the buffer has received a full message header (24 bytes) 205 | if (strlen($buffer) == 48) { 206 | 207 | // Parse the header 208 | $magic = substr($buffer, 0, 8); 209 | $command = commandName(substr($buffer, 8, 24)); 210 | $size = hexdec(swapEndian(substr($buffer, 32, 8))); 211 | $checksum = substr($buffer, 40, 8); 212 | 213 | // Read the size of the payload 214 | socket_recv($socket, $payload, $size, MSG_WAITALL); 215 | $payload = bin2hex($payload); 216 | 217 | // -------- 218 | // MESSAGES 219 | // -------- 220 | if ($command == 'version') { 221 | echo "version: ".$payload.PHP_EOL; 222 | 223 | } 224 | 225 | if ($command == 'verack') { 226 | echo "verack: ".PHP_EOL; 227 | 228 | // Must send a verack back (required in 0.14.0) 229 | $verack = makeMessage('verack', '', $testnet); 230 | socket_send($socket, hex2bin($verack), strlen($verack) / 2, 0); 231 | echo "verack->".PHP_EOL; 232 | 233 | // Connection is successful, so could we ask for the entire mempool (and get a big "inv message back") 234 | 235 | // $mempool = makeMessage('mempool', '', $testnet); 236 | // socket_send($socket, hex2bin($mempool), strlen($mempool) / 2, 0); 237 | 238 | } 239 | 240 | // Inventory - Node sends us some transactions and blocks they can send us. 241 | if ($command == 'inv') { 242 | // echo "inv: ".$payload.PHP_EOL; 243 | 244 | // Respond to the inv(entory) message with the same payload, so we they will send us all the txs and blocks in that inv message (in new individual tx and block messages) 245 | $getdata = makeMessage('getdata', $payload); 246 | socket_send($socket, hex2bin($getdata), strlen($getdata) / 2, 0); 247 | 248 | } 249 | 250 | if ($command == 'tx') { 251 | echo "tx: $payload".PHP_EOL.PHP_EOL; 252 | // do something with the $payload 253 | } 254 | 255 | if ($command == 'block') { 256 | echo "block: $payload".PHP_EOL.PHP_EOL; 257 | // do something with the $payload 258 | } 259 | 260 | if ($command == 'ping') { 261 | echo "ping: ".PHP_EOL; 262 | 263 | // reply with "pong" message to let node know that we're still alive. (Reply with same payload) 264 | $pong = makeMessage('pong', $payload, $testnet); 265 | socket_send($socket, hex2bin($pong), strlen($pong) / 2, 0); 266 | echo "pong->".PHP_EOL; 267 | } 268 | 269 | // Empty the buffer (after reading the 24 byte header and the full payload) 270 | $buffer = ''; 271 | 272 | // start reading next packet... 273 | } 274 | 275 | // Tiny sleep to prevent looping insanely fast and using up 100% CPU power on one core. 276 | // NOTE: You will want to decrease (or remove) this if you are printing 1 byte at a time and want to read every byte as quickly as possible. 277 | usleep(5000); // 1/5000th of a second 278 | 279 | } 280 | 281 | } 282 | 283 | /* Resources 284 | - https://wiki.bitcoin.com/w/Network 285 | - https://en.bitcoin.it/wiki/Protocol_documentation 286 | - https://coinlogic.wordpress.com/2014/03/09/the-bitcoin-protocol-4-network-messages-1-version/ 287 | */ 288 | --------------------------------------------------------------------------------