diff --git a/lib/bcoin/chain.js b/lib/bcoin/chain.js index f7358a75..1c458b5d 100644 --- a/lib/bcoin/chain.js +++ b/lib/bcoin/chain.js @@ -31,10 +31,7 @@ function Chain(options) { hashes: preload.hashes.slice(), ts: preload.ts.slice() }; - this.request = { - map: {}, - count: 0 - }; + this.request = new utils.RequestCache(); if (this.index.hashes.length === 0) this.add(new bcoin.block(constants.genesis)); @@ -122,14 +119,7 @@ Chain.prototype.add = function add(block) { this._bloomBlock(block); // Fullfill request - if (this.request.map[hash]) { - var req = this.request.map[hash]; - delete this.request.map[hash]; - this.request.count--; - req.forEach(function(cb) { - cb(block); - }); - } + this.request.fullfill(hash, block); if (!this.orphan.map[hash]) break; @@ -199,13 +189,8 @@ Chain.prototype.get = function get(hash, cb) { assert(false); } - if (this.request.map[hash]) { - this.request.map[hash].push(cb); - } else { - this.request.map[hash] = [ cb ]; - this.request.count++; + if (this.request.add(hash, cb)) this.emit('missing', hash); - } }; Chain.prototype.isFull = function isFull() { diff --git a/lib/bcoin/peer.js b/lib/bcoin/peer.js index 4175c07a..88002256 100644 --- a/lib/bcoin/peer.js +++ b/lib/bcoin/peer.js @@ -93,7 +93,7 @@ Peer.prototype.broadcast = function broadcast(items) { items = [ items ]; var self = this; - items.forEach(function(item) { + var result = items.map(function(item) { var key = item.hash('hex'); var old = this._broadcast.map[key]; if (old) @@ -101,21 +101,32 @@ Peer.prototype.broadcast = function broadcast(items) { // Auto-cleanup broadcast map after timeout var entry = { + e: new EventEmitter(), timeout: setTimeout(function() { + entry.e.emit('timeout'); delete self._broadcast.map[key]; }, this._broadcast.timout), value: item }; this._broadcast.map[key] = entry; + + return entry.e; }, this); - this._write(this.framer.inv(items)); + this._write(this.framer.inv(items.map(function(item) { + return { + type: item.type, + hash: item.hash() + }; + }))); + + return result; }; Peer.prototype.updateWatch = function updateWatch() { if (this.ack) - this._write(this.framer.filterLoad(this.bloom, 'pubkeyOnly')); + this._write(this.framer.filterLoad(this.bloom, 'none')); }; Peer.prototype.destroy = function destroy() { @@ -224,6 +235,8 @@ Peer.prototype._onPacket = function onPacket(packet) { if (this._res(cmd, payload)) { return; } else { + if (cmd !== 'merkleblock') + console.log(cmd); this.emit(cmd, payload); } }; @@ -244,8 +257,9 @@ Peer.prototype._handleGetData = function handleGetData(items) { if (!this._broadcast.map[hash]) return; - var entry = this._broadcast.map[hash].value; - this._write(entry.render(this.framer)); + var entry = this._broadcast.map[hash]; + this._write(entry.value.render()); + entry.e.emit('request'); }, this); }; diff --git a/lib/bcoin/pool.js b/lib/bcoin/pool.js index 9fcda252..ae020954 100644 --- a/lib/bcoin/pool.js +++ b/lib/bcoin/pool.js @@ -13,8 +13,9 @@ function Pool(options) { EventEmitter.call(this); this.options = options || {}; - this.size = options.size || 8; + this.size = options.size || 16; this.parallel = options.parallel || 2000; + this.redundancy = 2; this.load = { timeout: options.loadTimeout || 10000, window: options.loadWindow || 250, @@ -27,7 +28,7 @@ function Pool(options) { this.requestTimeout = options.requestTimeout || 10000; this.chain = new bcoin.chain(); this.watchMap = {}; - this.bloom = new bcoin.bloom(8 * 10 * 1024, + this.bloom = new bcoin.bloom(8 * 1024, 10, (Math.random() * 0xffffffff) | 0), this.peers = { @@ -54,12 +55,13 @@ function Pool(options) { minDepth: options.minValidateDepth || 0, // getTx map - map: {}, + map: {} + }; - // Validation cache - reqs: new bcoin.utils.RequestCache(), - cache: [], - cacheSize: 1000 + // Currently broadcasted TXs + this.tx = { + list: [], + timeout: options.txTimeout || 60000 }; this.createConnection = options.createConnection; @@ -211,6 +213,12 @@ Pool.prototype._addPeer = function _addPeer() { } peer.updateWatch(); + + self.tx.list.forEach(function(entry) { + peer.broadcast(entry.tx)[0].once('request', function() { + entry.e.emit('ack'); + }); + }); }); peer.on('merkleblock', function(block) { @@ -287,6 +295,12 @@ Pool.prototype.unwatch = function unwatch(id) { }; Pool.prototype.search = function search(id, range) { + // Optional id argument + if (typeof id === 'object' && !Array.isArray(id) || + typeof id === 'number') { + range = id; + id = null; + } if (typeof id === 'string') id = utils.toArray(id, 'hex'); @@ -307,7 +321,8 @@ Pool.prototype.search = function search(id, range) { var hashes = this.chain.hashesInRange(range.start, range.end); var waiting = hashes.length; - this.watch(id); + if (id) + this.watch(id); hashes.slice().reverse().forEach(function(hash) { // Get the block that is in index @@ -318,7 +333,8 @@ Pool.prototype.search = function search(id, range) { waiting--; e.emit('progress', hashes.length - waiting, hashes.length); if (waiting === 0) { - self.unwatch(id); + if (id) + self.unwatch(id); e.emit('end'); } }); @@ -407,14 +423,18 @@ Pool.prototype._doRequests = function _doRequests() { if (above && below && this.load.hiReached) this._load(); - // Split list between nodes + // Split list between peers + var red = this.redundancy; var count = this.peers.block.length; - var split = Math.ceil(items.length / count); - for (var i = 0, off = 0; i < count; i++, off += split) { - var peer = this.peers.block[i]; - peer.getData(items.slice(off, off + split).map(function(item) { - return item.start(peer); - })); + var split = Math.ceil(items.length * red / count); + for (var i = 0, off = 0; i < count; i += red, off += split) { + var req = items.slice(off, off + split).map(function(item) { + return item.start(this.peers.block[i]); + }, this); + + for (var j = 0; j < red && i + j < count; j++) { + this.peers.block[i + j].getData(req); + } } }; @@ -478,21 +498,28 @@ Pool.prototype.getTx = function getTx(hash, range, cb) { } }; -Pool.prototype._addValidateCache = function addValidateCache(tx, result) { - this.validate.cache.push({ - hash: tx.hash('hex'), - result: result - }); - if (this.validate.cache.length > this.validate.cacheSize) - this.validate.cache = this.validate.cache.slice(-this.validate.cacheSize); -}; +Pool.prototype.sendTx = function sendTx(tx) { + var e = new EventEmitter(); -Pool.prototype._probeValidateCache = function probeValidateCache(tx) { - for (var i = 0; i < this.validate.cache.length; i++) { - var entry = this.validate.cache[i]; - if (entry.hash === tx.hash('hex')) - return entry.result; - } + var self = this; + var entry = { + tx: tx, + e: e, + timer: setTimeout(function() { + var i = self.tx.list.indexOf(entry); + if (i !== -1) + self.tx.list.splice(i, 1); + }, this.tx.timeout) + }; + this.tx.list.push(entry); + + this.peers.block.forEach(function(peer) { + peer.broadcast(tx)[0].once('request', function() { + e.emit('ack'); + }); + }); + + return e; }; function LoadRequest(pool, type, hash, cb) { diff --git a/lib/bcoin/tx.js b/lib/bcoin/tx.js index 8b8a9b92..38cf3705 100644 --- a/lib/bcoin/tx.js +++ b/lib/bcoin/tx.js @@ -22,12 +22,12 @@ function TX(data) { if (data.inputs) { data.inputs.forEach(function(input) { - this.input(input); + this.input(input, null, this === data); }, this); } if (data.outputs) { data.outputs.forEach(function(out) { - this.out(out); + this.out(out, null, this === data); }, this); } } @@ -38,21 +38,15 @@ TX.prototype.clone = function clone() { }; 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; + var h = utils.dsha256(this.render()); + return enc === 'hex' ? utils.toHex(h) : h; }; TX.prototype.render = function render() { return bcoin.protocol.framer.tx(this); }; -TX.prototype.input = function input(i, index) { +TX.prototype.input = function input(i, index, clone) { if (i instanceof TX) i = { tx: i, index: index }; @@ -70,14 +64,22 @@ TX.prototype.input = function input(i, index) { hash: hash, index: i.out ? i.out.index : i.index, }, - script: bcoin.script.decode(i.script), + script: clone ? i.script.slice() : bcoin.script.decode(i.script), seq: i.seq === undefined ? 0xffffffff : i.seq }); return this; }; -TX.prototype.out = function out(output, value) { +TX.prototype.inputTx = function inputTx(i, tx) { + if (!(tx instanceof TX)) + tx = new TX(tx); + + assert(i <= this.inputs.length); + this.inputs[i].out.tx = tx; +}; + +TX.prototype.out = function out(output, value, clone) { if (typeof output === 'string') { output = { address: output, @@ -85,7 +87,8 @@ TX.prototype.out = function out(output, value) { }; } - var script = bcoin.script.decode(output.script); + var script = clone ? output.script.slice() : + bcoin.script.decode(output.script); // Default script if given address if (output.address) { diff --git a/lib/bcoin/utils.js b/lib/bcoin/utils.js index 13837cf3..db4f0836 100644 --- a/lib/bcoin/utils.js +++ b/lib/bcoin/utils.js @@ -290,6 +290,7 @@ utils.nextTick = function nextTick(fn) { function RequestCache() { this.map = {}; + this.count = 0; }; utils.RequestCache = RequestCache; @@ -299,6 +300,7 @@ RequestCache.prototype.add = function add(id, cb) { return false; } else { this.map[id] = [ cb ]; + this.count++; return true; } }; @@ -309,6 +311,7 @@ RequestCache.prototype.fullfill = function fullfill(id, err, data) { var cbs = this.map[id]; delete this.map[id]; + this.count--; cbs.forEach(function(cb) { cb(err, data); }); diff --git a/lib/bcoin/wallet.js b/lib/bcoin/wallet.js index 85301144..6f5e6cbb 100644 --- a/lib/bcoin/wallet.js +++ b/lib/bcoin/wallet.js @@ -1,15 +1,45 @@ var assert = require('assert'); var bcoin = require('../bcoin'); +var hash = require('hash.js'); var utils = bcoin.utils; -function Wallet() { +function Wallet(options, passphrase) { if (!(this instanceof Wallet)) - return new Wallet(); + return new Wallet(options, passphrase); - this.key = bcoin.ecdsa.genKeyPair(); + // bcoin.wallet('scope', 'password') + if (typeof options === 'string' && typeof passphrase === 'string') { + options = { + scope: options, + passphrase: passphrase + }; + } + if (!options) + options = {}; + + this.key = null; + + if (options.passphrase) { + this.key = bcoin.ecdsa.genKeyPair({ + pers: options.scope, + entropy: hash.sha256().update(options.passphrase).digest() + }); + } else if (options.priv) { + this.key = bcoin.ecdsa.keyPair(options.priv); + } else { + this.key = bcoin.ecdsa.genKeyPair(); + } } module.exports = Wallet; +Wallet.prototype.getPrivateKey = function getPrivateKey() { + return this.key.getPrivate().toArray(); +}; + +Wallet.prototype.getPublicKey = function getPublicKey() { + return this.key.getPublic('array'); +}; + Wallet.prototype.getHash = function getHash() { var pub = this.key.getPublic('array'); return utils.ripesha(pub); @@ -52,14 +82,21 @@ Wallet.prototype.validateAddress = function validateAddress(addr) { Wallet.validateAddress = Wallet.prototype.validateAddress; Wallet.prototype.own = function own(tx) { - return tx.outputs.some(function(output) { - return output.script.length === 5 && - output.script[0] === 'dup' && - output.script[1] === 'hash160' && - utils.isEqual(output.script[2], this.getHash()) && - output.script[3] === 'eqverify' && - output.script[4] === 'checksig'; + var outputs = tx.outputs.filter(function(output) { + if (output.script.length < 5) + return false; + + var s = output.script.slice(-5); + return s[0] === 'dup' && + s[1] === 'hash160' && + utils.isEqual(s[2], this.getHash()) && + s[3] === 'eqverify' && + s[4] === 'checksig'; }, this); + if (outputs.length === 0) + return false; + + return outputs; }; Wallet.prototype.sign = function sign(tx, type) { diff --git a/package.json b/package.json index 1fab6dea..e24d24b5 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "dependencies": { "async": "^0.8.0", "bn.js": "^0.3.0", - "elliptic": "^0.8.0", + "elliptic": "^0.9.0", "hash.js": "^0.2.0" }, "devDependencies": { diff --git a/test/tx-test.js b/test/tx-test.js index 7361ec04..e8312a77 100644 --- a/test/tx-test.js +++ b/test/tx-test.js @@ -48,8 +48,7 @@ describe('TX', function() { it('should be verifiable', function() { var tx = bcoin.tx(parser.parseTx(bcoin.utils.toArray(raw, 'hex'))); - tx.inputs[0].out.tx = - bcoin.tx(parser.parseTx(bcoin.utils.toArray(inp, 'hex'))); + tx.inputTx(0, bcoin.tx(parser.parseTx(bcoin.utils.toArray(inp, 'hex')))); assert(tx.verify()); }); diff --git a/test/wallet-test.js b/test/wallet-test.js index fb9ddd9d..1562d7b8 100644 --- a/test/wallet-test.js +++ b/test/wallet-test.js @@ -1,4 +1,5 @@ var assert = require('assert'); +var bn = require('bn.js'); var bcoin = require('../'); describe('Wallet', function() { @@ -25,9 +26,15 @@ describe('Wallet', function() { outputs: [{ value: 5460 * 2, address: w.getAddress() + }, { + value: 5460 * 2, + address: w.getAddress() + 'x' }] }); assert(w.own(src)); + assert.equal(w.own(src).reduce(function(acc, out) { + return acc.iadd(out.value); + }, new bn(0)).toString(10), 5460 * 2); var tx = bcoin.tx() .input(src, 0)