From 087c57e07739fd7335ac82fccfbcb68bdd0958f1 Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Mon, 2 Feb 2015 15:56:32 -0300 Subject: [PATCH 1/4] add notfound message --- integration/bitcoind.js | 27 ++++++++++---- lib/messages.js | 80 +++++++++++++++++++++++++++++------------ 2 files changed, 78 insertions(+), 29 deletions(-) diff --git a/integration/bitcoind.js b/integration/bitcoind.js index c3aadee..6325622 100644 --- a/integration/bitcoind.js +++ b/integration/bitcoind.js @@ -18,6 +18,7 @@ var Pool = p2p.Pool; var Networks = bitcore.Networks; var Messages = p2p.Messages; var Block = bitcore.Block; +var Transaction = bitcore.Transaction; // config var network = Networks.livenet; @@ -25,6 +26,10 @@ var blockHash = { 'livenet': '000000000000000013413cf2536b491bf0988f52e90c476ffeb701c8bfdb1db9', 'testnet': '0000000058cc069d964711cd25083c0a709f4df2b34c8ff9302ce71fe5b45786' }; +var txHash = { + 'livenet': '22231e8219a0617a0ded618b5dc713fdf9b0db8ebd5bb3322d3011a703119d3b', + 'testnet': '22231e8219a0617a0ded618b5dc713fdf9b0db8ebd5bb3322d3011a703119d3b' +}; // These tests require a running bitcoind instance describe('Integration with ' + network.name + ' bitcoind', function() { @@ -126,15 +131,25 @@ describe('Integration with ' + network.name + ' bitcoind', function() { }); it('can request block data', function(cb) { connect(function(peer) { - peer.on('block', function(message) { + peer.once('block', function(message) { (message.block instanceof Block).should.equal(true); cb(); }); - // TODO: replace this for a new Messages.GetData.forTransaction(hash) - var message = new Messages.GetData([{ - type: Messages.Inventory.TYPE.BLOCK, - hash: BufferUtil.reverse(new Buffer(blockHash[network.name], 'hex')) - }]); + // TODO: replace this for a new Messages.GetData.forBlock(hash) + var message = Messages.GetData.forBlock(blockHash[network.name]); + peer.sendMessage(message); + }); + }); + it('can handle request tx data not found', function(cb) { + connect(function(peer) { + var hash = 'e2dfb8afe1575bfacae1a0b4afc49af7ddda69285857267bae0e22be15f74a3a'; + var expected = Messages.NotFound.forTransaction(hash); + peer.once('notfound', function(message) { + (message instanceof Messages.NotFound).should.equal(true); + message.should.deep.equal(expected); + cb(); + }); + var message = Messages.GetData.forTransaction(hash); peer.sendMessage(message); }); }); diff --git a/lib/messages.js b/lib/messages.js index 456b3aa..89cc4b0 100644 --- a/lib/messages.js +++ b/lib/messages.js @@ -19,7 +19,6 @@ var $ = bitcore.util.preconditions; var Hash = bitcore.crypto.Hash; var Random = bitcore.crypto.Random; var TransactionModel = bitcore.Transaction; -var errors = bitcore.Errors; var CONNECTION_NONCE = Random.getPseudoRandomBuffer(8); var PROTOCOL_VERSION = 70000; @@ -130,7 +129,7 @@ Message.buildMessage = function(command, payload) { */ Message.prototype.fromBuffer = function(payload) { /* jshint unused: false */ - throw new errors.NotImplemented(); + return this; }; /** @@ -264,6 +263,13 @@ module.exports.Version = Message.COMMANDS.version = Version; * @param{Array} inventory - reported elements */ function Inventory(inventory) { + $.checkArgument(_.isUndefined(inventory) || + _.isArray(inventory), 'Inventory for ' + + this.constructor.name + ' must be an array of objects'); + $.checkArgument(_.isUndefined(inventory) || + inventory.length === 0 || + (inventory[0] && !_.isUndefined(inventory[0].type) && !_.isUndefined(inventory[0].hash)), + 'Inventory for ' + this.constructor.name + ' must be an array of objects'); this.command = 'inv'; /** * @name P2P.Message.Inventory.inventory @@ -287,16 +293,26 @@ Inventory.TYPE_NAME = [ 'FILTERED_BLOCK' ]; +Inventory.forItem = function(type, hash) { + $.checkArgument(hash); + if (_.isString(hash)) { + hash = new Buffer(hash, 'hex'); + hash = BufferUtil.reverse(hash); + } + return { + type: type, + typeName: Inventory.TYPE_NAME[type], + hash: hash + }; +}; + Inventory.prototype.fromBuffer = function(payload) { var parser = new BufferReader(payload); var count = parser.readVarintNum(); for (var i = 0; i < count; i++) { var type = parser.readUInt32LE(); - this.inventory.push({ - type: type, - typeName: Inventory.TYPE_NAME[type], - hash: parser.read(32) - }); + var hash = parser.read(32); + this.inventory.push(Inventory.forItem(type, hash)); } return this; @@ -314,8 +330,32 @@ Inventory.prototype.getPayload = function() { return put.buffer(); }; +var creatorForItem = function(clazz, type) { + return function(hash) { + return new clazz([Inventory.forItem(type, hash)]); + }; +}; + module.exports.Inventory = Message.COMMANDS.inv = Inventory; +/** + * notfound is a response to a getdata, sent if any requested data + * items could not be relayed, for example, because the requested + * transaction was not in the memory pool or relay set. + * + * (from bitcoin's protocol spec) + * + * @name P2P.Message.NotFound + * @param{Array} inventory - not found elements + */ +function NotFound(inventory) { + Inventory.call(this, inventory); + this.command = 'notfound'; +} + +util.inherits(NotFound, Inventory); +module.exports.NotFound = Message.COMMANDS.notfound = NotFound; + /** * getdata is used in response to inv, to retrieve the content of a specific * object, and is usually sent after receiving an inv packet, after filtering @@ -324,22 +364,18 @@ module.exports.Inventory = Message.COMMANDS.inv = Inventory; * chain is not allowed to avoid having clients start to depend on nodes having * full transaction indexes (which modern nodes do not). * - * (taken from bitcoin's protocol spec) + * (from bitcoin's protocol spec) * * @name P2P.Message.GetData * @param{Array} inventory - requested elements */ function GetData(inventory) { - $.checkArgument(_.isUndefined(inventory) || - _.isArray(inventory), 'Inventory for GetData must be an array of objects'); - $.checkArgument(_.isUndefined(inventory) || - inventory.length === 0 || - (inventory[0] && !_.isUndefined(inventory[0].type) && !_.isUndefined(inventory[0].hash)), - 'Inventory for GetData must be an array of objects'); + Inventory.call(this, inventory); this.command = 'getdata'; this.inventory = inventory || []; } + util.inherits(GetData, Inventory); module.exports.GetData = Message.COMMANDS.getdata = GetData; @@ -480,9 +516,6 @@ function GetAddresses() { } util.inherits(GetAddresses, Message); -GetAddresses.prototype.fromBuffer = function() { - return new GetAddresses(); -}; module.exports.GetAddresses = Message.COMMANDS.getaddr = GetAddresses; /** @@ -495,9 +528,6 @@ function VerAck() { } util.inherits(VerAck, Message); -VerAck.prototype.fromBuffer = function() { - return new VerAck(); -}; module.exports.VerAck = Message.COMMANDS.verack = VerAck; /** @@ -737,9 +767,6 @@ function GetHeaders(starts, stop) { } util.inherits(GetHeaders, GetBlocks); -GetHeaders.prototype.fromBuffer = function() { - return new GetHeaders(); -}; module.exports.GetHeaders = Message.COMMANDS.getheaders = GetHeaders; /** @@ -769,3 +796,10 @@ Buffers.prototype.skip = function(i) { this.buffers[0] = new Buffer(this.buffers[0].slice(pos.offset)); this.length -= i; }; + + + +[Inventory, GetData, NotFound].forEach(function(clazz) { + clazz.forBlock = creatorForItem(clazz, Inventory.TYPE.BLOCK); + clazz.forTransaction = creatorForItem(clazz, Inventory.TYPE.TX); +}); From cc7b58369ba569d58bd0479874093897e45e800d Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Mon, 2 Feb 2015 16:18:10 -0300 Subject: [PATCH 2/4] add unit tests for NotFound --- lib/messages.js | 2 +- test/data/messages.json | 4 ++++ test/messages.js | 3 ++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/messages.js b/lib/messages.js index 89cc4b0..8ff3a94 100644 --- a/lib/messages.js +++ b/lib/messages.js @@ -147,7 +147,7 @@ Message.prototype.getPayload = function() { * @returns{Buffer} the serialized message */ Message.prototype.serialize = function(network) { - $.checkArgument(network); + $.checkArgument(network, 'Must specify network for serialization'); var commandBuf = new Buffer(this.command, 'ascii'); $.checkState(commandBuf.length <= 12, 'Command name too long'); var magic = network.networkMagic; diff --git a/test/data/messages.json b/test/data/messages.json index 8e89c5f..ea75e8d 100644 --- a/test/data/messages.json +++ b/test/data/messages.json @@ -11,6 +11,10 @@ "message": "", "payload": "" }, + "NOTFOUND": { + "message": "f9beb4d96e6f74666f756e6400000000250000001d33d53201010000003a4af715be220eae7b2657582869daddf79ac4afb4a0e1cafa5b57e1afb8dfe2", + "payload": "01010000003a4af715be220eae7b2657582869daddf79ac4afb4a0e1cafa5b57e1afb8dfe2" + }, "GETBLOCKS": { "message": "", "payload": "" diff --git a/test/messages.js b/test/messages.js index bf1be20..d72e6a6 100644 --- a/test/messages.js +++ b/test/messages.js @@ -27,7 +27,8 @@ describe('Messages', function() { GetData: 'getdata', GetAddresses: 'getaddr', Headers: 'headers', - Transaction: 'tx' + Transaction: 'tx', + NotFound: 'notfound' }; // TODO: add data for these var noPayload = ['Alert', 'Reject', 'GetBlocks', 'GetHeaders', 'GetData', 'Headers']; From 9fcf186545d0f231130b95787fc4b72dea61497c Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Mon, 2 Feb 2015 16:20:30 -0300 Subject: [PATCH 3/4] remove TODO --- integration/bitcoind.js | 1 - 1 file changed, 1 deletion(-) diff --git a/integration/bitcoind.js b/integration/bitcoind.js index 6325622..61f4d12 100644 --- a/integration/bitcoind.js +++ b/integration/bitcoind.js @@ -135,7 +135,6 @@ describe('Integration with ' + network.name + ' bitcoind', function() { (message.block instanceof Block).should.equal(true); cb(); }); - // TODO: replace this for a new Messages.GetData.forBlock(hash) var message = Messages.GetData.forBlock(blockHash[network.name]); peer.sendMessage(message); }); From d303e46f6969f63a9dc0f0289708eb2d959a3b88 Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Mon, 2 Feb 2015 16:24:17 -0300 Subject: [PATCH 4/4] add docs --- docs/messages.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/messages.md b/docs/messages.md index 39124c6..bb674cc 100644 --- a/docs/messages.md +++ b/docs/messages.md @@ -25,6 +25,9 @@ From the bitcoin protocol spec: `getdata` is used in response to `inv`, to retri GetData inherits from Inventory, as they both have the same structure. +### NotFound +notfound is a response to a getdata, sent if any requested data items could not be relayed, for example, because the requested transaction was not in the memory pool or relay set. Contains inventory information specifying which items were not found. + ### Ping Sent to another peer mainly to check the connection is still alive.