From a20cb1688f6d949c5a2079608aa7c01d702e6255 Mon Sep 17 00:00:00 2001 From: Fedor Indutny Date: Wed, 30 Apr 2014 12:44:59 +0400 Subject: [PATCH] wip --- lib/bcoin.js | 4 + lib/bcoin/block.js | 42 +++++ lib/bcoin/bloom.js | 139 ++++++++++++++ lib/bcoin/peer.js | 312 ++++++++++++++++++++++++++++++++ lib/bcoin/protocol/constants.js | 25 +++ lib/bcoin/protocol/framer.js | 113 +++++++++++- lib/bcoin/protocol/parser.js | 72 +++++++- lib/bcoin/tx.js | 28 +++ lib/bcoin/utils.js | 53 +++++- package.json | 4 +- test/bloom-test.js | 29 +++ test/protocol-test.js | 26 ++- 12 files changed, 826 insertions(+), 21 deletions(-) create mode 100644 lib/bcoin/block.js create mode 100644 lib/bcoin/bloom.js create mode 100644 lib/bcoin/peer.js create mode 100644 lib/bcoin/tx.js create mode 100644 test/bloom-test.js diff --git a/lib/bcoin.js b/lib/bcoin.js index f12b9b09..279fd79d 100644 --- a/lib/bcoin.js +++ b/lib/bcoin.js @@ -3,5 +3,9 @@ var elliptic = require('elliptic'); bcoin.ecdsa = elliptic.ecdsa(elliptic.nist.secp256k1); bcoin.utils = require('./bcoin/utils'); +bcoin.bloom = require('./bcoin/bloom'); bcoin.protocol = require('./bcoin/protocol'); +bcoin.tx = require('./bcoin/tx'); +bcoin.block = require('./bcoin/block'); bcoin.wallet = require('./bcoin/wallet'); +bcoin.peer = require('./bcoin/peer'); diff --git a/lib/bcoin/block.js b/lib/bcoin/block.js new file mode 100644 index 00000000..f1ccc64d --- /dev/null +++ b/lib/bcoin/block.js @@ -0,0 +1,42 @@ +var bcoin = require('../bcoin'); +var utils = bcoin.utils; + +function Block(data) { + if (!(this instanceof Block)) + return new Block(data); + + this.type = 'block'; + this.version = data.version; + this.prevBlock = data.prevBlock; + this.merkleRoot = data.merkleRoot; + this.ts = data.ts; + this.bits = data.bits; + this.nonce = data.nonce; + + this._hash = null; +} +module.exports = Block; + +Block.prototype.hash = function hash(enc) { + // Hash it + if (!this._hash) + this._hash = utils.dsha256(this.abbr()); + return enc === 'hex' ? utils.toHex(this._hash) : this._hash; +}; + +Block.prototype.abbr = function abbr() { + var res = new Array(80); + utils.writeU32(res, this.version, 0); + utils.copy(this.prevBlock, res, 4); + utils.copy(this.merkleRoot, res, 36); + utils.writeU32(res, this.ts, 68); + utils.writeU32(res, this.bits, 72); + utils.writeU32(res, this.nonce, 76); + + return res; +}; + +Block.prototype.render = function render(framer) { + console.log('here'); + return []; +}; diff --git a/lib/bcoin/bloom.js b/lib/bcoin/bloom.js new file mode 100644 index 00000000..818d6299 --- /dev/null +++ b/lib/bcoin/bloom.js @@ -0,0 +1,139 @@ +var bcoin = require('../bcoin'); +var utils = bcoin.utils; + +function Bloom(size, n, tweak) { + if (!(this instanceof Bloom)) + return new Bloom(size, n, tweak); + + this.filter = new Array(Math.ceil(size / 32)); + for (var i = 0; i < this.filter.length; i++) + this.filter[i] = 0; + this.size = size; + this.n = n; + this.tweak = tweak; +} +module.exports = Bloom; + +Bloom.prototype.hash = function hash(val, n) { + return Bloom.hash(val, sum32(mul32(n, 0xfba4c795), this.tweak)) % this.size; +}; + +Bloom.prototype.add = function add(val) { + for (var i = 0; i < this.n; i++) { + var bit = this.hash(val, i); + var pos = 1 << (bit & 0x1f); + var shift = bit >> 5; + + this.filter[shift] |= pos; + } +}; + +Bloom.prototype.test = function test(val) { + for (var i = 0; i < this.n; i++) { + var bit = this.hash(val, i); + var pos = 1 << (bit & 0x1f); + var shift = bit >> 5; + + if ((this.filter[shift] & pos) === 0) + return false; + } + + return true; +}; + +Bloom.prototype.toArray = function toArray() { + var bytes = Math.ceil(this.size / 8); + var res = new Array(this.filter.length * 4); + for (var i = 0; i < this.filter.length; i++) { + var w = this.filter[i]; + res[i * 4] = w & 0xff; + res[i * 4 + 1] = (w >> 8) & 0xff; + res[i * 4 + 2] = (w >> 16) & 0xff; + res[i * 4 + 3] = (w >> 24) & 0xff; + } + + return res.slice(0, bytes); +}; + +function mul32(a, b) { + var alo = a & 0xffff; + var blo = b & 0xffff; + var ahi = a >>> 16; + var bhi = b >>> 16; + + var lo = alo * blo; + var hi = (ahi * blo + bhi * alo) & 0xffff; + hi += lo >>> 16; + lo &= 0xffff; + var r = (hi << 16) | lo; + + if (r < 0) + r += 0x100000000; + return r; +} + +function sum32(a, b) { + var r = (a + b) & 0xffffffff; + if (r < 0) + r += 0x100000000; + return r; +} + +function rotl32(w, b) { + return (w << b) | (w >>> (32 - b)); +} + +function hash(data, seed) { + data = utils.toArray(data); + + var c1 = 0xcc9e2d51; + var c2 = 0x1b873593; + var r1 = 15; + var r2 = 13; + var m = 5; + var n = 0xe6546b64; + + var hash = seed; + for (var i = 0; i + 4 <= data.length; i += 4) { + var w = data[i] | + (data[i + 1] << 8) | + (data[i + 2] << 16) | + (data[i + 3] << 24); + + w = mul32(w, c1); + w = rotl32(w, r1); + w = mul32(w, c2); + + hash ^= w; + hash = rotl32(hash, r2); + hash = mul32(hash, m); + hash = sum32(hash, n); + } + + if (i !== data.length) { + var r = 0; + for (var j = data.length - 1; j >= i; j--) + r = (r << 8) | data[j]; + + r = mul32(r, c1); + r = rotl32(r, r1); + if (r < 0) + r += 0x100000000; + r = mul32(r, c2); + + hash ^= r; + } + + hash ^= data.length; + hash ^= hash >>> 16; + hash = mul32(hash, 0x85ebca6b); + hash ^= hash >>> 13; + hash = mul32(hash, 0xc2b2ae35); + hash ^= hash >>> 16; + + if (hash < 0) + hash += 0x100000000; + + return hash; +} +Bloom.hash = hash; diff --git a/lib/bcoin/peer.js b/lib/bcoin/peer.js new file mode 100644 index 00000000..64ce767f --- /dev/null +++ b/lib/bcoin/peer.js @@ -0,0 +1,312 @@ +var assert = require('assert'); +var util = require('util'); +var EventEmitter = require('events').EventEmitter; + +var bcoin = require('../bcoin'); +var utils = bcoin.utils; +var constants = bcoin.protocol.constants; + +// Browserify, I'm looking at you +try { + var NodeBuffer = require('buf' + 'fer').Buffer; +} catch (e) { +} + +function Peer(socket, options) { + if (!(this instanceof Peer)) + return new Peer(socket, options); + + EventEmitter.call(this); + this.socket = socket; + this.parser = new bcoin.protocol.parser(); + this.framer = new bcoin.protocol.framer(); + this.bloom = new bcoin.bloom(8 * 10 * 1024, + 10, + (Math.random() * 0xffffffff) | 0), + this.version = null; + this.destroyed = false; + + this.options = options || {}; + this._broadcast = { + timout: this.options.broadcastTimeout || 30000, + map: {} + }; + + this._request = { + timeout: this.options.requestTimeout || 30000, + queue: [] + }; + + this._ping = { + timer: null, + interval: this.options.pingInterval || 5000 + }; + + this._init(); +} +util.inherits(Peer, EventEmitter); +module.exports = Peer; + +Peer.prototype._init = function init() { + var self = this; + this.socket.once('error', function(err) { + self._error(err); + }); + this.socket.once('close', function() { + self._error('socket hangup'); + }); + this.socket.on('data', function(chunk) { + self.parser.feed(chunk); + }); + this.parser.on('packet', function(packet) { + self._onPacket(packet); + }); + this.parser.on('error', function(err) { + self._error(err); + }); + + this._ping.timer = setInterval(function() { + self._write(self.framer.ping([ + 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef + ])); + }, this._ping.interval); + + // Send hello + this._write(this.framer.version()); + this._req('verack', function(err, payload) { + if (err) + return self._error(err); + self.emit('ack'); + }); +}; + +Peer.prototype.broadcast = function broadcast(items) { + if (!Array.isArray(items)) + items = [ items ]; + + var self = this; + items.forEach(function(item) { + var key = item.hash('hex'); + var old = this._broadcast.map[key]; + if (old) + clearTimeout(old.timer); + + // Auto-cleanup broadcast map after timeout + var entry = { + timeout: setTimeout(function() { + delete self._broadcast.map[key]; + }, this._broadcast.timout), + value: item + }; + + this._broadcast.map[key] = entry; + }, this); + + this._write(this.framer.inv(items)); +}; + +Peer.prototype.watch = function watch(id) { + this.bloom.add(id); + this._write(this.framer.filterLoad(this.bloom, 'pubkeyOnly')); +}; + +Peer.prototype.loadBlocks = function loadBlocks() { + if (this.loadingBlocks) + return; + this.loadingBlocks = true; + this._write(this.framer.getBlocks([ constants.genesis ])); +}; + +Peer.prototype.destroy = function destroy() { + if (this.destroyed) + return; + this.destroyed = true; + this.socket.destroy(); + this.socket = null; + + // Clean-up timeouts + Object.keys(this._broadcast.map).forEach(function(key) { + clearTimeout(this._broadcast.map[key].timer); + }, this); + + clearInterval(this._ping.timer); + this._ping.timer = null; +}; + +// Private APIs + +Peer.prototype._write = function write(chunk) { + if (NodeBuffer) + this.socket.write(new NodeBuffer(chunk)); + else + this.socket.write(chunk); +}; + +Peer.prototype._error = function error(err) { + if (this.destroyed) + return; + this.destroy(); + this.emit('error', typeof err === 'string' ? new Error(err) : err); +}; + +Peer.prototype._req = function _req(cmd, cb) { + var self = this; + var entry = { + cmd: cmd, + cb: cb, + ontimeout: function() { + var i = self._request.queue.indexOf(entry); + if (i !== -1) + self.request.queue.splice(i, 1); + cb(new Error('Timed out'), null); + }, + timer: null + }; + entry.timer = setTimeout(entry.ontimeout, this._request.timeout) + this._request.queue.push(entry); +}; + +Peer.prototype._res = function _res(cmd, payload) { + var entry = this._request.queue[0]; + if (!entry || entry.cmd && entry.cmd !== cmd) + return; + + var res = entry.cb(null, payload, cmd); + + // If callback returns false - it hasn't finished processing responses + if (res === false) { + assert(!entry.cmd); + + // Restart timer + entry.timer = setTimeout(entry.ontimeout, this._request.timeout) + } else { + this._request.queue.shift(); + clearTimeout(entry.timer); + entry.timer = null; + } +}; + +Peer.prototype._getData = function _getData(items, cb) { + var map = {}; + var waiting = items.length; + items.forEach(function(item) { + map[utils.toHex(item.hash)] = { + item: item, + once: false, + result: null + }; + }); + + var self = this; + + function markEntry(hash, result) { + var entry = map[utils.toHex(hash)]; + if (!entry || entry.once) { + done(new Error('Invalid notfound entry hash')); + return false; + } + + entry.once = true; + entry.result = result; + waiting--; + return true; + } + + this._write(this.framer.getData(items)); + // Process all incoming data, until all data is returned + this._req(null, function(err, payload, cmd) { + var ok = true; + if (cmd === 'notfound') { + ok = payload.every(function(item) { + return markEntry(item.hash, null); + }); + } else if (cmd === 'tx') { + var tx = bcoin.tx(payload); + ok = markEntry(tx.hash(), b); + } else if (cmd === 'merkleblock') { + var b = bcoin.block(payload); + ok = markEntry(b.hash(), b); + } else if (cmd === 'block') { + var b = bcoin.block(payload); + ok = markEntry(b.hash(), b); + } else { + done(new Error('Unknown packet in reply to getdata: ' + cmd)); + return; + } + if (!ok) + return; + + if (waiting === 0) + done(); + else + return false; + }); + + function done(err) { + if (err) + return cb(err); + + cb(null, items.map(function(item) { + return map[utils.toHex(item.hash)].result; + })); + } +}; + +Peer.prototype._onPacket = function onPacket(packet) { + var cmd = packet.cmd; + var payload = packet.payload; + + if (cmd === 'version') + return this._handleVersion(payload); + else if (cmd === 'inv') + return this._handleInv(payload); + else if (cmd === 'getdata') + return this._handleGetData(payload); + else + return this._res(cmd, payload); +}; + +Peer.prototype._handleVersion = function handleVersion(payload) { + if (payload.v < constants.minVersion) + return this._error('peer doesn\'t support required protocol version'); + + // ACK + this._write(this.framer.verack()); + this.version = payload; +}; + +Peer.prototype._handleGetData = function handleGetData(items) { + items.forEach(function(item) { + // Filter out not broadcasted things + var hash = utils.toHex(item.hash); + if (!this._broadcast.map[hash]) + return; + + var entry = this._broadcast.map[hash].value; + this._write(entry.render(this.framer)); + }, this); +}; + +Peer.prototype._handleInv = function handleInv(items) { + // Always request what advertised + var req = items.filter(function(item) { + return item.type === 'tx' || item.type === 'block'; + }).map(function(item) { + if (item.type === 'tx') + return item; + if (item.type === 'block') + return { type: 'filtered', hash: item.hash }; + }); + + var self = this; + this._getData(req, function(err, data) { + if (err) + return self._error(err); + console.log(data.join(', ')); + }); +}; + +Peer.prototype._handleMerkleBlock = function handleMerkleBlock(block) { + console.log(utils.toHex(block.prevBlock)); +}; diff --git a/lib/bcoin/protocol/constants.js b/lib/bcoin/protocol/constants.js index 7356a9ef..65c1e8fb 100644 --- a/lib/bcoin/protocol/constants.js +++ b/lib/bcoin/protocol/constants.js @@ -1,8 +1,33 @@ +var bcoin = require('../../bcoin'); +var utils = bcoin.utils; + exports.minVersion = 70001; exports.version = 70002; exports.magic = 0xd9b4bef9; +exports.genesis = utils.toArray( + '000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f', 'hex'); // version - services field exports.services = { network: 1 }; + +exports.inv = { + error: 0, + tx: 1, + block: 2, + filtered: 3 +}; + +exports.invByVal = { + 0: 'error', + 1: 'tx', + 2: 'block', + 3: 'filtered' +}; + +exports.filterFlags = { + none: 0, + all: 1, + pubkeyOnly: 2 +}; diff --git a/lib/bcoin/protocol/framer.js b/lib/bcoin/protocol/framer.js index fa4bdffd..ff775d33 100644 --- a/lib/bcoin/protocol/framer.js +++ b/lib/bcoin/protocol/framer.js @@ -37,7 +37,7 @@ Framer.prototype.header = function header(cmd, payload) { }; Framer.prototype.packet = function packet(cmd, payload) { - var h = this.header('version', payload); + var h = this.header(cmd, payload); return h.concat(payload); }; @@ -89,3 +89,114 @@ Framer.prototype.version = function version(packet) { return this.packet('version', p); }; + +Framer.prototype.verack = function verack() { + return this.packet('verack', []); +}; + +function varint(arr, value, off) { + if (!off) + off = 0; + if (value < 0xfd) { + arr[off] = value; + return 1; + } else if (value <= 0xffff) { + arr[off] = 0xfd; + arr[off + 1] = value & 0xff; + arr[off + 2] = value >>> 8; + return 3; + } else if (value <= 0xffffffff) { + arr[off] = 0xfd; + arr[off + 3] = value & 0xff; + arr[off + 4] = (value >>> 8) & 0xff; + arr[off + 5] = (value >>> 16) & 0xff; + arr[off + 6] = value >>> 24; + return 5; + } else { + throw new Error('64bit varint not supported yet'); + } +} + +Framer.prototype._inv = function _inv(command, items) { + var res = []; + var off = varint(res, items.length, 0); + + for (var i = 0; i < items.length; i++) { + // Type + off += writeU32(res, constants.inv[items[i].type], off); + + // Hash + var hash = items[i].hash; + assert.equal(hash.length, 32); + res = res.concat(hash); + + off += hash.length; + } + + return this.packet(command, res); +}; + +Framer.prototype.inv = function inv(items) { + return this._inv('inv', items); +}; + +Framer.prototype.getData = function getData(items) { + return this._inv('getdata', items); +}; + +Framer.prototype.notFound = function notFound(items) { + return this._inv('notfound', items); +}; + +Framer.prototype.ping = function ping(nonce) { + return this.packet('ping', nonce); +}; + +Framer.prototype.pong = function pong(nonce) { + return this.packet('pong', nonce.slice(0, 8)); +}; + +Framer.prototype.filterLoad = function filterLoad(bloom, update) { + var filter = bloom.toArray(); + var before = []; + varint(before, filter.length, 0) + + var after = new Array(9); + + // Number of hash functions + writeU32(after, bloom.n, 0); + + // nTweak + writeU32(after, bloom.tweak, 4); + + // nFlags + after[8] = constants.filterFlags[update]; + + var r = this.packet('filterload', before.concat(filter, after)); + return r; +}; + +Framer.prototype.filterClear = function filterClear() { + return this.packet('filterclear', []); +}; + +Framer.prototype.getBlocks = function getBlocks(hashes, stop) { + var p = []; + writeU32(p, constants.version, 0); + var off = 4 + varint(p, hashes.length, 4); + p.length = off + 32 * (hashes.length + 1); + + for (var i = 0; i < hashes.length; i++) { + var len = utils.copy(hashes[i], p, off); + for (; len < 32; len++) + p[off + len] = 0; + off += len; + } + + var len = stop ? utils.copy(stop, p, off) : 0; + for (; len < 32; len++) + p[off + len] = 0; + assert.equal(off + len, p.length); + + return this.packet('getblocks', p); +}; diff --git a/lib/bcoin/protocol/parser.js b/lib/bcoin/protocol/parser.js index 79662306..c294b6ed 100644 --- a/lib/bcoin/protocol/parser.js +++ b/lib/bcoin/protocol/parser.js @@ -23,11 +23,9 @@ function Parser() { util.inherits(Parser, EventEmitter); module.exports = Parser; -Parser.prototype.execute = function(data) { - if (data) { - this.pendingTotal += data.length; - this.pending.push(data); - } +Parser.prototype.feed = function feed(data) { + this.pendingTotal += data.length; + this.pending.push(data); while (this.pendingTotal >= this.waiting) { // Concat chunks var chunk = new Array(this.waiting); @@ -88,6 +86,10 @@ Parser.prototype.parseHeader = function parseHeader(h) { Parser.prototype.parsePayload = function parsePayload(cmd, p) { if (cmd === 'version') return this.parseVersion(p); + else if (cmd === 'getdata' || cmd === 'inv' || cmd === 'notfound') + return this.parseInvList(p); + else if (cmd === 'merkleblock') + return this.parseMerkleBlock(p); else return p; }; @@ -97,9 +99,6 @@ Parser.prototype.parseVersion = function parseVersion(p) { return this.emit('error', new Error('version packet is too small')); var v = readU32(p, 0); - if (v < constants.minVersion) - return this.emit('error', new Error('version number is too small')); - var services = readU64(p, 4); // Timestamp @@ -123,3 +122,60 @@ Parser.prototype.parseVersion = function parseVersion(p) { relay: relay }; }; + +function readIntv(p, off) { + if (!off) + off = 0; + + var r, bytes; + if (p[off] < 0xfd) { + r = p[off]; + bytes = 1; + } else if (p[off] === 0xfd) { + r = p[off + 1] | (p[off + 2] << 8); + bytes = 3; + } else if (p[off] === 0xfe) { + r = readU32(p, off + 1); + bytes = 5; + } else { + r = 0; + bytes = 9; + } + + return { off: bytes, r: r }; +} + +Parser.prototype.parseInvList = function parseInvList(p) { + var count = readIntv(p, 0); + p = p.slice(count.off); + count = count.r; + if (p.length < count * 36) + return this.emit('error', new Error('Invalid getdata size')); + + var items = []; + for (var i = 0, off = 0; i < count; i++, off += 36) { + items.push({ + type: constants.invByVal[readU32(p, off)], + hash: p.slice(off + 4, off + 36) + }); + } + return items; +}; + +Parser.prototype.parseMerkleBlock = function parsMerkleBlock(p) { + if (p.length < 84) + return this.emit('error', new Error('Invalid merkleblock size')); + + return { + version: readU32(p, 0), + prevBlock: p.slice(4, 36), + merkleRoot: p.slice(36, 68), + ts: readU32(p, 68), + bits: readU32(p, 72), + nonce: readU32(p, 76), + totalTx: readU32(p, 80) + + // hashes: + // flags: + }; +}; diff --git a/lib/bcoin/tx.js b/lib/bcoin/tx.js new file mode 100644 index 00000000..eb81b12c --- /dev/null +++ b/lib/bcoin/tx.js @@ -0,0 +1,28 @@ +var bcoin = require('../bcoin'); +var utils = bcoin.utils; + +function TX(data) { + if (!(this instanceof TX)) + return new TX(data); + this.type = 'tx'; + + this._hash = null; + this._raw = data || null; +} +module.exports = TX; + +TX.prototype.hash = function hash(enc) { + if (!this._hash) { + // First, obtain the raw TX data + this.render(); + + // Hash it + this._hash = utils.dsha256(this._raw); + } + return enc === 'hex' ? utils.toHex(this._hash) : this._hash; +}; + +TX.prototype.render = function render(framer) { + console.log('here'); + return []; +}; diff --git a/lib/bcoin/utils.js b/lib/bcoin/utils.js index b6eb8b44..0c5727f5 100644 --- a/lib/bcoin/utils.js +++ b/lib/bcoin/utils.js @@ -4,6 +4,37 @@ var assert = require('assert'); var bn = require('bn.js'); var hash = require('hash.js'); +function toArray(msg, enc) { + if (Array.isArray(msg)) + return msg.slice(); + if (!msg) + return []; + var res = []; + if (typeof msg === 'string') { + if (!enc) { + for (var i = 0; i < msg.length; i++) { + var c = msg.charCodeAt(i); + var hi = c >> 8; + var lo = c & 0xff; + if (hi) + res.push(hi, lo); + else + res.push(lo); + } + } else if (enc === 'hex') { + msg = msg.replace(/[^a-z0-9]+/ig, ''); + if (msg.length % 2 != 0) + msg = '0' + msg; + for (var i = 0; i < msg.length; i += 2) + res.push(parseInt(msg[i] + msg[i + 1], 16)); + } + } else { + for (var i = 0; i < msg.length; i++) + res[i] = msg[i] | 0; + } + return res; +} +utils.toArray = toArray; var base58 = '123456789ABCDEFGHJKLMNPQRSTUVWXYZ' + 'abcdefghijkmnopqrstuvwxyz'; @@ -82,13 +113,17 @@ utils.ripesha = function ripesha(data, enc) { }; utils.checksum = function checksum(data, enc) { - return utils.sha256(utils.sha256(data, enc)).slice(0, 4); + return utils.dsha256(data, enc).slice(0, 4); }; utils.sha256 = function sha256(data, enc) { return hash.sha256().update(data, enc).digest(); }; +utils.dsha256 = function dsha256(data, enc) { + return utils.sha256(utils.sha256(data, enc)); +}; + utils.readU32 = function readU32(arr, off) { if (!off) off = 0; @@ -114,6 +149,7 @@ utils.writeU32 = function writeU32(dst, num, off) { dst[off + 1] = (num >>> 8) & 0xff; dst[off + 2] = (num >>> 16) & 0xff; dst[off + 3] = (num >>> 24) & 0xff; + return 4; }; utils.writeAscii = function writeAscii(dst, str, off) { @@ -137,3 +173,18 @@ utils.stringify = function stringify(arr) { res += String.fromCharCode(arr[i]); return res; }; + +function zero2(word) { + if (word.length === 1) + return '0' + word; + else + return word; +} + +function toHex(msg) { + var res = ''; + for (var i = 0; i < msg.length; i++) + res += zero2(msg[i].toString(16)); + return res; +} +utils.toHex = toHex; diff --git a/package.json b/package.json index 17670e36..07940c92 100644 --- a/package.json +++ b/package.json @@ -21,8 +21,8 @@ }, "homepage": "https://github.com/indutny/bcoin", "dependencies": { - "bn.js": "^0.1.7", - "elliptic": "^0.6.0", + "bn.js": "^0.2.0", + "elliptic": "^0.7.0", "hash.js": "^0.2.0" }, "devDependencies": { diff --git a/test/bloom-test.js b/test/bloom-test.js new file mode 100644 index 00000000..a9385985 --- /dev/null +++ b/test/bloom-test.js @@ -0,0 +1,29 @@ +var assert = require('assert'); +var bcoin = require('../'); + +describe('Bloom', function() { + it('should do proper murmur3', function() { + var h = bcoin.bloom.hash; + + assert.equal(h('', 0), 0); + assert.equal(h('', 0xfba4c795), 0x6a396f08); + assert.equal(h('00', 0xfba4c795), 0x2a101837); + assert.equal(h('hello world', 0), 0x5e928f0f); + }); + + it('should test and add stuff', function() { + var b = bcoin.bloom(512, 10, 156); + + b.add('hello'); + assert(b.test('hello')); + assert(!b.test('hello!')); + assert(!b.test('ping')); + + b.add('hello!'); + assert(b.test('hello!')); + assert(!b.test('ping')); + + b.add('ping'); + assert(b.test('ping')); + }); +}); diff --git a/test/protocol-test.js b/test/protocol-test.js index c1d5f920..6da53bc1 100644 --- a/test/protocol-test.js +++ b/test/protocol-test.js @@ -9,15 +9,23 @@ describe('Protocol', function() { framer = bcoin.protocol.framer(); }); - it('should encode/decode version packet', function(cb) { - var ver = framer.version(); - parser.once('packet', function(packet) { - assert.equal(packet.cmd, 'version'); - assert.equal(packet.payload.v, 70002); - assert.equal(packet.payload.relay, false); - - cb(); + function packetTest(command, payload, test) { + it('should encode/decode ' + command, function(cb) { + var ver = framer[command](); + parser.once('packet', function(packet) { + assert.equal(packet.cmd, command); + test(packet.payload); + cb(); + }); + parser.feed(ver); }); - parser.execute(ver); + } + + packetTest('version', {}, function(payload) { + assert.equal(payload.v, 70002); + assert.equal(payload.relay, false); + }); + + packetTest('verack', {}, function(payload) { }); });