From ae83aa6fba2c05c4656fd63639c59ead3f7cd70d Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Thu, 22 Sep 2016 00:24:59 -0700 Subject: [PATCH] refactor: more work. --- bin/cli | 748 +++++++++++++++++++--------------------- lib/chain/chain.js | 2 +- lib/chain/chainentry.js | 2 +- lib/crypto/crypto.js | 103 +++--- lib/db/lowlevelup.js | 26 +- lib/http/rpc.js | 4 +- lib/mempool/mempool.js | 2 +- lib/miner/miner.js | 86 +++-- lib/net/peer.js | 394 +++++++++++---------- lib/net/pool.js | 10 +- lib/utils/async.js | 2 +- lib/utils/spawn.js | 184 +++++++++- lib/utils/utils.js | 22 -- lib/workers/workers.js | 2 +- test/chain-test.js | 20 +- test/http-test.js | 17 +- test/mempool-test.js | 17 +- test/wallet-test.js | 17 +- 18 files changed, 841 insertions(+), 817 deletions(-) diff --git a/bin/cli b/bin/cli index 5292354b..ec507552 100755 --- a/bin/cli +++ b/bin/cli @@ -27,159 +27,135 @@ CLI.prototype.log = function log(json) { console.log(JSON.stringify(json, null, 2)); }; -CLI.prototype.createWallet = function createWallet() { - return spawn(function *() { - var options = { id: this.argv[0] }; - var wallet; +CLI.prototype.createWallet = co(function* createWallet() { + var options = { id: this.argv[0] }; + var wallet; - if (this.config.type) - options.type = this.config.type; + if (this.config.type) + options.type = this.config.type; - if (this.config.master) - options.master = this.config.master; + if (this.config.master) + options.master = this.config.master; - if (this.config.key) - options.key = this.config.key; + if (this.config.key) + options.key = this.config.key; - if (this.config.m) - options.m = this.config.m >>> 0; + if (this.config.m) + options.m = this.config.m >>> 0; - if (this.config.n) - options.n = this.config.n >>> 0; + if (this.config.n) + options.n = this.config.n >>> 0; - if (this.config.witness != null) - options.witness = !!this.config.witness; + if (this.config.witness != null) + options.witness = !!this.config.witness; - if (this.config.passphrase) - options.passphrase = this.config.passphrase; + if (this.config.passphrase) + options.passphrase = this.config.passphrase; - wallet = yield this.client.createWallet(options); - this.log(wallet); - }, this); -}; + wallet = yield this.client.createWallet(options); + this.log(wallet); +}); -CLI.prototype.addKey = function addKey() { - return spawn(function *() { - var key = this.argv[0]; - yield this.wallet.addKey(this.config.account, key); - this.log('added'); - }, this); -}; +CLI.prototype.addKey = co(function* addKey() { + var key = this.argv[0]; + yield this.wallet.addKey(this.config.account, key); + this.log('added'); +}); -CLI.prototype.removeKey = function removeKey() { - return spawn(function *() { - var key = this.argv[0]; - yield this.wallet.removeKey(this.config.account, key); - this.log('removed'); - }, this); -}; +CLI.prototype.removeKey = co(function* removeKey() { + var key = this.argv[0]; + yield this.wallet.removeKey(this.config.account, key); + this.log('removed'); +}); -CLI.prototype.getAccount = function getAccount() { - return spawn(function *() { - var account = this.argv[0] || this.config.account; - yield this.wallet.getAccount(account); - this.log(account); - }, this); -}; +CLI.prototype.getAccount = co(function* getAccount() { + var account = this.argv[0] || this.config.account; + yield this.wallet.getAccount(account); + this.log(account); +}); -CLI.prototype.createAccount = function createAccount() { - return spawn(function *() { - var name = this.argv[0]; - var account = yield this.wallet.createAccount(name); - this.log(account); - }, this); -}; +CLI.prototype.createAccount = co(function* createAccount() { + var name = this.argv[0]; + var account = yield this.wallet.createAccount(name); + this.log(account); +}); -CLI.prototype.createAddress = function createAddress() { - return spawn(function *() { - var account = this.argv[0]; - var addr = yield this.wallet.createAddress(account); - this.log(addr); - }, this); -}; +CLI.prototype.createAddress = co(function* createAddress() { + var account = this.argv[0]; + var addr = yield this.wallet.createAddress(account); + this.log(addr); +}); -CLI.prototype.getAccounts = function getAccounts() { - return spawn(function *() { - var accounts = yield this.wallet.getAccounts(); - this.log(accounts); - }, this); -}; +CLI.prototype.getAccounts = co(function* getAccounts() { + var accounts = yield this.wallet.getAccounts(); + this.log(accounts); +}); -CLI.prototype.getWallet = function getWallet() { - return spawn(function *() { - var info = yield this.wallet.getInfo(); - this.log(wallet); - }, this); -}; +CLI.prototype.getWallet = co(function* getWallet() { + var info = yield this.wallet.getInfo(); + this.log(wallet); +}); -CLI.prototype.getTX = function getTX() { - return spawn(function *() { - var hash = this.argv[0]; - var txs, tx; +CLI.prototype.getTX = co(function* getTX() { + var hash = this.argv[0]; + var txs, tx; - if (utils.isBase58(hash)) { - txs = yield this.client.getTXByAddress(hash); - this.log(txs); - return; - } - - tx = yield this.client.getTX(hash); - - if (!tx) { - this.log('TX not found.'); - return; - } - - this.log(tx); - }, this); -}; - -CLI.prototype.getBlock = function getBlock() { - return spawn(function *() { - var hash = this.argv[0]; - if (hash.length !== 64) - hash = +hash; - - block = yield this.client.getBlock(hash); - - if (!block) { - this.log('Block not found.'); - return - } - - this.log(block); - }, this); -}; - -CLI.prototype.getCoin = function getCoin() { - return spawn(function *() { - var hash = this.argv[0]; - var index = this.argv[1]; - var coins, coin; - - if (utils.isBase58(hash)) { - coins = yield this.client.getCoinsByAddress(hash); - this.log(coins); - return; - } - - coin = yield this.client.getCoin(hash, index); - - if (!coin) { - this.log('Coin not found.'); - return; - } - - this.log(coin); - }, this); -}; - -CLI.prototype.getWalletHistory = function getWalletHistory() { - return spawn(function *() { - var txs = yield this.wallet.getHistory(this.config.account); + if (utils.isBase58(hash)) { + txs = yield this.client.getTXByAddress(hash); this.log(txs); - }, this); -}; + return; + } + + tx = yield this.client.getTX(hash); + + if (!tx) { + this.log('TX not found.'); + return; + } + + this.log(tx); +}); + +CLI.prototype.getBlock = co(function* getBlock() { + var hash = this.argv[0]; + if (hash.length !== 64) + hash = +hash; + + block = yield this.client.getBlock(hash); + + if (!block) { + this.log('Block not found.'); + return + } + + this.log(block); +}); + +CLI.prototype.getCoin = co(function* getCoin() { + var hash = this.argv[0]; + var index = this.argv[1]; + var coins, coin; + + if (utils.isBase58(hash)) { + coins = yield this.client.getCoinsByAddress(hash); + this.log(coins); + return; + } + + coin = yield this.client.getCoin(hash, index); + + if (!coin) { + this.log('Coin not found.'); + return; + } + + this.log(coin); +}); + +CLI.prototype.getWalletHistory = co(function* getWalletHistory() { + var txs = yield this.wallet.getHistory(this.config.account); + this.log(txs); +}); CLI.prototype.listenWallet = function listenWallet() { var self = this; @@ -210,283 +186,255 @@ CLI.prototype.listenWallet = function listenWallet() { return new Promise(function() {}); }; -CLI.prototype.getBalance = function getBalance() { - return spawn(function *() { - var balance = yield this.wallet.getBalance(this.config.account); - this.log(balance); - }, this); -}; +CLI.prototype.getBalance = co(function* getBalance() { + var balance = yield this.wallet.getBalance(this.config.account); + this.log(balance); +}); -CLI.prototype.getMempool = function getMempool() { - return spawn(function *() { - var txs = yield this.client.getMempool(); - this.log(txs); - }, this); -}; +CLI.prototype.getMempool = co(function* getMempool() { + var txs = yield this.client.getMempool(); + this.log(txs); +}); -CLI.prototype.sendTX = function sendTX() { - return spawn(function *() { - var output = {}; - var options, tx; +CLI.prototype.sendTX = co(function* sendTX() { + var output = {}; + var options, tx; - if (this.config.script) { - output.script = this.config.script; - output.value = utils.satoshi(this.config.value || this.argv[0]); - } else { - output.address = this.config.address || this.argv[0]; - output.value = utils.satoshi(this.config.value || this.argv[1]); + if (this.config.script) { + output.script = this.config.script; + output.value = utils.satoshi(this.config.value || this.argv[0]); + } else { + output.address = this.config.address || this.argv[0]; + output.value = utils.satoshi(this.config.value || this.argv[1]); + } + + options = { + account: this.config.account, + passphrase: this.config.passphrase, + outputs: [output] + }; + + tx = yield this.wallet.send(options); + + this.log(tx); +}); + +CLI.prototype.createTX = co(function* createTX() { + var output = {}; + var options, tx; + + if (this.config.script) { + output.script = this.config.script; + output.value = utils.satoshi(this.config.value || this.argv[0]); + } else { + output.address = this.config.address || this.argv[0]; + output.value = utils.satoshi(this.config.value || this.argv[1]); + } + + options = { + account: this.config.account, + passphrase: this.config.passphrase, + outputs: [output] + }; + + tx = yield this.wallet.createTX(options); + + this.log(tx); +}); + +CLI.prototype.signTX = co(function* signTX() { + var options = { passphrase: this.config.passphrase }; + var raw = options.tx || this.argv[0]; + var tx = yield this.wallet.sign(raw, options); + this.log(tx); +}); + +CLI.prototype.zap = co(function* zap() { + var age = (this.config.age >>> 0) || 72 * 60 * 60; + yield this.wallet.zap(this.config.account, age); + this.log('Zapped!'); +}); + +CLI.prototype.broadcast = co(function* broadcast() { + var self = this; + var raw = this.argv[0] || this.config.tx; + var tx = yield this.client.broadcast(raw); + this.log('Broadcasted:'); + this.log(tx); +}); + +CLI.prototype.viewTX = co(function* viewTX() { + var raw = this.argv[0] || this.config.tx; + var tx = yield this.wallet.fill(raw); + this.log(tx); +}); + +CLI.prototype.getDetails = co(function* getDetails() { + var hash = this.argv[0]; + var details = yield this.wallet.getTX(hash); + this.log(details); +}); + +CLI.prototype.retoken = co(function* retoken() { + var result = yield this.wallet.retoken(); + this.log(result); +}); + +CLI.prototype.rpc = co(function* rpc() { + var method = this.argv.shift(); + var params = []; + var i, arg, param, result; + + for (i = 0; i < this.argv.length; i++) { + arg = this.argv[i]; + try { + param = JSON.parse(arg); + } catch (e) { + param = arg; } + params.push(param); + } - options = { - account: this.config.account, - passphrase: this.config.passphrase, - outputs: [output] - }; + result = yield this.client.rpc.call(method, params); - tx = yield this.wallet.send(options); + this.log(result); +}); - this.log(tx); - }, this); -}; +CLI.prototype.handleWallet = co(function* handleWallet() { + var options = { + id: this.config.id || 'primary', + token: this.config.token + }; -CLI.prototype.createTX = function createTX() { - return spawn(function *() { - var output = {}; - var options, tx; + this.wallet = new Wallet({ + uri: this.config.url || this.config.uri, + apiKey: this.config.apikey, + network: this.config.network + }); - if (this.config.script) { - output.script = this.config.script; - output.value = utils.satoshi(this.config.value || this.argv[0]); - } else { - output.address = this.config.address || this.argv[0]; - output.value = utils.satoshi(this.config.value || this.argv[1]); - } + yield this.wallet.open(options); - options = { - account: this.config.account, - passphrase: this.config.passphrase, - outputs: [output] - }; - - tx = yield this.wallet.createTX(options); - - this.log(tx); - }, this); -}; - -CLI.prototype.signTX = function signTX() { - return spawn(function *() { - var options = { passphrase: this.config.passphrase }; - var raw = options.tx || this.argv[0]; - var tx = yield this.wallet.sign(raw, options); - this.log(tx); - }, this); -}; - -CLI.prototype.zap = function zap() { - return spawn(function *() { - var age = (this.config.age >>> 0) || 72 * 60 * 60; - yield this.wallet.zap(this.config.account, age); - this.log('Zapped!'); - }, this); -}; - -CLI.prototype.broadcast = function broadcast() { - return spawn(function *() { - var self = this; - var raw = this.argv[0] || this.config.tx; - var tx = yield this.client.broadcast(raw); - this.log('Broadcasted:'); - this.log(tx); - }, this); -}; - -CLI.prototype.viewTX = function viewTX() { - return spawn(function *() { - var raw = this.argv[0] || this.config.tx; - var tx = yield this.wallet.fill(raw); - this.log(tx); - }, this); -}; - -CLI.prototype.getDetails = function getDetails() { - return spawn(function *() { - var hash = this.argv[0]; - var details = yield this.wallet.getTX(hash); - this.log(details); - }, this); -}; - -CLI.prototype.retoken = function retoken() { - return spawn(function *() { - var result = yield this.wallet.retoken(); - this.log(result); - }, this); -}; - -CLI.prototype.rpc = function rpc() { - return spawn(function *() { - var method = this.argv.shift(); - var params = []; - var i, arg, param, result; - - for (i = 0; i < this.argv.length; i++) { - arg = this.argv[i]; - try { - param = JSON.parse(arg); - } catch (e) { - param = arg; - } - params.push(param); - } - - result = yield this.client.rpc.call(method, params); - - this.log(result); - }, this); -}; - -CLI.prototype.handleWallet = function handleWallet() { - return spawn(function *() { - var options = { - id: this.config.id || 'primary', - token: this.config.token - }; - - this.wallet = new Wallet({ - uri: this.config.url || this.config.uri, - apiKey: this.config.apikey, - network: this.config.network - }); - - yield this.wallet.open(options); - - switch (this.argv.shift()) { - case 'listen': - return yield this.listenWallet(); - case 'get': - return yield this.getWallet(); - case 'addkey': - return yield this.addKey(); - case 'rmkey': - return yield this.removeKey(); - case 'balance': - return yield this.getBalance(); - case 'history': - return yield this.getWalletHistory(); - case 'account': - if (this.argv[0] === 'list') { - this.argv.shift(); - return yield this.getAccounts(); - } - if (this.argv[0] === 'create') { - this.argv.shift(); - return yield this.createAccount(); - } - if (this.argv[0] === 'get') - this.argv.shift(); - return yield this.getAccount(); - case 'address': - return yield this.createAddress(); - case 'retoken': - return yield this.retoken(); - case 'sign': - return yield this.signTX(); - case 'mktx': - return yield this.createTX(); - case 'send': - return yield this.sendTX(); - case 'zap': - return yield this.zap(); - case 'tx': - return yield this.getDetails(); - case 'view': - return yield this.viewTX(); - default: - this.log('Unrecognized command.'); - this.log('Commands:'); - this.log(' $ listen: Listen for events.'); - this.log(' $ get: View wallet.'); - this.log(' $ addkey [xpubkey]: Add key to wallet.'); - this.log(' $ rmkey [xpubkey]: Remove key from wallet.'); - this.log(' $ balance: Get wallet balance.'); - this.log(' $ history: View wallet TX history.'); - this.log(' $ account list: List account names.'); - this.log(' $ account create [account-name]: Create account.'); - this.log(' $ account get [account-name]: Get account details.'); - this.log(' $ address: Derive new address.'); - this.log(' $ retoken: Create new api key.'); - this.log(' $ send [address] [value]: Send transaction.'); - this.log(' $ mktx [address] [value]: Create transaction.'); - this.log(' $ sign [tx-hex]: Sign transaction.'); - this.log(' $ zap --age [age]: Zap pending wallet TXs.'); - this.log(' $ tx [hash]: View transaction details.'); - this.log(' $ view [tx-hex]: Parse and view transaction.'); - this.log('Other Options:'); - this.log(' --passphrase [passphrase]: For signing and account creation.'); - this.log(' --account [account-name]: Account name.'); - return; - } - }, this); -}; - -CLI.prototype.handleNode = function handleNode() { - return spawn(function *() { - var info; - - this.client = new Client({ - uri: this.config.url || this.config.uri, - apiKey: this.config.apikey, - network: this.config.network - }); - - info = yield this.client.getInfo(); - - switch (this.argv.shift()) { - case 'mkwallet': - return yield this.createWallet(); - case 'broadcast': - return yield this.broadcast(); - case 'mempool': - return yield this.getMempool(); - case 'tx': - return yield this.getTX(); - case 'coin': - return yield this.getCoin(); - case 'block': - return yield this.getBlock(); - case 'rpc': - return yield this.rpc(); - default: - this.log('Unrecognized command.'); - this.log('Commands:'); - this.log(' $ wallet create [id]: Create wallet.'); - this.log(' $ broadcast [tx-hex]: Broadcast transaction.'); - this.log(' $ mempool: Get mempool snapshot.'); - this.log(' $ tx [hash/address]: View transactions.'); - this.log(' $ coin [hash+index/address]: View coins.'); - this.log(' $ block [hash/height]: View block.'); - return; - } - }, this); -}; - -CLI.prototype.open = function open() { - return spawn(function *() { - switch (this.argv[0]) { - case 'w': - case 'wallet': + switch (this.argv.shift()) { + case 'listen': + return yield this.listenWallet(); + case 'get': + return yield this.getWallet(); + case 'addkey': + return yield this.addKey(); + case 'rmkey': + return yield this.removeKey(); + case 'balance': + return yield this.getBalance(); + case 'history': + return yield this.getWalletHistory(); + case 'account': + if (this.argv[0] === 'list') { this.argv.shift(); - if (this.argv[0] === 'create') { - this.argv[0] = 'mkwallet'; - return yield this.handleNode(); - } - return yield this.handleWallet(); - default: + return yield this.getAccounts(); + } + if (this.argv[0] === 'create') { + this.argv.shift(); + return yield this.createAccount(); + } + if (this.argv[0] === 'get') + this.argv.shift(); + return yield this.getAccount(); + case 'address': + return yield this.createAddress(); + case 'retoken': + return yield this.retoken(); + case 'sign': + return yield this.signTX(); + case 'mktx': + return yield this.createTX(); + case 'send': + return yield this.sendTX(); + case 'zap': + return yield this.zap(); + case 'tx': + return yield this.getDetails(); + case 'view': + return yield this.viewTX(); + default: + this.log('Unrecognized command.'); + this.log('Commands:'); + this.log(' $ listen: Listen for events.'); + this.log(' $ get: View wallet.'); + this.log(' $ addkey [xpubkey]: Add key to wallet.'); + this.log(' $ rmkey [xpubkey]: Remove key from wallet.'); + this.log(' $ balance: Get wallet balance.'); + this.log(' $ history: View wallet TX history.'); + this.log(' $ account list: List account names.'); + this.log(' $ account create [account-name]: Create account.'); + this.log(' $ account get [account-name]: Get account details.'); + this.log(' $ address: Derive new address.'); + this.log(' $ retoken: Create new api key.'); + this.log(' $ send [address] [value]: Send transaction.'); + this.log(' $ mktx [address] [value]: Create transaction.'); + this.log(' $ sign [tx-hex]: Sign transaction.'); + this.log(' $ zap --age [age]: Zap pending wallet TXs.'); + this.log(' $ tx [hash]: View transaction details.'); + this.log(' $ view [tx-hex]: Parse and view transaction.'); + this.log('Other Options:'); + this.log(' --passphrase [passphrase]: For signing and account creation.'); + this.log(' --account [account-name]: Account name.'); + return; + } +}); + +CLI.prototype.handleNode = co(function* handleNode() { + var info; + + this.client = new Client({ + uri: this.config.url || this.config.uri, + apiKey: this.config.apikey, + network: this.config.network + }); + + info = yield this.client.getInfo(); + + switch (this.argv.shift()) { + case 'mkwallet': + return yield this.createWallet(); + case 'broadcast': + return yield this.broadcast(); + case 'mempool': + return yield this.getMempool(); + case 'tx': + return yield this.getTX(); + case 'coin': + return yield this.getCoin(); + case 'block': + return yield this.getBlock(); + case 'rpc': + return yield this.rpc(); + default: + this.log('Unrecognized command.'); + this.log('Commands:'); + this.log(' $ wallet create [id]: Create wallet.'); + this.log(' $ broadcast [tx-hex]: Broadcast transaction.'); + this.log(' $ mempool: Get mempool snapshot.'); + this.log(' $ tx [hash/address]: View transactions.'); + this.log(' $ coin [hash+index/address]: View coins.'); + this.log(' $ block [hash/height]: View block.'); + return; + } +}); + +CLI.prototype.open = co(function* open() { + switch (this.argv[0]) { + case 'w': + case 'wallet': + this.argv.shift(); + if (this.argv[0] === 'create') { + this.argv[0] = 'mkwallet'; return yield this.handleNode(); - } - }, this); -}; + } + return yield this.handleWallet(); + default: + return yield this.handleNode(); + } +}); CLI.prototype.destroy = function destroy() { if (this.wallet && !this.wallet.client.loading) diff --git a/lib/chain/chain.js b/lib/chain/chain.js index 92e2004d..0300ce39 100644 --- a/lib/chain/chain.js +++ b/lib/chain/chain.js @@ -1223,7 +1223,7 @@ Chain.prototype.add = co(function* add(block) { if (this.orphan.size > this.orphanLimit) this.pruneOrphans(); - yield utils.wait(); + yield spawn.wait(); if (!this.synced && this.isFull()) { this.synced = true; diff --git a/lib/chain/chainentry.js b/lib/chain/chainentry.js index 03b93927..c8a588d5 100644 --- a/lib/chain/chainentry.js +++ b/lib/chain/chainentry.js @@ -235,7 +235,7 @@ ChainEntry.prototype.getAncestorByHeight = co(function* getAncestorByHeight(heig var main, entry; if (height < 0) - return yield utils.wait(); + return yield spawn.wait(); assert(height >= 0); assert(height <= this.height); diff --git a/lib/crypto/crypto.js b/lib/crypto/crypto.js index c9025713..e3772f83 100644 --- a/lib/crypto/crypto.js +++ b/lib/crypto/crypto.js @@ -14,15 +14,16 @@ var scryptAsync = require('./scrypt-async'); var utils = require('../utils/utils'); var spawn = require('../utils/spawn'); var co = spawn.co; +var wrap = spawn.wrap; var native = require('../utils/native'); -var nativeCrypto, hash, aes; +var nodeCrypto, hash, aes; var isBrowser = (typeof process !== 'undefined' && process.browser) || typeof window !== 'undefined'; if (!isBrowser) { - nativeCrypto = require('crypto'); + nodeCrypto = require('crypto'); } else { hash = require('hash.js'); aes = require('./aes'); @@ -42,10 +43,10 @@ var crypto = exports; */ crypto.hash = function _hash(alg, data) { - if (!nativeCrypto) + if (!nodeCrypto) return new Buffer(hash[alg]().update(data).digest()); - return nativeCrypto.createHash(alg).update(data).digest(); + return nodeCrypto.createHash(alg).update(data).digest(); }; if (native) @@ -131,12 +132,12 @@ crypto.checksum = function checksum(data) { crypto.hmac = function hmac(alg, data, salt) { var hmac; - if (!nativeCrypto) { + if (!nodeCrypto) { hmac = hash.hmac(hash[alg], salt); return new Buffer(hmac.update(data).digest()); } - hmac = nativeCrypto.createHmac(alg, salt); + hmac = nodeCrypto.createHmac(alg, salt); return hmac.update(data).digest(); }; @@ -160,8 +161,8 @@ crypto.pbkdf2 = function pbkdf2(key, salt, iter, len, alg) { if (typeof salt === 'string') salt = new Buffer(salt, 'utf8'); - if (nativeCrypto && nativeCrypto.pbkdf2Sync) - return nativeCrypto.pbkdf2Sync(key, salt, iter, len, alg); + if (nodeCrypto && nodeCrypto.pbkdf2Sync) + return nodeCrypto.pbkdf2Sync(key, salt, iter, len, alg); return crypto._pbkdf2(key, salt, iter, len, alg); }; @@ -185,13 +186,9 @@ crypto.pbkdf2Async = function pbkdf2Async(key, salt, iter, len, alg) { if (typeof salt === 'string') salt = new Buffer(salt, 'utf8'); - if (nativeCrypto && nativeCrypto.pbkdf2) { + if (nodeCrypto && nodeCrypto.pbkdf2) { return new Promise(function(resolve, reject) { - nativeCrypto.pbkdf2(key, salt, iter, len, alg, function(err, key) { - if (err) - return reject(err); - resolve(key); - }); + nodeCrypto.pbkdf2(key, salt, iter, len, alg, wrap(resolve, reject)); }); } @@ -244,11 +241,7 @@ crypto.scryptAsync = function _scrypt(passwd, salt, N, r, p, len) { salt = new Buffer(salt, 'utf8'); return new Promise(function(resolve, reject) { - scryptAsync(passwd, salt, N, r, p, len, function(err, key) { - if (err) - return reject(err); - resolve(key); - }); + scryptAsync(passwd, salt, N, r, p, len, wrap(resolve, reject)); }); }; @@ -270,28 +263,26 @@ crypto.derive = function derive(passphrase) { * @param {Function} callback */ -crypto.encrypt = function encrypt(data, passphrase, iv) { - return spawn(function *() { - var key; +crypto.encrypt = co(function* encrypt(data, passphrase, iv) { + var key; - assert(Buffer.isBuffer(data)); - assert(passphrase, 'No passphrase.'); - assert(Buffer.isBuffer(iv)); + assert(Buffer.isBuffer(data)); + assert(passphrase, 'No passphrase.'); + assert(Buffer.isBuffer(iv)); - key = yield crypto.derive(passphrase); - - try { - data = crypto.encipher(data, key, iv); - } catch (e) { - key.fill(0); - throw e; - } + key = yield crypto.derive(passphrase); + try { + data = crypto.encipher(data, key, iv); + } catch (e) { key.fill(0); + throw e; + } - return data; - }); -}; + key.fill(0); + + return data; +}); /** * Encrypt with aes-256-cbc. @@ -304,10 +295,10 @@ crypto.encrypt = function encrypt(data, passphrase, iv) { crypto.encipher = function encipher(data, key, iv) { var cipher; - if (!nativeCrypto) + if (!nodeCrypto) return aes.cbc.encrypt(data, key, iv); - cipher = nativeCrypto.createCipheriv('aes-256-cbc', key, iv); + cipher = nodeCrypto.createCipheriv('aes-256-cbc', key, iv); return Buffer.concat([ cipher.update(data), @@ -323,28 +314,26 @@ crypto.encipher = function encipher(data, key, iv) { * @param {Function} callback */ -crypto.decrypt = function decrypt(data, passphrase, iv) { - return spawn(function *() { - var key; +crypto.decrypt = co(function* decrypt(data, passphrase, iv) { + var key; - assert(Buffer.isBuffer(data)); - assert(passphrase, 'No passphrase.'); - assert(Buffer.isBuffer(iv)); + assert(Buffer.isBuffer(data)); + assert(passphrase, 'No passphrase.'); + assert(Buffer.isBuffer(iv)); - key = yield crypto.derive(passphrase); - - try { - data = crypto.decipher(data, key, iv); - } catch (e) { - key.fill(0); - throw e; - } + key = yield crypto.derive(passphrase); + try { + data = crypto.decipher(data, key, iv); + } catch (e) { key.fill(0); + throw e; + } - return data; - }); -}; + key.fill(0); + + return data; +}); /** * Decrypt with aes-256-cbc. @@ -357,10 +346,10 @@ crypto.decrypt = function decrypt(data, passphrase, iv) { crypto.decipher = function decipher(data, key, iv) { var decipher; - if (!nativeCrypto) + if (!nodeCrypto) return aes.cbc.decrypt(data, key, iv); - decipher = nativeCrypto.createDecipheriv('aes-256-cbc', key, iv); + decipher = nodeCrypto.createDecipheriv('aes-256-cbc', key, iv); return Buffer.concat([ decipher.update(data), diff --git a/lib/db/lowlevelup.js b/lib/db/lowlevelup.js index 1f5a5ec1..2b9c5b72 100644 --- a/lib/db/lowlevelup.js +++ b/lib/db/lowlevelup.js @@ -12,7 +12,7 @@ var assert = utils.assert; var AsyncObject = require('../utils/async'); var spawn = require('../utils/spawn'); var co = spawn.co; -var P = utils.P; +var wrap = spawn.wrap; var VERSION_ERROR; /** @@ -66,7 +66,7 @@ utils.inherits(LowlevelUp, AsyncObject); LowlevelUp.prototype._open = function open() { var self = this; return new Promise(function(resolve, reject) { - self.binding.open(self.options, P(resolve, reject)); + self.binding.open(self.options, wrap(resolve, reject)); }); }; @@ -79,7 +79,7 @@ LowlevelUp.prototype._open = function open() { LowlevelUp.prototype._close = function close() { var self = this; return new Promise(function(resolve, reject) { - self.binding.close(P(resolve, reject)); + self.binding.close(wrap(resolve, reject)); }); }; @@ -98,7 +98,7 @@ LowlevelUp.prototype.destroy = function destroy() { return new Promise(function(resolve, reject) { if (!self.backend.destroy) return utils.asyncify(reject)(new Error('Cannot destroy.')); - self.backend.destroy(self.location, P(resolve, reject)); + self.backend.destroy(self.location, wrap(resolve, reject)); }); }; @@ -117,7 +117,7 @@ LowlevelUp.prototype.repair = function repair() { return new Promise(function(resolve, reject) { if (!self.backend.repair) return utils.asyncify(reject)(new Error('Cannot repair.')); - self.backend.repair(self.location, P(resolve, reject)); + self.backend.repair(self.location, wrap(resolve, reject)); }); }; @@ -138,7 +138,7 @@ LowlevelUp.prototype.backup = function backup(path) { return this.clone(path); return new Promise(function(resolve, reject) { - self.binding.backup(path, P(resolve, reject)); + self.binding.backup(path, wrap(resolve, reject)); }); }; @@ -178,7 +178,7 @@ LowlevelUp.prototype.put = function put(key, value, options) { var self = this; assert(this.loaded, 'Cannot use database before it is loaded.'); return new Promise(function(resolve, reject) { - self.binding.put(key, value, options || {}, P(resolve, reject)); + self.binding.put(key, value, options || {}, wrap(resolve, reject)); }); }; @@ -193,7 +193,7 @@ LowlevelUp.prototype.del = function del(key, options) { var self = this; assert(this.loaded, 'Cannot use database before it is loaded.'); return new Promise(function(resolve, reject) { - self.binding.del(key, options || {}, P(resolve, reject)); + self.binding.del(key, options || {}, wrap(resolve, reject)); }); }; @@ -214,7 +214,7 @@ LowlevelUp.prototype.batch = function batch(ops, options) { return new Batch(this); return new Promise(function(resolve, reject) { - self.binding.batch(ops, options || {}, P(resolve, reject)); + self.binding.batch(ops, options || {}, wrap(resolve, reject)); }); }; @@ -281,7 +281,7 @@ LowlevelUp.prototype.approximateSize = function approximateSize(start, end) { if (!self.binding.approximateSize) return utils.asyncify(reject)(new Error('Cannot get size.')); - self.binding.approximateSize(start, end, P(resolve, reject)); + self.binding.approximateSize(start, end, wrap(resolve, reject)); }); }; @@ -439,7 +439,7 @@ Batch.prototype.del = function del(key) { Batch.prototype.write = function write() { var self = this; return new Promise(function(resolve, reject) { - self.batch.write(P(resolve, reject)); + self.batch.write(wrap(resolve, reject)); }); }; @@ -465,7 +465,7 @@ Iterator.prototype.next = function() { } if (key === undefined && value === undefined) { - self.iter.end(P(resolve, reject)); + self.iter.end(wrap(resolve, reject)); return; } @@ -481,7 +481,7 @@ Iterator.prototype.seek = function seek(key) { Iterator.prototype.end = function end() { var self = this; return new Promise(function(resolve, reject) { - self.iter.end(P(resolve, reject)); + self.iter.end(wrap(resolve, reject)); }); }; diff --git a/lib/http/rpc.js b/lib/http/rpc.js index b5ccc9f3..6de1ae30 100644 --- a/lib/http/rpc.js +++ b/lib/http/rpc.js @@ -4145,13 +4145,13 @@ function reverseEndian(data) { function writeFile(file, data) { return new Promise(function(resolve, reject) { - fs.writeFile(file, data, utils.P(resolve, reject)); + fs.writeFile(file, data, spawn.wrap(resolve, reject)); }); } function readFile(file, enc) { return new Promise(function(resolve, reject) { - fs.readFile(file, enc, utils.P(resolve, reject)); + fs.readFile(file, enc, spawn.wrap(resolve, reject)); }); } diff --git a/lib/mempool/mempool.js b/lib/mempool/mempool.js index 689de518..f98825b1 100644 --- a/lib/mempool/mempool.js +++ b/lib/mempool/mempool.js @@ -189,7 +189,7 @@ Mempool.prototype.addBlock = co(function* addBlock(block) { // There may be a locktime in a TX that is now valid. this.rejects.reset(); - yield utils.wait(); + yield spawn.wait(); unlock(); }); diff --git a/lib/miner/miner.js b/lib/miner/miner.js index e65251ac..a2a90fca 100644 --- a/lib/miner/miner.js +++ b/lib/miner/miner.js @@ -160,61 +160,57 @@ Miner.prototype._close = function close() { * @param {Number?} version - Custom block version. */ -Miner.prototype.start = function start() { +Miner.prototype.start = co(function* start() { var self = this; - spawn(function *() { - var attempt, block; + var attempt, block; - this.stop(); + this.stop(); - this.running = true; + this.running = true; - // Create a new block and start hashing - try { - attempt = yield this.createBlock(); - } catch (e) { - this.emit('error', e); - return; - } + // Create a new block and start hashing + try { + attempt = yield this.createBlock(); + } catch (e) { + this.emit('error', e); + return; + } + if (!this.running) + return; + + this.attempt = attempt; + + attempt.on('status', function(status) { + self.emit('status', status); + }); + + try { + block = yield attempt.mineAsync(); + } catch (e) { if (!this.running) return; + this.emit('error', e); + return this.start(); + } - this.attempt = attempt; + // Add our block to the chain + try { + yield this.chain.add(block); + } catch (err) { + if (err.type === 'VerifyError') + this.logger.warning('%s could not be added to chain.', block.rhash); + this.emit('error', err); + this.start(); + return; + } - attempt.on('status', function(status) { - self.emit('status', status); - }); + // Emit our newly found block + this.emit('block', block); - try { - block = yield attempt.mineAsync(); - } catch (e) { - if (!this.running) - return; - this.emit('error', e); - return this.start(); - } - - // Add our block to the chain - try { - yield this.chain.add(block); - } catch (err) { - if (err.type === 'VerifyError') - this.logger.warning('%s could not be added to chain.', block.rhash); - this.emit('error', err); - this.start(); - return; - } - - // Emit our newly found block - this.emit('block', block); - - // `tip` will now be emitted by chain - // and the whole process starts over. - }, this).catch(function(err) { - self.emit('error', err); - }); -}; + // `tip` will now be emitted by chain + // and the whole process starts over. +}); /** * Stop mining. diff --git a/lib/net/peer.js b/lib/net/peer.js index 9e370230..bac80356 100644 --- a/lib/net/peer.js +++ b/lib/net/peer.js @@ -1119,66 +1119,71 @@ Peer.prototype._handleUTXOs = function _handleUTXOs(utxos) { * @private */ -Peer.prototype._handleGetUTXOs = function _handleGetUTXOs(packet) { - var self = this; - spawn(function *() { - var unlock = yield this._lock(); - var i, utxos, prevout, hash, index, coin; +Peer.prototype._handleGetUTXOs = co(function* _handleGetUTXOs(packet) { + var unlock = yield this._lock(); + var i, utxos, prevout, hash, index, coin; - if (!this.chain.synced) - return unlock(); + if (!this.chain.synced) + return unlock(); - if (this.options.selfish) - return unlock(); + if (this.options.selfish) + return unlock(); - if (this.chain.db.options.spv) - return unlock(); + if (this.chain.db.options.spv) + return unlock(); - if (packet.prevout.length > 15) - return unlock(); + if (packet.prevout.length > 15) + return unlock(); - utxos = new packets.GetUTXOsPacket(); + utxos = new packets.GetUTXOsPacket(); - for (i = 0; i < packet.prevout.length; i++) { - prevout = packet.prevout[i]; - hash = prevout.hash; - index = prevout.index; + for (i = 0; i < packet.prevout.length; i++) { + prevout = packet.prevout[i]; + hash = prevout.hash; + index = prevout.index; - if (this.mempool && packet.mempool) { + if (this.mempool && packet.mempool) { + try { coin = this.mempool.getCoin(hash, index); - - if (coin) { - utxos.hits.push(1); - utxos.coins.push(coin); - continue; - } - - if (this.mempool.isSpent(hash, index)) { - utxos.hits.push(0); - continue; - } + } catch (e) { + this.emit('error', e); + return; } - coin = yield this.chain.db.getCoin(hash, index); - - if (!coin) { - utxos.hits.push(0); + if (coin) { + utxos.hits.push(1); + utxos.coins.push(coin); continue; } - utxos.hits.push(1); - utxos.coins.push(coin); + if (this.mempool.isSpent(hash, index)) { + utxos.hits.push(0); + continue; + } } - utxos.height = this.chain.height; - utxos.tip = this.chain.tip.hash; + try { + coin = yield this.chain.db.getCoin(hash, index); + } catch (e) { + this.emit('error', e); + return; + } - this.send(utxos); - unlock(); - }, this).catch(function(err) { - self.emit('error', err); - }); -}; + if (!coin) { + utxos.hits.push(0); + continue; + } + + utxos.hits.push(1); + utxos.coins.push(coin); + } + + utxos.height = this.chain.height; + utxos.tip = this.chain.tip.hash; + + this.send(utxos); + unlock(); +}); /** * Handle `havewitness` packet. @@ -1197,55 +1202,50 @@ Peer.prototype._handleHaveWitness = function _handleHaveWitness(packet) { * @param {GetHeadersPacket} */ -Peer.prototype._handleGetHeaders = function _handleGetHeaders(packet) { - var self = this; - spawn(function *() { - var unlock = yield this._lock(); - var headers = []; - var hash, entry; +Peer.prototype._handleGetHeaders = co(function* _handleGetHeaders(packet) { + var unlock = yield this._lock(); + var headers = []; + var hash, entry; - if (!this.chain.synced) - return unlock(); + if (!this.chain.synced) + return unlock(); - if (this.options.selfish) - return unlock(); + if (this.options.selfish) + return unlock(); - if (this.chain.db.options.spv) - return unlock(); + if (this.chain.db.options.spv) + return unlock(); - if (this.chain.db.options.prune) - return unlock(); - - if (packet.locator.length > 0) { - hash = yield this.chain.findLocator(packet.locator); - if (hash) - hash = yield this.chain.db.getNextHash(hash); - } else { - hash = packet.stop; - } + if (this.chain.db.options.prune) + return unlock(); + if (packet.locator.length > 0) { + hash = yield this.chain.findLocator(packet.locator); if (hash) - entry = yield this.chain.db.get(hash); + hash = yield this.chain.db.getNextHash(hash); + } else { + hash = packet.stop; + } - while (entry) { - headers.push(entry.toHeaders()); + if (hash) + entry = yield this.chain.db.get(hash); - if (headers.length === 2000) - break; + while (entry) { + headers.push(entry.toHeaders()); - if (entry.hash === packet.stop) - break; + if (headers.length === 2000) + break; - entry = yield entry.getNext(); - } + if (entry.hash === packet.stop) + break; - this.sendHeaders(headers); + entry = yield entry.getNext(); + } - unlock(); - }, this).catch(function(err) { - self.emit('error', err); - }); -}; + this.sendHeaders(headers); + + unlock(); +}); /** * Handle `getblocks` packet. @@ -1253,50 +1253,45 @@ Peer.prototype._handleGetHeaders = function _handleGetHeaders(packet) { * @param {GetBlocksPacket} */ -Peer.prototype._handleGetBlocks = function _handleGetBlocks(packet) { - var self = this; - spawn(function *() { - var unlock = yield this._lock(); - var blocks = []; - var hash; +Peer.prototype._handleGetBlocks = co(function* _handleGetBlocks(packet) { + var unlock = yield this._lock(); + var blocks = []; + var hash; - if (!this.chain.synced) - return unlock(); + if (!this.chain.synced) + return unlock(); - if (this.options.selfish) - return unlock(); + if (this.options.selfish) + return unlock(); - if (this.chain.db.options.spv) - return unlock(); + if (this.chain.db.options.spv) + return unlock(); - if (this.chain.db.options.prune) - return unlock(); + if (this.chain.db.options.prune) + return unlock(); - hash = yield this.chain.findLocator(packet.locator); + hash = yield this.chain.findLocator(packet.locator); - if (hash) - hash = yield this.chain.db.getNextHash(hash); + if (hash) + hash = yield this.chain.db.getNextHash(hash); - while (hash) { - blocks.push(new InvItem(constants.inv.BLOCK, hash)); + while (hash) { + blocks.push(new InvItem(constants.inv.BLOCK, hash)); - if (hash === packet.stop) - break; + if (hash === packet.stop) + break; - if (blocks.length === 500) { - this.hashContinue = hash; - break; - } - - hash = yield this.chain.db.getNextHash(hash); + if (blocks.length === 500) { + this.hashContinue = hash; + break; } - this.sendInv(blocks); - unlock(); - }, this).catch(function(err) { - self.emit('error', err); - }); -}; + hash = yield this.chain.db.getNextHash(hash); + } + + this.sendInv(blocks); + unlock(); +}); /** * Handle `version` packet. @@ -1480,119 +1475,114 @@ Peer.prototype._getItem = co(function* _getItem(item) { * @param {GetDataPacket} */ -Peer.prototype._handleGetData = function _handleGetData(packet) { - var self = this; - spawn(function *() { - var unlock = yield this._lock(); - var notFound = []; - var items = packet.items; - var i, j, item, entry, tx, block; +Peer.prototype._handleGetData = co(function* _handleGetData(packet) { + var unlock = yield this._lock(); + var notFound = []; + var items = packet.items; + var i, j, item, entry, tx, block; - if (items.length > 50000) { - this.error('getdata size too large (%s).', items.length); - return; + if (items.length > 50000) { + this.error('getdata size too large (%s).', items.length); + return; + } + + for (i = 0; i < items.length; i++) { + item = items[i]; + entry = yield this._getItem(item); + + if (!entry) { + notFound.push(item); + continue; } - for (i = 0; i < items.length; i++) { - item = items[i]; - entry = yield this._getItem(item); + if (item.isTX()) { + tx = entry; - if (!entry) { + // Coinbases are an insta-ban from any node. + // This should technically never happen, but + // it's worth keeping here just in case. A + // 24-hour ban from any node is rough. + if (tx.isCoinbase()) { notFound.push(item); + this.logger.warning('Failsafe: tried to relay a coinbase.'); continue; } - if (item.isTX()) { - tx = entry; + this.send(new packets.TXPacket(tx, item.hasWitness())); - // Coinbases are an insta-ban from any node. - // This should technically never happen, but - // it's worth keeping here just in case. A - // 24-hour ban from any node is rough. - if (tx.isCoinbase()) { + continue; + } + + block = entry; + + switch (item.type) { + case constants.inv.BLOCK: + case constants.inv.WITNESS_BLOCK: + this.send(new packets.BlockPacket(block, item.hasWitness())); + break; + case constants.inv.FILTERED_BLOCK: + case constants.inv.WITNESS_FILTERED_BLOCK: + if (!this.spvFilter) { notFound.push(item); - this.logger.warning('Failsafe: tried to relay a coinbase.'); continue; } - this.send(new packets.TXPacket(tx, item.hasWitness())); + block = block.toMerkle(this.spvFilter); - continue; - } + this.send(new packets.MerkleBlockPacket(block)); - block = entry; + for (j = 0; j < block.txs.length; j++) { + tx = block.txs[j]; + this.send(new packets.TXPacket(tx, item.hasWitness())); + } - switch (item.type) { - case constants.inv.BLOCK: - case constants.inv.WITNESS_BLOCK: - this.send(new packets.BlockPacket(block, item.hasWitness())); + break; + case constants.inv.CMPCT_BLOCK: + // Fallback to full block. + if (block.height < this.chain.tip.height - 10) { + this.send(new packets.BlockPacket(block, false)); break; - case constants.inv.FILTERED_BLOCK: - case constants.inv.WITNESS_FILTERED_BLOCK: - if (!this.spvFilter) { - notFound.push(item); + } + + // Try again with a new nonce + // if we get a siphash collision. + for (;;) { + try { + block = bcoin.bip152.CompactBlock.fromBlock(block); + } catch (e) { continue; } - - block = block.toMerkle(this.spvFilter); - - this.send(new packets.MerkleBlockPacket(block)); - - for (j = 0; j < block.txs.length; j++) { - tx = block.txs[j]; - this.send(new packets.TXPacket(tx, item.hasWitness())); - } - break; - case constants.inv.CMPCT_BLOCK: - // Fallback to full block. - if (block.height < this.chain.tip.height - 10) { - this.send(new packets.BlockPacket(block, false)); - break; - } + } - // Try again with a new nonce - // if we get a siphash collision. - for (;;) { - try { - block = bcoin.bip152.CompactBlock.fromBlock(block); - } catch (e) { - continue; - } - break; - } - - this.send(new packets.CmpctBlockPacket(block, false)); - break; - default: - this.logger.warning( - 'Peer sent an unknown getdata type: %s (%s).', - item.type, - this.hostname); - notFound.push(item); - continue; - } - - if (item.hash === this.hashContinue) { - this.sendInv(new InvItem(constants.inv.BLOCK, this.chain.tip.hash)); - this.hashContinue = null; - } + this.send(new packets.CmpctBlockPacket(block, false)); + break; + default: + this.logger.warning( + 'Peer sent an unknown getdata type: %s (%s).', + item.type, + this.hostname); + notFound.push(item); + continue; } - this.logger.debug( - 'Served %d items with getdata (notfound=%d) (%s).', - items.length - notFound.length, - notFound.length, - this.hostname); + if (item.hash === this.hashContinue) { + this.sendInv(new InvItem(constants.inv.BLOCK, this.chain.tip.hash)); + this.hashContinue = null; + } + } - if (notFound.length > 0) - this.send(new packets.NotFoundPacket(notFound)); + this.logger.debug( + 'Served %d items with getdata (notfound=%d) (%s).', + items.length - notFound.length, + notFound.length, + this.hostname); - unlock(); - }, this).catch(function(err) { - self.emit('error', err); - }); -}; + if (notFound.length > 0) + this.send(new packets.NotFoundPacket(notFound)); + + unlock(); +}); /** * Handle `notfound` packet. diff --git a/lib/net/pool.js b/lib/net/pool.js index f78a1315..1b2dcd8e 100644 --- a/lib/net/pool.js +++ b/lib/net/pool.js @@ -431,7 +431,7 @@ Pool.prototype.listen = function listen() { }); return new Promise(function(resolve, reject) { - self.server.listen(self.port, '0.0.0.0', utils.P(resolve, reject)); + self.server.listen(self.port, '0.0.0.0', spawn.wrap(resolve, reject)); }); }; @@ -450,7 +450,7 @@ Pool.prototype.unlisten = function unlisten() { return; return new Promise(function(resolve, reject) { - self.server.close(utils.P(resolve, reject)); + self.server.close(spawn.wrap(resolve, reject)); self.server = null; }); }; @@ -902,7 +902,7 @@ Pool.prototype._handleBlock = co(function* _handleBlock(block, peer) { this.logger.warning( 'Received unrequested block: %s (%s).', block.rhash, peer.hostname); - return yield utils.wait(); + return yield spawn.wait(); } try { @@ -1062,7 +1062,7 @@ Pool.prototype.createPeer = function createPeer(addr, socket) { self.startInterval(); self.startTimeout(); } - }).catch(function(err) { + }, function(err) { self.emit('error', err); }); }); @@ -1081,7 +1081,7 @@ Pool.prototype.createPeer = function createPeer(addr, socket) { self.startInterval(); self.startTimeout(); } - }).catch(function(err) {; + }, function(err) { self.emit('error', err); }); }); diff --git a/lib/utils/async.js b/lib/utils/async.js index c9a88d39..581c7c16 100644 --- a/lib/utils/async.js +++ b/lib/utils/async.js @@ -10,8 +10,8 @@ var utils = require('../utils/utils'); var spawn = require('../utils/spawn'); var co = spawn.co; var assert = utils.assert; +var wait = spawn.wait; var EventEmitter = require('events').EventEmitter; -var wait = utils.wait; /** * An abstract object that handles state and diff --git a/lib/utils/spawn.js b/lib/utils/spawn.js index 4cdff405..88ee9424 100644 --- a/lib/utils/spawn.js +++ b/lib/utils/spawn.js @@ -1,5 +1,20 @@ +/*! + * spawn.js - promise and generator control flow for bcoin + * Originally based on yoursnetwork's "asink" module. + * Copyright (c) 2014-2016, Christopher Jeffrey (MIT License). + * https://github.com/bcoin-org/bcoin + */ + 'use strict'; +var utils = require('./utils'); + +/** + * Execute an instantiated generator. + * @param {Generator} gen + * @returns {Promise} + */ + function exec(gen) { return new Promise(function(resolve, reject) { function step(value, rejection) { @@ -34,11 +49,27 @@ function exec(gen) { }); } +/** + * Execute generator function + * with a context and execute. + * @param {GeneratorFunction} generator + * @param {Object} self + * @returns {Promise} + */ + function spawn(generator, self) { var gen = generator.call(self); return exec(gen); } +/** + * Wrap a generator function to be + * executed into a function that + * returns a promise. + * @param {GeneratorFunction} + * @returns {Function} + */ + function co(generator) { return function() { var gen = generator.apply(this, arguments); @@ -46,6 +77,157 @@ function co(generator) { }; } -spawn.co = co; +/** + * Wrap a generator function to be + * executed into a function that + * accepts a node.js style callback. + * @param {GeneratorFunction} + * @returns {Function} + */ + +function cob(generator) { + return function() { + var i, args, callback, gen; + + if (arguments.length === 0 + || typeof arguments[arguments.length - 1] !== 'function') { + throw new Error('Function must accept a callback.'); + } + + args = new Array(arguments.length - 1); + callback = arguments[arguments.length - 1]; + + for (i = 0; i < args.length; i++) + args[i] = arguments[i]; + + gen = generator.apply(this, args); + + return cb(exec(gen), callback); + }; +} + +/** + * Wait for promise to resolve and + * execute a node.js style callback. + * @param {Promise} promise + * @param {Function} callback + */ + +function cb(promise, callback) { + promise.then(function(value) { + callback(null, value); + }, function(err) { + callback(err); + }); +} + +/** + * Wait for a nextTick with a promise. + * @returns {Promise} + */ + +function wait() { + return new Promise(function(resolve, reject) { + utils.nextTick(resolve); + }); +}; + +/** + * Wait for a timeout with a promise. + * @param {Number} time + * @returns {Promise} + */ + +function timeout(time) { + return new Promise(function(resolve, reject) { + setTimeout(function() { + resolve(); + }, time); + }); +} + +/** + * Wrap `resolve` and `reject` into + * a node.js style callback. + * @param {Function} resolve + * @param {Function} reject + * @returns {Function} + */ + +function wrap(resolve, reject) { + return function(err, result) { + if (err) + return reject(err); + resolve(result); + }; +} + +/** + * Call a function that accepts node.js + * style callbacks, wrap with a promise. + * @param {Function} func + * @returns {Promise} + */ + +function call(func) { + var args = new Array(Math.max(0, arguments.length - 1)); + 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(self, args); + }); +} + +/** + * Wrap a function that accepts node.js + * style callbacks into a function that + * returns a promise. + * @param {Function} func + * @param {Object?} self + * @returns {Function} + */ + +function promisify(func, self) { + 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); + }); + }; +} + +/* + * Expose + */ + +exports = spawn; +exports.exec = exec; +exports.spawn = spawn; +exports.co = co; +exports.cob = cob; +exports.cb = cb; +exports.wait = wait; +exports.timeout = timeout; +exports.wrap = wrap; +exports.call = call; +exports.promisify = promisify; module.exports = spawn; diff --git a/lib/utils/utils.js b/lib/utils/utils.js index 74acfc00..474ad6dd 100644 --- a/lib/utils/utils.js +++ b/lib/utils/utils.js @@ -326,28 +326,6 @@ if (typeof setImmediate === 'function') { }; } -utils.wait = function wait() { - return new Promise(function(resolve, reject) { - utils.nextTick(resolve); - }); -}; - -utils.timeout = function timeout(time) { - return new Promise(function(resolve, reject) { - setTimeout(function() { - resolve(); - }, time); - }); -}; - -utils.P = function P(resolve, reject) { - return function(err, result) { - if (err) - return reject(err); - resolve(result); - }; -}; - /** * Wrap a function in a `nextTick`. * @param {Function} callback diff --git a/lib/workers/workers.js b/lib/workers/workers.js index 39873583..27ad3805 100644 --- a/lib/workers/workers.js +++ b/lib/workers/workers.js @@ -576,7 +576,7 @@ Worker.prototype._execute = function _execute(method, args, timeout, callback) { Worker.prototype.execute = function execute(method, args, timeout) { var self = this; return new Promise(function(resolve, reject) { - self._execute(method, args, timeout, utils.P(resolve, reject)); + self._execute(method, args, timeout, spawn.wrap(resolve, reject)); }); }; diff --git a/test/chain-test.js b/test/chain-test.js index c2b8a0a7..6f9cb1d6 100644 --- a/test/chain-test.js +++ b/test/chain-test.js @@ -7,6 +7,8 @@ var utils = bcoin.utils; var crypto = require('../lib/crypto/crypto'); var assert = require('assert'); var opcodes = constants.opcodes; +var spawn = require('../lib/utils/spawn'); +var c = require('../lib/utils/spawn').cb; describe('Chain', function() { var chain, wallet, node, miner, walletdb; @@ -14,22 +16,6 @@ describe('Chain', function() { this.timeout(5000); - function c(p, cb) { - var called = false; - p.then(function(result) { - called = true; - cb(null, result); - }).catch(function(err) { - if (called) { - utils.nextTick(function() { - throw err; - }); - return; - } - cb(err); - }); - } - node = new bcoin.fullnode({ db: 'memory' }); chain = node.chain; walletdb = node.walletdb; @@ -250,7 +236,7 @@ describe('Chain', function() { assert.ifError(err); c(chain.db.scan(null, hashes, function *(block, txs) { total += txs.length; - yield utils.wait(); + yield spawn.wait(); }), function(err) { assert.ifError(err); assert.equal(total, 25); diff --git a/test/http-test.js b/test/http-test.js index 1a7a959f..56bf86bf 100644 --- a/test/http-test.js +++ b/test/http-test.js @@ -8,6 +8,7 @@ var utils = bcoin.utils; var crypto = require('../lib/crypto/crypto'); var assert = require('assert'); var scriptTypes = constants.scriptTypes; +var c = require('../lib/utils/spawn').cb; var dummyInput = { prevout: { @@ -28,22 +29,6 @@ var dummyInput = { sequence: 0xffffffff }; -function c(p, cb) { - var called = false; - p.then(function(result) { - called = true; - cb(null, result); - }).catch(function(err) { - if (called) { - utils.nextTick(function() { - throw err; - }); - return; - } - cb(err); - }); -} - describe('HTTP', function() { var request = bcoin.http.request; var w, addr, hash; diff --git a/test/mempool-test.js b/test/mempool-test.js index d28cce7f..49fd5e1b 100644 --- a/test/mempool-test.js +++ b/test/mempool-test.js @@ -7,22 +7,7 @@ var utils = bcoin.utils; var crypto = require('../lib/crypto/crypto'); var assert = require('assert'); var opcodes = constants.opcodes; - -function c(p, cb) { - var called = false; - p.then(function(result) { - called = true; - cb(null, result); - }).catch(function(err) { - if (called) { - utils.nextTick(function() { - throw err; - }); - return; - } - cb(err); - }); -} +var c = require('../lib/utils/spawn').cb; describe('Mempool', function() { this.timeout(5000); diff --git a/test/wallet-test.js b/test/wallet-test.js index 52ca09d8..64e67ad7 100644 --- a/test/wallet-test.js +++ b/test/wallet-test.js @@ -9,6 +9,7 @@ var crypto = require('../lib/crypto/crypto'); var spawn = require('../lib/utils/spawn'); var assert = require('assert'); var scriptTypes = constants.scriptTypes; +var c = require('../lib/utils/spawn').cb; var FAKE_SIG = new Buffer([0,0,0,0,0,0,0,0,0]); @@ -53,22 +54,6 @@ assert.range = function range(value, lo, hi, message) { } }; -function c(p, cb) { - var called = false; - p.then(function(result) { - called = true; - cb(null, result); - }).catch(function(err) { - if (called) { - utils.nextTick(function() { - throw err; - }); - return; - } - cb(err); - }); -} - describe('Wallet', function() { var walletdb = new bcoin.walletdb({ name: 'wallet-test',