stratum-pool-sugarchain
Version:
High performance Sugarchain Stratum poolserver in Node.js
228 lines (190 loc) • 7.41 kB
JavaScript
var net = require('net');
var crypto = require('crypto');
var events = require('events');
var util = require('./util.js');
//Example of p2p in node from TheSeven: http://paste.pm/e54.js
var fixedLenStringBuffer = function(s, len) {
var buff = new Buffer(len);
buff.fill(0);
buff.write(s);
return buff;
};
var commandStringBuffer = function (s) {
return fixedLenStringBuffer(s, 12);
};
/* Reads a set amount of bytes from a flowing stream, argument descriptions:
- stream to read from, must have data emitter
- amount of bytes to read
- preRead argument can be used to set start with an existing data buffer
- callback returns 1) data buffer and 2) lopped/over-read data */
var readFlowingBytes = function (stream, amount, preRead, callback) {
var buff = preRead ? preRead : new Buffer([]);
var readData = function (data) {
buff = Buffer.concat([buff, data]);
if (buff.length >= amount) {
var returnData = buff.slice(0, amount);
var lopped = buff.length > amount ? buff.slice(amount) : null;
callback(returnData, lopped);
}
else
stream.once('data', readData);
};
readData(new Buffer([]));
};
var Peer = module.exports = function (options) {
var _this = this;
var client;
var magic = new Buffer(options.testnet ? options.coin.peerMagicTestnet : options.coin.peerMagic, 'hex');
var magicInt = magic.readUInt32LE(0);
var verack = false;
var validConnectionConfig = true;
//https://en.bitcoin.it/wiki/Protocol_specification#Inventory_Vectors
var invCodes = {
error: 0,
tx: 1,
block: 2
};
var networkServices = new Buffer('0100000000000000', 'hex'); //NODE_NETWORK services (value 1 packed as uint64)
var emptyNetAddress = new Buffer('010000000000000000000000000000000000ffff000000000000', 'hex');
var userAgent = util.varStringBuffer('/node-stratum/');
var blockStartHeight = new Buffer('00000000', 'hex'); //block start_height, can be empty
//If protocol version is new enough, add do not relay transactions flag byte, outlined in BIP37
//https://github.com/bitcoin/bips/blob/master/bip-0037.mediawiki#extensions-to-existing-messages
var relayTransactions = options.p2p.disableTransactions === true ? new Buffer([false]) : new Buffer([]);
var commands = {
version: commandStringBuffer('version'),
inv: commandStringBuffer('inv'),
verack: commandStringBuffer('verack'),
addr: commandStringBuffer('addr'),
getblocks: commandStringBuffer('getblocks')
};
(function init() {
Connect();
})();
function Connect() {
client = net.connect({
host: options.p2p.host,
port: options.p2p.port
}, function () {
SendVersion();
});
client.on('close', function () {
if (verack) {
_this.emit('disconnected');
verack = false;
Connect();
}
else if (validConnectionConfig)
_this.emit('connectionRejected');
});
client.on('error', function (e) {
if (e.code === 'ECONNREFUSED') {
validConnectionConfig = false;
_this.emit('connectionFailed');
}
else
_this.emit('socketError', e);
});
SetupMessageParser(client);
}
function SetupMessageParser(client) {
var beginReadingMessage = function (preRead) {
readFlowingBytes(client, 24, preRead, function (header, lopped) {
var msgMagic = header.readUInt32LE(0);
if (msgMagic !== magicInt) {
_this.emit('error', 'bad magic number from peer');
while (header.readUInt32LE(0) !== magicInt && header.length >= 4) {
header = header.slice(1);
}
if (header.readUInt32LE(0) === magicInt) {
beginReadingMessage(header);
} else {
beginReadingMessage(new Buffer([]));
}
return;
}
var msgCommand = header.slice(4, 16).toString();
var msgLength = header.readUInt32LE(16);
var msgChecksum = header.readUInt32LE(20);
readFlowingBytes(client, msgLength, lopped, function (payload, lopped) {
if (util.sha256d(payload).readUInt32LE(0) !== msgChecksum) {
_this.emit('error', 'bad payload - failed checksum');
beginReadingMessage(null);
return;
}
HandleMessage(msgCommand, payload);
beginReadingMessage(lopped);
});
});
};
beginReadingMessage(null);
}
//Parsing inv message https://en.bitcoin.it/wiki/Protocol_specification#inv
function HandleInv(payload) {
//sloppy varint decoding
var count = payload.readUInt8(0);
payload = payload.slice(1);
if (count >= 0xfd)
{
count = payload.readUInt16LE(0);
payload = payload.slice(2);
}
while (count--) {
switch(payload.readUInt32LE(0)) {
case invCodes.error:
break;
case invCodes.tx:
var tx = payload.slice(4, 36).toString('hex');
break;
case invCodes.block:
var block = payload.slice(4, 36).toString('hex');
_this.emit('blockFound', block);
break;
}
payload = payload.slice(36);
}
}
function HandleMessage(command, payload) {
_this.emit('peerMessage', {command: command, payload: payload});
switch (command) {
case commands.inv.toString():
HandleInv(payload);
break;
case commands.verack.toString():
if(!verack) {
verack = true;
_this.emit('connected');
}
break;
default:
break;
}
}
//Message structure defined at: https://en.bitcoin.it/wiki/Protocol_specification#Message_structure
function SendMessage(command, payload) {
var message = Buffer.concat([
magic,
command,
util.packUInt32LE(payload.length),
util.sha256d(payload).slice(0, 4),
payload
]);
client.write(message);
_this.emit('sentMessage', message);
}
function SendVersion() {
var payload = Buffer.concat([
util.packUInt32LE(options.protocolVersion),
networkServices,
util.packInt64LE(Date.now() / 1000 | 0),
emptyNetAddress, //addr_recv, can be empty
emptyNetAddress, //addr_from, can be empty
crypto.pseudoRandomBytes(8), //nonce, random unique ID
userAgent,
blockStartHeight,
relayTransactions
]);
SendMessage(commands.version, payload);
}
};
Peer.prototype.__proto__ = events.EventEmitter.prototype;