|
1 |
| -var net = require('net'); |
2 |
| -var crypto = require('crypto'); |
3 |
| -var events = require('events'); |
4 |
| - |
5 |
| -var util = require('./util.js'); |
6 |
| - |
7 |
| - |
8 |
| -//Example of p2p in node from TheSeven: http://paste.pm/e54.js |
9 |
| - |
10 |
| - |
11 |
| -var fixedLenStringBuffer = function(s, len) { |
12 |
| - var buff = new Buffer(len); |
13 |
| - buff.fill(0); |
14 |
| - buff.write(s); |
15 |
| - return buff; |
16 |
| -}; |
17 |
| - |
18 |
| -var commandStringBuffer = function (s) { |
19 |
| - return fixedLenStringBuffer(s, 12); |
20 |
| -}; |
| 1 | +const net = require('net'); |
| 2 | +const crypto = require('crypto'); |
| 3 | +const { EventEmitter } = require('events'); |
| 4 | +const util = require('./util.js'); |
| 5 | + |
| 6 | +class Peer extends EventEmitter { |
| 7 | + constructor(options) { |
| 8 | + super(); |
| 9 | + this.options = options; |
| 10 | + this.client = null; |
| 11 | + this.verack = false; |
| 12 | + this.validConnectionConfig = true; |
| 13 | + this.magic = Buffer.from(options.testnet ? options.coin.peerMagicTestnet : options.coin.peerMagic, 'hex'); |
| 14 | + this.magicInt = this.magic.readUInt32LE(0); |
| 15 | + this.commands = { |
| 16 | + version: this.commandStringBuffer('version'), |
| 17 | + inv: this.commandStringBuffer('inv'), |
| 18 | + verack: this.commandStringBuffer('verack'), |
| 19 | + addr: this.commandStringBuffer('addr'), |
| 20 | + getblocks: this.commandStringBuffer('getblocks') |
| 21 | + }; |
| 22 | + this.networkServices = Buffer.from('0100000000000000', 'hex'); // NODE_NETWORK services (value 1 packed as uint64) |
| 23 | + this.emptyNetAddress = Buffer.from('010000000000000000000000000000000000ffff000000000000', 'hex'); |
| 24 | + this.userAgent = util.varStringBuffer('/node-stratum/'); |
| 25 | + this.blockStartHeight = Buffer.from('00000000', 'hex'); // block start_height, can be empty |
| 26 | + this.relayTransactions = options.p2p.disableTransactions ? Buffer.from([0]) : Buffer.from([1]); |
21 | 27 |
|
22 |
| -/* Reads a set amount of bytes from a flowing stream, argument descriptions: |
23 |
| - - stream to read from, must have data emitter |
24 |
| - - amount of bytes to read |
25 |
| - - preRead argument can be used to set start with an existing data buffer |
26 |
| - - callback returns 1) data buffer and 2) lopped/over-read data */ |
27 |
| -var readFlowingBytes = function (stream, amount, preRead, callback) { |
| 28 | + this.connect(); |
| 29 | + } |
28 | 30 |
|
29 |
| - var buff = preRead ? preRead : new Buffer([]); |
| 31 | + commandStringBuffer(s) { |
| 32 | + const buffer = Buffer.alloc(12); |
| 33 | + buffer.write(s); |
| 34 | + return buffer; |
| 35 | + } |
30 | 36 |
|
31 |
| - var readData = function (data) { |
32 |
| - buff = Buffer.concat([buff, data]); |
33 |
| - if (buff.length >= amount) { |
34 |
| - var returnData = buff.slice(0, amount); |
35 |
| - var lopped = buff.length > amount ? buff.slice(amount) : null; |
36 |
| - callback(returnData, lopped); |
37 |
| - } |
38 |
| - else |
39 |
| - stream.once('data', readData); |
40 |
| - }; |
41 |
| - |
42 |
| - readData(new Buffer([])); |
43 |
| -}; |
44 |
| - |
45 |
| -var Peer = module.exports = function (options) { |
46 |
| - |
47 |
| - var _this = this; |
48 |
| - var client; |
49 |
| - var magic = new Buffer(options.testnet ? options.coin.peerMagicTestnet : options.coin.peerMagic, 'hex'); |
50 |
| - var magicInt = magic.readUInt32LE(0); |
51 |
| - var verack = false; |
52 |
| - var validConnectionConfig = true; |
53 |
| - |
54 |
| - //https://en.bitcoin.it/wiki/Protocol_specification#Inventory_Vectors |
55 |
| - var invCodes = { |
56 |
| - error: 0, |
57 |
| - tx: 1, |
58 |
| - block: 2 |
59 |
| - }; |
60 |
| - |
61 |
| - var networkServices = new Buffer('0100000000000000', 'hex'); //NODE_NETWORK services (value 1 packed as uint64) |
62 |
| - var emptyNetAddress = new Buffer('010000000000000000000000000000000000ffff000000000000', 'hex'); |
63 |
| - var userAgent = util.varStringBuffer('/node-stratum/'); |
64 |
| - var blockStartHeight = new Buffer('00000000', 'hex'); //block start_height, can be empty |
65 |
| - |
66 |
| - //If protocol version is new enough, add do not relay transactions flag byte, outlined in BIP37 |
67 |
| - //https://github.com/bitcoin/bips/blob/master/bip-0037.mediawiki#extensions-to-existing-messages |
68 |
| - var relayTransactions = options.p2p.disableTransactions === true ? new Buffer([false]) : new Buffer([]); |
69 |
| - |
70 |
| - var commands = { |
71 |
| - version: commandStringBuffer('version'), |
72 |
| - inv: commandStringBuffer('inv'), |
73 |
| - verack: commandStringBuffer('verack'), |
74 |
| - addr: commandStringBuffer('addr'), |
75 |
| - getblocks: commandStringBuffer('getblocks') |
76 |
| - }; |
77 |
| - |
78 |
| - |
79 |
| - (function init() { |
80 |
| - Connect(); |
81 |
| - })(); |
82 |
| - |
83 |
| - |
84 |
| - function Connect() { |
85 |
| - |
86 |
| - client = net.connect({ |
87 |
| - host: options.p2p.host, |
88 |
| - port: options.p2p.port |
89 |
| - }, function () { |
90 |
| - SendVersion(); |
| 37 | + async connect() { |
| 38 | + this.client = net.createConnection({ |
| 39 | + host: this.options.p2p.host, |
| 40 | + port: this.options.p2p.port |
| 41 | + }, () => { |
| 42 | + this.sendVersion(); |
91 | 43 | });
|
92 |
| - client.on('close', function () { |
93 |
| - if (verack) { |
94 |
| - _this.emit('disconnected'); |
95 |
| - verack = false; |
96 |
| - Connect(); |
97 |
| - } |
98 |
| - else if (validConnectionConfig) |
99 |
| - _this.emit('connectionRejected'); |
100 | 44 |
|
| 45 | + this.client.on('close', () => { |
| 46 | + if (this.verack) { |
| 47 | + this.emit('disconnected'); |
| 48 | + this.verack = false; |
| 49 | + this.connect(); |
| 50 | + } else if (this.validConnectionConfig) { |
| 51 | + this.emit('connectionRejected'); |
| 52 | + } |
101 | 53 | });
|
102 |
| - client.on('error', function (e) { |
103 |
| - if (e.code === 'ECONNREFUSED') { |
104 |
| - validConnectionConfig = false; |
105 |
| - _this.emit('connectionFailed'); |
| 54 | + |
| 55 | + this.client.on('error', (err) => { |
| 56 | + if (err.code === 'ECONNREFUSED') { |
| 57 | + this.validConnectionConfig = false; |
| 58 | + this.emit('connectionFailed'); |
| 59 | + } else { |
| 60 | + this.emit('socketError', err); |
106 | 61 | }
|
107 |
| - else |
108 |
| - _this.emit('socketError', e); |
109 | 62 | });
|
110 | 63 |
|
| 64 | + this.setupMessageParser(); |
| 65 | + } |
111 | 66 |
|
112 |
| - SetupMessageParser(client); |
| 67 | + setupMessageParser() { |
| 68 | + let buffer = Buffer.alloc(0); |
113 | 69 |
|
114 |
| - } |
| 70 | + this.client.on('data', (chunk) => { |
| 71 | + buffer = Buffer.concat([buffer, chunk]); |
115 | 72 |
|
116 |
| - function SetupMessageParser(client) { |
117 |
| - |
118 |
| - var beginReadingMessage = function (preRead) { |
119 |
| - |
120 |
| - readFlowingBytes(client, 24, preRead, function (header, lopped) { |
121 |
| - var msgMagic = header.readUInt32LE(0); |
122 |
| - if (msgMagic !== magicInt) { |
123 |
| - _this.emit('error', 'bad magic number from peer'); |
124 |
| - while (header.readUInt32LE(0) !== magicInt && header.length >= 4) { |
125 |
| - header = header.slice(1); |
126 |
| - } |
127 |
| - if (header.readUInt32LE(0) === magicInt) { |
128 |
| - beginReadingMessage(header); |
129 |
| - } else { |
130 |
| - beginReadingMessage(new Buffer([])); |
131 |
| - } |
132 |
| - return; |
| 73 | + while (buffer.length >= 24) { |
| 74 | + const msgMagic = buffer.readUInt32LE(0); |
| 75 | + if (msgMagic !== this.magicInt) { |
| 76 | + this.emit('error', 'bad magic number from peer'); |
| 77 | + buffer = buffer.slice(1); |
| 78 | + continue; |
133 | 79 | }
|
134 |
| - var msgCommand = header.slice(4, 16).toString(); |
135 |
| - var msgLength = header.readUInt32LE(16); |
136 |
| - var msgChecksum = header.readUInt32LE(20); |
137 |
| - readFlowingBytes(client, msgLength, lopped, function (payload, lopped) { |
138 |
| - if (util.sha256d(payload).readUInt32LE(0) !== msgChecksum) { |
139 |
| - _this.emit('error', 'bad payload - failed checksum'); |
140 |
| - beginReadingMessage(null); |
141 |
| - return; |
142 |
| - } |
143 |
| - HandleMessage(msgCommand, payload); |
144 |
| - beginReadingMessage(lopped); |
145 |
| - }); |
146 |
| - }); |
147 |
| - }; |
148 | 80 |
|
149 |
| - beginReadingMessage(null); |
150 |
| - } |
| 81 | + const msgCommand = buffer.slice(4, 16).toString().replace(/\0+$/, ''); |
| 82 | + const msgLength = buffer.readUInt32LE(16); |
| 83 | + const msgChecksum = buffer.readUInt32LE(20); |
151 | 84 |
|
| 85 | + if (buffer.length < 24 + msgLength) break; // Check if the full message is buffered |
152 | 86 |
|
153 |
| - //Parsing inv message https://en.bitcoin.it/wiki/Protocol_specification#inv |
154 |
| - function HandleInv(payload) { |
155 |
| - //sloppy varint decoding |
156 |
| - var count = payload.readUInt8(0); |
157 |
| - payload = payload.slice(1); |
158 |
| - if (count >= 0xfd) |
159 |
| - { |
160 |
| - count = payload.readUInt16LE(0); |
161 |
| - payload = payload.slice(2); |
162 |
| - } |
163 |
| - while (count--) { |
164 |
| - switch(payload.readUInt32LE(0)) { |
165 |
| - case invCodes.error: |
166 |
| - break; |
167 |
| - case invCodes.tx: |
168 |
| - var tx = payload.slice(4, 36).toString('hex'); |
169 |
| - break; |
170 |
| - case invCodes.block: |
171 |
| - var block = payload.slice(4, 36).toString('hex'); |
172 |
| - _this.emit('blockFound', block); |
173 |
| - break; |
| 87 | + const payload = buffer.slice(24, 24 + msgLength); |
| 88 | + buffer = buffer.slice(24 + msgLength); |
| 89 | + |
| 90 | + if (util.sha256d(payload).readUInt32LE(0) !== msgChecksum) { |
| 91 | + this.emit('error', 'bad payload - failed checksum'); |
| 92 | + continue; |
| 93 | + } |
| 94 | + |
| 95 | + this.handleMessage(msgCommand, payload); |
174 | 96 | }
|
175 |
| - payload = payload.slice(36); |
176 |
| - } |
| 97 | + }); |
177 | 98 | }
|
178 | 99 |
|
179 |
| - function HandleMessage(command, payload) { |
180 |
| - _this.emit('peerMessage', {command: command, payload: payload}); |
| 100 | + handleMessage(command, payload) { |
| 101 | + this.emit('peerMessage', { command, payload }); |
| 102 | + |
181 | 103 | switch (command) {
|
182 |
| - case commands.inv.toString(): |
183 |
| - HandleInv(payload); |
| 104 | + case 'inv': |
| 105 | + this.handleInv(payload); |
184 | 106 | break;
|
185 |
| - case commands.verack.toString(): |
186 |
| - if(!verack) { |
187 |
| - verack = true; |
188 |
| - _this.emit('connected'); |
189 |
| - } |
| 107 | + case 'verack': |
| 108 | + this.verack = true; |
| 109 | + this.emit('connected'); |
190 | 110 | break;
|
191 |
| - case commands.version.toString(): |
192 |
| - SendMessage(commands.verack, Buffer.alloc(0)); |
193 |
| - break; |
194 |
| - default: |
| 111 | + case 'version': |
| 112 | + this.sendMessage(this.commands.verack, Buffer.alloc(0)); |
195 | 113 | break;
|
196 | 114 | }
|
| 115 | + } |
197 | 116 |
|
| 117 | + handleInv(payload) { |
| 118 | + const count = payload.readUInt8(0); |
| 119 | + payload = payload.slice(1); |
| 120 | + for (let i = 0; i < count; i++) { |
| 121 | + const type = payload.readUInt32LE(i * 36); |
| 122 | + const hash = payload.slice(i * 36 + 4, i * 36 + 36).toString('hex'); |
| 123 | + switch (type) { |
| 124 | + case 0: // ERROR |
| 125 | + break; |
| 126 | + case 1: // TX |
| 127 | + this.emit('txFound', hash); |
| 128 | + break; |
| 129 | + case 2: // BLOCK |
| 130 | + this.emit('blockFound', hash); |
| 131 | + break; |
| 132 | + } |
| 133 | + } |
198 | 134 | }
|
199 | 135 |
|
200 |
| - //Message structure defined at: https://en.bitcoin.it/wiki/Protocol_specification#Message_structure |
201 |
| - function SendMessage(command, payload) { |
202 |
| - var message = Buffer.concat([ |
203 |
| - magic, |
| 136 | + sendMessage(command, payload) { |
| 137 | + const message = Buffer.concat([ |
| 138 | + this.magic, |
204 | 139 | command,
|
205 | 140 | util.packUInt32LE(payload.length),
|
206 | 141 | util.sha256d(payload).slice(0, 4),
|
207 | 142 | payload
|
208 | 143 | ]);
|
209 |
| - client.write(message); |
210 |
| - _this.emit('sentMessage', message); |
| 144 | + this.client.write(message); |
| 145 | + this.emit('sentMessage', message); |
211 | 146 | }
|
212 | 147 |
|
213 |
| - function SendVersion() { |
214 |
| - var payload = Buffer.concat([ |
215 |
| - util.packUInt32LE(options.protocolVersion), |
216 |
| - networkServices, |
217 |
| - util.packInt64LE(Date.now() / 1000 | 0), |
218 |
| - emptyNetAddress, //addr_recv, can be empty |
219 |
| - emptyNetAddress, //addr_from, can be empty |
220 |
| - crypto.pseudoRandomBytes(8), //nonce, random unique ID |
221 |
| - userAgent, |
222 |
| - blockStartHeight, |
223 |
| - relayTransactions |
| 148 | + sendVersion() { |
| 149 | + const payload = Buffer.concat([ |
| 150 | + util.packUInt32LE(this.options.protocolVersion), |
| 151 | + this.networkServices, |
| 152 | + util.packInt64LE(Math.floor(Date.now() / 1000)), |
| 153 | + this.emptyNetAddress, |
| 154 | + this.emptyNetAddress, |
| 155 | + crypto.randomBytes(8), |
| 156 | + this.userAgent, |
| 157 | + this.blockStartHeight, |
| 158 | + this.relayTransactions |
224 | 159 | ]);
|
225 |
| - SendMessage(commands.version, payload); |
| 160 | + this.sendMessage(this.commands.version, payload); |
226 | 161 | }
|
| 162 | +} |
227 | 163 |
|
228 |
| -}; |
229 |
| - |
230 |
| -Peer.prototype.__proto__ = events.EventEmitter.prototype; |
| 164 | +module.exports = Peer; |
0 commit comments