diff --git a/Makefile b/Makefile index f0d4d487..9efdbe03 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,5 @@ all: @npm run browserify - @npm run uglify clean: @npm run clean diff --git a/bench/walletdb.js b/bench/walletdb.js index e01f0e74..d4d7f6a0 100644 --- a/bench/walletdb.js +++ b/bench/walletdb.js @@ -7,6 +7,7 @@ var utils = bcoin.utils; var assert = require('assert'); var scriptTypes = constants.scriptTypes; var bench = require('./bench'); +var co = require('../lib/utils/spawn').co; bcoin.cache(); @@ -35,115 +36,89 @@ var walletdb = new bcoin.walletdb({ // db: 'leveldb' db: 'memory' }); -var wallet; -var addrs = []; -function runBench(callback) { - utils.serial([ - function(next) { - walletdb.create(function(err, w) { - assert.ifError(err); - wallet = w; - next(); - }); - }, - function(next) { - var end = bench('accounts'); - utils.forRange(0, 1000, function(i, next) { - wallet.createAccount({}, function(err, account) { - assert.ifError(err); - addrs.push(account.receiveAddress.getAddress()); - next(); - }); - }, function(err) { - assert.ifError(err); - end(1000); - next(); - }); - }, - function(next) { - var end = bench('addrs'); - utils.forRange(0, 1000, function(i, next) { - utils.forRange(0, 10, function(j, next) { - wallet.createReceive(i, function(err, addr) { - assert.ifError(err); - addrs.push(addr); - next(); - }); - }, next); - }, function(err) { - assert.ifError(err); - end(1000 * 10); - next(); - }); - }, - function(next) { - var nonce = new bn(0); - var end; - utils.forRange(0, 10000, function(i, next) { - var t1 = bcoin.mtx() - .addOutput(addrs[(i + 0) % addrs.length], 50460) - .addOutput(addrs[(i + 1) % addrs.length], 50460) - .addOutput(addrs[(i + 2) % addrs.length], 50460) - .addOutput(addrs[(i + 3) % addrs.length], 50460); +var runBench = co(function* runBench() { + var i, j, wallet, addrs, jobs, end; + var result, nonce, tx, options; - t1.addInput(dummyInput); - nonce.addn(1); - t1.inputs[0].script.set(0, nonce); - t1.inputs[0].script.compile(); + // Open and Create + yield walletdb.open(); + wallet = yield walletdb.create(); + addrs = []; - walletdb.addTX(t1.toTX(), function(err) { - assert.ifError(err); - next(); - }); - }, function(err) { - assert.ifError(err); - end(10000); - next(); - }); - end = bench('tx'); - }, - function(next) { - var end = bench('balance'); - wallet.getBalance(function(err, balance) { - assert.ifError(err); - end(1); - next(); - }); - }, - function(next) { - var end = bench('coins'); - wallet.getCoins(function(err) { - assert.ifError(err); - end(1); - next(); - }); - }, - function(next) { - var end = bench('create'); - var options = { - rate: 10000, - outputs: [{ - value: 50460, - address: addrs[0] - }] - }; - wallet.createTX(options, function(err) { - assert.ifError(err); - end(1); - next(); - }); - } - ], function(err) { - assert.ifError(err); - callback(); - }); -} + // Accounts + jobs = []; + for (i = 0; i < 1000; i++) + jobs.push(wallet.createAccount({})); -walletdb.open(function(err) { - assert.ifError(err); - runBench(function(err) { - assert.ifError(err); - process.exit(0); + end = bench('accounts'); + result = yield Promise.all(jobs); + end(1000); + + for (i = 0; i < result.length; i++) + addrs.push(result[i].receiveAddress.getAddress()); + + // Addresses + jobs = []; + for (i = 0; i < 1000; i++) { + for (j = 0; j < 10; j++) + jobs.push(wallet.createReceive(i)); + } + + end = bench('addrs'); + result = yield Promise.all(jobs); + end(1000 * 10); + + for (i = 0; i < result.length; i++) + addrs.push(result[i].getAddress()); + + // TX + jobs = []; + nonce = new bn(0); + for (i = 0; i < 10000; i++) { + tx = bcoin.mtx() + .addOutput(addrs[(i + 0) % addrs.length], 50460) + .addOutput(addrs[(i + 1) % addrs.length], 50460) + .addOutput(addrs[(i + 2) % addrs.length], 50460) + .addOutput(addrs[(i + 3) % addrs.length], 50460); + + tx.addInput(dummyInput); + nonce.addn(1); + tx.inputs[0].script.set(0, nonce); + tx.inputs[0].script.compile(); + + jobs.push(walletdb.addTX(tx.toTX())); + } + + end = bench('tx'); + result = yield Promise.all(jobs); + end(10000); + + // Balance + end = bench('balance'); + result = yield wallet.getBalance(); + end(1); + + // Coins + end = bench('coins'); + result = yield wallet.getCoins(); + end(1); + + // Create + end = bench('create'); + options = { + rate: 10000, + outputs: [{ + value: 50460, + address: addrs[0] + }] + }; + yield wallet.createTX(options); + end(1); +}); + +runBench().then(process.exit).catch(function(err) { + utils.nextTick(function() { + throw err; }); }); diff --git a/bin/node b/bin/node index 2434d4a6..5550be7a 100755 --- a/bin/node +++ b/bin/node @@ -32,16 +32,7 @@ process.on('uncaughtException', function(err) { process.exit(1); }); -process.on('unhandledRejection', function(err, promise) { - node.logger.debug('Unhandled Rejection'); - node.logger.debug(err.stack); - node.logger.error(err); - process.exit(1); -}); - node.open().then(function() { node.pool.connect(); node.startSync(); -}).catch(function(e) { - throw e; }); diff --git a/bin/spvnode b/bin/spvnode index 8f475aae..a2d3c2fc 100755 --- a/bin/spvnode +++ b/bin/spvnode @@ -31,13 +31,6 @@ process.on('uncaughtException', function(err) { process.exit(1); }); -process.on('unhandledRejection', function(err, promise) { - node.logger.debug('Unhandled Rejection'); - node.logger.debug(err.stack); - node.logger.error(err); - process.exit(1); -}); - node.open().then(function() { if (process.argv.indexOf('--test') !== -1) { node.pool.watchAddress('1VayNert3x1KzbpzMGt2qdqrAThiRovi8'); @@ -50,6 +43,4 @@ node.open().then(function() { } node.startSync(); -}).catch(function(err) { - throw err; }); diff --git a/browser/index.html b/browser/index.html index 9f794b91..810e63b0 100644 --- a/browser/index.html +++ b/browser/index.html @@ -92,220 +92,6 @@ more bitcoin magic).
- + diff --git a/browser/index.js b/browser/index.js new file mode 100644 index 00000000..f399b668 --- /dev/null +++ b/browser/index.js @@ -0,0 +1,216 @@ +;(function() { + +'use strict'; + +var utils = bcoin.utils; +var body = document.getElementsByTagName('body')[0]; +var log = document.getElementById('log'); +var wdiv = document.getElementById('wallet'); +var tdiv = document.getElementById('tx'); +var floating = document.getElementById('floating'); +var send = document.getElementById('send'); +var newaddr = document.getElementById('newaddr'); +var chainState = document.getElementById('state'); +var cb = bcoin.spawn.cb; +var items = []; +var scrollback = 0; +var logger, node, options; + +body.onmouseup = function() { + floating.style.display = 'none'; +}; + +floating.onmouseup = function(ev) { + ev.stopPropagation(); + return false; +}; + +function show(obj) { + floating.innerHTML = escape(utils.inspectify(obj, false)); + floating.style.display = 'block'; +} + +logger = new bcoin.logger({ level: 'debug' }); +logger.writeConsole = function(level, args) { + var msg = utils.format(args, false); + if (++scrollback > 1000) { + log.innerHTML = ''; + scrollback = 1; + } + log.innerHTML += '' + utils.now() + ' '; + if (level === 'error') + log.innerHTML += '[' + level + '] '; + else + log.innerHTML += '[' + level + '] '; + log.innerHTML += escape(msg) + '\n'; + log.scrollTop = log.scrollHeight; +}; + +send.onsubmit = function(ev) { + var value = document.getElementById('amount').value; + var address = document.getElementById('address').value; + + var options = { + outputs: [{ + address: address, + value: utils.satoshi(value) + }] + }; + + cb(node.wallet.createTX(options), function(err, tx) { + if (err) + return node.logger.error(err); + + cb(node.wallet.sign(tx), function(err) { + if (err) + return node.logger.error(err); + + cb(node.sendTX(tx), function(err) { + if (err) + return node.logger.error(err); + + show(tx); + }); + }); + }); + + ev.preventDefault(); + ev.stopPropagation(); + return false; +}; + +newaddr.onmouseup = function() { + cb(node.wallet.createReceive(), function(err) { + if (err) + throw err; + formatWallet(node.wallet); + }); +}; + +function kb(size) { + size /= 1000; + return size.toFixed(2) + 'kb'; +} + +function create(html) { + var el = document.createElement('div'); + el.innerHTML = html; + return el.firstChild; +} + +function escape(html, encode) { + return html + .replace(!encode ? /&(?!#?\w+;)/g : /&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); +} + +function addItem(tx) { + var el; + + if (items.length === 20) { + el = items.shift(); + tdiv.removeChild(el); + el.onmouseup = null; + } + + el = create('' + tx.rhash + ' (' + tx.height + + ' - ' + kb(tx.getSize()) + ')'); + tdiv.appendChild(el); + + el.onmouseup = function(ev) { + show(tx); + ev.stopPropagation(); + return false; + }; + + items.push(el); + + chainState.innerHTML = '' + + 'tx=' + node.chain.db.state.tx + + ' coin=' + node.chain.db.state.coin + + ' value=' + utils.btc(node.chain.db.state.value); +} + +function formatWallet(wallet) { + var html = ''; + var key = wallet.master.toJSON().key; + html += 'Wallet
'; + if (bcoin.network.get().type === 'segnet4') { + html += 'Current Address (p2wpkh): ' + wallet.getAddress() + '
'; + html += 'Current Address (p2wpkh behind p2sh): ' + wallet.getProgramAddress() + '
'; + } else { + html += 'Current Address: ' + wallet.getAddress() + '
'; + } + html += 'Extended Private Key: ' + key.xprivkey + '
'; + html += 'Mnemonic: ' + key.mnemonic.phrase + '
'; + cb(wallet.getBalance(), function(err, balance) { + if (err) + throw err; + + html += 'Confirmed Balance: ' + utils.btc(balance.confirmed) + '
'; + html += 'Unconfirmed Balance: ' + utils.btc(balance.unconfirmed) + '
'; + html += 'Balance: ' + utils.btc(balance.total) + '
'; + + cb(wallet.getHistory(), function(err, txs) { + if (err) + throw err; + + cb(wallet.toDetails(txs), function(err, txs) { + if (err) + throw err; + + html += 'TXs:\n'; + wdiv.innerHTML = html; + + txs.forEach(function(tx) { + var el = create('' + tx.hash + ''); + wdiv.appendChild(el); + el.onmouseup = function(ev) { + show(tx.toJSON()); + ev.stopPropagation(); + return false; + }; + }); + }); + }); + }); +} + +options = bcoin.config({ + query: true, + network: 'segnet4', + db: 'leveldb', + useWorkers: true, + coinCache: true, + logger: logger +}); + +bcoin.set(options); + +node = new bcoin.fullnode(options); + +node.on('error', function(err) { + ; +}); + +node.chain.on('block', addItem); +node.mempool.on('tx', addItem); + +cb(node.open(), function(err) { + if (err) + throw err; + + node.startSync(); + + formatWallet(node.wallet); + + node.wallet.on('update', function() { + formatWallet(node.wallet); + }); +}); + +})(); diff --git a/browser/server.js b/browser/server.js index dc8d0817..ab271791 100644 --- a/browser/server.js +++ b/browser/server.js @@ -15,22 +15,27 @@ proxy.on('error', function(err) { }); var index = fs.readFileSync(__dirname + '/index.html'); +var indexjs = fs.readFileSync(__dirname + '/index.js'); var bcoin = fs.readFileSync(__dirname + '/bcoin.js'); var worker = fs.readFileSync(__dirname + '/../lib/workers/worker.js'); -server.get('/favicon.ico', function(req, res, next, send) { +server.get('/favicon.ico', function(req, res, send, next) { send(404, '', 'text'); }); -server.get('/', function(req, res, next, send) { +server.get('/', function(req, res, send, next) { send(200, index, 'html'); }); -server.get('/bcoin.js', function(req, res, next, send) { +server.get('/index.js', function(req, res, send, next) { + send(200, indexjs, 'js'); +}); + +server.get('/bcoin.js', function(req, res, send, next) { send(200, bcoin, 'js'); }); -server.get('/bcoin-worker.js', function(req, res, next, send) { +server.get('/bcoin-worker.js', function(req, res, send, next) { send(200, worker, 'js'); }); diff --git a/lib/utils/spawn.js b/lib/utils/spawn.js index 80afc313..05e849f9 100644 --- a/lib/utils/spawn.js +++ b/lib/utils/spawn.js @@ -53,12 +53,12 @@ function exec(gen) { * Execute generator function * with a context and execute. * @param {GeneratorFunction} generator - * @param {Object} self + * @param {Object} ctx * @returns {Promise} */ -function spawn(generator, self) { - var gen = generator.call(self); +function spawn(generator, ctx) { + var gen = generator.call(ctx); return exec(gen); } @@ -80,7 +80,8 @@ function co(generator) { /** * Wrap a generator function to be * executed into a function that - * returns a promise. + * returns a promise (with a lock). + * @param {String} name - lock name. * @param {GeneratorFunction} * @returns {Function} */ @@ -115,7 +116,7 @@ function cob(generator) { if (arguments.length === 0 || typeof arguments[arguments.length - 1] !== 'function') { - throw new Error('Function must accept a callback.'); + throw new Error((generator.name || 'Function') + ' requires a callback.'); } args = new Array(arguments.length - 1); @@ -126,12 +127,7 @@ function cob(generator) { gen = generator.apply(this, args); - return cb(exec(gen), function(err, result) { - // Escape the promise's scope: - utils.nextTick(function() { - callback(err, result); - }); - }); + return cb(exec(gen), callback); }; } @@ -150,7 +146,7 @@ function con(generator) { if (arguments.length === 0 || typeof arguments[arguments.length - 1] !== 'function') { - throw new Error('Function must accept a callback.'); + throw new Error((generator.name || 'Function') + ' requires a callback.'); } args = new Array(arguments.length); @@ -179,9 +175,13 @@ function con(generator) { function cb(promise, callback) { promise.then(function(value) { - callback(null, value); + utils.nextTick(function() { + callback(null, value); + }); }, function(err) { - callback(err); + utils.nextTick(function() { + callback(err); + }); }); } @@ -204,9 +204,7 @@ function wait() { function timeout(time) { return new Promise(function(resolve, reject) { - setTimeout(function() { - resolve(); - }, time); + setTimeout(resolve, time); }); } @@ -220,8 +218,10 @@ function timeout(time) { function wrap(resolve, reject) { return function(err, result) { - if (err) - return reject(err); + if (err) { + reject(err); + return; + } resolve(result); }; } @@ -234,19 +234,16 @@ function wrap(resolve, reject) { */ function call(func) { - var args = new Array(Math.max(0, arguments.length - 1)); + var self = this; + var args = new Array(arguments.length); var i; for (i = 1; i < arguments.length; i++) args[i] = arguments[i]; return new Promise(function(resolve, reject) { - args.push(function(err, result) { - if (err) - return reject(err); - resolve(result); - }); - func.apply(null, args); + args.push(wrap(resolve, reject)); + func.apply(self, args); }); } @@ -255,29 +252,30 @@ function call(func) { * style callbacks into a function that * returns a promise. * @param {Function} func - * @param {Object?} self + * @param {Object?} ctx * @returns {Function} */ -function promisify(func, self) { +function promisify(func, ctx) { return function() { - var args = new Array(arguments.length); - var i; - - for (i = 0; i < args.length; i++) - args[i] = arguments[i]; - - return new Promise(function(resolve, reject) { - args.push(function(err, result) { - if (err) - return reject(err); - resolve(result); - }); - func.apply(self, args); - }); + return call.call(ctx, arguments); }; } +/* + * This drives me nuts. + */ + +if (typeof window !== 'undefined') { + window.onunhandledrejection = function(event) { + throw event.reason; + }; +} else { + process.on('unhandledRejection', function(err, promise) { + throw err; + }); +} + /* * Expose */ diff --git a/lib/wallet/wallet.js b/lib/wallet/wallet.js index a417bdbb..792ea13d 100644 --- a/lib/wallet/wallet.js +++ b/lib/wallet/wallet.js @@ -1435,7 +1435,7 @@ Wallet.prototype.getTX = function getTX(hash) { */ Wallet.prototype.addTX = function addTX(tx) { - this.db.addTX(tx); + return this.db.addTX(tx); }; /**