Esteban Ordano
10 years ago
4 changed files with 1 additions and 1086 deletions
@ -1,613 +0,0 @@ |
|||||
'use strict'; |
|
||||
/** |
|
||||
* @namespace Transport.Message |
|
||||
*/ |
|
||||
/* jshint curly: false */ |
|
||||
|
|
||||
var Buffers = require('buffers'); |
|
||||
var Put = require('bufferput'); |
|
||||
var util = require('util'); |
|
||||
|
|
||||
var BlockHeaderModel = require('../blockheader'); |
|
||||
var BlockModel = require('../block'); |
|
||||
var BufferReader = require('../encoding/bufferreader'); |
|
||||
var BufferUtil = require('../util/buffer'); |
|
||||
var Hash = require('../crypto/hash'); |
|
||||
var Random = require('../crypto/random'); |
|
||||
var TransactionModel = require('../transaction'); |
|
||||
|
|
||||
var CONNECTION_NONCE = Random.getPseudoRandomBuffer(8); |
|
||||
var PROTOCOL_VERSION = 70000; |
|
||||
|
|
||||
/** |
|
||||
* Static helper for consuming a data buffer until the next message. |
|
||||
* |
|
||||
* @name Transport.Message#parseMessage |
|
||||
* @param{Network} network - the network object |
|
||||
* @param{Buffer} dataBuffer - the buffer to read from |
|
||||
* @returns{Message|undefined} A message or undefined if there is nothing to read. |
|
||||
*/ |
|
||||
var parseMessage = function(network, dataBuffer) { |
|
||||
if (dataBuffer.length < 20) return; |
|
||||
|
|
||||
// Search the next magic number
|
|
||||
if (!discardUntilNextMessage(network, dataBuffer)) return; |
|
||||
|
|
||||
var PAYLOAD_START = 16; |
|
||||
var payloadLen = (dataBuffer.get(PAYLOAD_START)) + |
|
||||
(dataBuffer.get(PAYLOAD_START + 1) << 8) + |
|
||||
(dataBuffer.get(PAYLOAD_START + 2) << 16) + |
|
||||
(dataBuffer.get(PAYLOAD_START + 3) << 24); |
|
||||
|
|
||||
var messageLength = 24 + payloadLen; |
|
||||
if (dataBuffer.length < messageLength) return; |
|
||||
|
|
||||
var command = dataBuffer.slice(4, 16).toString('ascii').replace(/\0+$/, ''); |
|
||||
var payload = dataBuffer.slice(24, messageLength); |
|
||||
var checksum = dataBuffer.slice(20, 24); |
|
||||
|
|
||||
var checksumConfirm = Hash.sha256sha256(payload).slice(0, 4); |
|
||||
if (!BufferUtil.equals(checksumConfirm, checksum)) { |
|
||||
dataBuffer.skip(messageLength); |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
dataBuffer.skip(messageLength); |
|
||||
return Message.buildMessage(command, payload); |
|
||||
}; |
|
||||
|
|
||||
module.exports.parseMessage = parseMessage; |
|
||||
|
|
||||
/** |
|
||||
* @desc Internal function that discards data until founds the next message. |
|
||||
* @name Transport.Message#discardUntilNextMessage |
|
||||
*/ |
|
||||
function discardUntilNextMessage(network, dataBuffer) { |
|
||||
var magicNumber = network.networkMagic; |
|
||||
|
|
||||
var i = 0; |
|
||||
for (;;) { |
|
||||
// check if it's the beginning of a new message
|
|
||||
var packageNumber = dataBuffer.slice(0, 4); |
|
||||
if (BufferUtil.equals(packageNumber, magicNumber)) { |
|
||||
dataBuffer.skip(i); |
|
||||
return true; |
|
||||
} |
|
||||
|
|
||||
// did we reach the end of the buffer?
|
|
||||
if (i > (dataBuffer.length - 4)) { |
|
||||
dataBuffer.skip(i); |
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
i++; // continue scanning
|
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Abstract Message that knows how to parse and serialize itself. |
|
||||
* Concret subclases should implement {fromBuffer} and {getPayload} methods. |
|
||||
* @name Transport.Message |
|
||||
*/ |
|
||||
function Message() {} |
|
||||
|
|
||||
/** |
|
||||
* @value |
|
||||
* @name Transport.Message.COMMANDS |
|
||||
*/ |
|
||||
Message.COMMANDS = {}; |
|
||||
|
|
||||
/** |
|
||||
* Look up a message type by command name and instantiate the correct Message |
|
||||
* @name Transport.Message#buildMessage |
|
||||
*/ |
|
||||
Message.buildMessage = function(command, payload) { |
|
||||
try { |
|
||||
var CommandClass = Message.COMMANDS[command]; |
|
||||
return new CommandClass().fromBuffer(payload); |
|
||||
} catch (err) { |
|
||||
console.log('Error while parsing message', err); |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Parse instance state from buffer. |
|
||||
* |
|
||||
* @param{Buffer} payload - the buffer to read from |
|
||||
* @returns{Message} The same message instance |
|
||||
*/ |
|
||||
Message.prototype.fromBuffer = function(payload) { |
|
||||
/* jshint unused: false */ |
|
||||
return this; |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Serialize the payload into a buffer. |
|
||||
* |
|
||||
* @returns{Buffer} the serialized payload |
|
||||
*/ |
|
||||
Message.prototype.getPayload = function() { |
|
||||
return BufferUtil.EMPTY_BUFFER; |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Serialize the message into a buffer. |
|
||||
* |
|
||||
* @returns{Buffer} the serialized message |
|
||||
*/ |
|
||||
Message.prototype.serialize = function(network) { |
|
||||
var magic = network.networkMagic; |
|
||||
var commandBuf = new Buffer(this.command, 'ascii'); |
|
||||
if (commandBuf.length > 12) throw 'Command name too long'; |
|
||||
|
|
||||
var payload = this.getPayload(); |
|
||||
var checksum = Hash.sha256sha256(payload).slice(0, 4); |
|
||||
|
|
||||
// -- HEADER --
|
|
||||
var message = new Put(); |
|
||||
message.put(magic); |
|
||||
message.put(commandBuf); |
|
||||
message.pad(12 - commandBuf.length); // zero-padded
|
|
||||
message.word32le(payload.length); |
|
||||
message.put(checksum); |
|
||||
|
|
||||
// -- BODY --
|
|
||||
message.put(payload); |
|
||||
|
|
||||
return message.buffer(); |
|
||||
}; |
|
||||
|
|
||||
module.exports.Message = Message; |
|
||||
|
|
||||
/** |
|
||||
* Version Message |
|
||||
* |
|
||||
* @name Transport.Message.Version |
|
||||
* @param{string} subversion - version of the client |
|
||||
* @param{Buffer} nonce - a random 8 bytes buffer |
|
||||
*/ |
|
||||
function Version(subversion, nonce) { |
|
||||
this.command = 'version'; |
|
||||
this.version = PROTOCOL_VERSION; |
|
||||
this.subversion = subversion || '/BitcoinX:0.1/'; |
|
||||
this.nonce = nonce || CONNECTION_NONCE; |
|
||||
} |
|
||||
util.inherits(Version, Message); |
|
||||
|
|
||||
Version.prototype.fromBuffer = function(payload) { |
|
||||
var parser = new BufferReader(payload); |
|
||||
this.version = parser.readUInt32LE(); |
|
||||
this.services = parser.readUInt64LEBN(); |
|
||||
this.timestamp = parser.readUInt64LEBN(); |
|
||||
this.addr_me = parser.read(26); |
|
||||
this.addr_you = parser.read(26); |
|
||||
this.nonce = parser.read(8); |
|
||||
this.subversion = parser.readVarintBuf().toString(); |
|
||||
this.start_height = parser.readUInt32LE(); |
|
||||
|
|
||||
return this; |
|
||||
}; |
|
||||
|
|
||||
Version.prototype.getPayload = function() { |
|
||||
var put = new Put(); |
|
||||
put.word32le(this.version); // version
|
|
||||
put.word64le(1); // services
|
|
||||
put.word64le(Math.round(new Date().getTime() / 1000)); // timestamp
|
|
||||
put.pad(26); // addr_me
|
|
||||
put.pad(26); // addr_you
|
|
||||
put.put(this.nonce); |
|
||||
put.varint(this.subversion.length); |
|
||||
put.put(new Buffer(this.subversion, 'ascii')); |
|
||||
put.word32le(0); |
|
||||
|
|
||||
return put.buffer(); |
|
||||
}; |
|
||||
|
|
||||
module.exports.Version = Message.COMMANDS.version = Version; |
|
||||
|
|
||||
/** |
|
||||
* Inv Message |
|
||||
* |
|
||||
* @name Transport.Message.Inventory |
|
||||
* @param{Array} inventory - reported elements |
|
||||
*/ |
|
||||
function Inventory(inventory) { |
|
||||
this.command = 'inv'; |
|
||||
this.inventory = inventory || []; |
|
||||
} |
|
||||
util.inherits(Inventory, Message); |
|
||||
|
|
||||
Inventory.prototype.fromBuffer = function(payload) { |
|
||||
var parser = new BufferReader(payload); |
|
||||
var count = parser.readVarintNum(); |
|
||||
for (var i = 0; i < count; i++) { |
|
||||
this.inventory.push({ |
|
||||
type: parser.readUInt32LE(), |
|
||||
hash: parser.read(32) |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
return this; |
|
||||
}; |
|
||||
|
|
||||
Inventory.prototype.getPayload = function() { |
|
||||
var put = new Put(); |
|
||||
|
|
||||
put.varint(this.inventory.length); |
|
||||
this.inventory.forEach(function(value) { |
|
||||
put.word32le(value.type); |
|
||||
put.put(value.hash); |
|
||||
}); |
|
||||
|
|
||||
return put.buffer(); |
|
||||
}; |
|
||||
|
|
||||
module.exports.Inventory = Message.COMMANDS.inv = Inventory; |
|
||||
|
|
||||
/** |
|
||||
* Getdata Message |
|
||||
* |
|
||||
* @name Transport.Message.GetData |
|
||||
* @param{Array} inventory - requested elements |
|
||||
*/ |
|
||||
function GetData(inventory) { |
|
||||
this.command = 'getdata'; |
|
||||
this.inventory = inventory || []; |
|
||||
} |
|
||||
|
|
||||
util.inherits(GetData, Inventory); |
|
||||
module.exports.GetData = GetData; |
|
||||
|
|
||||
/** |
|
||||
* Ping Message |
|
||||
* |
|
||||
* @name Transport.Message.Ping |
|
||||
* @param{Buffer} nonce - a random 8 bytes buffer |
|
||||
*/ |
|
||||
function Ping(nonce) { |
|
||||
this.command = 'ping'; |
|
||||
this.nonce = nonce || CONNECTION_NONCE; |
|
||||
} |
|
||||
util.inherits(Ping, Message); |
|
||||
|
|
||||
Ping.prototype.fromBuffer = function(payload) { |
|
||||
this.nonce = new BufferReader(payload).read(8); |
|
||||
return this; |
|
||||
}; |
|
||||
|
|
||||
Ping.prototype.getPayload = function() { |
|
||||
return this.nonce; |
|
||||
}; |
|
||||
|
|
||||
module.exports.Ping = Message.COMMANDS.ping = Ping; |
|
||||
|
|
||||
/** |
|
||||
* Pong Message |
|
||||
* |
|
||||
* @name Transport.Message.Pong |
|
||||
* @param{Buffer} nonce - a random 8 bytes buffer |
|
||||
*/ |
|
||||
function Pong(nonce) { |
|
||||
this.command = 'pong'; |
|
||||
this.nonce = nonce || CONNECTION_NONCE; |
|
||||
} |
|
||||
|
|
||||
util.inherits(Pong, Ping); |
|
||||
module.exports.Pong = Message.COMMANDS.pong = Pong; |
|
||||
|
|
||||
/** |
|
||||
* Addr Message |
|
||||
* |
|
||||
* @name Transport.Message.Addressess |
|
||||
* @param{Array} addresses - array of know addresses |
|
||||
*/ |
|
||||
function Addresses(addresses) { |
|
||||
this.command = 'addr'; |
|
||||
this.addresses = addresses || []; |
|
||||
} |
|
||||
util.inherits(Addresses, Message); |
|
||||
|
|
||||
Addresses.prototype.fromBuffer = function(payload) { |
|
||||
var parser = new BufferReader(payload); |
|
||||
var addrCount = Math.min(parser.readVarintNum(), 1000); |
|
||||
|
|
||||
this.addresses = []; |
|
||||
for (var i = 0; i < addrCount; i++) { |
|
||||
// TODO: Time actually depends on the version of the other peer (>=31402)
|
|
||||
|
|
||||
var time = parser.readUInt32LE(); |
|
||||
var services = parser.readUInt64LEBN(); |
|
||||
|
|
||||
// parse the ipv6 to a string
|
|
||||
var ipv6 = []; |
|
||||
for (var a = 0; a < 6; a++) { |
|
||||
ipv6.push(parser.read(2).toString('hex')); |
|
||||
} |
|
||||
ipv6 = ipv6.join(':'); |
|
||||
|
|
||||
// parse the ipv4 to a string
|
|
||||
var ipv4 = []; |
|
||||
for (var b = 0; b < 4; b++) { |
|
||||
ipv4.push(parser.read(1)[0]); |
|
||||
} |
|
||||
ipv4 = ipv4.join('.'); |
|
||||
|
|
||||
var port = parser.readUInt16BE(); |
|
||||
|
|
||||
this.addresses.push({ |
|
||||
time: time, |
|
||||
services: services, |
|
||||
ip: { v6: ipv6, v4: ipv4 }, |
|
||||
port: port |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
return this; |
|
||||
}; |
|
||||
|
|
||||
Addresses.prototype.getPayload = function() { |
|
||||
var put = new Put(); |
|
||||
put.varint(this.addresses.length); |
|
||||
|
|
||||
for (var i = 0; i < this.addresses.length; i++) { |
|
||||
put.word32le(this.addresses[i].time); |
|
||||
put.word64le(this.addresses[i].services); |
|
||||
put.put(this.addresses[i].ip); |
|
||||
put.word16be(this.addresses[i].port); |
|
||||
} |
|
||||
|
|
||||
return put.buffer(); |
|
||||
}; |
|
||||
|
|
||||
module.exports.Addresses = Message.COMMANDS.addr = Addresses; |
|
||||
|
|
||||
/** |
|
||||
* GetAddr Message |
|
||||
* |
|
||||
* @name Transport.Message.GetAddresses |
|
||||
*/ |
|
||||
function GetAddresses() { |
|
||||
this.command = 'getaddr'; |
|
||||
} |
|
||||
|
|
||||
util.inherits(GetAddresses, Message); |
|
||||
module.exports.GetAddresses = Message.COMMANDS.getaddr = GetAddresses; |
|
||||
|
|
||||
/** |
|
||||
* Verack Message |
|
||||
* |
|
||||
* @name Transport.Message.VerAck |
|
||||
*/ |
|
||||
function VerAck() { |
|
||||
this.command = 'verack'; |
|
||||
} |
|
||||
|
|
||||
util.inherits(VerAck, Message); |
|
||||
module.exports.VerAck = Message.COMMANDS.verack = VerAck; |
|
||||
|
|
||||
/** |
|
||||
* Reject Message |
|
||||
* |
|
||||
* @name Transport.Message.Reject |
|
||||
*/ |
|
||||
function Reject() { |
|
||||
this.command = 'reject'; |
|
||||
} |
|
||||
util.inherits(Reject, Message); |
|
||||
|
|
||||
// TODO: Parse REJECT message
|
|
||||
|
|
||||
module.exports.Reject = Message.COMMANDS.reject = Reject; |
|
||||
|
|
||||
/** |
|
||||
* Alert Message |
|
||||
* |
|
||||
* @name Transport.Message.Alert |
|
||||
*/ |
|
||||
function Alert(payload, signature) { |
|
||||
this.command = 'alert'; |
|
||||
this.payload = payload || new Buffer(32); |
|
||||
this.signature = signature || new Buffer(32); |
|
||||
} |
|
||||
util.inherits(Alert, Message); |
|
||||
|
|
||||
Alert.prototype.fromBuffer = function(payload) { |
|
||||
var parser = new BufferReader(payload); |
|
||||
this.payload = parser.readVarintBuf(); // TODO: Use current format
|
|
||||
this.signature = parser.readVarintBuf(); |
|
||||
return this; |
|
||||
}; |
|
||||
|
|
||||
Alert.prototype.getPayload = function() { |
|
||||
var put = new Put(); |
|
||||
put.varint(this.payload.length); |
|
||||
put.put(this.payload); |
|
||||
|
|
||||
put.varint(this.signature.length); |
|
||||
put.put(this.signature); |
|
||||
|
|
||||
return put.buffer(); |
|
||||
}; |
|
||||
|
|
||||
module.exports.Alert = Message.COMMANDS.alert = Alert; |
|
||||
|
|
||||
/** |
|
||||
* Headers Message |
|
||||
* |
|
||||
* @name Transport.Message.Headers |
|
||||
* @param{Array} blockheaders - array of block headers |
|
||||
*/ |
|
||||
function Headers(blockheaders) { |
|
||||
this.command = 'headers'; |
|
||||
this.headers = blockheaders || []; |
|
||||
} |
|
||||
util.inherits(Headers, Message); |
|
||||
|
|
||||
Headers.prototype.fromBuffer = function(payload) { |
|
||||
var parser = new BufferReader(payload); |
|
||||
var count = parser.readVarintNum(); |
|
||||
|
|
||||
this.headers = []; |
|
||||
for (var i = 0; i < count; i++) { |
|
||||
var header = BlockHeaderModel._fromBufferReader(parser); |
|
||||
this.headers.push(header); |
|
||||
} |
|
||||
|
|
||||
return this; |
|
||||
}; |
|
||||
|
|
||||
Headers.prototype.getPayload = function() { |
|
||||
var put = new Put(); |
|
||||
put.varint(this.headers.length); |
|
||||
|
|
||||
for (var i = 0; i < this.headers.length; i++) { |
|
||||
var buffer = this.headers[i].toBuffer(); |
|
||||
put.put(buffer); |
|
||||
} |
|
||||
|
|
||||
return put.buffer(); |
|
||||
}; |
|
||||
|
|
||||
module.exports.Headers = Message.COMMANDS.headers = Headers; |
|
||||
|
|
||||
/** |
|
||||
* Block Message |
|
||||
* |
|
||||
* @name Transport.Message.Block |
|
||||
* @param{Block} block |
|
||||
*/ |
|
||||
function Block(block) { |
|
||||
this.command = 'block'; |
|
||||
this.block = block; |
|
||||
} |
|
||||
util.inherits(Block, Message); |
|
||||
|
|
||||
Block.prototype.fromBuffer = function(payload) { |
|
||||
this.block = BlockModel(payload); |
|
||||
return this; |
|
||||
}; |
|
||||
|
|
||||
Block.prototype.getPayload = function() { |
|
||||
return this.block.toBuffer(); |
|
||||
}; |
|
||||
|
|
||||
module.exports.Block = Message.COMMANDS.block = Block; |
|
||||
|
|
||||
/** |
|
||||
* Tx Message |
|
||||
* |
|
||||
* @name Transport.Message.Transaction |
|
||||
* @param{Transaction} transaction |
|
||||
*/ |
|
||||
function Transaction(transaction) { |
|
||||
this.command = 'tx'; |
|
||||
this.transaction = transaction; |
|
||||
} |
|
||||
util.inherits(Transaction, Message); |
|
||||
|
|
||||
Transaction.prototype.fromBuffer = function(payload) { |
|
||||
this.transaction = TransactionModel(payload); |
|
||||
return this; |
|
||||
}; |
|
||||
|
|
||||
Transaction.prototype.getPayload = function() { |
|
||||
return this.transaction.toBuffer(); |
|
||||
}; |
|
||||
|
|
||||
module.exports.Transaction = Message.COMMANDS.tx = Transaction; |
|
||||
|
|
||||
/** |
|
||||
* Getblocks Message |
|
||||
* |
|
||||
* @name Transport.Message.GetBlocks |
|
||||
* @param{Array} starts - array of buffers with the starting block hashes |
|
||||
* @param{Buffer} [stop] - hash of the last block |
|
||||
*/ |
|
||||
function GetBlocks(starts, stop) { |
|
||||
this.command = 'getblocks'; |
|
||||
this.version = PROTOCOL_VERSION; |
|
||||
this.starts = starts || []; |
|
||||
this.stop = stop || BufferUtil.NULL_HASH; |
|
||||
} |
|
||||
util.inherits(GetBlocks, Message); |
|
||||
|
|
||||
GetBlocks.prototype.fromBuffer = function(payload) { |
|
||||
var parser = new BufferReader(payload); |
|
||||
this.version = parser.readUInt32LE(); |
|
||||
|
|
||||
var startCount = Math.min(parser.readVarintNum(), 500); |
|
||||
this.starts = []; |
|
||||
for (var i = 0; i < startCount; i++) { |
|
||||
this.starts.push(parser.read(32)); |
|
||||
} |
|
||||
this.stop = parser.read(32); |
|
||||
|
|
||||
return this; |
|
||||
}; |
|
||||
|
|
||||
GetBlocks.prototype.getPayload = function() { |
|
||||
var put = new Put(); |
|
||||
put.word32le(this.version); |
|
||||
put.varint(this.starts.length); |
|
||||
|
|
||||
for (var i = 0; i < this.starts.length; i++) { |
|
||||
if (this.starts[i].length !== 32) { |
|
||||
throw new Error('Invalid hash length'); |
|
||||
} |
|
||||
put.put(this.starts[i]); |
|
||||
} |
|
||||
|
|
||||
if (this.stop.length !== 32) { |
|
||||
throw new Error('Invalid hash length'); |
|
||||
} |
|
||||
put.put(this.stop); |
|
||||
|
|
||||
return put.buffer(); |
|
||||
}; |
|
||||
|
|
||||
module.exports.GetBlocks = Message.COMMANDS.getblocks = GetBlocks; |
|
||||
|
|
||||
/** |
|
||||
* Getheaders Message |
|
||||
* |
|
||||
* @name Transport.Message.GetHeaders |
|
||||
* @param{Array} starts - array of buffers with the starting block hashes |
|
||||
* @param{Buffer} [stop] - hash of the last block |
|
||||
*/ |
|
||||
function GetHeaders(starts, stop) { |
|
||||
this.command = 'getheaders'; |
|
||||
this.version = PROTOCOL_VERSION; |
|
||||
this.starts = starts || []; |
|
||||
this.stop = stop || BufferUtil.NULL_HASH; |
|
||||
} |
|
||||
|
|
||||
util.inherits(GetHeaders, GetBlocks); |
|
||||
module.exports.GetHeaders = Message.COMMANDS.getheaders = GetHeaders; |
|
||||
|
|
||||
/** |
|
||||
* GetMempool Message |
|
||||
* |
|
||||
* @name Transport.Message.GetMempool |
|
||||
*/ |
|
||||
function GetMempool() { |
|
||||
this.command = 'mempool'; |
|
||||
} |
|
||||
|
|
||||
util.inherits(GetMempool, Message); |
|
||||
module.exports.GetMempool = Message.COMMANDS.mempool = GetMempool; |
|
||||
|
|
||||
// TODO: Remove this PATCH (yemel)
|
|
||||
Buffers.prototype.skip = function (i) { |
|
||||
if (i === 0) return; |
|
||||
|
|
||||
if (i === this.length) { |
|
||||
this.buffers = []; |
|
||||
this.length = 0; |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
var pos = this.pos(i); |
|
||||
this.buffers = this.buffers.slice(pos.buf); |
|
||||
this.buffers[0] = new Buffer(this.buffers[0].slice(pos.offset)); |
|
||||
this.length -= i; |
|
||||
}; |
|
@ -1,194 +0,0 @@ |
|||||
'use strict'; |
|
||||
|
|
||||
var Buffers = require('buffers'); |
|
||||
var EventEmitter = require('events').EventEmitter; |
|
||||
var Net = require('net'); |
|
||||
var Socks5Client = require('socks5-client'); |
|
||||
var util = require('util'); |
|
||||
|
|
||||
var Networks = require('../networks'); |
|
||||
var Messages = require('./messages'); |
|
||||
|
|
||||
var MAX_RECEIVE_BUFFER = 10000000; |
|
||||
|
|
||||
/** |
|
||||
* A Peer instance represents a remote bitcoin node and allows to communicate |
|
||||
* with it using the standar messages of the bitcoin p2p protocol. |
|
||||
* |
|
||||
* @example |
|
||||
* ```javascript
|
|
||||
* |
|
||||
* var peer = new Peer('127.0.0.1').setProxy('127.0.0.1', 9050); |
|
||||
* peer.on('tx', function(tx) { |
|
||||
* console.log('New transaction: ', tx.id); |
|
||||
* }); |
|
||||
* peer.connect(); |
|
||||
* ``` |
|
||||
* |
|
||||
* @param {String} host - IP address of the remote host |
|
||||
* @param {Number} [port] - Port number of the remote host |
|
||||
* @param {Network} [network] - The context for this communication |
|
||||
* @returns {Peer} A new instance of Peer. |
|
||||
* @constructor |
|
||||
*/ |
|
||||
function Peer(host, port, network) { |
|
||||
if (!(this instanceof Peer)) { |
|
||||
return new Peer(host, port, network); |
|
||||
} |
|
||||
|
|
||||
// overloading stuff
|
|
||||
if (port instanceof Object && !network) { |
|
||||
network = port; |
|
||||
port = undefined; |
|
||||
} |
|
||||
|
|
||||
this.host = host; |
|
||||
this.status = Peer.STATUS.DISCONNECTED; |
|
||||
this.network = network || Networks.livenet; |
|
||||
this.port = port || this.network.port; |
|
||||
|
|
||||
this.dataBuffer = new Buffers(); |
|
||||
|
|
||||
this.version = 0; |
|
||||
this.bestHeight = 0; |
|
||||
this.subversion = null; |
|
||||
|
|
||||
// set message handlers
|
|
||||
var self = this; |
|
||||
this.on('verack', function() { |
|
||||
self.status = Peer.STATUS.READY; |
|
||||
self.emit('ready'); |
|
||||
}); |
|
||||
|
|
||||
this.on('version', function(message) { |
|
||||
self.version = message.version; |
|
||||
self.subversion = message.subversion; |
|
||||
self.bestHeight = message.start_height |
|
||||
}); |
|
||||
|
|
||||
this.on('ping', function(message) { |
|
||||
self._sendPong(message.nonce); |
|
||||
}); |
|
||||
|
|
||||
} |
|
||||
util.inherits(Peer, EventEmitter); |
|
||||
|
|
||||
Peer.STATUS = { |
|
||||
DISCONNECTED: 'disconnected', |
|
||||
CONNECTING: 'connecting', |
|
||||
CONNECTED: 'connected', |
|
||||
READY: 'ready' |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Set a socks5 proxy for the connection. Enables the use of the TOR network. |
|
||||
* |
|
||||
* @param {String} host - IP address of the proxy |
|
||||
* @param {Number} port - Port number of the proxy |
|
||||
* @returns {Peer} The same Peer instance. |
|
||||
*/ |
|
||||
Peer.prototype.setProxy = function(host, port) { |
|
||||
if (this.status != Peer.STATUS.DISCONNECTED) { |
|
||||
throw Error('Invalid State'); |
|
||||
} |
|
||||
|
|
||||
this.proxy = { |
|
||||
host: host, |
|
||||
port: port |
|
||||
}; |
|
||||
return this; |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Init the connection with the remote peer. |
|
||||
* |
|
||||
* @returns {Socket} The same peer instance. |
|
||||
*/ |
|
||||
Peer.prototype.connect = function() { |
|
||||
this.socket = this._getSocket(); |
|
||||
this.status = Peer.STATUS.CONNECTING; |
|
||||
|
|
||||
var self = this; |
|
||||
this.socket.on('connect', function(ev) { |
|
||||
self.status = Peer.STATUS.CONNECTED; |
|
||||
self.emit('connect'); |
|
||||
self._sendVersion(); |
|
||||
}); |
|
||||
|
|
||||
this.socket.on('error', self.disconnect.bind(this)); |
|
||||
this.socket.on('end', self.disconnect.bind(this)); |
|
||||
|
|
||||
this.socket.on('data', function(data) { |
|
||||
self.dataBuffer.push(data); |
|
||||
|
|
||||
if (self.dataBuffer.length > MAX_RECEIVE_BUFFER) return self.disconnect(); |
|
||||
self._readMessage(); |
|
||||
}); |
|
||||
|
|
||||
this.socket.connect(this.port, this.host); |
|
||||
return this; |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Disconnects the remote connection. |
|
||||
* |
|
||||
* @returns {Socket} The same peer instance. |
|
||||
*/ |
|
||||
Peer.prototype.disconnect = function() { |
|
||||
this.status = Peer.STATUS.DISCONNECTED; |
|
||||
this.socket.destroy(); |
|
||||
this.emit('disconnect'); |
|
||||
return this; |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Send a Message to the remote peer. |
|
||||
* |
|
||||
* @param {Message} message - A message instance |
|
||||
*/ |
|
||||
Peer.prototype.sendMessage = function(message) { |
|
||||
this.socket.write(message.serialize(this.network)); |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Internal function that sends VERSION message to the remote peer. |
|
||||
*/ |
|
||||
Peer.prototype._sendVersion = function() { |
|
||||
var message = new Messages.Version(); |
|
||||
this.sendMessage(message); |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Send a PONG message to the remote peer. |
|
||||
*/ |
|
||||
Peer.prototype._sendPong = function(nonce) { |
|
||||
var message = new Messages.Pong(nonce); |
|
||||
this.sendMessage(message); |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Internal function that tries to read a message from the data buffer |
|
||||
*/ |
|
||||
Peer.prototype._readMessage = function() { |
|
||||
var message = Messages.parseMessage(this.network, this.dataBuffer); |
|
||||
|
|
||||
if (message) { |
|
||||
this.emit(message.command, message); |
|
||||
this._readMessage(); |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Internal function that creates a socket using a proxy if neccesary. |
|
||||
* |
|
||||
* @returns {Socket} A Socket instance not yet connected. |
|
||||
*/ |
|
||||
Peer.prototype._getSocket = function() { |
|
||||
if (this.proxy) { |
|
||||
return new Socks5Client(this.proxy.host, this.proxy.port); |
|
||||
} |
|
||||
|
|
||||
return new Net.Socket(); |
|
||||
}; |
|
||||
|
|
||||
module.exports = Peer; |
|
@ -1,275 +0,0 @@ |
|||||
'use strict'; |
|
||||
|
|
||||
var dns = require('dns'); |
|
||||
var EventEmitter = require('events').EventEmitter; |
|
||||
var Networks = require('../networks'); |
|
||||
var sha256 = require('../crypto/hash').sha256; |
|
||||
var Peer = require('./peer'); |
|
||||
var util = require('util'); |
|
||||
|
|
||||
function now() { |
|
||||
return Math.floor(new Date().getTime() / 1000); |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* A pool is a collection of Peers. A pool will discover peers from DNS seeds, and |
|
||||
* collect information about new peers in the network. When a peer disconnects the pool |
|
||||
* will connect to others that are available to maintain a max number of |
|
||||
* ongoing peer connections. Peer events are relayed to the pool. |
|
||||
* |
|
||||
* @example |
|
||||
* ```javascript
|
|
||||
* |
|
||||
* var pool = new Pool(Networks.livenet); |
|
||||
* pool.on('peerinv', function(peer, message) { |
|
||||
* // do something with the inventory announcement
|
|
||||
* }); |
|
||||
* pool.connect(); |
|
||||
* ``` |
|
||||
* |
|
||||
* @param {Network|String} network - The network to connect |
|
||||
* @returns {Pool} |
|
||||
* @constructor |
|
||||
*/ |
|
||||
function Pool(network) { |
|
||||
|
|
||||
var self = this; |
|
||||
|
|
||||
this.network = Networks.get(network) || Networks.defaultNetwork; |
|
||||
this.keepalive = false; |
|
||||
this._connectedPeers = {}; |
|
||||
this._addrs = []; |
|
||||
|
|
||||
this.on('peeraddr', function peerAddrEvent(peer, message) { |
|
||||
var addrs = message.addresses; |
|
||||
var length = addrs.length; |
|
||||
for (var i = 0; i < length; i++) { |
|
||||
var addr = addrs[i]; |
|
||||
// In case of an invalid time, assume "5 days ago"
|
|
||||
if (addr.time <= 100000000 || addr.time > (now() + 10 * 60)) { |
|
||||
addr.time = now() - 5 * 24 * 60 * 60; |
|
||||
} |
|
||||
this._addAddr(addr); |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
this.on('seed', function seedEvent(ips) { |
|
||||
ips.forEach(function(ip) { |
|
||||
self._addAddr({ |
|
||||
ip: { |
|
||||
v4: ip |
|
||||
} |
|
||||
}); |
|
||||
}); |
|
||||
if (self.keepalive) { |
|
||||
self._fillConnections(); |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
this.on('peerdisconnect', function peerDisconnectEvent(peer, addr) { |
|
||||
self._deprioritizeAddr(addr); |
|
||||
self._removeConnectedPeer(addr); |
|
||||
if (self.keepalive) { |
|
||||
self._fillConnections(); |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
return this; |
|
||||
|
|
||||
} |
|
||||
|
|
||||
util.inherits(Pool, EventEmitter); |
|
||||
|
|
||||
Pool.MaxConnectedPeers = 8; |
|
||||
Pool.RetrySeconds = 30; |
|
||||
Pool.PeerEvents = ['version', 'inv', 'getdata', 'ping', 'ping', 'addr', |
|
||||
'getaddr', 'verack', 'reject', 'alert', 'headers', 'block', |
|
||||
'tx', 'getblocks', 'getheaders' |
|
||||
]; |
|
||||
|
|
||||
|
|
||||
/** |
|
||||
* Will initiatiate connection to peers, if available peers have been added to |
|
||||
* the pool, it will connect to those, otherwise will use DNS seeds to find |
|
||||
* peers to connect. When a peer disconnects it will add another. |
|
||||
*/ |
|
||||
Pool.prototype.connect = function connect() { |
|
||||
this.keepalive = true; |
|
||||
var self = this; |
|
||||
if (self._addrs.length === 0) { |
|
||||
self._addAddrsFromSeeds(); |
|
||||
} else { |
|
||||
self._fillConnections(); |
|
||||
} |
|
||||
return this; |
|
||||
}; |
|
||||
|
|
||||
|
|
||||
/** |
|
||||
* Will disconnect all peers that are connected. |
|
||||
*/ |
|
||||
Pool.prototype.disconnect = function disconnect() { |
|
||||
this.keepalive = false; |
|
||||
for (var i in this._connectedPeers) { |
|
||||
this._connectedPeers[i].disconnect(); |
|
||||
} |
|
||||
return this; |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* @returns {Number} The number of peers currently connected. |
|
||||
*/ |
|
||||
Pool.prototype.numberConnected = function numberConnected() { |
|
||||
return Object.keys(this._connectedPeers).length; |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Will fill the conneted peers to the maximum amount. |
|
||||
*/ |
|
||||
Pool.prototype._fillConnections = function _fillConnections() { |
|
||||
var length = this._addrs.length; |
|
||||
for (var i = 0; i < length; i++) { |
|
||||
if (this.numberConnected() >= Pool.MaxConnectedPeers) { |
|
||||
break; |
|
||||
} |
|
||||
var addr = this._addrs[i]; |
|
||||
if (!addr.retryTime || now() > addr.retryTime) { |
|
||||
this._connectPeer(addr); |
|
||||
} |
|
||||
} |
|
||||
return this; |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Will remove a peer from the list of connected peers. |
|
||||
* @param {Object} addr - An addr from the list of addrs |
|
||||
*/ |
|
||||
Pool.prototype._removeConnectedPeer = function _removeConnectedPeer(addr) { |
|
||||
if (this._connectedPeers[addr.hash].status !== Peer.STATUS.DISCONNECTED) { |
|
||||
this._connectedPeers[addr.hash].disconnect(); |
|
||||
} else { |
|
||||
delete this._connectedPeers[addr.hash]; |
|
||||
} |
|
||||
return this; |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Will connect a peer and add to the list of connected peers. |
|
||||
* @param {Object} addr - An addr from the list of addrs |
|
||||
*/ |
|
||||
Pool.prototype._connectPeer = function _connectPeer(addr) { |
|
||||
var self = this; |
|
||||
|
|
||||
function addConnectedPeer(addr) { |
|
||||
var port = addr.port || self.network.port; |
|
||||
var ip = addr.ip.v4 || addr.ip.v6; |
|
||||
var peer = new Peer(ip, port, self.network); |
|
||||
peer.on('disconnect', function peerDisconnect() { |
|
||||
self.emit('peerdisconnect', peer, addr); |
|
||||
}); |
|
||||
peer.on('ready', function peerReady() { |
|
||||
self.emit('peerready', peer, addr); |
|
||||
}); |
|
||||
Pool.PeerEvents.forEach(function addPeerEvents(event) { |
|
||||
peer.on(event, function peerEvent(message) { |
|
||||
self.emit('peer' + event, peer, message); |
|
||||
}); |
|
||||
}); |
|
||||
peer.connect(); |
|
||||
self._connectedPeers[addr.hash] = peer; |
|
||||
} |
|
||||
|
|
||||
if (!this._connectedPeers[addr.hash]) { |
|
||||
addConnectedPeer(addr); |
|
||||
} |
|
||||
|
|
||||
return this; |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Will deprioritize an addr in the list of addrs by moving it to the end |
|
||||
* of the array, and setting a retryTime |
|
||||
* @param {Object} addr - An addr from the list of addrs |
|
||||
*/ |
|
||||
Pool.prototype._deprioritizeAddr = function _deprioritizeAddr(addr) { |
|
||||
for (var i = 0; i < this._addrs.length; i++) { |
|
||||
if (this._addrs[i].hash === addr.hash) { |
|
||||
var middle = this._addrs[i]; |
|
||||
middle.retryTime = now() + Pool.RetrySeconds; |
|
||||
var beginning = this._addrs.splice(0, i); |
|
||||
var end = this._addrs.splice(i + 1, this._addrs.length); |
|
||||
var combined = beginning.concat(end); |
|
||||
this._addrs = combined.concat([middle]); |
|
||||
} |
|
||||
} |
|
||||
return this; |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Will add an addr to the beginning of the addrs array |
|
||||
* @param {Object} |
|
||||
*/ |
|
||||
Pool.prototype._addAddr = function _addAddr(addr) { |
|
||||
|
|
||||
// make a unique key
|
|
||||
addr.hash = sha256(new Buffer(addr.ip.v6 + addr.ip.v4 + addr.port)).toString('hex'); |
|
||||
|
|
||||
var length = this._addrs.length; |
|
||||
var exists = false; |
|
||||
for (var i = 0; i < length; i++) { |
|
||||
if (this._addrs[i].hash === addr.hash) { |
|
||||
exists = true; |
|
||||
} |
|
||||
} |
|
||||
if (!exists) { |
|
||||
this._addrs.unshift(addr); |
|
||||
} |
|
||||
return this; |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Will add addrs to the list of addrs from a DNS seed |
|
||||
* @param {String} seed - A domain name to resolve known peers |
|
||||
* @param {Function} done |
|
||||
*/ |
|
||||
Pool.prototype._addAddrsFromSeed = function _addAddrsFromSeed(seed) { |
|
||||
var self = this; |
|
||||
dns.resolve(seed, function(err, ips) { |
|
||||
if (err) { |
|
||||
self.emit('seederror', err); |
|
||||
return; |
|
||||
} |
|
||||
if (!ips || !ips.length) { |
|
||||
self.emit('seederror', new Error('No IPs found from seed lookup.')); |
|
||||
return; |
|
||||
} |
|
||||
// announce to pool
|
|
||||
self.emit('seed', ips); |
|
||||
}); |
|
||||
return this; |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Will add addrs to the list of addrs from network DNS seeds |
|
||||
* @param {Function} done |
|
||||
*/ |
|
||||
Pool.prototype._addAddrsFromSeeds = function _addAddrsFromSeeds() { |
|
||||
var self = this; |
|
||||
var seeds = this.network.dnsSeeds; |
|
||||
seeds.forEach(function(seed) { |
|
||||
self._addAddrsFromSeed(seed); |
|
||||
}); |
|
||||
return this; |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* @returns {String} A string formatted for the console |
|
||||
*/ |
|
||||
Pool.prototype.inspect = function inspect() { |
|
||||
return '<Pool network: ' + |
|
||||
this.network + ', connected: ' + |
|
||||
this.numberConnected() + ', available: ' + |
|
||||
this._addrs.length + '>'; |
|
||||
}; |
|
||||
|
|
||||
module.exports = Pool; |
|
Loading…
Reference in new issue