diff --git a/lib/messages/builder.js b/lib/messages/builder.js index 7a0ad4f..97814c9 100644 --- a/lib/messages/builder.js +++ b/lib/messages/builder.js @@ -2,7 +2,7 @@ var Message = require('./message'); var BloomFilter = require('../bloomfilter'); -var packageInfo = require('../../package.json'); + var inherits = require('util').inherits; var bitcore = require('bitcore'); var BN = bitcore.crypto.BN; @@ -11,6 +11,7 @@ var BufferWriter = bitcore.encoding.BufferWriter; var BufferUtil = bitcore.util.buffer; var $ = bitcore.util.preconditions; var _ = bitcore.deps._; +var utils = require('./utils'); function builder(options) { /* jshint maxstatements: 150 */ @@ -46,192 +47,7 @@ function builder(options) { commands: commands }; - /* shared */ - - function checkFinished(parser) { - if(!parser.finished()) { - throw new Error('Data still available after parsing'); - } - } - - function getNonce() { - return bitcore.crypto.Random.getRandomBuffer(8); - } - - function writeIP(ip, bw) { - var words = ip.v6.split(':').map(function(s) { - return new Buffer(s, 'hex'); - }); - for (var i = 0; i < words.length; i++) { - var word = words[i]; - bw.write(word); - } - } - - function writeAddr(addr, bw) { - if (_.isUndefined(addr)) { - var pad = new Buffer(Array(26)); - bw.write(pad); - return; - } - - bw.writeUInt64LEBN(addr.services); - writeIP(addr.ip, bw); - bw.writeUInt16BE(addr.port); - } - - function writeInventory(inventory, bw) { - bw.writeVarintNum(inventory.length); - inventory.forEach(function(value) { - bw.writeUInt32LE(value.type); - bw.write(value.hash); - }); - } - - function parseIP(parser) { - var ipv6 = []; - var ipv4 = []; - for (var a = 0; a < 8; a++) { - var word = parser.read(2); - ipv6.push(word.toString('hex')); - if (a >= 6) { - ipv4.push(word[0]); - ipv4.push(word[1]); - } - } - ipv6 = ipv6.join(':'); - ipv4 = ipv4.join('.'); - return { - v6: ipv6, - v4: ipv4 - }; - } - - function parseAddr(parser) { - var services = parser.readUInt64LEBN(); - var ip = parseIP(parser); - var port = parser.readUInt16BE(); - return { - services: services, - ip: ip, - port: port - }; - } - - function sanitizeStartStop(obj) { - /* jshint maxcomplexity: 10 */ - $.checkArgument(_.isUndefined(options.starts) || _.isArray(options.starts)); - var starts = obj.starts; - var stop = obj.stop; - if (starts) { - starts = starts.map(function(hash) { - if (_.isString(hash)) { - return BufferUtil.reverse(new Buffer(hash, 'hex')); - } else { - return hash; - } - }); - } else { - starts = []; - } - - for (var i = 0; i < starts.length; i++) { - if (starts[i].length !== 32) { - throw new Error('Invalid hash ' + i + ' length: ' + starts[i].length); - } - } - - stop = obj.stop; - if (_.isString(stop)) { - stop = BufferUtil.reverse(new Buffer(stop, 'hex')); - } - if (!stop) { - stop = BufferUtil.NULL_HASH; - } - obj.starts = starts; - obj.stop = stop; - - return obj; - } - - /** - * The version message is used on connection creation to advertise - * the type of node. The remote node will respond with its version, and no - * communication is possible until both peers have exchanged their versions. - * By default, bitcore advertises itself as named `bitcore:0.8`. - * - * @param{Object} obj - properties for the version - * @param{String} obj.subversion - version of the client - * @param{Buffer} obj.nonce - a random 8 byte buffer - */ - commands.version = function(obj) { - Message.call(this, obj); - this.command = 'version'; - _.assign(this, obj); - this.magicNumber = magicNumber; - this.nonce = this.nonce || getNonce(); - this.services = this.services || new BN(1, 10); - this.timestamp = this.timestamp || new Date(); - this.version = this.version || protocolVersion; - this.subversion = this.subversion || '/bitcore:' + packageInfo.version + '/'; - this.startHeight = this.startHeight || 0; - }; - inherits(commands.version, Message); - - commands.version.fromObject = function(obj) { - return new commands.version(obj); - }; - - commands.version.fromBuffer = function(payload) { - var parser = new BufferReader(payload); - var obj = {}; - obj.version = parser.readUInt32LE(); - obj.services = parser.readUInt64LEBN(); - obj.timestamp = new Date(parser.readUInt64LEBN().toNumber() * 1000); - - obj.addrMe = { - services: parser.readUInt64LEBN(), - ip: parseIP(parser), - port: parser.readUInt16BE() - }; - obj.addrYou = { - services: parser.readUInt64LEBN(), - ip: parseIP(parser), - port: parser.readUInt16BE() - }; - obj.nonce = parser.read(8); - obj.subversion = parser.readVarLengthBuffer().toString(); - obj.startHeight = parser.readUInt32LE(); - - if(parser.finished()) { - obj.relay = true; - } else { - obj.relay = !!parser.readUInt8(); - } - checkFinished(parser); - - return commands.version.fromObject(obj); - }; - - commands.version.prototype.getPayload = function() { - var bw = new BufferWriter(); - bw.writeUInt32LE(this.version); - bw.writeUInt64LEBN(this.services); - - var timestampBuffer = new Buffer(Array(8)); - timestampBuffer.writeUInt32LE(Math.round(this.timestamp.getTime() / 1000), 0); - bw.write(timestampBuffer); - - writeAddr(this.addrMe, bw); - writeAddr(this.addrYou, bw); - bw.write(this.nonce); - bw.writeVarintNum(this.subversion.length); - bw.write(new Buffer(this.subversion, 'ascii')); - bw.writeUInt32LE(this.startHeight); - bw.writeUInt8(this.relay); - - return bw.concat(); - }; + commands.version = require('./commands/version')({magicNumber: magicNumber}); /* verack */ @@ -263,7 +79,7 @@ function builder(options) { Message.call(this, options); this.command = 'ping'; this.magicNumber = magicNumber; - this.nonce = options.nonce || getNonce(); + this.nonce = options.nonce || utils.getNonce(); }; inherits(commands.ping, Message); @@ -280,7 +96,7 @@ function builder(options) { var parser = new BufferReader(payload); obj.nonce = parser.read(8); - checkFinished(parser); + utils.checkFinished(parser); return commands.ping.fromObject(obj); }; @@ -303,7 +119,7 @@ function builder(options) { var parser = new BufferReader(payload); obj.nonce = parser.read(8); - checkFinished(parser); + utils.checkFinished(parser); return commands.pong.fromObject(obj); }; @@ -311,28 +127,7 @@ function builder(options) { return this.nonce; }; - /* block */ - - commands.block = function(options) { - Message.call(this, options); - this.command = 'block'; - this.magicNumber = magicNumber; - this.block = options.block; - }; - inherits(commands.block, Message); - - commands.block.fromObject = function(options) { - return new commands.block(options); - }; - - commands.block.fromBuffer = function(payload) { - var block = Block.fromBuffer(payload); - return commands.block.fromObject({block: block}); - }; - - commands.block.prototype.getPayload = function() { - return this.block.toBuffer(); - }; + commands.block = require('./commands/block')({Block: Block, magicNumber: magicNumber}); /* tx */ @@ -390,13 +185,13 @@ function builder(options) { obj.inventory.push({type: type, hash: hash}); } - checkFinished(parser); + utils.checkFinished(parser); return commands.getdata.fromObject(obj); }; commands.getdata.prototype.getPayload = function() { var bw = new BufferWriter(); - writeInventory(this.inventory, bw); + utils.writeInventory(this.inventory, bw); return bw.concat(); }; @@ -433,7 +228,7 @@ function builder(options) { $.checkState(txn_count === 0, 'txn_count should always be 0'); } - checkFinished(parser); + utils.checkFinished(parser); return commands.headers.fromObject(obj); }; @@ -476,14 +271,14 @@ function builder(options) { obj.inventory.push({type: type, hash: hash}); } - checkFinished(parser); + utils.checkFinished(parser); return commands.notfound.fromObject(obj); }; commands.notfound.prototype.getPayload = function() { var bw = new BufferWriter(); - writeInventory(this.inventory, bw); + utils.writeInventory(this.inventory, bw); return bw.concat(); }; @@ -504,7 +299,7 @@ function builder(options) { commands.inv.prototype.getPayload = function() { var bw = new BufferWriter(); - writeInventory(this.inventory, bw); + utils.writeInventory(this.inventory, bw); return bw.concat(); }; @@ -521,7 +316,7 @@ function builder(options) { obj.inventory.push({type: type, hash: hash}); } - checkFinished(parser); + utils.checkFinished(parser); return commands.inv.fromObject(obj); }; @@ -550,12 +345,12 @@ function builder(options) { // todo: time only available on versions >=31402 var time = new Date(parser.readUInt32LE() * 1000); - var addr = parseAddr(parser); + var addr = utils.parseAddr(parser); addr.time = time; obj.addresses.push(addr); } - checkFinished(parser); + utils.checkFinished(parser); return commands.addr.fromObject(obj); }; @@ -566,7 +361,7 @@ function builder(options) { for (var i = 0; i < this.addresses.length; i++) { var addr = this.addresses[i]; bw.writeUInt32LE(addr.time.getTime() / 1000); - writeAddr(addr, bw); + utils.writeAddr(addr, bw); } return bw.concat(); @@ -594,7 +389,7 @@ function builder(options) { var parser = new BufferReader(payload); obj.payload = parser.readVarLengthBuffer(); obj.signature = parser.readVarLengthBuffer(); - checkFinished(parser); + utils.checkFinished(parser); return commands.alert.fromObject(obj); }; @@ -717,7 +512,7 @@ function builder(options) { $.checkArgument(payload); var parser = new BufferReader(payload); obj.data = parser.readVarLengthBuffer(); - checkFinished(parser); + utils.checkFinished(parser); return commands.filteradd.fromObject(obj); }; @@ -763,7 +558,7 @@ function builder(options) { this.version = protocolVersion; this.magicNumber = magicNumber; - options = sanitizeStartStop(options); + options = utils.sanitizeStartStop(options); this.starts = options.starts; this.stop = options.stop; @@ -787,7 +582,7 @@ function builder(options) { obj.starts.push(parser.read(32)); } obj.stop = parser.read(32); - checkFinished(parser); + utils.checkFinished(parser); return commands.getblocks.fromObject(obj); }; @@ -818,7 +613,7 @@ function builder(options) { this.version = protocolVersion; this.magicNumber = magicNumber; - options = sanitizeStartStop(options); + options = utils.sanitizeStartStop(options); this.starts = options.starts; this.stop = options.stop; @@ -842,7 +637,7 @@ function builder(options) { obj.starts.push(parser.read(32)); } obj.stop = parser.read(32); - checkFinished(parser); + utils.checkFinished(parser); return commands.getheaders.fromObject(obj); }; @@ -881,7 +676,6 @@ function builder(options) { }; /* getaddr */ - //todo: need test data commands.getaddr = function(options) { Message.call(this, options); this.magicNumber = magicNumber; diff --git a/lib/messages/commands/block.js b/lib/messages/commands/block.js new file mode 100644 index 0000000..f29450f --- /dev/null +++ b/lib/messages/commands/block.js @@ -0,0 +1,35 @@ +'use strict'; + +var Message = require('../message'); +var inherits = require('util').inherits; +var bitcore = require('bitcore'); + +var Block = bitcore.Block; +var magicNumber = bitcore.Networks.defaultNetwork.networkMagic.readUInt32LE(0); + +function BlockMessage(options) { + Message.call(this, options); + this.command = 'block'; + this.magicNumber = magicNumber; + this.block = options.block; +} +inherits(BlockMessage, Message); + +BlockMessage.fromObject = function(options) { + return new BlockMessage(options); +}; + +BlockMessage.fromBuffer = function(payload) { + var block = Block.fromBuffer(payload); + return BlockMessage.fromObject({block: block}); +}; + +BlockMessage.prototype.getPayload = function() { + return this.block.toBuffer(); +}; + +module.exports = function(options) { + Block = options.Block; + magicNumber = options.magicNumber; + return BlockMessage; +}; diff --git a/lib/messages/commands/version.js b/lib/messages/commands/version.js new file mode 100644 index 0000000..dc78c37 --- /dev/null +++ b/lib/messages/commands/version.js @@ -0,0 +1,100 @@ +'use strict'; + +var Message = require('../message'); +var inherits = require('util').inherits; +var bitcore = require('bitcore'); +var BufferWriter = bitcore.encoding.BufferWriter; +var BufferReader = bitcore.encoding.BufferReader; +var _ = bitcore.deps._; +var BN = bitcore.crypto.BN; + +var utils = require('../utils'); +var magicNumber = bitcore.Networks.defaultNetwork.networkMagic.readUInt32LE(0); +var protocolVersion = 70000; +var packageInfo = require('../../../package.json'); + +/** + * The version message is used on connection creation to advertise + * the type of node. The remote node will respond with its version, and no + * communication is possible until both peers have exchanged their versions. + * By default, bitcore advertises itself as named `bitcore:0.8`. + * + * @param{Object} obj - properties for the version + * @param{String} obj.subversion - version of the client + * @param{Buffer} obj.nonce - a random 8 byte buffer + */ +function VersionMessage(obj) { + /* jshint maxcomplexity: 10 */ + Message.call(this, obj); + this.command = 'version'; + _.assign(this, obj); + this.magicNumber = magicNumber; + this.nonce = this.nonce || utils.getNonce(); + this.services = this.services || new BN(1, 10); + this.timestamp = this.timestamp || new Date(); + this.version = this.version || protocolVersion; + this.subversion = this.subversion || '/bitcore:' + packageInfo.version + '/'; + this.startHeight = this.startHeight || 0; +}; +inherits(VersionMessage, Message); + +VersionMessage.fromObject = function(obj) { + return new VersionMessage(obj); +}; + +VersionMessage.fromBuffer = function(payload) { + var parser = new BufferReader(payload); + var obj = {}; + obj.version = parser.readUInt32LE(); + obj.services = parser.readUInt64LEBN(); + obj.timestamp = new Date(parser.readUInt64LEBN().toNumber() * 1000); + + obj.addrMe = { + services: parser.readUInt64LEBN(), + ip: utils.parseIP(parser), + port: parser.readUInt16BE() + }; + obj.addrYou = { + services: parser.readUInt64LEBN(), + ip: utils.parseIP(parser), + port: parser.readUInt16BE() + }; + obj.nonce = parser.read(8); + obj.subversion = parser.readVarLengthBuffer().toString(); + obj.startHeight = parser.readUInt32LE(); + + if(parser.finished()) { + obj.relay = true; + } else { + obj.relay = !!parser.readUInt8(); + } + utils.checkFinished(parser); + + return VersionMessage.fromObject(obj); +}; + +VersionMessage.prototype.getPayload = function() { + var bw = new BufferWriter(); + bw.writeUInt32LE(this.version); + bw.writeUInt64LEBN(this.services); + + var timestampBuffer = new Buffer(Array(8)); + timestampBuffer.writeUInt32LE(Math.round(this.timestamp.getTime() / 1000), 0); + bw.write(timestampBuffer); + + utils.writeAddr(this.addrMe, bw); + utils.writeAddr(this.addrYou, bw); + bw.write(this.nonce); + bw.writeVarintNum(this.subversion.length); + bw.write(new Buffer(this.subversion, 'ascii')); + bw.writeUInt32LE(this.startHeight); + bw.writeUInt8(this.relay); + + return bw.concat(); +}; + +module.exports = function(options) { + magicNumber = options.magicNumber || magicNumber; + protocolVersion = options.protocolVersion || protocolVersion; + return VersionMessage; +}; diff --git a/lib/messages/utils.js b/lib/messages/utils.js new file mode 100644 index 0000000..b97fec8 --- /dev/null +++ b/lib/messages/utils.js @@ -0,0 +1,109 @@ +'use strict'; + +var bitcore = require('bitcore'); +var BufferUtil = bitcore.util.buffer; +var $ = bitcore.util.preconditions; +var _ = bitcore.deps._; +var utils; + +module.exports = utils = { + checkFinished: function checkFinished(parser) { + if(!parser.finished()) { + throw new Error('Data still available after parsing'); + } + }, + getNonce: function getNonce() { + return bitcore.crypto.Random.getRandomBuffer(8); + }, + writeIP: function writeIP(ip, bw) { + var words = ip.v6.split(':').map(function(s) { + return new Buffer(s, 'hex'); + }); + for (var i = 0; i < words.length; i++) { + var word = words[i]; + bw.write(word); + } + }, + writeAddr: function writeAddr(addr, bw) { + if (_.isUndefined(addr)) { + var pad = new Buffer(Array(26)); + bw.write(pad); + return; + } + + bw.writeUInt64LEBN(addr.services); + utils.writeIP(addr.ip, bw); + bw.writeUInt16BE(addr.port); + }, + writeInventory: function writeInventory(inventory, bw) { + bw.writeVarintNum(inventory.length); + inventory.forEach(function(value) { + bw.writeUInt32LE(value.type); + bw.write(value.hash); + }); + }, + parseIP: function parseIP(parser) { + var ipv6 = []; + var ipv4 = []; + for (var a = 0; a < 8; a++) { + var word = parser.read(2); + ipv6.push(word.toString('hex')); + if (a >= 6) { + ipv4.push(word[0]); + ipv4.push(word[1]); + } + } + ipv6 = ipv6.join(':'); + ipv4 = ipv4.join('.'); + return { + v6: ipv6, + v4: ipv4 + }; + }, + parseAddr: function parseAddr(parser) { + var services = parser.readUInt64LEBN(); + var ip = utils.parseIP(parser); + var port = parser.readUInt16BE(); + return { + services: services, + ip: ip, + port: port + }; + }, + sanitizeStartStop: function sanitizeStartStop(obj) { + /* jshint maxcomplexity: 10 */ + /* jshint maxstatements: 20 */ + $.checkArgument(_.isUndefined(obj.starts) || _.isArray(obj.starts)); + var starts = obj.starts; + var stop = obj.stop; + if (starts) { + starts = starts.map(function(hash) { + if (_.isString(hash)) { + return BufferUtil.reverse(new Buffer(hash, 'hex')); + } else { + return hash; + } + }); + } else { + starts = []; + } + + for (var i = 0; i < starts.length; i++) { + if (starts[i].length !== 32) { + throw new Error('Invalid hash ' + i + ' length: ' + starts[i].length); + } + } + + stop = obj.stop; + if (_.isString(stop)) { + stop = BufferUtil.reverse(new Buffer(stop, 'hex')); + } + if (!stop) { + stop = BufferUtil.NULL_HASH; + } + obj.starts = starts; + obj.stop = stop; + + return obj; + } +};