From ec0d50d506d5522bd446ea4e65c35b9f241ec182 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 21 Sep 2016 22:58:27 -0700 Subject: [PATCH] refactor: improve generator perf. --- lib/chain/chain.js | 2072 ++++++++++---------- lib/chain/chaindb.js | 1498 +++++++-------- lib/chain/chainentry.js | 150 +- lib/db/lowlevelup.js | 170 +- lib/http/client.js | 210 +-- lib/http/rpc.js | 3984 +++++++++++++++++++-------------------- lib/http/rpcclient.js | 56 +- lib/http/server.js | 22 +- lib/http/wallet.js | 64 +- lib/mempool/mempool.js | 798 ++++---- lib/miner/miner.js | 54 +- lib/miner/minerblock.js | 38 +- lib/net/peer.js | 112 +- lib/net/pool.js | 690 ++++--- lib/node/fullnode.js | 128 +- lib/node/node.js | 40 +- lib/node/spvnode.js | 70 +- lib/utils/async.js | 120 +- lib/utils/spawn.js | 20 +- lib/wallet/account.js | 186 +- lib/wallet/txdb.js | 1478 +++++++-------- lib/wallet/wallet.js | 1552 ++++++++------- lib/wallet/walletdb.js | 1038 +++++----- lib/workers/workers.js | 28 +- 24 files changed, 7003 insertions(+), 7575 deletions(-) diff --git a/lib/chain/chain.js b/lib/chain/chain.js index 964abce3..39d11bed 100644 --- a/lib/chain/chain.js +++ b/lib/chain/chain.js @@ -183,48 +183,46 @@ Chain.prototype._init = function _init() { * @param {Function} callback */ -Chain.prototype._open = function open() { - return spawn(function *() { - var tip; +Chain.prototype._open = spawn.co(function* open() { + var tip; - this.logger.info('Chain is loading.'); + this.logger.info('Chain is loading.'); - if (this.options.useCheckpoints) - this.logger.info('Checkpoints are enabled.'); + if (this.options.useCheckpoints) + this.logger.info('Checkpoints are enabled.'); - if (this.options.coinCache) - this.logger.info('Coin cache is enabled.'); + if (this.options.coinCache) + this.logger.info('Coin cache is enabled.'); - yield this.db.open(); + yield this.db.open(); - tip = yield this.db.getTip(); + tip = yield this.db.getTip(); - assert(tip); + assert(tip); - this.tip = tip; - this.height = tip.height; + this.tip = tip; + this.height = tip.height; - this.logger.info('Chain Height: %d', tip.height); + this.logger.info('Chain Height: %d', tip.height); - if (tip.height > this.bestHeight) { - this.bestHeight = tip.height; - this.network.updateHeight(tip.height); - } + if (tip.height > this.bestHeight) { + this.bestHeight = tip.height; + this.network.updateHeight(tip.height); + } - this.logger.memory(); + this.logger.memory(); - this.state = yield this.getDeploymentState(); + this.state = yield this.getDeploymentState(); - this.logger.memory(); + this.logger.memory(); - this.emit('tip', tip); + this.emit('tip', tip); - if (!this.synced && this.isFull()) { - this.synced = true; - this.emit('full'); - } - }, this); -}; + if (!this.synced && this.isFull()) { + this.synced = true; + this.emit('full'); + } +}); /** * Close the chain, wait for the database to close. @@ -254,22 +252,20 @@ Chain.prototype._lock = function _lock(block, force) { * @param {Function} callback - Returns [{@link VerifyError}]. */ -Chain.prototype.verifyContext = function verifyContext(block, prev) { - return spawn(function *() { - var state, view; +Chain.prototype.verifyContext = spawn.co(function* verifyContext(block, prev) { + var state, view; - state = yield this.verify(block, prev); + state = yield this.verify(block, prev); - yield this.checkDuplicates(block, prev); + yield this.checkDuplicates(block, prev); - view = yield this.checkInputs(block, prev, state); + view = yield this.checkInputs(block, prev, state); - // Expose the state globally. - this.state = state; + // Expose the state globally. + this.state = state; - return view; - }, this); -}; + return view; +}); /** * Test whether a block is the genesis block. @@ -292,128 +288,126 @@ Chain.prototype.isGenesis = function isGenesis(block) { * [{@link VerifyError}, {@link VerifyFlags}]. */ -Chain.prototype.verify = function verify(block, prev) { - return spawn(function *() { - var ret = new VerifyResult(); - var i, height, ts, tx, medianTime, commitmentHash, ancestors, state; +Chain.prototype.verify = spawn.co(function* verify(block, prev) { + var ret = new VerifyResult(); + var i, height, ts, tx, medianTime, commitmentHash, ancestors, state; - if (!block.verify(ret)) { - throw new VerifyError(block, - 'invalid', - ret.reason, - ret.score); - } + if (!block.verify(ret)) { + throw new VerifyError(block, + 'invalid', + ret.reason, + ret.score); + } - // Skip the genesis block. Skip all blocks in spv mode. - if (this.options.spv || this.isGenesis(block)) - return this.state; + // Skip the genesis block. Skip all blocks in spv mode. + if (this.options.spv || this.isGenesis(block)) + return this.state; - // Ensure it's not an orphan - if (!prev) { - throw new VerifyError(block, - 'invalid', - 'bad-prevblk', - 0); - } + // Ensure it's not an orphan + if (!prev) { + throw new VerifyError(block, + 'invalid', + 'bad-prevblk', + 0); + } - if (prev.isHistorical()) - return this.state; + if (prev.isHistorical()) + return this.state; - ancestors = yield prev.getRetargetAncestors(); + ancestors = yield prev.getRetargetAncestors(); - height = prev.height + 1; - medianTime = prev.getMedianTime(ancestors); + height = prev.height + 1; + medianTime = prev.getMedianTime(ancestors); - // Ensure the timestamp is correct - if (block.ts <= medianTime) { - throw new VerifyError(block, - 'invalid', - 'time-too-old', - 0); - } + // Ensure the timestamp is correct + if (block.ts <= medianTime) { + throw new VerifyError(block, + 'invalid', + 'time-too-old', + 0); + } - if (block.bits !== this.getTarget(block, prev, ancestors)) { - throw new VerifyError(block, - 'invalid', - 'bad-diffbits', - 100); - } + if (block.bits !== this.getTarget(block, prev, ancestors)) { + throw new VerifyError(block, + 'invalid', + 'bad-diffbits', + 100); + } - state = yield this.getDeployments(block, prev, ancestors); - - // Can't verify any further when merkleblock or headers. - if (this.options.spv) - return state; - - // Make sure the height contained in the coinbase is correct. - if (state.hasBIP34()) { - if (block.getCoinbaseHeight() !== height) { - throw new VerifyError(block, - 'invalid', - 'bad-cb-height', - 100); - } - } - - // Check the commitment hash for segwit. - if (state.hasWitness()) { - commitmentHash = block.commitmentHash; - if (commitmentHash) { - if (!block.witnessNonce) { - throw new VerifyError(block, - 'invalid', - 'bad-witness-merkle-size', - 100); - } - if (commitmentHash !== block.getCommitmentHash('hex')) { - throw new VerifyError(block, - 'invalid', - 'bad-witness-merkle-match', - 100); - } - } - } - - // Blocks that do not commit to - // witness data cannot contain it. - if (!commitmentHash) { - if (block.hasWitness()) { - throw new VerifyError(block, - 'invalid', - 'unexpected-witness', - 100); - } - } - - // Check block weight (different from block size - // check in non-contextual verification). - if (block.getWeight() > constants.block.MAX_WEIGHT) { - throw new VerifyError(block, - 'invalid', - 'bad-blk-weight', - 100); - } - - // Get timestamp for tx.isFinal(). - ts = state.hasMTP() ? medianTime : block.ts; - - // Check all transactions - for (i = 0; i < block.txs.length; i++) { - tx = block.txs[i]; - - // Transactions must be finalized with - // regards to nSequence and nLockTime. - if (!tx.isFinal(height, ts)) { - throw new VerifyError(block, - 'invalid', - 'bad-txns-nonfinal', - 10); - } - } + state = yield this.getDeployments(block, prev, ancestors); + // Can't verify any further when merkleblock or headers. + if (this.options.spv) return state; - }, this); -}; + + // Make sure the height contained in the coinbase is correct. + if (state.hasBIP34()) { + if (block.getCoinbaseHeight() !== height) { + throw new VerifyError(block, + 'invalid', + 'bad-cb-height', + 100); + } + } + + // Check the commitment hash for segwit. + if (state.hasWitness()) { + commitmentHash = block.commitmentHash; + if (commitmentHash) { + if (!block.witnessNonce) { + throw new VerifyError(block, + 'invalid', + 'bad-witness-merkle-size', + 100); + } + if (commitmentHash !== block.getCommitmentHash('hex')) { + throw new VerifyError(block, + 'invalid', + 'bad-witness-merkle-match', + 100); + } + } + } + + // Blocks that do not commit to + // witness data cannot contain it. + if (!commitmentHash) { + if (block.hasWitness()) { + throw new VerifyError(block, + 'invalid', + 'unexpected-witness', + 100); + } + } + + // Check block weight (different from block size + // check in non-contextual verification). + if (block.getWeight() > constants.block.MAX_WEIGHT) { + throw new VerifyError(block, + 'invalid', + 'bad-blk-weight', + 100); + } + + // Get timestamp for tx.isFinal(). + ts = state.hasMTP() ? medianTime : block.ts; + + // Check all transactions + for (i = 0; i < block.txs.length; i++) { + tx = block.txs[i]; + + // Transactions must be finalized with + // regards to nSequence and nLockTime. + if (!tx.isFinal(height, ts)) { + throw new VerifyError(block, + 'invalid', + 'bad-txns-nonfinal', + 10); + } + } + + return state; +}); /** * Check all deployments on a chain, ranging from p2sh to segwit. @@ -424,105 +418,103 @@ Chain.prototype.verify = function verify(block, prev) { * [{@link VerifyError}, {@link DeploymentState}]. */ -Chain.prototype.getDeployments = function getDeployments(block, prev, ancestors) { - return spawn(function *() { - var state = new DeploymentState(); - var active; +Chain.prototype.getDeployments = spawn.co(function* getDeployments(block, prev, ancestors) { + var state = new DeploymentState(); + var active; - // For some reason bitcoind has p2sh in the - // mandatory flags by default, when in reality - // it wasn't activated until march 30th 2012. - // The first p2sh output and redeem script - // appeared on march 7th 2012, only it did - // not have a signature. See: - // 6a26d2ecb67f27d1fa5524763b49029d7106e91e3cc05743073461a719776192 - // 9c08a4d78931342b37fd5f72900fb9983087e6f46c4a097d8a1f52c74e28eaf6 - if (block.ts >= constants.block.BIP16_TIME) { - state.flags |= constants.flags.VERIFY_P2SH; - if (!this.state.hasP2SH()) - this.logger.warning('P2SH has been activated.'); - } + // For some reason bitcoind has p2sh in the + // mandatory flags by default, when in reality + // it wasn't activated until march 30th 2012. + // The first p2sh output and redeem script + // appeared on march 7th 2012, only it did + // not have a signature. See: + // 6a26d2ecb67f27d1fa5524763b49029d7106e91e3cc05743073461a719776192 + // 9c08a4d78931342b37fd5f72900fb9983087e6f46c4a097d8a1f52c74e28eaf6 + if (block.ts >= constants.block.BIP16_TIME) { + state.flags |= constants.flags.VERIFY_P2SH; + if (!this.state.hasP2SH()) + this.logger.warning('P2SH has been activated.'); + } - // Only allow version 2 blocks (coinbase height) - // once the majority of blocks are using it. - if (block.version < 2 && prev.isOutdated(2, ancestors)) + // Only allow version 2 blocks (coinbase height) + // once the majority of blocks are using it. + if (block.version < 2 && prev.isOutdated(2, ancestors)) + throw new VerifyError(block, 'obsolete', 'bad-version', 0); + + // Only allow version 3 blocks (sig validation) + // once the majority of blocks are using it. + if (block.version < 3 && prev.isOutdated(3, ancestors)) + throw new VerifyError(block, 'obsolete', 'bad-version', 0); + + // Only allow version 4 blocks (checklocktimeverify) + // once the majority of blocks are using it. + if (block.version < 4 && prev.isOutdated(4, ancestors)) + throw new VerifyError(block, 'obsolete', 'bad-version', 0); + + // Only allow version 5 blocks (bip141 - segnet3) + // once the majority of blocks are using it. + if (this.options.witness && this.network.oldWitness) { + if (block.version < 5 && prev.isOutdated(5, ancestors)) throw new VerifyError(block, 'obsolete', 'bad-version', 0); + } - // Only allow version 3 blocks (sig validation) - // once the majority of blocks are using it. - if (block.version < 3 && prev.isOutdated(3, ancestors)) - throw new VerifyError(block, 'obsolete', 'bad-version', 0); + // Make sure the height contained in the coinbase is correct. + if (block.version >= 2 && prev.isUpgraded(2, ancestors)) { + state.bip34 = true; + if (!this.state.hasBIP34()) + this.logger.warning('BIP34 has been activated.'); + } - // Only allow version 4 blocks (checklocktimeverify) - // once the majority of blocks are using it. - if (block.version < 4 && prev.isOutdated(4, ancestors)) - throw new VerifyError(block, 'obsolete', 'bad-version', 0); + // Signature validation is now enforced (bip66) + if (block.version >= 3 && prev.isUpgraded(3, ancestors)) { + state.flags |= constants.flags.VERIFY_DERSIG; + if (!this.state.hasBIP66()) + this.logger.warning('BIP66 has been activated.'); + } - // Only allow version 5 blocks (bip141 - segnet3) - // once the majority of blocks are using it. - if (this.options.witness && this.network.oldWitness) { - if (block.version < 5 && prev.isOutdated(5, ancestors)) - throw new VerifyError(block, 'obsolete', 'bad-version', 0); + // CHECKLOCKTIMEVERIFY is now usable (bip65) + if (block.version >= 4 && prev.isUpgraded(4, ancestors)) { + state.flags |= constants.flags.VERIFY_CHECKLOCKTIMEVERIFY; + if (!this.state.hasCLTV()) + this.logger.warning('BIP65 has been activated.'); + } + + // Segregrated witness is now usable (bip141 - segnet3) + if (this.options.witness && this.network.oldWitness) { + if (block.version >= 5 && prev.isUpgraded(5, ancestors)) { + state.flags |= constants.flags.VERIFY_WITNESS; + if (!this.state.hasWitness()) + this.logger.warning('Segwit has been activated.'); } + } - // Make sure the height contained in the coinbase is correct. - if (block.version >= 2 && prev.isUpgraded(2, ancestors)) { - state.bip34 = true; - if (!this.state.hasBIP34()) - this.logger.warning('BIP34 has been activated.'); - } + // CHECKSEQUENCEVERIFY and median time + // past locktimes are now usable (bip9 & bip113). + active = yield this.isActive(prev, 'csv'); + if (active) { + state.flags |= constants.flags.VERIFY_CHECKSEQUENCEVERIFY; + state.lockFlags |= constants.flags.VERIFY_SEQUENCE; + state.lockFlags |= constants.flags.MEDIAN_TIME_PAST; + if (!this.state.hasCSV()) + this.logger.warning('CSV has been activated.'); + } - // Signature validation is now enforced (bip66) - if (block.version >= 3 && prev.isUpgraded(3, ancestors)) { - state.flags |= constants.flags.VERIFY_DERSIG; - if (!this.state.hasBIP66()) - this.logger.warning('BIP66 has been activated.'); - } - - // CHECKLOCKTIMEVERIFY is now usable (bip65) - if (block.version >= 4 && prev.isUpgraded(4, ancestors)) { - state.flags |= constants.flags.VERIFY_CHECKLOCKTIMEVERIFY; - if (!this.state.hasCLTV()) - this.logger.warning('BIP65 has been activated.'); - } - - // Segregrated witness is now usable (bip141 - segnet3) - if (this.options.witness && this.network.oldWitness) { - if (block.version >= 5 && prev.isUpgraded(5, ancestors)) { + // Segregrated witness is now usable (bip141 - segnet4) + if (!this.network.oldWitness) { + active = yield this.isActive(prev, 'witness'); + if (active) { + // BIP147 + // state.flags |= constants.flags.VERIFY_NULLDUMMY; + if (this.options.witness) { state.flags |= constants.flags.VERIFY_WITNESS; if (!this.state.hasWitness()) this.logger.warning('Segwit has been activated.'); } } + } - // CHECKSEQUENCEVERIFY and median time - // past locktimes are now usable (bip9 & bip113). - active = yield this.isActive(prev, 'csv'); - if (active) { - state.flags |= constants.flags.VERIFY_CHECKSEQUENCEVERIFY; - state.lockFlags |= constants.flags.VERIFY_SEQUENCE; - state.lockFlags |= constants.flags.MEDIAN_TIME_PAST; - if (!this.state.hasCSV()) - this.logger.warning('CSV has been activated.'); - } - - // Segregrated witness is now usable (bip141 - segnet4) - if (!this.network.oldWitness) { - active = yield this.isActive(prev, 'witness'); - if (active) { - // BIP147 - // state.flags |= constants.flags.VERIFY_NULLDUMMY; - if (this.options.witness) { - state.flags |= constants.flags.VERIFY_WITNESS; - if (!this.state.hasWitness()) - this.logger.warning('Segwit has been activated.'); - } - } - } - - return state; - }, this); -}; + return state; +}); /** * Determine whether to check block for duplicate txids in blockchain @@ -535,37 +527,35 @@ Chain.prototype.getDeployments = function getDeployments(block, prev, ancestors) * @param {Function} callback - Returns [{@link VerifyError}]. */ -Chain.prototype.checkDuplicates = function checkDuplicates(block, prev) { - return spawn(function *() { - var height = prev.height + 1; - var entry; +Chain.prototype.checkDuplicates = spawn.co(function* checkDuplicates(block, prev) { + var height = prev.height + 1; + var entry; - if (this.options.spv) - return; + if (this.options.spv) + return; - if (this.isGenesis(block)) - return; + if (this.isGenesis(block)) + return; - if (prev.isHistorical()) - return; - - if (this.network.block.bip34height === -1 - || height <= this.network.block.bip34height) { - yield this.findDuplicates(block, prev); - return; - } - - // It was no longer possible to create duplicate - // TXs once bip34 went into effect. We can check - // for this to avoid a DB lookup. - entry = yield this.db.get(this.network.block.bip34height); - - if (entry && entry.hash === this.network.block.bip34hash) - return; + if (prev.isHistorical()) + return; + if (this.network.block.bip34height === -1 + || height <= this.network.block.bip34height) { yield this.findDuplicates(block, prev); - }, this); -}; + return; + } + + // It was no longer possible to create duplicate + // TXs once bip34 went into effect. We can check + // for this to avoid a DB lookup. + entry = yield this.db.get(this.network.block.bip34height); + + if (entry && entry.hash === this.network.block.bip34hash) + return; + + yield this.findDuplicates(block, prev); +}); /** * Check block for duplicate txids in blockchain @@ -578,29 +568,27 @@ Chain.prototype.checkDuplicates = function checkDuplicates(block, prev) { * @param {Function} callback - Returns [{@link VerifyError}]. */ -Chain.prototype.findDuplicates = function findDuplicates(block, prev) { - return spawn(function *() { - var height = prev.height + 1; - var i, tx, result; +Chain.prototype.findDuplicates = spawn.co(function* findDuplicates(block, prev) { + var height = prev.height + 1; + var i, tx, result; - // Check all transactions - for (i = 0; i < block.txs.length; i++) { - tx = block.txs[i]; - result = yield this.db.hasCoins(tx.hash()); + // Check all transactions + for (i = 0; i < block.txs.length; i++) { + tx = block.txs[i]; + result = yield this.db.hasCoins(tx.hash()); - if (result) { - // Blocks 91842 and 91880 created duplicate - // txids by using the same exact output script - // and extraNonce. - if (constants.bip30[height]) { - if (block.hash('hex') === constants.bip30[height]) - continue; - } - throw new VerifyError(block, 'invalid', 'bad-txns-BIP30', 100); + if (result) { + // Blocks 91842 and 91880 created duplicate + // txids by using the same exact output script + // and extraNonce. + if (constants.bip30[height]) { + if (block.hash('hex') === constants.bip30[height]) + continue; } + throw new VerifyError(block, 'invalid', 'bad-txns-BIP30', 100); } - }, this); -}; + } +}); /** * Check block transactions for all things pertaining @@ -619,109 +607,107 @@ Chain.prototype.findDuplicates = function findDuplicates(block, prev) { * @param {Function} callback - Returns [{@link VerifyError}]. */ -Chain.prototype.checkInputs = function checkInputs(block, prev, state) { - return spawn(function *() { - var height = prev.height + 1; - var historical = prev.isHistorical(); - var sigops = 0; - var jobs = []; - var ret = new VerifyResult(); - var i, view, tx, valid, result; +Chain.prototype.checkInputs = spawn.co(function* checkInputs(block, prev, state) { + var height = prev.height + 1; + var historical = prev.isHistorical(); + var sigops = 0; + var jobs = []; + var ret = new VerifyResult(); + var i, view, tx, valid, result; - if (this.options.spv) - return; + if (this.options.spv) + return; - if (this.isGenesis(block)) - return; + if (this.isGenesis(block)) + return; - view = yield this.db.getCoinView(block); + view = yield this.db.getCoinView(block); - // Check all transactions - for (i = 0; i < block.txs.length; i++) { - tx = block.txs[i]; + // Check all transactions + for (i = 0; i < block.txs.length; i++) { + tx = block.txs[i]; - // Ensure tx is not double spending an output. - if (!tx.isCoinbase()) { - if (!view.fillCoins(tx)) { - assert(!historical, 'BUG: Spent inputs in historical data!'); - throw new VerifyError(block, - 'invalid', - 'bad-txns-inputs-missingorspent', - 100); - } - } - - // Skip everything if we're - // using checkpoints. - if (historical) { - view.addTX(tx); - continue; - } - - // Verify sequence locks. - valid = yield this.checkLocks(prev, tx, state.lockFlags); - - if (!valid) { + // Ensure tx is not double spending an output. + if (!tx.isCoinbase()) { + if (!view.fillCoins(tx)) { + assert(!historical, 'BUG: Spent inputs in historical data!'); throw new VerifyError(block, 'invalid', - 'bad-txns-nonfinal', + 'bad-txns-inputs-missingorspent', 100); } + } - // Count sigops (legacy + scripthash? + witness?) - sigops += tx.getSigopsWeight(state.flags); - - if (sigops > constants.block.MAX_SIGOPS_WEIGHT) { - throw new VerifyError(block, - 'invalid', - 'bad-blk-sigops', - 100); - } - - // Contextual sanity checks. - if (!tx.isCoinbase()) { - if (!tx.checkInputs(height, ret)) { - throw new VerifyError(block, - 'invalid', - ret.reason, - ret.score); - } - - // Push onto verification queue. - jobs.push(tx.verifyAsync(state.flags)); - } - - // Add new coins. + // Skip everything if we're + // using checkpoints. + if (historical) { view.addTX(tx); + continue; } - if (historical) - return view; + // Verify sequence locks. + valid = yield this.checkLocks(prev, tx, state.lockFlags); - // Verify all txs in parallel. - result = yield Promise.all(jobs); - - for (i = 0; i < result.length; i++) { - valid = result[i]; - if (!valid) { - throw new VerifyError(block, - 'invalid', - 'mandatory-script-verify-flag-failed', - 100); - } - } - - // Make sure the miner isn't trying to conjure more coins. - if (block.getClaimed() > block.getReward(this.network)) { + if (!valid) { throw new VerifyError(block, 'invalid', - 'bad-cb-amount', + 'bad-txns-nonfinal', 100); } + // Count sigops (legacy + scripthash? + witness?) + sigops += tx.getSigopsWeight(state.flags); + + if (sigops > constants.block.MAX_SIGOPS_WEIGHT) { + throw new VerifyError(block, + 'invalid', + 'bad-blk-sigops', + 100); + } + + // Contextual sanity checks. + if (!tx.isCoinbase()) { + if (!tx.checkInputs(height, ret)) { + throw new VerifyError(block, + 'invalid', + ret.reason, + ret.score); + } + + // Push onto verification queue. + jobs.push(tx.verifyAsync(state.flags)); + } + + // Add new coins. + view.addTX(tx); + } + + if (historical) return view; - }, this); -}; + + // Verify all txs in parallel. + result = yield Promise.all(jobs); + + for (i = 0; i < result.length; i++) { + valid = result[i]; + if (!valid) { + throw new VerifyError(block, + 'invalid', + 'mandatory-script-verify-flag-failed', + 100); + } + } + + // Make sure the miner isn't trying to conjure more coins. + if (block.getClaimed() > block.getReward(this.network)) { + throw new VerifyError(block, + 'invalid', + 'bad-cb-amount', + 100); + } + + return view; +}); /** * Get the cached height for a hash if present. @@ -745,27 +731,25 @@ Chain.prototype._getCachedHeight = function _getCachedHeight(hash) { * @param {Function} callback - Returns [{@link Error}, {@link ChainEntry}]. */ -Chain.prototype.findFork = function findFork(fork, longer) { - return spawn(function *() { - while (fork.hash !== longer.hash) { - while (longer.height > fork.height) { - longer = yield longer.getPrevious(); - if (!longer) - throw new Error('No previous entry for new tip.'); - } - - if (fork.hash === longer.hash) - return fork; - - fork = yield fork.getPrevious(); - - if (!fork) - throw new Error('No previous entry for old tip.'); +Chain.prototype.findFork = spawn.co(function* findFork(fork, longer) { + while (fork.hash !== longer.hash) { + while (longer.height > fork.height) { + longer = yield longer.getPrevious(); + if (!longer) + throw new Error('No previous entry for new tip.'); } - return fork; - }, this); -}; + if (fork.hash === longer.hash) + return fork; + + fork = yield fork.getPrevious(); + + if (!fork) + throw new Error('No previous entry for old tip.'); + } + + return fork; +}); /** * Reorganize the blockchain (connect and disconnect inputs). @@ -777,47 +761,45 @@ Chain.prototype.findFork = function findFork(fork, longer) { * @param {Function} callback */ -Chain.prototype.reorganize = function reorganize(entry, block) { - return spawn(function *() { - var tip = this.tip; - var fork = yield this.findFork(tip, entry); - var disconnect = []; - var connect = []; - var i, e; +Chain.prototype.reorganize = spawn.co(function* reorganize(entry, block) { + var tip = this.tip; + var fork = yield this.findFork(tip, entry); + var disconnect = []; + var connect = []; + var i, e; - assert(fork); + assert(fork); - // Disconnect blocks/txs. - e = tip; - while (e.hash !== fork.hash) { - disconnect.push(e); - e = yield e.getPrevious(); - assert(e); - } + // Disconnect blocks/txs. + e = tip; + while (e.hash !== fork.hash) { + disconnect.push(e); + e = yield e.getPrevious(); + assert(e); + } - for (i = 0; i < disconnect.length; i++) { - e = disconnect[i]; - yield this.disconnect(e); - } + for (i = 0; i < disconnect.length; i++) { + e = disconnect[i]; + yield this.disconnect(e); + } - // Disconnect blocks/txs. - e = entry; - while (e.hash !== fork.hash) { - connect.push(e); - e = yield e.getPrevious(); - assert(e); - } + // Disconnect blocks/txs. + e = entry; + while (e.hash !== fork.hash) { + connect.push(e); + e = yield e.getPrevious(); + assert(e); + } - // We don't want to connect the new tip here. - // That will be done outside in setBestChain. - for (i = connect.length - 1; i >= 1; i--) { - e = connect[i]; - yield this.reconnect(e); - } + // We don't want to connect the new tip here. + // That will be done outside in setBestChain. + for (i = connect.length - 1; i >= 1; i--) { + e = connect[i]; + yield this.reconnect(e); + } - this.emit('reorganize', block, tip.height, tip.hash); - }, this); -}; + this.emit('reorganize', block, tip.height, tip.hash); +}); /** * Disconnect an entry from the chain (updates the tip). @@ -825,24 +807,22 @@ Chain.prototype.reorganize = function reorganize(entry, block) { * @param {Function} callback */ -Chain.prototype.disconnect = function disconnect(entry) { - return spawn(function *() { - var items = yield this.db.disconnect(entry); - var block = items[1]; - var prev = yield entry.getPrevious(); +Chain.prototype.disconnect = spawn.co(function* disconnect(entry) { + var items = yield this.db.disconnect(entry); + var block = items[1]; + var prev = yield entry.getPrevious(); - assert(prev); + assert(prev); - this.tip = prev; - this.height = prev.height; + this.tip = prev; + this.height = prev.height; - this.bestHeight = prev.height; - this.network.updateHeight(prev.height); + this.bestHeight = prev.height; + this.network.updateHeight(prev.height); - this.emit('tip', prev); - this.emit('disconnect', entry, block); - }, this); -}; + this.emit('tip', prev); + this.emit('disconnect', entry, block); +}); /** * Reconnect an entry to the chain (updates the tip). @@ -853,42 +833,40 @@ Chain.prototype.disconnect = function disconnect(entry) { * @param {Function} callback */ -Chain.prototype.reconnect = function reconnect(entry) { - return spawn(function *() { - var block = yield this.db.getBlock(entry.hash); - var prev, view; +Chain.prototype.reconnect = spawn.co(function* reconnect(entry) { + var block = yield this.db.getBlock(entry.hash); + var prev, view; - if (!block) { - assert(this.options.spv); - block = entry.toHeaders(); + if (!block) { + assert(this.options.spv); + block = entry.toHeaders(); + } + + prev = yield entry.getPrevious(); + assert(prev); + + try { + view = yield this.verifyContext(block, prev); + } catch (e) { + if (e.type === 'VerifyError') { + this.invalid[entry.hash] = true; + this.emit('invalid', block, entry.height); } + throw e; + } - prev = yield entry.getPrevious(); - assert(prev); + yield this.db.reconnect(entry, block, view); - try { - view = yield this.verifyContext(block, prev); - } catch (e) { - if (e.type === 'VerifyError') { - this.invalid[entry.hash] = true; - this.emit('invalid', block, entry.height); - } - throw e; - } + this.tip = entry; + this.height = entry.height; - yield this.db.reconnect(entry, block, view); + this.bestHeight = entry.height; + this.network.updateHeight(entry.height); - this.tip = entry; - this.height = entry.height; - - this.bestHeight = entry.height; - this.network.updateHeight(entry.height); - - this.emit('tip', entry); - this.emit('reconnect', entry, block); - this.emit('connect', entry, block); - }, this); -}; + this.emit('tip', entry); + this.emit('reconnect', entry, block); + this.emit('connect', entry, block); +}); /** * Set the best chain. This is called on every valid block @@ -902,48 +880,46 @@ Chain.prototype.reconnect = function reconnect(entry) { * @param {Function} callback - Returns [{@link VerifyError}]. */ -Chain.prototype.setBestChain = function setBestChain(entry, block, prev) { - return spawn(function *() { - var view; +Chain.prototype.setBestChain = spawn.co(function* setBestChain(entry, block, prev) { + var view; - assert(this.tip); + assert(this.tip); - // A higher fork has arrived. - // Time to reorganize the chain. - if (entry.prevBlock !== this.tip.hash) { - this.logger.warning('WARNING: Reorganizing chain.'); - yield this.reorganize(entry, block); + // A higher fork has arrived. + // Time to reorganize the chain. + if (entry.prevBlock !== this.tip.hash) { + this.logger.warning('WARNING: Reorganizing chain.'); + yield this.reorganize(entry, block); + } + + // Otherwise, everything is in order. + + // Do "contextual" verification on our block + // now that we're certain its previous + // block is in the chain. + try { + view = yield this.verifyContext(block, prev); + } catch (e) { + // Couldn't verify block. + // Revert the height. + block.setHeight(-1); + + if (e.type === 'VerifyError') { + this.invalid[entry.hash] = true; + this.emit('invalid', block, entry.height); } - // Otherwise, everything is in order. + throw e; + } - // Do "contextual" verification on our block - // now that we're certain its previous - // block is in the chain. - try { - view = yield this.verifyContext(block, prev); - } catch (e) { - // Couldn't verify block. - // Revert the height. - block.setHeight(-1); + // Save block and connect inputs. + yield this.db.save(entry, block, view, true); - if (e.type === 'VerifyError') { - this.invalid[entry.hash] = true; - this.emit('invalid', block, entry.height); - } + this.tip = entry; + this.height = entry.height; - throw e; - } - - // Save block and connect inputs. - yield this.db.save(entry, block, view, true); - - this.tip = entry; - this.height = entry.height; - - this.emit('tip', entry); - }, this); -}; + this.emit('tip', entry); +}); /** * Reset the chain to the desired height. This @@ -953,27 +929,25 @@ Chain.prototype.setBestChain = function setBestChain(entry, block, prev) { * @param {Function} callback */ -Chain.prototype.reset = function reset(height, force) { - return spawn(function *() { - var unlock = yield this._lock(null, force); - var result; - - try { - result = yield this.db.reset(height); - } catch (e) { - unlock(); - throw e; - } - - // Reset the orphan map completely. There may - // have been some orphans on a forked chain we - // no longer need. - this.purgeOrphans(); +Chain.prototype.reset = spawn.co(function* reset(height, force) { + var unlock = yield this._lock(null, force); + var result; + try { + result = yield this.db.reset(height); + } catch (e) { unlock(); - return result; - }, this); -}; + throw e; + } + + // Reset the orphan map completely. There may + // have been some orphans on a forked chain we + // no longer need. + this.purgeOrphans(); + + unlock(); + return result; +}); /** * Reset the chain to the desired timestamp (within 2 @@ -983,21 +957,19 @@ Chain.prototype.reset = function reset(height, force) { * @param {Function} callback */ -Chain.prototype.resetTime = function resetTime(ts) { - return spawn(function *() { - var unlock = yield this._lock(); - var entry = yield this.byTime(ts); +Chain.prototype.resetTime = spawn.co(function* resetTime(ts) { + var unlock = yield this._lock(); + var entry = yield this.byTime(ts); - if (!entry) - return unlock(); + if (!entry) + return unlock(); - try { - yield this.reset(entry.height, true); - } finally { - unlock(); - } - }, this); -}; + try { + yield this.reset(entry.height, true); + } finally { + unlock(); + } +}); /** * Wait for the chain to drain (finish processing @@ -1026,243 +998,241 @@ Chain.prototype.isBusy = function isBusy() { * @param {Function} callback - Returns [{@link VerifyError}]. */ -Chain.prototype.add = function add(block) { - return spawn(function *() { - var ret, unlock, initial, hash, prevBlock; - var height, checkpoint, orphan, entry; - var existing, prev; +Chain.prototype.add = spawn.co(function* add(block) { + var ret, unlock, initial, hash, prevBlock; + var height, checkpoint, orphan, entry; + var existing, prev; - assert(this.loaded); + assert(this.loaded); - unlock = yield this._lock(block); + unlock = yield this._lock(block); - ret = new VerifyResult(); - initial = true; + ret = new VerifyResult(); + initial = true; - while (block) { - hash = block.hash('hex'); - prevBlock = block.prevBlock; + while (block) { + hash = block.hash('hex'); + prevBlock = block.prevBlock; - this.currentBlock = hash; - this._mark(); + this.currentBlock = hash; + this._mark(); - // Do not revalidate known invalid blocks. - if (this.invalid[hash] || this.invalid[prevBlock]) { - this.emit('invalid', block, block.getCoinbaseHeight()); - this.invalid[hash] = true; - this.currentBlock = null; - unlock(); - throw new VerifyError(block, 'duplicate', 'duplicate', 100); + // Do not revalidate known invalid blocks. + if (this.invalid[hash] || this.invalid[prevBlock]) { + this.emit('invalid', block, block.getCoinbaseHeight()); + this.invalid[hash] = true; + this.currentBlock = null; + unlock(); + throw new VerifyError(block, 'duplicate', 'duplicate', 100); + } + + // Do we already have this block? + if (this.hasPending(hash)) { + this.emit('exists', block, block.getCoinbaseHeight()); + this.currentBlock = null; + unlock(); + throw new VerifyError(block, 'duplicate', 'duplicate', 0); + } + + // If the block is already known to be + // an orphan, ignore it. + orphan = this.orphan.map[prevBlock]; + if (orphan) { + // The orphan chain forked. + if (orphan.hash('hex') !== hash) { + this.emit('fork', block, + block.getCoinbaseHeight(), + orphan.hash('hex')); } - // Do we already have this block? - if (this.hasPending(hash)) { - this.emit('exists', block, block.getCoinbaseHeight()); - this.currentBlock = null; - unlock(); - throw new VerifyError(block, 'duplicate', 'duplicate', 0); + this.emit('orphan', block, block.getCoinbaseHeight()); + + this.currentBlock = null; + unlock(); + throw new VerifyError(block, 'invalid', 'bad-prevblk', 0); + } + + // Special case for genesis block. + if (this.isGenesis(block)) + break; + + // Validate the block we want to add. + // This is only necessary for new + // blocks coming in, not the resolving + // orphans. + if (initial && !block.verify(ret)) { + this.invalid[hash] = true; + this.emit('invalid', block, block.getCoinbaseHeight()); + this.currentBlock = null; + unlock(); + throw new VerifyError(block, 'invalid', ret.reason, ret.score); + } + + existing = yield this.db.has(hash); + + // Do we already have this block? + if (existing) { + this.emit('exists', block, block.getCoinbaseHeight()); + this.currentBlock = null; + unlock(); + throw new VerifyError(block, 'duplicate', 'duplicate', 0); + } + + // Find the previous block height/index. + prev = yield this.db.get(prevBlock); + + height = !prev ? -1 : prev.height + 1; + + if (height > this.bestHeight) { + this.bestHeight = height; + this.network.updateHeight(height); + } + + // If previous block wasn't ever seen, + // add it current to orphans and break. + if (!prev) { + this.orphan.count++; + this.orphan.size += block.getSize(); + this.orphan.map[prevBlock] = block; + this.orphan.bmap[hash] = block; + + // Update the best height based on the coinbase. + // We do this even for orphans (peers will send + // us their highest block during the initial + // getblocks sync, making it an orphan). + if (block.getCoinbaseHeight() > this.bestHeight) { + this.bestHeight = block.getCoinbaseHeight(); + this.network.updateHeight(this.bestHeight); } - // If the block is already known to be - // an orphan, ignore it. - orphan = this.orphan.map[prevBlock]; - if (orphan) { - // The orphan chain forked. - if (orphan.hash('hex') !== hash) { - this.emit('fork', block, - block.getCoinbaseHeight(), - orphan.hash('hex')); - } + this.emit('orphan', block, block.getCoinbaseHeight()); - this.emit('orphan', block, block.getCoinbaseHeight()); + this.currentBlock = null; + unlock(); + throw new VerifyError(block, 'invalid', 'bad-prevblk', 0); + } - this.currentBlock = null; - unlock(); - throw new VerifyError(block, 'invalid', 'bad-prevblk', 0); - } + // Verify the checkpoint. + if (this.options.useCheckpoints) { + checkpoint = this.network.checkpoints[height]; + if (checkpoint) { + // Someone is very likely trying to fool us. + if (hash !== checkpoint) { + this.purgeOrphans(); - // Special case for genesis block. - if (this.isGenesis(block)) - break; + this.emit('fork', block, height, checkpoint); - // Validate the block we want to add. - // This is only necessary for new - // blocks coming in, not the resolving - // orphans. - if (initial && !block.verify(ret)) { - this.invalid[hash] = true; - this.emit('invalid', block, block.getCoinbaseHeight()); - this.currentBlock = null; - unlock(); - throw new VerifyError(block, 'invalid', ret.reason, ret.score); - } - - existing = yield this.db.has(hash); - - // Do we already have this block? - if (existing) { - this.emit('exists', block, block.getCoinbaseHeight()); - this.currentBlock = null; - unlock(); - throw new VerifyError(block, 'duplicate', 'duplicate', 0); - } - - // Find the previous block height/index. - prev = yield this.db.get(prevBlock); - - height = !prev ? -1 : prev.height + 1; - - if (height > this.bestHeight) { - this.bestHeight = height; - this.network.updateHeight(height); - } - - // If previous block wasn't ever seen, - // add it current to orphans and break. - if (!prev) { - this.orphan.count++; - this.orphan.size += block.getSize(); - this.orphan.map[prevBlock] = block; - this.orphan.bmap[hash] = block; - - // Update the best height based on the coinbase. - // We do this even for orphans (peers will send - // us their highest block during the initial - // getblocks sync, making it an orphan). - if (block.getCoinbaseHeight() > this.bestHeight) { - this.bestHeight = block.getCoinbaseHeight(); - this.network.updateHeight(this.bestHeight); - } - - this.emit('orphan', block, block.getCoinbaseHeight()); - - this.currentBlock = null; - unlock(); - throw new VerifyError(block, 'invalid', 'bad-prevblk', 0); - } - - // Verify the checkpoint. - if (this.options.useCheckpoints) { - checkpoint = this.network.checkpoints[height]; - if (checkpoint) { - // Someone is very likely trying to fool us. - if (hash !== checkpoint) { - this.purgeOrphans(); - - this.emit('fork', block, height, checkpoint); - - this.currentBlock = null; - unlock(); - throw new VerifyError(block, - 'checkpoint', - 'checkpoint mismatch', - 100); - } - - this.emit('checkpoint', block, height); - } - } - - // Explanation: we try to keep as much data - // off the javascript heap as possible. Blocks - // in the future may be 8mb or 20mb, who knows. - // In fullnode-mode we store the blocks in - // "compact" form (the headers plus the raw - // Buffer object) until they're ready to be - // fully validated here. They are deserialized, - // validated, and emitted. Hopefully the deserialized - // blocks get cleaned up by the GC quickly. - if (block.memory) { - try { - block = block.toBlock(); - } catch (e) { - this.logger.error(e); this.currentBlock = null; unlock(); throw new VerifyError(block, - 'malformed', - 'error parsing message', + 'checkpoint', + 'checkpoint mismatch', 100); } + + this.emit('checkpoint', block, height); } - - // Update the block height early - // Some things in verifyContext may - // need access to height on txs. - block.setHeight(height); - - // Create a new chain entry. - entry = bcoin.chainentry.fromBlock(this, block, prev); - - // The block is on a alternate chain if the - // chainwork is less than or equal to - // our tip's. Add the block but do _not_ - // connect the inputs. - if (entry.chainwork.cmp(this.tip.chainwork) <= 0) { - try { - yield this.db.save(entry, block, null, false); - } catch (e) { - this.currentBlock = null; - unlock(); - throw e; - } - - this.emit('competitor', block, entry); - - if (!initial) - this.emit('competitor resolved', block, entry); - } else { - // Attempt to add block to the chain index. - try { - yield this.setBestChain(entry, block, prev); - } catch (e) { - this.currentBlock = null; - unlock(); - throw e; - } - - // Emit our block (and potentially resolved - // orphan) only if it is on the main chain. - this.emit('block', block, entry); - this.emit('connect', entry, block); - - if (!initial) - this.emit('resolved', block, entry); - } - - // Keep track of stats. - this._done(block, entry); - - // No orphan chain. - if (!this.orphan.map[hash]) - break; - - // An orphan chain was found, start resolving. - initial = false; - block = this.orphan.map[hash]; - delete this.orphan.bmap[block.hash('hex')]; - delete this.orphan.map[hash]; - this.orphan.count--; - this.orphan.size -= block.getSize(); } - // Failsafe for large orphan chains. Do not - // allow more than 20mb stored in memory. - if (this.orphan.size > this.orphanLimit) - this.pruneOrphans(); - - yield utils.wait(); - - if (!this.synced && this.isFull()) { - this.synced = true; - this.emit('full'); + // Explanation: we try to keep as much data + // off the javascript heap as possible. Blocks + // in the future may be 8mb or 20mb, who knows. + // In fullnode-mode we store the blocks in + // "compact" form (the headers plus the raw + // Buffer object) until they're ready to be + // fully validated here. They are deserialized, + // validated, and emitted. Hopefully the deserialized + // blocks get cleaned up by the GC quickly. + if (block.memory) { + try { + block = block.toBlock(); + } catch (e) { + this.logger.error(e); + this.currentBlock = null; + unlock(); + throw new VerifyError(block, + 'malformed', + 'error parsing message', + 100); + } } - this.currentBlock = null; + // Update the block height early + // Some things in verifyContext may + // need access to height on txs. + block.setHeight(height); - unlock(); - }, this); -}; + // Create a new chain entry. + entry = bcoin.chainentry.fromBlock(this, block, prev); + + // The block is on a alternate chain if the + // chainwork is less than or equal to + // our tip's. Add the block but do _not_ + // connect the inputs. + if (entry.chainwork.cmp(this.tip.chainwork) <= 0) { + try { + yield this.db.save(entry, block, null, false); + } catch (e) { + this.currentBlock = null; + unlock(); + throw e; + } + + this.emit('competitor', block, entry); + + if (!initial) + this.emit('competitor resolved', block, entry); + } else { + // Attempt to add block to the chain index. + try { + yield this.setBestChain(entry, block, prev); + } catch (e) { + this.currentBlock = null; + unlock(); + throw e; + } + + // Emit our block (and potentially resolved + // orphan) only if it is on the main chain. + this.emit('block', block, entry); + this.emit('connect', entry, block); + + if (!initial) + this.emit('resolved', block, entry); + } + + // Keep track of stats. + this._done(block, entry); + + // No orphan chain. + if (!this.orphan.map[hash]) + break; + + // An orphan chain was found, start resolving. + initial = false; + block = this.orphan.map[hash]; + delete this.orphan.bmap[block.hash('hex')]; + delete this.orphan.map[hash]; + this.orphan.count--; + this.orphan.size -= block.getSize(); + } + + // Failsafe for large orphan chains. Do not + // allow more than 20mb stored in memory. + if (this.orphan.size > this.orphanLimit) + this.pruneOrphans(); + + yield utils.wait(); + + if (!this.synced && this.isFull()) { + this.synced = true; + this.emit('full'); + } + + this.currentBlock = null; + + unlock(); +}); /** * Test whether the chain has reached its slow height. @@ -1393,20 +1363,18 @@ Chain.prototype.pruneOrphans = function pruneOrphans() { * @param {Function} callback - Returns [Error, Boolean]. */ -Chain.prototype.has = function has(hash) { - return spawn(function *() { - if (this.hasOrphan(hash)) - return true; +Chain.prototype.has = spawn.co(function* has(hash) { + if (this.hasOrphan(hash)) + return true; - if (this.hasPending(hash)) - return true; + if (this.hasPending(hash)) + return true; - if (hash === this.currentBlock) - return true; + if (hash === this.currentBlock) + return true; - return yield this.hasBlock(hash); - }, this); -}; + return yield this.hasBlock(hash); +}); /** * Find a block entry by timestamp. @@ -1414,39 +1382,37 @@ Chain.prototype.has = function has(hash) { * @param {Function} callback - Returns [Error, {@link ChainEntry}]. */ -Chain.prototype.byTime = function byTime(ts) { - return spawn(function *() { - var start = 0; - var end = this.height; - var pos, delta, entry; +Chain.prototype.byTime = spawn.co(function* byTime(ts) { + var start = 0; + var end = this.height; + var pos, delta, entry; - if (ts >= this.tip.ts) - return this.tip; + if (ts >= this.tip.ts) + return this.tip; - // Do a binary search for a block - // mined within an hour of the - // timestamp. - while (start < end) { - pos = (start + end) >>> 1; - entry = yield this.db.get(pos); + // Do a binary search for a block + // mined within an hour of the + // timestamp. + while (start < end) { + pos = (start + end) >>> 1; + entry = yield this.db.get(pos); - if (!entry) - return; + if (!entry) + return; - delta = Math.abs(ts - entry.ts); + delta = Math.abs(ts - entry.ts); - if (delta <= 60 * 60) - break; + if (delta <= 60 * 60) + break; - if (ts < entry.ts) - end = pos - 1; - else - start = pos + 1; - } + if (ts < entry.ts) + end = pos - 1; + else + start = pos + 1; + } - return entry; - }, this); -}; + return entry; +}); /** * Test the chain to see if it contains a block. @@ -1555,68 +1521,66 @@ Chain.prototype.getProgress = function getProgress() { * @param {Function} callback - Returns [Error, {@link Hash}[]]. */ -Chain.prototype.getLocator = function getLocator(start) { - return spawn(function *() { - var unlock = yield this._lock(); - var hashes = []; - var step = 1; - var height, entry, main, hash; +Chain.prototype.getLocator = spawn.co(function* getLocator(start) { + var unlock = yield this._lock(); + var hashes = []; + var step = 1; + var height, entry, main, hash; - if (start == null) - start = this.tip.hash; + if (start == null) + start = this.tip.hash; - entry = yield this.db.get(start); + entry = yield this.db.get(start); - if (!entry) { - // We could simply return `start` here, - // but there is no required "spacing" - // for locator hashes. Pretend this hash - // is our tip. This is useful for - // getheaders. - if (typeof start === 'string') - hashes.push(start); - entry = this.tip; + if (!entry) { + // We could simply return `start` here, + // but there is no required "spacing" + // for locator hashes. Pretend this hash + // is our tip. This is useful for + // getheaders. + if (typeof start === 'string') + hashes.push(start); + entry = this.tip; + } + + height = entry.height; + main = yield entry.isMainChain(); + hash = entry.hash; + + while (hash) { + hashes.push(hash); + + if (height === 0) + break; + + height = Math.max(height - step, 0); + + if (hashes.length > 10) + step *= 2; + + if (height === 0) { + hash = this.network.genesis.hash; + continue; } - height = entry.height; - main = yield entry.isMainChain(); + // If we're on the main chain, we can + // do a fast lookup of the hash. + if (main) { + hash = yield this.db.getHash(height); + continue; + } + + entry = yield entry.getAncestorByHeight(height); + + if (!entry) + break; + hash = entry.hash; + } - while (hash) { - hashes.push(hash); - - if (height === 0) - break; - - height = Math.max(height - step, 0); - - if (hashes.length > 10) - step *= 2; - - if (height === 0) { - hash = this.network.genesis.hash; - continue; - } - - // If we're on the main chain, we can - // do a fast lookup of the hash. - if (main) { - hash = yield this.db.getHash(height); - continue; - } - - entry = yield entry.getAncestorByHeight(height); - - if (!entry) - break; - - hash = entry.hash; - } - - unlock(); - return hashes; - }, this); -}; + unlock(); + return hashes; +}); /** * Calculate the orphan root of the hash (if it is an orphan). @@ -1645,13 +1609,11 @@ Chain.prototype.getOrphanRoot = function getOrphanRoot(hash) { * (target is in compact/mantissa form). */ -Chain.prototype.getCurrentTarget = function getCurrentTarget() { - return spawn(function *() { - if (!this.tip) - return this.network.pow.bits; - return yield this.getTargetAsync(null, this.tip); - }, this); -}; +Chain.prototype.getCurrentTarget = spawn.co(function* getCurrentTarget() { + if (!this.tip) + return this.network.pow.bits; + return yield this.getTargetAsync(null, this.tip); +}); /** * Calculate the target based on the passed-in chain entry. @@ -1661,20 +1623,18 @@ Chain.prototype.getCurrentTarget = function getCurrentTarget() { * (target is in compact/mantissa form). */ -Chain.prototype.getTargetAsync = function getTargetAsync(block, prev) { - return spawn(function *() { - var ancestors; +Chain.prototype.getTargetAsync = spawn.co(function* getTargetAsync(block, prev) { + var ancestors; - if ((prev.height + 1) % this.network.pow.retargetInterval !== 0) { - if (!this.network.pow.difficultyReset) - return this.getTarget(block, prev); - } + if ((prev.height + 1) % this.network.pow.retargetInterval !== 0) { + if (!this.network.pow.difficultyReset) + return this.getTarget(block, prev); + } - ancestors = yield prev.getAncestors(this.network.pow.retargetInterval); + ancestors = yield prev.getAncestors(this.network.pow.retargetInterval); - return this.getTarget(block, prev, ancestors); - }, this); -}; + return this.getTarget(block, prev, ancestors); +}); /** * Calculate the target synchronously. _Must_ @@ -1758,21 +1718,19 @@ Chain.prototype.retarget = function retarget(prev, first) { * hash of the latest known block). */ -Chain.prototype.findLocator = function findLocator(locator) { - return spawn(function *() { - var i, hash, main; +Chain.prototype.findLocator = spawn.co(function* findLocator(locator) { + var i, hash, main; - for (i = 0; i < locator.length; i++) { - hash = locator[i]; - main = yield this.db.isMainChain(hash); + for (i = 0; i < locator.length; i++) { + hash = locator[i]; + main = yield this.db.isMainChain(hash); - if (main) - return hash; - } + if (main) + return hash; + } - return this.network.genesis.hash; - }, this); -}; + return this.network.genesis.hash; +}); /** * Check whether a versionbits deployment is active (BIP9: versionbits). @@ -1784,18 +1742,16 @@ Chain.prototype.findLocator = function findLocator(locator) { * @param {Function} callback - Returns [Error, Number]. */ -Chain.prototype.isActive = function isActive(prev, id) { - return spawn(function *() { - var state; +Chain.prototype.isActive = spawn.co(function* isActive(prev, id) { + var state; - if (prev.isHistorical()) - return false; + if (prev.isHistorical()) + return false; - state = yield this.getState(prev, id); + state = yield this.getState(prev, id); - return state === constants.thresholdStates.ACTIVE; - }, this); -}; + return state === constants.thresholdStates.ACTIVE; +}); /** * Get chain entry state for a deployment (BIP9: versionbits). @@ -1807,125 +1763,123 @@ Chain.prototype.isActive = function isActive(prev, id) { * @param {Function} callback - Returns [Error, Number]. */ -Chain.prototype.getState = function getState(prev, id) { - return spawn(function *() { - var period = this.network.minerWindow; - var threshold = this.network.activationThreshold; - var deployment = this.network.deployments[id]; - var stateCache = this.stateCache[id]; - var timeStart, timeTimeout, compute, height; - var i, entry, count, state, block, medianTime; +Chain.prototype.getState = spawn.co(function* getState(prev, id) { + var period = this.network.minerWindow; + var threshold = this.network.activationThreshold; + var deployment = this.network.deployments[id]; + var stateCache = this.stateCache[id]; + var timeStart, timeTimeout, compute, height; + var i, entry, count, state, block, medianTime; - if (!deployment) - return constants.thresholdStates.FAILED; + if (!deployment) + return constants.thresholdStates.FAILED; - timeStart = deployment.startTime; - timeTimeout = deployment.timeout; - compute = []; + timeStart = deployment.startTime; + timeTimeout = deployment.timeout; + compute = []; + + if (!prev) + return constants.thresholdStates.DEFINED; + + if (((prev.height + 1) % period) !== 0) { + height = prev.height - ((prev.height + 1) % period); + prev = yield prev.getAncestorByHeight(height); if (!prev) - return constants.thresholdStates.DEFINED; + return constants.thresholdStates.FAILED; - if (((prev.height + 1) % period) !== 0) { - height = prev.height - ((prev.height + 1) % period); - prev = yield prev.getAncestorByHeight(height); + assert(prev.height === height); + assert(((prev.height + 1) % period) === 0); + } - if (!prev) - return constants.thresholdStates.FAILED; + entry = prev; + state = constants.thresholdStates.DEFINED; - assert(prev.height === height); - assert(((prev.height + 1) % period) === 0); + while (entry) { + if (stateCache[entry.hash] != null) { + state = stateCache[entry.hash]; + break; } - entry = prev; - state = constants.thresholdStates.DEFINED; + medianTime = yield entry.getMedianTimeAsync(); - while (entry) { - if (stateCache[entry.hash] != null) { - state = stateCache[entry.hash]; - break; - } - - medianTime = yield entry.getMedianTimeAsync(); - - if (medianTime < timeStart) { - state = constants.thresholdStates.DEFINED; - break; - } - - compute.push(entry); - - height = entry.height - period; - - entry = yield entry.getAncestorByHeight(height); + if (medianTime < timeStart) { + state = constants.thresholdStates.DEFINED; + break; } - while (compute.length) { - entry = compute.pop(); + compute.push(entry); - switch (state) { - case constants.thresholdStates.DEFINED: - medianTime = yield entry.getMedianTimeAsync(); + height = entry.height - period; - if (medianTime >= timeTimeout) { - state = constants.thresholdStates.FAILED; - stateCache[entry.hash] = state; - continue; - } + entry = yield entry.getAncestorByHeight(height); + } - if (medianTime >= timeStart) { - state = constants.thresholdStates.STARTED; - stateCache[entry.hash] = state; - continue; - } + while (compute.length) { + entry = compute.pop(); + switch (state) { + case constants.thresholdStates.DEFINED: + medianTime = yield entry.getMedianTimeAsync(); + + if (medianTime >= timeTimeout) { + state = constants.thresholdStates.FAILED; stateCache[entry.hash] = state; continue; - case constants.thresholdStates.STARTED: - medianTime = yield entry.getMedianTimeAsync(); + } - if (medianTime >= timeTimeout) { - state = constants.thresholdStates.FAILED; - stateCache[entry.hash] = state; + if (medianTime >= timeStart) { + state = constants.thresholdStates.STARTED; + stateCache[entry.hash] = state; + continue; + } + + stateCache[entry.hash] = state; + continue; + case constants.thresholdStates.STARTED: + medianTime = yield entry.getMedianTimeAsync(); + + if (medianTime >= timeTimeout) { + state = constants.thresholdStates.FAILED; + stateCache[entry.hash] = state; + break; + } + + i = 0; + count = 0; + block = entry; + + while (block) { + if (i++ >= period) break; - } - i = 0; - count = 0; - block = entry; + if (hasBit(block, deployment)) + count++; - while (block) { - if (i++ >= period) - break; + block = yield block.getPrevious(); + } - if (hasBit(block, deployment)) - count++; + if (count >= threshold) + state = constants.thresholdStates.LOCKED_IN; - block = yield block.getPrevious(); - } - - if (count >= threshold) - state = constants.thresholdStates.LOCKED_IN; - - stateCache[entry.hash] = state; - break; - case constants.thresholdStates.LOCKED_IN: - state = constants.thresholdStates.ACTIVE; - stateCache[entry.hash] = state; - break; - case constants.thresholdStates.FAILED: - case constants.thresholdStates.ACTIVE: - stateCache[entry.hash] = state; - break; - default: - assert(false, 'Bad state.'); - break; - } + stateCache[entry.hash] = state; + break; + case constants.thresholdStates.LOCKED_IN: + state = constants.thresholdStates.ACTIVE; + stateCache[entry.hash] = state; + break; + case constants.thresholdStates.FAILED: + case constants.thresholdStates.ACTIVE: + stateCache[entry.hash] = state; + break; + default: + assert(false, 'Bad state.'); + break; } + } - return state; - }, this); -}; + return state; +}); /** * Compute the version for a new block (BIP9: versionbits). @@ -1934,29 +1888,27 @@ Chain.prototype.getState = function getState(prev, id) { * @param {Function} callback - Returns [Error, Number]. */ -Chain.prototype.computeBlockVersion = function computeBlockVersion(prev) { - return spawn(function *() { - var keys = Object.keys(this.network.deployments); - var version = 0; - var i, id, deployment, state; +Chain.prototype.computeBlockVersion = spawn.co(function* computeBlockVersion(prev) { + var keys = Object.keys(this.network.deployments); + var version = 0; + var i, id, deployment, state; - for (i = 0; i < keys.length; i++) { - id = keys[i]; - deployment = this.network.deployments[id]; - state = yield this.getState(prev, id); + for (i = 0; i < keys.length; i++) { + id = keys[i]; + deployment = this.network.deployments[id]; + state = yield this.getState(prev, id); - if (state === constants.thresholdStates.LOCKED_IN - || state === constants.thresholdStates.STARTED) { - version |= (1 << deployment.bit); - } + if (state === constants.thresholdStates.LOCKED_IN + || state === constants.thresholdStates.STARTED) { + version |= (1 << deployment.bit); } + } - version |= constants.versionbits.TOP_BITS; - version >>>= 0; + version |= constants.versionbits.TOP_BITS; + version >>>= 0; - return version; - }, this); -}; + return version; +}); /** * Get the current deployment state of the chain. Called on load. @@ -1964,23 +1916,21 @@ Chain.prototype.computeBlockVersion = function computeBlockVersion(prev) { * @param {Function} callback - Returns [Error, {@link DeploymentState}]. */ -Chain.prototype.getDeploymentState = function getDeploymentState() { - return spawn(function *() { - var prev, ancestors; +Chain.prototype.getDeploymentState = spawn.co(function* getDeploymentState() { + var prev, ancestors; - if (!this.tip) - return this.state; + if (!this.tip) + return this.state; - prev = yield this.tip.getPrevious(); + prev = yield this.tip.getPrevious(); - if (!prev) - return this.state; + if (!prev) + return this.state; - ancestors = yield prev.getRetargetAncestors(); + ancestors = yield prev.getRetargetAncestors(); - return yield this.getDeployments(this.tip, prev, ancestors); - }, this); -}; + return yield this.getDeployments(this.tip, prev, ancestors); +}); /** * Check transaction finality, taking into account MEDIAN_TIME_PAST @@ -1991,23 +1941,21 @@ Chain.prototype.getDeploymentState = function getDeploymentState() { * @param {Function} callback - Returns [Error, Boolean]. */ -Chain.prototype.checkFinal = function checkFinal(prev, tx, flags) { - return spawn(function *() { - var height = prev.height + 1; - var ts; +Chain.prototype.checkFinal = spawn.co(function* checkFinal(prev, tx, flags) { + var height = prev.height + 1; + var ts; - // We can skip MTP if the locktime is height. - if (tx.locktime < constants.LOCKTIME_THRESHOLD) - return tx.isFinal(height, -1); + // We can skip MTP if the locktime is height. + if (tx.locktime < constants.LOCKTIME_THRESHOLD) + return tx.isFinal(height, -1); - if (flags & constants.flags.MEDIAN_TIME_PAST) { - ts = yield prev.getMedianTimeAsync(); - return tx.isFinal(height, ts); - } + if (flags & constants.flags.MEDIAN_TIME_PAST) { + ts = yield prev.getMedianTimeAsync(); + return tx.isFinal(height, ts); + } - return tx.isFinal(height, bcoin.now()); - }, this); -}; + return tx.isFinal(height, bcoin.now()); +}); /** * Get the necessary minimum time and height sequence locks for a transaction. @@ -2018,48 +1966,46 @@ Chain.prototype.checkFinal = function checkFinal(prev, tx, flags) { * [Error, Number(minTime), Number(minHeight)]. */ -Chain.prototype.getLocks = function getLocks(prev, tx, flags) { - return spawn(function *() { - var mask = constants.sequence.MASK; - var granularity = constants.sequence.GRANULARITY; - var disableFlag = constants.sequence.DISABLE_FLAG; - var typeFlag = constants.sequence.TYPE_FLAG; - var hasFlag = flags & constants.flags.VERIFY_SEQUENCE; - var minHeight = -1; - var minTime = -1; - var coinHeight, coinTime; - var i, input, entry; +Chain.prototype.getLocks = spawn.co(function* getLocks(prev, tx, flags) { + var mask = constants.sequence.MASK; + var granularity = constants.sequence.GRANULARITY; + var disableFlag = constants.sequence.DISABLE_FLAG; + var typeFlag = constants.sequence.TYPE_FLAG; + var hasFlag = flags & constants.flags.VERIFY_SEQUENCE; + var minHeight = -1; + var minTime = -1; + var coinHeight, coinTime; + var i, input, entry; - if (tx.isCoinbase() || tx.version < 2 || !hasFlag) - return [minHeight, minTime]; + if (tx.isCoinbase() || tx.version < 2 || !hasFlag) + return [minHeight, minTime]; - for (i = 0; i < tx.inputs.length; i++) { - input = tx.inputs[i]; + for (i = 0; i < tx.inputs.length; i++) { + input = tx.inputs[i]; - if (input.sequence & disableFlag) - continue; + if (input.sequence & disableFlag) + continue; - coinHeight = input.coin.height === -1 - ? this.height + 1 - : input.coin.height; + coinHeight = input.coin.height === -1 + ? this.height + 1 + : input.coin.height; - if ((input.sequence & typeFlag) === 0) { - coinHeight += (input.sequence & mask) - 1; - minHeight = Math.max(minHeight, coinHeight); - continue; - } - - entry = yield prev.getAncestorByHeight(Math.max(coinHeight - 1, 0)); - assert(entry, 'Database is corrupt.'); - - coinTime = yield entry.getMedianTimeAsync(); - coinTime += ((input.sequence & mask) << granularity) - 1; - minTime = Math.max(minTime, coinTime); + if ((input.sequence & typeFlag) === 0) { + coinHeight += (input.sequence & mask) - 1; + minHeight = Math.max(minHeight, coinHeight); + continue; } - return [minHeight, minTime]; - }, this); -}; + entry = yield prev.getAncestorByHeight(Math.max(coinHeight - 1, 0)); + assert(entry, 'Database is corrupt.'); + + coinTime = yield entry.getMedianTimeAsync(); + coinTime += ((input.sequence & mask) << granularity) - 1; + minTime = Math.max(minTime, coinTime); + } + + return [minHeight, minTime]; +}); /** * Evaluate sequence locks. @@ -2069,24 +2015,22 @@ Chain.prototype.getLocks = function getLocks(prev, tx, flags) { * @param {Function} callback - Returns [Error, Boolean]. */ -Chain.prototype.evalLocks = function evalLocks(prev, minHeight, minTime) { - return spawn(function *() { - var medianTime; +Chain.prototype.evalLocks = spawn.co(function* evalLocks(prev, minHeight, minTime) { + var medianTime; - if (minHeight >= prev.height + 1) - return false; - - if (minTime === -1) - return true; - - medianTime = yield prev.getMedianTimeAsync(); - - if (minTime >= medianTime) - return false; + if (minHeight >= prev.height + 1) + return false; + if (minTime === -1) return true; - }, this); -}; + + medianTime = yield prev.getMedianTimeAsync(); + + if (minTime >= medianTime) + return false; + + return true; +}); /** * Verify sequence locks. @@ -2096,14 +2040,12 @@ Chain.prototype.evalLocks = function evalLocks(prev, minHeight, minTime) { * @param {Function} callback - Returns [Error, Boolean]. */ -Chain.prototype.checkLocks = function checkLocks(prev, tx, flags) { - return spawn(function *() { - var times = yield this.getLocks(prev, tx, flags); - var minHeight = times[0]; - var minTime = times[1]; - return yield this.evalLocks(prev, minHeight, minTime); - }, this); -}; +Chain.prototype.checkLocks = spawn.co(function* checkLocks(prev, tx, flags) { + var times = yield this.getLocks(prev, tx, flags); + var minHeight = times[0]; + var minTime = times[1]; + return yield this.evalLocks(prev, minHeight, minTime); +}); /** * Represents the deployment state of the chain. diff --git a/lib/chain/chaindb.js b/lib/chain/chaindb.js index 3cafbcf2..f8a77120 100644 --- a/lib/chain/chaindb.js +++ b/lib/chain/chaindb.js @@ -214,39 +214,37 @@ ChainDB.layout = layout; * @param {Function} callback */ -ChainDB.prototype._open = function open() { - return spawn(function *() { - var result, genesis, block; +ChainDB.prototype._open = spawn.co(function* open() { + var result, genesis, block; - this.logger.info('Starting chain load.'); + this.logger.info('Starting chain load.'); - yield this.db.open(); + yield this.db.open(); - result = yield this.db.has(layout.e(this.network.genesis.hash)); + result = yield this.db.has(layout.e(this.network.genesis.hash)); - if (result) { - yield this.initState(); - } else { - block = bcoin.block.fromRaw(this.network.genesisBlock, 'hex'); - block.setHeight(0); + if (result) { + yield this.initState(); + } else { + block = bcoin.block.fromRaw(this.network.genesisBlock, 'hex'); + block.setHeight(0); - genesis = bcoin.chainentry.fromBlock(this.chain, block); + genesis = bcoin.chainentry.fromBlock(this.chain, block); - yield this.save(genesis, block, null, true); - } + yield this.save(genesis, block, null, true); + } - this.logger.info('Chain successfully loaded.'); + this.logger.info('Chain successfully loaded.'); - this.logger.info( - 'Chain State: hash=%s tx=%d coin=%d value=%s.', - this.state.rhash, - this.state.tx, - this.state.coin, - utils.btc(this.state.value)); + this.logger.info( + 'Chain State: hash=%s tx=%d coin=%d value=%s.', + this.state.rhash, + this.state.tx, + this.state.coin, + utils.btc(this.state.value)); - yield this.db.checkVersion('V', 1); - }, this); -}; + yield this.db.checkVersion('V', 1); +}); /** * Close the chain db, wait for the database to close. @@ -320,32 +318,30 @@ ChainDB.prototype.drop = function drop() { * @param {Function} callback */ -ChainDB.prototype.commit = function commit() { - return spawn(function *() { - assert(this.current); - assert(this.pending); - - try { - yield this.current.write(); - } catch (e) { - this.current = null; - this.pending = null; - throw e; - } +ChainDB.prototype.commit = spawn.co(function* commit() { + assert(this.current); + assert(this.pending); + try { + yield this.current.write(); + } catch (e) { this.current = null; - - // Overwrite the entire state - // with our new best state - // only if it is committed. - // Note that alternate chain - // tips do not commit anything. - if (this.pending.committed) - this.state = this.pending; - this.pending = null; - }, this); -}; + throw e; + } + + this.current = null; + + // Overwrite the entire state + // with our new best state + // only if it is committed. + // Note that alternate chain + // tips do not commit anything. + if (this.pending.committed) + this.state = this.pending; + + this.pending = null; +}); /** * Add an entry to the LRU cache. @@ -393,34 +389,32 @@ ChainDB.prototype.getCache = function getCache(hash) { * @param {Function} callback - Returns [Error, Number]. */ -ChainDB.prototype.getHeight = function getHeight(hash) { - return spawn(function *() { - var entry, height; +ChainDB.prototype.getHeight = spawn.co(function* getHeight(hash) { + var entry, height; - checkHash(hash); + checkHash(hash); - if (typeof hash === 'number') - return hash; + if (typeof hash === 'number') + return hash; - if (hash === constants.NULL_HASH) - return -1; + if (hash === constants.NULL_HASH) + return -1; - entry = this.cacheHash.get(hash); + entry = this.cacheHash.get(hash); - if (entry) - return entry.height; + if (entry) + return entry.height; - height = yield this.db.fetch(layout.h(hash), function(data) { - assert(data.length === 4, 'Database corruption.'); - return data.readUInt32LE(0, true); - }); + height = yield this.db.fetch(layout.h(hash), function(data) { + assert(data.length === 4, 'Database corruption.'); + return data.readUInt32LE(0, true); + }); - if (height == null) - return -1; + if (height == null) + return -1; - return height; - }, this); -}; + return height; +}); /** * Get the hash of a block by height. Note that this @@ -429,41 +423,37 @@ ChainDB.prototype.getHeight = function getHeight(hash) { * @param {Function} callback - Returns [Error, {@link Hash}]. */ -ChainDB.prototype.getHash = function getHash(height) { - return spawn(function *() { - var entry; +ChainDB.prototype.getHash = spawn.co(function* getHash(height) { + var entry; - checkHash(height); + checkHash(height); - if (typeof height === 'string') - return height; + if (typeof height === 'string') + return height; - entry = this.cacheHeight.get(height); + entry = this.cacheHeight.get(height); - if (entry) - return entry.hash; + if (entry) + return entry.hash; - return yield this.db.fetch(layout.H(height), function(data) { - assert(data.length === 32, 'Database corruption.'); - return data.toString('hex'); - }); - }, this); -}; + return yield this.db.fetch(layout.H(height), function(data) { + assert(data.length === 32, 'Database corruption.'); + return data.toString('hex'); + }); +}); /** * Get the current chain height from the tip record. * @param {Function} callback - Returns [Error, Number]. */ -ChainDB.prototype.getChainHeight = function getChainHeight() { - return spawn(function *() { - var entry = yield this.getTip(); - if (!entry) - return -1; +ChainDB.prototype.getChainHeight = spawn.co(function* getChainHeight() { + var entry = yield this.getTip(); + if (!entry) + return -1; - return entry.height; - }, this); -}; + return entry.height; +}); /** * Get both hash and height depending on the value passed in. @@ -471,34 +461,32 @@ ChainDB.prototype.getChainHeight = function getChainHeight() { * @param {Function} callback - Returns [Error, {@link Hash}, Number]. */ -ChainDB.prototype.getBoth = function getBoth(block) { - return spawn(function *() { - var hash, height; +ChainDB.prototype.getBoth = spawn.co(function* getBoth(block) { + var hash, height; - checkHash(block); + checkHash(block); - if (typeof block === 'string') - hash = block; - else - height = block; + if (typeof block === 'string') + hash = block; + else + height = block; - if (!hash) { - hash = yield this.getHash(height); + if (!hash) { + hash = yield this.getHash(height); - if (hash == null) - height = -1; - - return [hash, height]; - } - - height = yield this.getHeight(hash); - - if (height === -1) - hash = null; + if (hash == null) + height = -1; return [hash, height]; - }, this); -}; + } + + height = yield this.getHeight(hash); + + if (height === -1) + hash = null; + + return [hash, height]; +}); /** * Retrieve a chain entry but do _not_ add it to the LRU cache. @@ -506,28 +494,26 @@ ChainDB.prototype.getBoth = function getBoth(block) { * @param {Function} callback - Returns [Error, {@link ChainEntry}]. */ -ChainDB.prototype.getEntry = function getEntry(hash) { - return spawn(function *() { - var self = this; - var entry; +ChainDB.prototype.getEntry = spawn.co(function* getEntry(hash) { + var self = this; + var entry; - checkHash(hash); + checkHash(hash); - hash = yield this.getHash(hash); + hash = yield this.getHash(hash); - if (!hash) - return; + if (!hash) + return; - entry = this.cacheHash.get(hash); + entry = this.cacheHash.get(hash); - if (entry) - return entry; + if (entry) + return entry; - return yield this.db.fetch(layout.e(hash), function(data) { - return bcoin.chainentry.fromRaw(self.chain, data); - }); - }, this); -}; + return yield this.db.fetch(layout.e(hash), function(data) { + return bcoin.chainentry.fromRaw(self.chain, data); + }); +}); /** * Retrieve a chain entry and add it to the LRU cache. @@ -535,21 +521,19 @@ ChainDB.prototype.getEntry = function getEntry(hash) { * @param {Function} callback - Returns [Error, {@link ChainEntry}]. */ -ChainDB.prototype.get = function get(hash) { - return spawn(function *() { - var entry = yield this.getEntry(hash); +ChainDB.prototype.get = spawn.co(function* get(hash) { + var entry = yield this.getEntry(hash); - if (!entry) - return; + if (!entry) + return; - // There's no efficient way to check whether - // this is in the main chain or not, so - // don't add it to the height cache. - this.cacheHash.set(entry.hash, entry); + // There's no efficient way to check whether + // this is in the main chain or not, so + // don't add it to the height cache. + this.cacheHash.set(entry.hash, entry); - return entry; - }, this); -}; + return entry; +}); /** * Save an entry to the database and optionally @@ -564,65 +548,61 @@ ChainDB.prototype.get = function get(hash) { * @param {Function} callback */ -ChainDB.prototype.save = function save(entry, block, view, connect) { - return spawn(function *() { - var hash = block.hash(); - var height = new Buffer(4); +ChainDB.prototype.save = spawn.co(function* save(entry, block, view, connect) { + var hash = block.hash(); + var height = new Buffer(4); - this.start(); + this.start(); - height.writeUInt32LE(entry.height, 0, true); + height.writeUInt32LE(entry.height, 0, true); - this.put(layout.h(hash), height); - this.put(layout.e(hash), entry.toRaw()); + this.put(layout.h(hash), height); + this.put(layout.e(hash), entry.toRaw()); - this.cacheHash.set(entry.hash, entry); - - if (!connect) { - try { - yield this.saveBlock(block, view, false); - } catch (e) { - this.drop(); - throw e; - } - return yield this.commit(); - } - - this.cacheHeight.set(entry.height, entry); - - this.put(layout.n(entry.prevBlock), hash); - this.put(layout.H(entry.height), hash); + this.cacheHash.set(entry.hash, entry); + if (!connect) { try { - yield this.saveBlock(block, view, true); + yield this.saveBlock(block, view, false); } catch (e) { this.drop(); throw e; } + return yield this.commit(); + } - this.put(layout.R, this.pending.commit(hash)); - yield this.commit(); - }, this); -}; + this.cacheHeight.set(entry.height, entry); + + this.put(layout.n(entry.prevBlock), hash); + this.put(layout.H(entry.height), hash); + + try { + yield this.saveBlock(block, view, true); + } catch (e) { + this.drop(); + throw e; + } + + this.put(layout.R, this.pending.commit(hash)); + yield this.commit(); +}); /** * Retrieve the chain state. * @param {Function} callback - Returns [Error, {@link ChainState}]. */ -ChainDB.prototype.initState = function initState() { - return spawn(function *() { - var state = yield this.db.fetch(layout.R, function(data) { - return ChainState.fromRaw(data); - }); +ChainDB.prototype.initState = spawn.co(function* initState() { + var state = yield this.db.fetch(layout.R, function(data) { + return ChainState.fromRaw(data); + }); - assert(state); + assert(state); - this.state = state; + this.state = state; - return state; - }, this); -}; + return state; +}); /** * Retrieve the tip entry from the tip record. @@ -642,32 +622,30 @@ ChainDB.prototype.getTip = function getTip() { * Returns [Error, {@link ChainEntry}, {@link Block}]. */ -ChainDB.prototype.reconnect = function reconnect(entry, block, view) { - return spawn(function *() { - var hash = block.hash(); +ChainDB.prototype.reconnect = spawn.co(function* reconnect(entry, block, view) { + var hash = block.hash(); - this.start(); + this.start(); - this.put(layout.n(entry.prevBlock), hash); - this.put(layout.H(entry.height), hash); + this.put(layout.n(entry.prevBlock), hash); + this.put(layout.H(entry.height), hash); - this.cacheHash.set(entry.hash, entry); - this.cacheHeight.set(entry.height, entry); + this.cacheHash.set(entry.hash, entry); + this.cacheHeight.set(entry.height, entry); - try { - yield this.connectBlock(block, view); - } catch (e) { - this.drop(); - throw e; - } + try { + yield this.connectBlock(block, view); + } catch (e) { + this.drop(); + throw e; + } - this.put(layout.R, this.pending.commit(hash)); + this.put(layout.R, this.pending.commit(hash)); - yield this.commit(); + yield this.commit(); - return [entry, block]; - }, this); -}; + return [entry, block]; +}); /** * Disconnect block from the chain. @@ -676,48 +654,46 @@ ChainDB.prototype.reconnect = function reconnect(entry, block, view) { * Returns [Error, {@link ChainEntry}, {@link Block}]. */ -ChainDB.prototype.disconnect = function disconnect(entry) { - return spawn(function *() { - var block; +ChainDB.prototype.disconnect = spawn.co(function* disconnect(entry) { + var block; - this.start(); - this.del(layout.n(entry.prevBlock)); - this.del(layout.H(entry.height)); + this.start(); + this.del(layout.n(entry.prevBlock)); + this.del(layout.H(entry.height)); - this.cacheHeight.remove(entry.height); - - if (this.options.spv) { - this.put(layout.R, this.pending.commit(entry.prevBlock)); - yield this.commit(); - return [entry, entry.toHeaders()]; - } - - try { - block = yield this.getBlock(entry.hash); - } catch (e) { - this.drop(); - throw e; - } - - if (!block) { - this.drop(); - throw new Error('Block not found.'); - } - - try { - yield this.disconnectBlock(block); - } catch (e) { - this.drop(); - throw e; - } + this.cacheHeight.remove(entry.height); + if (this.options.spv) { this.put(layout.R, this.pending.commit(entry.prevBlock)); - yield this.commit(); + return [entry, entry.toHeaders()]; + } - return [entry, block]; - }, this); -}; + try { + block = yield this.getBlock(entry.hash); + } catch (e) { + this.drop(); + throw e; + } + + if (!block) { + this.drop(); + throw new Error('Block not found.'); + } + + try { + yield this.disconnectBlock(block); + } catch (e) { + this.drop(); + throw e; + } + + this.put(layout.R, this.pending.commit(entry.prevBlock)); + + yield this.commit(); + + return [entry, block]; +}); /** * Get the _next_ block hash (does not work by height). @@ -738,31 +714,29 @@ ChainDB.prototype.getNextHash = function getNextHash(hash) { * @param {Function} callback - Returns [Error, Boolean]. */ -ChainDB.prototype.isMainChain = function isMainChain(hash) { - return spawn(function *() { - var query, height, existing; +ChainDB.prototype.isMainChain = spawn.co(function* isMainChain(hash) { + var query, height, existing; - if (hash instanceof bcoin.chainentry) { - query = hash.height; - hash = hash.hash; - } else { - query = hash; - } + if (hash instanceof bcoin.chainentry) { + query = hash.height; + hash = hash.hash; + } else { + query = hash; + } - if (hash === this.chain.tip.hash - || hash === this.network.genesis.hash) { - return true; - } + if (hash === this.chain.tip.hash + || hash === this.network.genesis.hash) { + return true; + } - height = yield this.getHeight(query); - existing = yield this.getHash(height); + height = yield this.getHeight(query); + existing = yield this.getHash(height); - if (!existing) - return false; + if (!existing) + return false; - return hash === existing; - }, this); -}; + return hash === existing; +}); /** * Reset the chain to a height or hash. Useful for replaying @@ -771,44 +745,42 @@ ChainDB.prototype.isMainChain = function isMainChain(hash) { * @param {Function} callback */ -ChainDB.prototype.reset = function reset(block) { - return spawn(function *() { - var entry = yield this.get(block); - var tip; +ChainDB.prototype.reset = spawn.co(function* reset(block) { + var entry = yield this.get(block); + var tip; - if (!entry) - return; + if (!entry) + return; - tip = yield this.getTip(); + tip = yield this.getTip(); - while (tip) { - this.start(); + while (tip) { + this.start(); - if (tip.hash === entry.hash) { - this.put(layout.R, this.pending.commit(tip.hash)); - return yield this.commit(); - } - - this.del(layout.H(tip.height)); - this.del(layout.h(tip.hash)); - this.del(layout.e(tip.hash)); - this.del(layout.n(tip.prevBlock)); - - try { - yield this.removeBlock(tip.hash); - } catch (e) { - this.drop(); - throw e; - } - - this.put(layout.R, this.pending.commit(tip.prevBlock)); - - yield this.commit(); - - tip = yield this.get(tip.prevBlock); + if (tip.hash === entry.hash) { + this.put(layout.R, this.pending.commit(tip.hash)); + return yield this.commit(); } - }, this); -}; + + this.del(layout.H(tip.height)); + this.del(layout.h(tip.hash)); + this.del(layout.e(tip.hash)); + this.del(layout.n(tip.prevBlock)); + + try { + yield this.removeBlock(tip.hash); + } catch (e) { + this.drop(); + throw e; + } + + this.put(layout.R, this.pending.commit(tip.prevBlock)); + + yield this.commit(); + + tip = yield this.get(tip.prevBlock); + } +}); /** * Test whether the chain contains a block in the @@ -818,18 +790,16 @@ ChainDB.prototype.reset = function reset(block) { * @param {Function} callback - Returns [Error, Boolean]. */ -ChainDB.prototype.has = function has(height) { - return spawn(function *() { - var items, hash; +ChainDB.prototype.has = spawn.co(function* has(height) { + var items, hash; - checkHash(height); + checkHash(height); - items = yield this.getBoth(height); - hash = items[0]; + items = yield this.getBoth(height); + hash = items[0]; - return hash != null; - }, this); -}; + return hash != null; +}); /** * Save a block (not an entry) to the @@ -839,21 +809,19 @@ ChainDB.prototype.has = function has(height) { * @param {Function} callback - Returns [Error, {@link Block}]. */ -ChainDB.prototype.saveBlock = function saveBlock(block, view, connect) { - return spawn(function *() { - if (this.options.spv) - return block; - - this.put(layout.b(block.hash()), block.toRaw()); - - if (!connect) - return block; - - yield this.connectBlock(block, view); - +ChainDB.prototype.saveBlock = spawn.co(function* saveBlock(block, view, connect) { + if (this.options.spv) return block; - }, this); -}; + + this.put(layout.b(block.hash()), block.toRaw()); + + if (!connect) + return block; + + yield this.connectBlock(block, view); + + return block; +}); /** * Remove a block (not an entry) to the database. @@ -862,18 +830,16 @@ ChainDB.prototype.saveBlock = function saveBlock(block, view, connect) { * @param {Function} callback - Returns [Error, {@link Block}]. */ -ChainDB.prototype.removeBlock = function removeBlock(hash) { - return spawn(function *() { - var block = yield this.getBlock(hash); +ChainDB.prototype.removeBlock = spawn.co(function* removeBlock(hash) { + var block = yield this.getBlock(hash); - if (!block) - return; + if (!block) + return; - this.del(layout.b(block.hash())); + this.del(layout.b(block.hash())); - return yield this.disconnectBlock(block); - }, this); -}; + return yield this.disconnectBlock(block); +}); /** * Connect block inputs. @@ -881,100 +847,98 @@ ChainDB.prototype.removeBlock = function removeBlock(hash) { * @param {Function} callback - Returns [Error, {@link Block}]. */ -ChainDB.prototype.connectBlock = function connectBlock(block, view) { - return spawn(function *() { - var undo = new BufferWriter(); - var i, j, tx, input, output, prev; - var hashes, address, hash, coins, raw; - - if (this.options.spv) - return block; - - // Genesis block's coinbase is unspendable. - if (this.chain.isGenesis(block)) { - this.pending.connect(block); - return block; - } - - this.pending.connect(block); - - for (i = 0; i < block.txs.length; i++) { - tx = block.txs[i]; - hash = tx.hash(); - - if (this.options.indexTX) { - this.put(layout.t(hash), tx.toExtended()); - if (this.options.indexAddress) { - hashes = tx.getHashes(); - for (j = 0; j < hashes.length; j++) { - address = hashes[j]; - this.put(layout.T(address, hash), DUMMY); - } - } - } - - if (!tx.isCoinbase()) { - for (j = 0; j < tx.inputs.length; j++) { - input = tx.inputs[j]; - - assert(input.coin); - - if (this.options.indexAddress) { - address = input.getHash(); - if (address) { - prev = input.prevout; - this.del(layout.C(address, prev.hash, prev.index)); - } - } - - // Add coin to set of undo - // coins for the block. - input.coin.toRaw(undo); - - this.pending.spend(input.coin); - } - } - - for (j = 0; j < tx.outputs.length; j++) { - output = tx.outputs[j]; - - if (output.script.isUnspendable()) - continue; - - if (this.options.indexAddress) { - address = output.getHash(); - if (address) - this.put(layout.C(address, hash, j), DUMMY); - } - - this.pending.add(output); - } - } - - // Commit new coin state. - view = view.toArray(); - - for (i = 0; i < view.length; i++) { - coins = view[i]; - raw = coins.toRaw(); - if (!raw) { - this.del(layout.c(coins.hash)); - this.coinCache.remove(coins.hash); - } else { - this.put(layout.c(coins.hash), raw); - this.coinCache.set(coins.hash, raw); - } - } - - // Write undo coins (if there are any). - if (undo.written > 0) - this.put(layout.u(block.hash()), undo.render()); - - yield this.pruneBlock(block); +ChainDB.prototype.connectBlock = spawn.co(function* connectBlock(block, view) { + var undo = new BufferWriter(); + var i, j, tx, input, output, prev; + var hashes, address, hash, coins, raw; + if (this.options.spv) return block; - }, this); -}; + + // Genesis block's coinbase is unspendable. + if (this.chain.isGenesis(block)) { + this.pending.connect(block); + return block; + } + + this.pending.connect(block); + + for (i = 0; i < block.txs.length; i++) { + tx = block.txs[i]; + hash = tx.hash(); + + if (this.options.indexTX) { + this.put(layout.t(hash), tx.toExtended()); + if (this.options.indexAddress) { + hashes = tx.getHashes(); + for (j = 0; j < hashes.length; j++) { + address = hashes[j]; + this.put(layout.T(address, hash), DUMMY); + } + } + } + + if (!tx.isCoinbase()) { + for (j = 0; j < tx.inputs.length; j++) { + input = tx.inputs[j]; + + assert(input.coin); + + if (this.options.indexAddress) { + address = input.getHash(); + if (address) { + prev = input.prevout; + this.del(layout.C(address, prev.hash, prev.index)); + } + } + + // Add coin to set of undo + // coins for the block. + input.coin.toRaw(undo); + + this.pending.spend(input.coin); + } + } + + for (j = 0; j < tx.outputs.length; j++) { + output = tx.outputs[j]; + + if (output.script.isUnspendable()) + continue; + + if (this.options.indexAddress) { + address = output.getHash(); + if (address) + this.put(layout.C(address, hash, j), DUMMY); + } + + this.pending.add(output); + } + } + + // Commit new coin state. + view = view.toArray(); + + for (i = 0; i < view.length; i++) { + coins = view[i]; + raw = coins.toRaw(); + if (!raw) { + this.del(layout.c(coins.hash)); + this.coinCache.remove(coins.hash); + } else { + this.put(layout.c(coins.hash), raw); + this.coinCache.set(coins.hash, raw); + } + } + + // Write undo coins (if there are any). + if (undo.written > 0) + this.put(layout.u(block.hash()), undo.render()); + + yield this.pruneBlock(block); + + return block; +}); /** * Disconnect block inputs. @@ -982,95 +946,93 @@ ChainDB.prototype.connectBlock = function connectBlock(block, view) { * @param {Function} callback - Returns [Error, {@link Block}]. */ -ChainDB.prototype.disconnectBlock = function disconnectBlock(block) { - return spawn(function *() { - var i, j, tx, input, output, prev, view; - var hashes, address, hash, coins, raw; - - if (this.options.spv) - return block; - - view = yield this.getUndoView(block); - - this.pending.disconnect(block); - - for (i = block.txs.length - 1; i >= 0; i--) { - tx = block.txs[i]; - hash = tx.hash('hex'); - - if (this.options.indexTX) { - this.del(layout.t(hash)); - if (this.options.indexAddress) { - hashes = tx.getHashes(); - for (j = 0; j < hashes.length; j++) { - address = hashes[j]; - this.del(layout.T(address, hash)); - } - } - } - - if (!tx.isCoinbase()) { - for (j = 0; j < tx.inputs.length; j++) { - input = tx.inputs[j]; - - assert(input.coin); - - if (this.options.indexAddress) { - address = input.getHash(); - if (address) { - prev = input.prevout; - this.put(layout.C(address, prev.hash, prev.index), DUMMY); - } - } - - this.pending.add(input.coin); - } - } - - // Add all of the coins we are about to - // remove. This is to ensure they appear - // in the view array below. - view.addTX(tx); - - for (j = 0; j < tx.outputs.length; j++) { - output = tx.outputs[j]; - - if (output.script.isUnspendable()) - continue; - - if (this.options.indexAddress) { - address = output.getHash(); - if (address) - this.del(layout.C(address, hash, j)); - } - - // Spend added coin. - view.spend(hash, j); - - this.pending.spend(output); - } - } - - // Commit new coin state. - view = view.toArray(); - - for (i = 0; i < view.length; i++) { - coins = view[i]; - raw = coins.toRaw(); - if (!raw) { - this.del(layout.c(coins.hash)); - this.coinCache.remove(coins.hash); - } else { - this.put(layout.c(coins.hash), raw); - this.coinCache.set(coins.hash, raw); - } - } - - this.del(layout.u(block.hash())); +ChainDB.prototype.disconnectBlock = spawn.co(function* disconnectBlock(block) { + var i, j, tx, input, output, prev, view; + var hashes, address, hash, coins, raw; + if (this.options.spv) return block; - }, this); -}; + + view = yield this.getUndoView(block); + + this.pending.disconnect(block); + + for (i = block.txs.length - 1; i >= 0; i--) { + tx = block.txs[i]; + hash = tx.hash('hex'); + + if (this.options.indexTX) { + this.del(layout.t(hash)); + if (this.options.indexAddress) { + hashes = tx.getHashes(); + for (j = 0; j < hashes.length; j++) { + address = hashes[j]; + this.del(layout.T(address, hash)); + } + } + } + + if (!tx.isCoinbase()) { + for (j = 0; j < tx.inputs.length; j++) { + input = tx.inputs[j]; + + assert(input.coin); + + if (this.options.indexAddress) { + address = input.getHash(); + if (address) { + prev = input.prevout; + this.put(layout.C(address, prev.hash, prev.index), DUMMY); + } + } + + this.pending.add(input.coin); + } + } + + // Add all of the coins we are about to + // remove. This is to ensure they appear + // in the view array below. + view.addTX(tx); + + for (j = 0; j < tx.outputs.length; j++) { + output = tx.outputs[j]; + + if (output.script.isUnspendable()) + continue; + + if (this.options.indexAddress) { + address = output.getHash(); + if (address) + this.del(layout.C(address, hash, j)); + } + + // Spend added coin. + view.spend(hash, j); + + this.pending.spend(output); + } + } + + // Commit new coin state. + view = view.toArray(); + + for (i = 0; i < view.length; i++) { + coins = view[i]; + raw = coins.toRaw(); + if (!raw) { + this.del(layout.c(coins.hash)); + this.coinCache.remove(coins.hash); + } else { + this.put(layout.c(coins.hash), raw); + this.coinCache.set(coins.hash, raw); + } + } + + this.del(layout.u(block.hash())); + + return block; +}); /** * Fill a transaction with coins (only unspents). @@ -1078,28 +1040,26 @@ ChainDB.prototype.disconnectBlock = function disconnectBlock(block) { * @param {Function} callback - Returns [Error, {@link TX}]. */ -ChainDB.prototype.fillCoins = function fillCoins(tx) { - return spawn(function *() { - var i, input, coin; - - if (tx.isCoinbase()) - return tx; - - for (i = 0; i < tx.inputs.length; i++) { - input = tx.inputs[i]; - - if (input.coin) - continue; - - coin = yield this.getCoin(input.prevout.hash, input.prevout.index); - - if (coin) - input.coin = coin; - } +ChainDB.prototype.fillCoins = spawn.co(function* fillCoins(tx) { + var i, input, coin; + if (tx.isCoinbase()) return tx; - }, this); -}; + + for (i = 0; i < tx.inputs.length; i++) { + input = tx.inputs[i]; + + if (input.coin) + continue; + + coin = yield this.getCoin(input.prevout.hash, input.prevout.index); + + if (coin) + input.coin = coin; + } + + return tx; +}); /** * Fill a transaction with coins (all historical coins). @@ -1107,31 +1067,29 @@ ChainDB.prototype.fillCoins = function fillCoins(tx) { * @param {Function} callback - Returns [Error, {@link TX}]. */ -ChainDB.prototype.fillHistory = function fillHistory(tx) { - return spawn(function *() { - var i, input, tx; - - if (!this.options.indexTX) - return tx; - - if (tx.isCoinbase()) - return tx; - - for (i = 0; i < tx.inputs.length; i++) { - input = tx.inputs[i]; - - if (input.coin) - continue; - - tx = yield this.getTX(input.prevout.hash); - - if (tx) - input.coin = bcoin.coin.fromTX(tx, input.prevout.index); - } +ChainDB.prototype.fillHistory = spawn.co(function* fillHistory(tx) { + var i, input, tx; + if (!this.options.indexTX) return tx; - }, this); -}; + + if (tx.isCoinbase()) + return tx; + + for (i = 0; i < tx.inputs.length; i++) { + input = tx.inputs[i]; + + if (input.coin) + continue; + + tx = yield this.getTX(input.prevout.hash); + + if (tx) + input.coin = bcoin.coin.fromTX(tx, input.prevout.index); + } + + return tx; +}); /** * Get a coin (unspents only). @@ -1140,20 +1098,18 @@ ChainDB.prototype.fillHistory = function fillHistory(tx) { * @param {Function} callback - Returns [Error, {@link Coin}]. */ -ChainDB.prototype.getCoin = function getCoin(hash, index) { - return spawn(function *() { - var self = this; - var coins = this.coinCache.get(hash); +ChainDB.prototype.getCoin = spawn.co(function* getCoin(hash, index) { + var self = this; + var coins = this.coinCache.get(hash); - if (coins) - return bcoin.coins.parseCoin(coins, hash, index); + if (coins) + return bcoin.coins.parseCoin(coins, hash, index); - return yield this.db.fetch(layout.c(hash), function(data) { - self.coinCache.set(hash, data); - return bcoin.coins.parseCoin(data, hash, index); - }); - }, this); -}; + return yield this.db.fetch(layout.c(hash), function(data) { + self.coinCache.set(hash, data); + return bcoin.coins.parseCoin(data, hash, index); + }); +}); /** * Get coins (unspents only). @@ -1161,20 +1117,18 @@ ChainDB.prototype.getCoin = function getCoin(hash, index) { * @param {Function} callback - Returns [Error, {@link Coins}]. */ -ChainDB.prototype.getCoins = function getCoins(hash) { - return spawn(function *() { - var self = this; - var coins = this.coinCache.get(hash); +ChainDB.prototype.getCoins = spawn.co(function* getCoins(hash) { + var self = this; + var coins = this.coinCache.get(hash); - if (coins) - return bcoin.coins.fromRaw(coins, hash); + if (coins) + return bcoin.coins.fromRaw(coins, hash); - return yield this.db.fetch(layout.c(hash), function(data) { - self.coinCache.set(hash, data); - return bcoin.coins.fromRaw(data, hash); - }); - }, this); -}; + return yield this.db.fetch(layout.c(hash), function(data) { + self.coinCache.set(hash, data); + return bcoin.coins.fromRaw(data, hash); + }); +}); /** * Scan the blockchain for transactions containing specified address hashes. @@ -1184,60 +1138,58 @@ ChainDB.prototype.getCoins = function getCoins(hash) { * @param {Function} callback */ -ChainDB.prototype.scan = function scan(start, filter, iter) { - return spawn(function *() { - var total = 0; - var i, j, entry, hashes, hash, tx, txs, block; +ChainDB.prototype.scan = spawn.co(function* scan(start, filter, iter) { + var total = 0; + var i, j, entry, hashes, hash, tx, txs, block; - if (this.options.spv) - throw new Error('Cannot scan in spv mode.'); + if (this.options.spv) + throw new Error('Cannot scan in spv mode.'); - if (start == null) - start = this.network.genesis.hash; + if (start == null) + start = this.network.genesis.hash; - if (typeof start === 'number') - this.logger.info('Scanning from height %d.', start); - else - this.logger.info('Scanning from block %s.', utils.revHex(start)); + if (typeof start === 'number') + this.logger.info('Scanning from height %d.', start); + else + this.logger.info('Scanning from block %s.', utils.revHex(start)); - if (Array.isArray(filter)) - filter = utils.toMap(filter); + if (Array.isArray(filter)) + filter = utils.toMap(filter); - entry = yield this.getEntry(start); + entry = yield this.getEntry(start); - while (entry) { - block = yield this.getFullBlock(entry.hash); - total++; + while (entry) { + block = yield this.getFullBlock(entry.hash); + total++; - if (!block) - throw new Error('Block not found.'); + if (!block) + throw new Error('Block not found.'); - this.logger.info( - 'Scanning block %s (%d).', - entry.rhash, block.height); + this.logger.info( + 'Scanning block %s (%d).', + entry.rhash, block.height); - txs = []; + txs = []; - for (i = 0; i < block.txs.length; i++) { - tx = block.txs[i]; - hashes = tx.getHashes('hex'); + for (i = 0; i < block.txs.length; i++) { + tx = block.txs[i]; + hashes = tx.getHashes('hex'); - for (j = 0; j < hashes.length; j++) { - hash = hashes[j]; - if (filter[hash]) { - txs.push(tx); - break; - } + for (j = 0; j < hashes.length; j++) { + hash = hashes[j]; + if (filter[hash]) { + txs.push(tx); + break; } } - - yield* iter(entry, txs); - entry = yield entry.getNext(); } - this.logger.info('Finished scanning %d blocks.', total); - }, this); -}; + yield* iter(entry, txs); + entry = yield entry.getNext(); + } + + this.logger.info('Finished scanning %d blocks.', total); +}); /** * Retrieve a transaction (not filled with coins). @@ -1272,42 +1224,40 @@ ChainDB.prototype.hasTX = function hasTX(hash) { * @param {Function} callback - Returns [Error, {@link Coin}[]]. */ -ChainDB.prototype.getCoinsByAddress = function getCoinsByAddress(addresses) { - return spawn(function *() { - var coins = []; - var i, j, address, hash, keys, key, coin; - - if (!this.options.indexAddress) - return coins; - - if (!Array.isArray(addresses)) - addresses = [addresses]; - - for (i = 0; i < addresses.length; i++) { - address = addresses[i]; - hash = bcoin.address.getHash(address); - - if (!hash) - continue; - - keys = yield this.db.iterate({ - gte: layout.C(hash, constants.ZERO_HASH, 0), - lte: layout.C(hash, constants.MAX_HASH, 0xffffffff), - parse: layout.Cc - }); - - for (j = 0; j < keys.length; j++) { - key = keys[j]; - coin = yield this.getCoin(key[0], key[1]); - - if (coin) - coins.push(coin); - } - } +ChainDB.prototype.getCoinsByAddress = spawn.co(function* getCoinsByAddress(addresses) { + var coins = []; + var i, j, address, hash, keys, key, coin; + if (!this.options.indexAddress) return coins; - }, this); -}; + + if (!Array.isArray(addresses)) + addresses = [addresses]; + + for (i = 0; i < addresses.length; i++) { + address = addresses[i]; + hash = bcoin.address.getHash(address); + + if (!hash) + continue; + + keys = yield this.db.iterate({ + gte: layout.C(hash, constants.ZERO_HASH, 0), + lte: layout.C(hash, constants.MAX_HASH, 0xffffffff), + parse: layout.Cc + }); + + for (j = 0; j < keys.length; j++) { + key = keys[j]; + coin = yield this.getCoin(key[0], key[1]); + + if (coin) + coins.push(coin); + } + } + + return coins; +}); /** * Get all entries. @@ -1333,34 +1283,32 @@ ChainDB.prototype.getEntries = function getEntries() { * @param {Function} callback - Returns [Error, {@link Hash}[]]. */ -ChainDB.prototype.getHashesByAddress = function getHashesByAddress(addresses) { - return spawn(function *() { - var hashes = {}; - var i, address, hash; +ChainDB.prototype.getHashesByAddress = spawn.co(function* getHashesByAddress(addresses) { + var hashes = {}; + var i, address, hash; - if (!this.options.indexTX || !this.options.indexAddress) - return []; + if (!this.options.indexTX || !this.options.indexAddress) + return []; - for (i = 0; i < addresses.length; i++) { - address = addresses[i]; - hash = bcoin.address.getHash(address); + for (i = 0; i < addresses.length; i++) { + address = addresses[i]; + hash = bcoin.address.getHash(address); - if (!hash) - continue; + if (!hash) + continue; - yield this.db.iterate({ - gte: layout.T(hash, constants.ZERO_HASH), - lte: layout.T(hash, constants.MAX_HASH), - parse: function(key) { - var hash = layout.Tt(key); - hashes[hash] = true; - } - }); - } + yield this.db.iterate({ + gte: layout.T(hash, constants.ZERO_HASH), + lte: layout.T(hash, constants.MAX_HASH), + parse: function(key) { + var hash = layout.Tt(key); + hashes[hash] = true; + } + }); + } - return Object.keys(hashes); - }, this); -}; + return Object.keys(hashes); +}); /** * Get all transactions pertinent to an address. @@ -1368,29 +1316,27 @@ ChainDB.prototype.getHashesByAddress = function getHashesByAddress(addresses) { * @param {Function} callback - Returns [Error, {@link TX}[]]. */ -ChainDB.prototype.getTXByAddress = function getTXByAddress(addresses) { - return spawn(function *() { - var txs = []; - var i, hashes, hash, tx; - - if (!this.options.indexTX || !this.options.indexAddress) - return txs; - - if (!Array.isArray(addresses)) - addresses = [addresses]; - - hashes = yield this.getHashesByAddress(addresses); - - for (i = 0; i < hashes.length; i++) { - hash = hashes[i]; - tx = yield this.getTX(hash); - if (tx) - txs.push(tx); - } +ChainDB.prototype.getTXByAddress = spawn.co(function* getTXByAddress(addresses) { + var txs = []; + var i, hashes, hash, tx; + if (!this.options.indexTX || !this.options.indexAddress) return txs; - }, this); -}; + + if (!Array.isArray(addresses)) + addresses = [addresses]; + + hashes = yield this.getHashesByAddress(addresses); + + for (i = 0; i < hashes.length; i++) { + hash = hashes[i]; + tx = yield this.getTX(hash); + if (tx) + txs.push(tx); + } + + return txs; +}); /** * Get a transaction and fill it with coins (historical). @@ -1398,23 +1344,21 @@ ChainDB.prototype.getTXByAddress = function getTXByAddress(addresses) { * @param {Function} callback - Returns [Error, {@link TX}]. */ -ChainDB.prototype.getFullTX = function getFullTX(hash) { - return spawn(function *() { - var tx; +ChainDB.prototype.getFullTX = spawn.co(function* getFullTX(hash) { + var tx; - if (!this.options.indexTX) - return; + if (!this.options.indexTX) + return; - tx = yield this.getTX(hash); + tx = yield this.getTX(hash); - if (!tx) - return; + if (!tx) + return; - yield this.fillHistory(tx); + yield this.fillHistory(tx); - return tx; - }, this); -}; + return tx; +}); /** * Get a block and fill it with coins (historical). @@ -1422,19 +1366,17 @@ ChainDB.prototype.getFullTX = function getFullTX(hash) { * @param {Function} callback - Returns [Error, {@link Block}]. */ -ChainDB.prototype.getFullBlock = function getFullBlock(hash) { - return spawn(function *() { - var block = yield this.getBlock(hash); - var view; +ChainDB.prototype.getFullBlock = spawn.co(function* getFullBlock(hash) { + var block = yield this.getBlock(hash); + var view; - if (!block) - return; + if (!block) + return; - view = yield this.getUndoView(block); + view = yield this.getUndoView(block); - return block; - }, this); -}; + return block; +}); /** * Get a view of the existing coins necessary to verify a block. @@ -1442,22 +1384,20 @@ ChainDB.prototype.getFullBlock = function getFullBlock(hash) { * @param {Function} callback - Returns [Error, {@link CoinView}]. */ -ChainDB.prototype.getCoinView = function getCoinView(block, callback) { - return spawn(function *() { - var view = new bcoin.coinview(); - var prevout = block.getPrevout(); - var i, prev, coins; +ChainDB.prototype.getCoinView = spawn.co(function* getCoinView(block, callback) { + var view = new bcoin.coinview(); + var prevout = block.getPrevout(); + var i, prev, coins; - for (i = 0; i < prevout.length; i++) { - prev = prevout[i]; - coins = yield this.getCoins(prev); - if (coins) - view.add(coins); - } + for (i = 0; i < prevout.length; i++) { + prev = prevout[i]; + coins = yield this.getCoins(prev); + if (coins) + view.add(coins); + } - return view; - }, this); -}; + return view; +}); /** * Get coins necessary to be resurrected during a reorg. @@ -1485,35 +1425,33 @@ ChainDB.prototype.getUndoCoins = function getUndoCoins(hash) { * @param {Function} callback - Returns [Error, {@link CoinView}]. */ -ChainDB.prototype.getUndoView = function getUndoView(block) { - return spawn(function *() { - var i, j, k, tx, input, coin, view, coins; +ChainDB.prototype.getUndoView = spawn.co(function* getUndoView(block) { + var i, j, k, tx, input, coin, view, coins; - view = yield this.getCoinView(block); - coins = yield this.getUndoCoins(block.hash()); - - if (!coins) - return view; - - for (i = 0, k = 0; i < block.txs.length; i++) { - tx = block.txs[i]; - - if (tx.isCoinbase()) - continue; - - for (j = 0; j < tx.inputs.length; j++) { - input = tx.inputs[j]; - coin = coins[k++]; - coin.hash = input.prevout.hash; - coin.index = input.prevout.index; - input.coin = coin; - view.addCoin(coin); - } - } + view = yield this.getCoinView(block); + coins = yield this.getUndoCoins(block.hash()); + if (!coins) return view; - }, this); -}; + + for (i = 0, k = 0; i < block.txs.length; i++) { + tx = block.txs[i]; + + if (tx.isCoinbase()) + continue; + + for (j = 0; j < tx.inputs.length; j++) { + input = tx.inputs[j]; + coin = coins[k++]; + coin.hash = input.prevout.hash; + coin.index = input.prevout.index; + input.coin = coin; + view.addCoin(coin); + } + } + + return view; +}); /** * Retrieve a block from the database (not filled with coins). @@ -1521,24 +1459,22 @@ ChainDB.prototype.getUndoView = function getUndoView(block) { * @param {Function} callback - Returns [Error, {@link Block}]. */ -ChainDB.prototype.getBlock = function getBlock(hash) { - return spawn(function *() { - var items = yield this.getBoth(hash); - var height; +ChainDB.prototype.getBlock = spawn.co(function* getBlock(hash) { + var items = yield this.getBoth(hash); + var height; - if (!items) - return; + if (!items) + return; - hash = items[0]; - height = items[1]; + hash = items[0]; + height = items[1]; - return yield this.db.fetch(layout.b(hash), function(data) { - var block = bcoin.block.fromRaw(data); - block.setHeight(height); - return block; - }); - }, this); -}; + return yield this.db.fetch(layout.b(hash), function(data) { + var block = bcoin.block.fromRaw(data); + block.setHeight(height); + return block; + }); +}); /** * Check whether coins are still unspent. Necessary for bip30. @@ -1559,38 +1495,36 @@ ChainDB.prototype.hasCoins = function hasCoins(hash) { * @param {Function} callback */ -ChainDB.prototype.pruneBlock = function pruneBlock(block) { - return spawn(function *() { - var futureHeight, key, hash; +ChainDB.prototype.pruneBlock = spawn.co(function* pruneBlock(block) { + var futureHeight, key, hash; - if (this.options.spv) - return; + if (this.options.spv) + return; - if (!this.prune) - return; + if (!this.prune) + return; - if (block.height <= this.network.block.pruneAfterHeight) - return; + if (block.height <= this.network.block.pruneAfterHeight) + return; - futureHeight = block.height + this.keepBlocks; + futureHeight = block.height + this.keepBlocks; - this.put(layout.q(futureHeight), block.hash()); + this.put(layout.q(futureHeight), block.hash()); - key = layout.q(block.height); + key = layout.q(block.height); - hash = yield this.db.fetch(key, function(data) { - assert(data.length === 32, 'Database corruption.'); - return data.toString('hex'); - }); + hash = yield this.db.fetch(key, function(data) { + assert(data.length === 32, 'Database corruption.'); + return data.toString('hex'); + }); - if (!hash) - return; + if (!hash) + return; - this.del(key); - this.del(layout.b(hash)); - this.del(layout.u(hash)); - }, this); -}; + this.del(key); + this.del(layout.b(hash)); + this.del(layout.u(hash)); +}); /** * Chain State diff --git a/lib/chain/chainentry.js b/lib/chain/chainentry.js index 88c1d7dd..d1d3bea3 100644 --- a/lib/chain/chainentry.js +++ b/lib/chain/chainentry.js @@ -177,45 +177,43 @@ ChainEntry.prototype.getRetargetAncestors = function getRetargetAncestors() { * @param {Function} callback - Returns [Error, ChainEntry[]]. */ -ChainEntry.prototype.getAncestors = function getAncestors(max) { - return spawn(function *() { - var entry = this; - var ancestors = []; - var cached; +ChainEntry.prototype.getAncestors = spawn.co(function* getAncestors(max) { + var entry = this; + var ancestors = []; + var cached; - if (max === 0) + if (max === 0) + return ancestors; + + assert(utils.isNumber(max)); + + // Try to do this iteratively and synchronously + // so we don't have to wait on nextTicks. + for (;;) { + ancestors.push(entry); + + if (ancestors.length >= max) return ancestors; - assert(utils.isNumber(max)); + cached = this.chain.db.getCache(entry.prevBlock); - // Try to do this iteratively and synchronously - // so we don't have to wait on nextTicks. - for (;;) { - ancestors.push(entry); - - if (ancestors.length >= max) - return ancestors; - - cached = this.chain.db.getCache(entry.prevBlock); - - if (!cached) { - ancestors.pop(); - break; - } - - entry = cached; + if (!cached) { + ancestors.pop(); + break; } - while (entry) { - ancestors.push(entry); - if (ancestors.length >= max) - break; - entry = yield entry.getPrevious(); - } + entry = cached; + } - return ancestors; - }, this); -}; + while (entry) { + ancestors.push(entry); + if (ancestors.length >= max) + break; + entry = yield entry.getPrevious(); + } + + return ancestors; +}); /** * Test whether the entry is in the main chain. @@ -232,31 +230,29 @@ ChainEntry.prototype.isMainChain = function isMainChain() { * @param {Function} callback - Returns [Error, ChainEntry[]]. */ -ChainEntry.prototype.getAncestorByHeight = function getAncestorByHeight(height) { - return spawn(function *() { - var main, entry; +ChainEntry.prototype.getAncestorByHeight = spawn.co(function* getAncestorByHeight(height) { + var main, entry; - if (height < 0) - return yield utils.wait(); + if (height < 0) + return yield utils.wait(); - assert(height >= 0); - assert(height <= this.height); + assert(height >= 0); + assert(height <= this.height); - main = yield this.isMainChain(); + main = yield this.isMainChain(); - if (main) - return yield this.chain.db.get(height); + if (main) + return yield this.chain.db.get(height); - entry = yield this.getAncestor(this.height - height); + entry = yield this.getAncestor(this.height - height); - if (!entry) - return; + if (!entry) + return; - assert(entry.height === height); + assert(entry.height === height); - return entry; - }, this); -}; + return entry; +}); /** * Get a single ancestor by index. Note that index-0 is @@ -266,20 +262,18 @@ ChainEntry.prototype.getAncestorByHeight = function getAncestorByHeight(height) * @returns {Function} callback - Returns [Error, ChainEntry]. */ -ChainEntry.prototype.getAncestor = function getAncestor(index) { - return spawn(function *() { - var ancestors; +ChainEntry.prototype.getAncestor = spawn.co(function* getAncestor(index) { + var ancestors; - assert(index >= 0); + assert(index >= 0); - ancestors = yield this.getAncestors(index + 1); + ancestors = yield this.getAncestors(index + 1); - if (ancestors.length < index + 1) - return; + if (ancestors.length < index + 1) + return; - return ancestors[index]; - }, this); -}; + return ancestors[index]; +}); /** * Get previous entry. @@ -295,14 +289,12 @@ ChainEntry.prototype.getPrevious = function getPrevious() { * @param {Function} callback - Returns [Error, ChainEntry]. */ -ChainEntry.prototype.getNext = function getNext() { - return spawn(function *() { - var hash = yield this.chain.db.getNextHash(this.hash); - if (!hash) - return; - return yield this.chain.db.get(hash); - }, this); -}; +ChainEntry.prototype.getNext = spawn.co(function* getNext() { + var hash = yield this.chain.db.getNextHash(this.hash); + if (!hash) + return; + return yield this.chain.db.get(hash); +}); /** * Get median time past. @@ -330,13 +322,11 @@ ChainEntry.prototype.getMedianTime = function getMedianTime(ancestors) { * @param {Function} callback - Returns [Error, Number]. */ -ChainEntry.prototype.getMedianTimeAsync = function getMedianTimeAsync() { - return spawn(function *() { - var MEDIAN_TIMESPAN = constants.block.MEDIAN_TIMESPAN; - var ancestors = yield this.getAncestors(MEDIAN_TIMESPAN); - return this.getMedianTime(ancestors); - }, this); -}; +ChainEntry.prototype.getMedianTimeAsync = spawn.co(function* getMedianTimeAsync() { + var MEDIAN_TIMESPAN = constants.block.MEDIAN_TIMESPAN; + var ancestors = yield this.getAncestors(MEDIAN_TIMESPAN); + return this.getMedianTime(ancestors); +}); /** * Check isSuperMajority against majorityRejectOutdated. @@ -419,13 +409,11 @@ ChainEntry.prototype.isSuperMajority = function isSuperMajority(version, require * @returns {Boolean} */ -ChainEntry.prototype.isSuperMajorityAsync = function isSuperMajorityAsync(version, required) { - return spawn(function *() { - var majorityWindow = this.network.block.majorityWindow; - var ancestors = yield this.getAncestors(majorityWindow); - return this.isSuperMajority(version, required, ancestors); - }, this); -}; +ChainEntry.prototype.isSuperMajorityAsync = spawn.co(function* isSuperMajorityAsync(version, required) { + var majorityWindow = this.network.block.majorityWindow; + var ancestors = yield this.getAncestors(majorityWindow); + return this.isSuperMajority(version, required, ancestors); +}); /** * Test whether the entry is potentially an ancestor of a checkpoint. diff --git a/lib/db/lowlevelup.js b/lib/db/lowlevelup.js index d439d170..072a9d50 100644 --- a/lib/db/lowlevelup.js +++ b/lib/db/lowlevelup.js @@ -290,12 +290,10 @@ LowlevelUp.prototype.approximateSize = function approximateSize(start, end) { * @param {Function} callback - Returns [Error, Boolean]. */ -LowlevelUp.prototype.has = function has(key) { - return spawn(function *() { - var value = yield this.get(key); - return value != null; - }, this); -}; +LowlevelUp.prototype.has = spawn.co(function* has(key) { + var value = yield this.get(key); + return value != null; +}); /** * Get and deserialize a record with a callback. @@ -305,15 +303,13 @@ LowlevelUp.prototype.has = function has(key) { * @param {Function} callback - Returns [Error, Object]. */ -LowlevelUp.prototype.fetch = function fetch(key, parse) { - return spawn(function *() { - var value = yield this.get(key); - if (!value) - return; +LowlevelUp.prototype.fetch = spawn.co(function* fetch(key, parse) { + var value = yield this.get(key); + if (!value) + return; - return parse(value, key); - }, this); -}; + return parse(value, key); +}); /** * Collect all keys from iterator options. @@ -321,29 +317,27 @@ LowlevelUp.prototype.fetch = function fetch(key, parse) { * @param {Function} callback - Returns [Error, Array]. */ -LowlevelUp.prototype.iterate = function iterate(options) { - return spawn(function *() { - var items = []; - var iter, kv, result; +LowlevelUp.prototype.iterate = spawn.co(function* iterate(options) { + var items = []; + var iter, kv, result; - assert(typeof options.parse === 'function', 'Parse must be a function.'); + assert(typeof options.parse === 'function', 'Parse must be a function.'); - iter = this.iterator(options); + iter = this.iterator(options); - for (;;) { - kv = yield iter.next(); - if (!kv) - return items; + for (;;) { + kv = yield iter.next(); + if (!kv) + return items; - result = options.parse(kv[0], kv[1]); + result = options.parse(kv[0], kv[1]); - if (result) - items.push(result); - } + if (result) + items.push(result); + } - return items; - }, this); -}; + return items; +}); /** * Write and assert a version number for the database. @@ -351,23 +345,21 @@ LowlevelUp.prototype.iterate = function iterate(options) { * @param {Function} callback */ -LowlevelUp.prototype.checkVersion = function checkVersion(key, version) { - return spawn(function *() { - var data = yield this.get(key); +LowlevelUp.prototype.checkVersion = spawn.co(function* checkVersion(key, version) { + var data = yield this.get(key); - if (!data) { - data = new Buffer(4); - data.writeUInt32LE(version, 0, true); - yield this.put(key, data); - return; - } + if (!data) { + data = new Buffer(4); + data.writeUInt32LE(version, 0, true); + yield this.put(key, data); + return; + } - data = data.readUInt32LE(0, true); + data = data.readUInt32LE(0, true); - if (data !== version) - throw new Error(VERSION_ERROR); - }, this); -}; + if (data !== version) + throw new Error(VERSION_ERROR); +}); /** * Clone the database. @@ -375,60 +367,58 @@ LowlevelUp.prototype.checkVersion = function checkVersion(key, version) { * @param {Function} callback */ -LowlevelUp.prototype.clone = function clone(path) { - return spawn(function *() { - var opt = { keys: true, values: true }; - var options = utils.merge({}, this.options); - var hwm = 256 << 20; - var total = 0; - var tmp, batch, iter, items, key, value; +LowlevelUp.prototype.clone = spawn.co(function* clone(path) { + var opt = { keys: true, values: true }; + var options = utils.merge({}, this.options); + var hwm = 256 << 20; + var total = 0; + var tmp, batch, iter, items, key, value; - assert(!this.loading); - assert(!this.closing); - assert(this.loaded); + assert(!this.loading); + assert(!this.closing); + assert(this.loaded); - options.createIfMissing = true; - options.errorIfExists = true; + options.createIfMissing = true; + options.errorIfExists = true; - tmp = new LowlevelUp(path, options); + tmp = new LowlevelUp(path, options); - yield tmp.open(); + yield tmp.open(); - batch = tmp.batch(); - iter = this.iterator(opt); + batch = tmp.batch(); + iter = this.iterator(opt); - for (;;) { - items = yield iter.next(); + for (;;) { + items = yield iter.next(); - if (!items) { - try { - yield batch.write(); - } catch (e) { - yield tmp.close(); - throw e; - } - return; - } - - key = items[0]; - value = items[0]; - - batch.put(key, value); - total += value.length; - - if (total >= hwm) { - total = 0; - try { - yield batch.write(); - } catch (e) { - yield tmp.close(); - throw e; - } - batch = tmp.batch(); + if (!items) { + try { + yield batch.write(); + } catch (e) { + yield tmp.close(); + throw e; } + return; } - }, this); -}; + + key = items[0]; + value = items[0]; + + batch.put(key, value); + total += value.length; + + if (total >= hwm) { + total = 0; + try { + yield batch.write(); + } catch (e) { + yield tmp.close(); + throw e; + } + batch = tmp.batch(); + } + } +}); function Batch(db) { this.db = db; diff --git a/lib/http/client.js b/lib/http/client.js index 189cac62..9b435d25 100644 --- a/lib/http/client.js +++ b/lib/http/client.js @@ -56,71 +56,69 @@ utils.inherits(HTTPClient, AsyncObject); * @param {Function} callback */ -HTTPClient.prototype._open = function _open() { - return spawn(function *() { - var self = this; - var IOClient; +HTTPClient.prototype._open = spawn.co(function* _open() { + var self = this; + var IOClient; - try { - IOClient = require('socket.io-client'); - } catch (e) { - ; - } + try { + IOClient = require('socket.io-client'); + } catch (e) { + ; + } - if (!IOClient) - return; + if (!IOClient) + return; - this.socket = new IOClient(this.uri, { - transports: ['websocket'], - forceNew: true + this.socket = new IOClient(this.uri, { + transports: ['websocket'], + forceNew: true + }); + + this.socket.on('error', function(err) { + self.emit('error', err); + }); + + this.socket.on('version', function(info) { + if (info.network !== self.network.type) + self.emit('error', new Error('Wrong network.')); + }); + + this.socket.on('wallet tx', function(details) { + self.emit('tx', details); + }); + + this.socket.on('wallet confirmed', function(details) { + self.emit('confirmed', details); + }); + + this.socket.on('wallet unconfirmed', function(details) { + self.emit('unconfirmed', details); + }); + + this.socket.on('wallet conflict', function(details) { + self.emit('conflict', details); + }); + + this.socket.on('wallet updated', function(details) { + self.emit('updated', details); + }); + + this.socket.on('wallet address', function(receive) { + self.emit('address', receive); + }); + + this.socket.on('wallet balance', function(balance) { + self.emit('balance', { + id: balance.id, + confirmed: utils.satoshi(balance.confirmed), + unconfirmed: utils.satoshi(balance.unconfirmed), + total: utils.satoshi(balance.total) }); + }); - this.socket.on('error', function(err) { - self.emit('error', err); - }); - - this.socket.on('version', function(info) { - if (info.network !== self.network.type) - self.emit('error', new Error('Wrong network.')); - }); - - this.socket.on('wallet tx', function(details) { - self.emit('tx', details); - }); - - this.socket.on('wallet confirmed', function(details) { - self.emit('confirmed', details); - }); - - this.socket.on('wallet unconfirmed', function(details) { - self.emit('unconfirmed', details); - }); - - this.socket.on('wallet conflict', function(details) { - self.emit('conflict', details); - }); - - this.socket.on('wallet updated', function(details) { - self.emit('updated', details); - }); - - this.socket.on('wallet address', function(receive) { - self.emit('address', receive); - }); - - this.socket.on('wallet balance', function(balance) { - self.emit('balance', { - id: balance.id, - confirmed: utils.satoshi(balance.confirmed), - unconfirmed: utils.satoshi(balance.unconfirmed), - total: utils.satoshi(balance.total) - }); - }); - - yield this._onConnect(); - yield this._sendAuth(); - }, this); -}; + yield this._onConnect(); + yield this._sendAuth(); +}); HTTPClient.prototype._onConnect = function _onConnect() { var self = this; @@ -165,58 +163,56 @@ HTTPClient.prototype._close = function close() { * @param {Function} callback - Returns [Error, Object?]. */ -HTTPClient.prototype._request = function _request(method, endpoint, json) { - return spawn(function *() { - var query, network, height, res; +HTTPClient.prototype._request = spawn.co(function* _request(method, endpoint, json) { + var query, network, height, res; - if (this.token) { - if (!json) - json = {}; - json.token = this.token; - } + if (this.token) { + if (!json) + json = {}; + json.token = this.token; + } - if (json && method === 'get') { - query = json; - json = null; - } + if (json && method === 'get') { + query = json; + json = null; + } - res = yield request({ - method: method, - uri: this.uri + endpoint, - query: query, - json: json, - auth: { - username: 'bitcoinrpc', - password: this.apiKey || '' - }, - expect: 'json' - }); + res = yield request({ + method: method, + uri: this.uri + endpoint, + query: query, + json: json, + auth: { + username: 'bitcoinrpc', + password: this.apiKey || '' + }, + expect: 'json' + }); - network = res.headers['x-bcoin-network']; + network = res.headers['x-bcoin-network']; - if (network !== this.network.type) - throw new Error('Wrong network.'); + if (network !== this.network.type) + throw new Error('Wrong network.'); - height = +res.headers['x-bcoin-height']; + height = +res.headers['x-bcoin-height']; - if (utils.isNumber(height)) - this.network.updateHeight(height); + if (utils.isNumber(height)) + this.network.updateHeight(height); - if (res.statusCode === 404) - return; + if (res.statusCode === 404) + return; - if (!res.body) - throw new Error('No body.'); + if (!res.body) + throw new Error('No body.'); - if (res.statusCode !== 200) { - if (res.body.error) - throw new Error(res.body.error); - throw new Error('Status code: ' + res.statusCode); - } + if (res.statusCode !== 200) { + if (res.body.error) + throw new Error(res.body.error); + throw new Error('Status code: ' + res.statusCode); + } - return res.body; - }, this); -}; + return res.body; +}); /** * Make a GET http request to endpoint. @@ -569,13 +565,11 @@ HTTPClient.prototype.send = function send(id, options) { * @param {Function} callback */ -HTTPClient.prototype.retoken = function retoken(id, passphrase) { - return spawn(function *() { - var options = { passphrase: passphrase }; - var body = yield this._post('/wallet/' + id + '/retoken', options); - return body.token; - }, this); -}; +HTTPClient.prototype.retoken = spawn.co(function* retoken(id, passphrase) { + var options = { passphrase: passphrase }; + var body = yield this._post('/wallet/' + id + '/retoken', options); + return body.token; +}); /** * Change or set master key's passphrase. diff --git a/lib/http/rpc.js b/lib/http/rpc.js index 787bc21b..cd4355c5 100644 --- a/lib/http/rpc.js +++ b/lib/http/rpc.js @@ -293,35 +293,33 @@ RPC.prototype.execute = function execute(json) { * Overall control/query calls */ -RPC.prototype.getinfo = function getinfo(args) { - return spawn(function *() { - var balance; +RPC.prototype.getinfo = spawn.co(function* getinfo(args) { + var balance; - if (args.help || args.length !== 0) - throw new RPCError('getinfo'); + if (args.help || args.length !== 0) + throw new RPCError('getinfo'); - balance = yield this.wallet.getBalance(); + balance = yield this.wallet.getBalance(); - return { - version: constants.USER_VERSION, - protocolversion: constants.VERSION, - walletversion: 0, - balance: +utils.btc(balance.total), - blocks: this.chain.height, - timeoffset: bcoin.time.offset, - connections: this.pool.peers.all.length, - proxy: '', - difficulty: this._getDifficulty(), - testnet: this.network.type !== bcoin.network.main, - keypoololdest: 0, - keypoolsize: 0, - unlocked_until: this.wallet.master.until, - paytxfee: +utils.btc(this.network.getRate()), - relayfee: +utils.btc(this.network.getMinRelay()), - errors: '' - }; - }, this); -}; + return { + version: constants.USER_VERSION, + protocolversion: constants.VERSION, + walletversion: 0, + balance: +utils.btc(balance.total), + blocks: this.chain.height, + timeoffset: bcoin.time.offset, + connections: this.pool.peers.all.length, + proxy: '', + difficulty: this._getDifficulty(), + testnet: this.network.type !== bcoin.network.main, + keypoololdest: 0, + keypoolsize: 0, + unlocked_until: this.wallet.master.until, + paytxfee: +utils.btc(this.network.getRate()), + relayfee: +utils.btc(this.network.getMinRelay()), + errors: '' + }; +}); RPC.prototype.help = function help(args) { var json; @@ -617,71 +615,67 @@ RPC.prototype._getSoftforks = function _getSoftforks() { ]; }; -RPC.prototype._getBIP9Softforks = function _getBIP9Softforks() { - return spawn(function *() { - var forks = {}; - var keys = Object.keys(this.network.deployments); - var i, id, deployment, state; +RPC.prototype._getBIP9Softforks = spawn.co(function* _getBIP9Softforks() { + var forks = {}; + var keys = Object.keys(this.network.deployments); + var i, id, deployment, state; - for (i = 0; i < keys.length; i++) { - id = keys[i]; - deployment = this.network.deployments[id]; - state = yield this.chain.getState(this.chain.tip, id); + for (i = 0; i < keys.length; i++) { + id = keys[i]; + deployment = this.network.deployments[id]; + state = yield this.chain.getState(this.chain.tip, id); - switch (state) { - case constants.thresholdStates.DEFINED: - state = 'defined'; - break; - case constants.thresholdStates.STARTED: - state = 'started'; - break; - case constants.thresholdStates.LOCKED_IN: - state = 'locked_in'; - break; - case constants.thresholdStates.ACTIVE: - state = 'active'; - break; - case constants.thresholdStates.FAILED: - state = 'failed'; - break; - } - - forks[id] = { - status: state, - bit: deployment.bit, - startTime: deployment.startTime, - timeout: deployment.timeout - }; + switch (state) { + case constants.thresholdStates.DEFINED: + state = 'defined'; + break; + case constants.thresholdStates.STARTED: + state = 'started'; + break; + case constants.thresholdStates.LOCKED_IN: + state = 'locked_in'; + break; + case constants.thresholdStates.ACTIVE: + state = 'active'; + break; + case constants.thresholdStates.FAILED: + state = 'failed'; + break; } - return forks; - }, this); -}; + forks[id] = { + status: state, + bit: deployment.bit, + startTime: deployment.startTime, + timeout: deployment.timeout + }; + } + + return forks; +}); /* Block chain and UTXO */ -RPC.prototype.getblockchaininfo = function getblockchaininfo(args) { - return spawn(function *() { - if (args.help || args.length !== 0) - throw new RPCError('getblockchaininfo'); +RPC.prototype.getblockchaininfo = spawn.co(function* getblockchaininfo(args) { + if (args.help || args.length !== 0) + throw new RPCError('getblockchaininfo'); - return { - chain: 'main', - blocks: this.chain.height, - headers: this.chain.bestHeight, - bestblockhash: utils.revHex(this.chain.tip.hash), - difficulty: this._getDifficulty(), - mediantime: yield this.chain.tip.getMedianTimeAsync(), - verificationprogress: this.chain.getProgress(), - chainwork: this.chain.tip.chainwork.toString('hex', 64), - pruned: this.chain.db.options.prune, - softforks: this._getSoftforks(), - bip9_softforks: yield this._getBIP9Softforks(), - pruneheight: this.chain.db.prune - ? Math.max(0, this.chain.height - this.chain.db.keepBlocks) - : null - }; - }, this); -}; + return { + chain: 'main', + blocks: this.chain.height, + headers: this.chain.bestHeight, + bestblockhash: utils.revHex(this.chain.tip.hash), + difficulty: this._getDifficulty(), + mediantime: yield this.chain.tip.getMedianTimeAsync(), + verificationprogress: this.chain.getProgress(), + chainwork: this.chain.tip.chainwork.toString('hex', 64), + pruned: this.chain.db.options.prune, + softforks: this._getSoftforks(), + bip9_softforks: yield this._getBIP9Softforks(), + pruneheight: this.chain.db.prune + ? Math.max(0, this.chain.height - this.chain.db.keepBlocks) + : null + }; +}); RPC.prototype._getDifficulty = function getDifficulty(entry) { var shift, diff; @@ -722,46 +716,44 @@ RPC.prototype.getblockcount = function getblockcount(args) { return Promise.resolve(this.chain.tip.height); }; -RPC.prototype.getblock = function getblock(args) { - return spawn(function *() { - var hash, verbose, entry, block; +RPC.prototype.getblock = spawn.co(function* getblock(args) { + var hash, verbose, entry, block; - if (args.help || args.length < 1 || args.length > 2) - throw new RPCError('getblock "hash" ( verbose )'); + if (args.help || args.length < 1 || args.length > 2) + throw new RPCError('getblock "hash" ( verbose )'); - hash = toHash(args[0]); + hash = toHash(args[0]); - if (!hash) - throw new RPCError('Invalid parameter.'); + if (!hash) + throw new RPCError('Invalid parameter.'); - verbose = true; + verbose = true; - if (args.length > 1) - verbose = toBool(args[1]); + if (args.length > 1) + verbose = toBool(args[1]); - entry = yield this.chain.db.get(hash); + entry = yield this.chain.db.get(hash); - if (!entry) - throw new RPCError('Block not found'); + if (!entry) + throw new RPCError('Block not found'); - block = yield this.chain.db.getBlock(entry.hash); + block = yield this.chain.db.getBlock(entry.hash); - if (!block) { - if (this.chain.db.options.spv) - throw new RPCError('Block not available (spv mode)'); + if (!block) { + if (this.chain.db.options.spv) + throw new RPCError('Block not available (spv mode)'); - if (this.chain.db.prune) - throw new RPCError('Block not available (pruned data)'); + if (this.chain.db.prune) + throw new RPCError('Block not available (pruned data)'); - throw new RPCError('Can\'t read block from disk'); - } + throw new RPCError('Can\'t read block from disk'); + } - if (!verbose) - return block.toRaw().toString('hex'); + if (!verbose) + return block.toRaw().toString('hex'); - return yield this._blockToJSON(entry, block, false); - }, this); -}; + return yield this._blockToJSON(entry, block, false); +}); RPC.prototype._txToJSON = function _txToJSON(tx) { var self = this; @@ -827,171 +819,159 @@ RPC.prototype._scriptToJSON = function scriptToJSON(script, hex) { return out; }; -RPC.prototype.getblockhash = function getblockhash(args) { - return spawn(function *() { - var height, entry; +RPC.prototype.getblockhash = spawn.co(function* getblockhash(args) { + var height, entry; - if (args.help || args.length !== 1) - throw new RPCError('getblockhash index'); + if (args.help || args.length !== 1) + throw new RPCError('getblockhash index'); - height = toNumber(args[0]); + height = toNumber(args[0]); - if (height < 0 || height > this.chain.height) - throw new RPCError('Block height out of range.'); + if (height < 0 || height > this.chain.height) + throw new RPCError('Block height out of range.'); - entry = yield this.chain.db.get(height); + entry = yield this.chain.db.get(height); - if (!entry) - throw new RPCError('Not found.'); + if (!entry) + throw new RPCError('Not found.'); - return entry.rhash; - }, this); -}; + return entry.rhash; +}); -RPC.prototype.getblockheader = function getblockheader(args) { - return spawn(function *() { - var hash, verbose, entry; +RPC.prototype.getblockheader = spawn.co(function* getblockheader(args) { + var hash, verbose, entry; - if (args.help || args.length < 1 || args.length > 2) - throw new RPCError('getblockheader "hash" ( verbose )'); + if (args.help || args.length < 1 || args.length > 2) + throw new RPCError('getblockheader "hash" ( verbose )'); - hash = toHash(args[0]); + hash = toHash(args[0]); - if (!hash) - throw new RPCError('Invalid parameter.'); + if (!hash) + throw new RPCError('Invalid parameter.'); - verbose = true; + verbose = true; - if (args.length > 1) - verbose = toBool(args[1], true); + if (args.length > 1) + verbose = toBool(args[1], true); - entry = yield this.chain.db.get(hash); + entry = yield this.chain.db.get(hash); - if (!entry) - throw new RPCError('Block not found'); + if (!entry) + throw new RPCError('Block not found'); - if (!verbose) - return entry.toRaw().toString('hex', 0, 80); + if (!verbose) + return entry.toRaw().toString('hex', 0, 80); - return yield this._headerToJSON(entry); - }, this); -}; + return yield this._headerToJSON(entry); +}); -RPC.prototype._headerToJSON = function _headerToJSON(entry) { - return spawn(function *() { - var medianTime = yield entry.getMedianTimeAsync(); - var nextHash = yield this.chain.db.getNextHash(entry.hash); +RPC.prototype._headerToJSON = spawn.co(function* _headerToJSON(entry) { + var medianTime = yield entry.getMedianTimeAsync(); + var nextHash = yield this.chain.db.getNextHash(entry.hash); - return { - hash: utils.revHex(entry.hash), - confirmations: this.chain.height - entry.height + 1, + return { + hash: utils.revHex(entry.hash), + confirmations: this.chain.height - entry.height + 1, + height: entry.height, + version: entry.version, + merkleroot: utils.revHex(entry.merkleRoot), + time: entry.ts, + mediantime: medianTime, + bits: entry.bits, + difficulty: this._getDifficulty(entry), + chainwork: entry.chainwork.toString('hex', 64), + previousblockhash: entry.prevBlock !== constants.NULL_HASH + ? utils.revHex(entry.prevBlock) + : null, + nextblockhash: nextHash ? utils.revHex(nextHash) : null + }; +}); + +RPC.prototype._blockToJSON = spawn.co(function* _blockToJSON(entry, block, txDetails) { + var self = this; + var medianTime = yield entry.getMedianTimeAsync(); + var nextHash = yield this.chain.db.getNextHash(entry.hash); + + return { + hash: utils.revHex(entry.hash), + confirmations: this.chain.height - entry.height + 1, + strippedsize: block.getBaseSize(), + size: block.getSize(), + weight: block.getWeight(), + height: entry.height, + version: entry.version, + merkleroot: utils.revHex(entry.merkleRoot), + tx: block.txs.map(function(tx) { + if (txDetails) + return self._txToJSON(tx); + return tx.rhash; + }), + time: entry.ts, + mediantime: medianTime, + bits: entry.bits, + difficulty: this._getDifficulty(entry), + chainwork: entry.chainwork.toString('hex', 64), + previousblockhash: entry.prevBlock !== constants.NULL_HASH + ? utils.revHex(entry.prevBlock) + : null, + nextblockhash: nextHash ? utils.revHex(nextHash) : null + }; +}); + +RPC.prototype.getchaintips = spawn.co(function* getchaintips(args) { + var i, tips, orphans, prevs, result; + var orphan, entries, entry, main, fork; + + if (args.help || args.length !== 0) + throw new RPCError('getchaintips'); + + tips = []; + orphans = []; + prevs = {}; + result = []; + + entries = yield this.chain.db.getEntries(); + + for (i = 0; i < entries.length; i++) { + entry = entries[i]; + main = yield entry.isMainChain(); + if (!main) { + orphans.push(entry); + prevs[entry.prevBlock] = true; + } + } + + for (i = 0; i < orphans.length; i++) { + orphan = orphans[i]; + if (!prevs[orphan.hash]) + tips.push(orphan); + } + + tips.push(this.chain.tip); + + for (i = 0; i < tips.length; i++) { + entry = tips[i]; + fork = yield this._findFork(entry); + main = yield entry.isMainChain(); + result.push({ height: entry.height, - version: entry.version, - merkleroot: utils.revHex(entry.merkleRoot), - time: entry.ts, - mediantime: medianTime, - bits: entry.bits, - difficulty: this._getDifficulty(entry), - chainwork: entry.chainwork.toString('hex', 64), - previousblockhash: entry.prevBlock !== constants.NULL_HASH - ? utils.revHex(entry.prevBlock) - : null, - nextblockhash: nextHash ? utils.revHex(nextHash) : null - }; - }, this); -}; + hash: entry.rhash, + branchlen: entry.height - fork.height, + status: main ? 'active' : 'valid-headers' + }); + } -RPC.prototype._blockToJSON = function _blockToJSON(entry, block, txDetails) { - return spawn(function *() { - var self = this; - var medianTime = yield entry.getMedianTimeAsync(); - var nextHash = yield this.chain.db.getNextHash(entry.hash); + return result; +}); - return { - hash: utils.revHex(entry.hash), - confirmations: this.chain.height - entry.height + 1, - strippedsize: block.getBaseSize(), - size: block.getSize(), - weight: block.getWeight(), - height: entry.height, - version: entry.version, - merkleroot: utils.revHex(entry.merkleRoot), - tx: block.txs.map(function(tx) { - if (txDetails) - return self._txToJSON(tx); - return tx.rhash; - }), - time: entry.ts, - mediantime: medianTime, - bits: entry.bits, - difficulty: this._getDifficulty(entry), - chainwork: entry.chainwork.toString('hex', 64), - previousblockhash: entry.prevBlock !== constants.NULL_HASH - ? utils.revHex(entry.prevBlock) - : null, - nextblockhash: nextHash ? utils.revHex(nextHash) : null - }; - }, this); -}; - -RPC.prototype.getchaintips = function getchaintips(args) { - return spawn(function *() { - var i, tips, orphans, prevs, result; - var orphan, entries, entry, main, fork; - - if (args.help || args.length !== 0) - throw new RPCError('getchaintips'); - - tips = []; - orphans = []; - prevs = {}; - result = []; - - entries = yield this.chain.db.getEntries(); - - for (i = 0; i < entries.length; i++) { - entry = entries[i]; - main = yield entry.isMainChain(); - if (!main) { - orphans.push(entry); - prevs[entry.prevBlock] = true; - } - } - - for (i = 0; i < orphans.length; i++) { - orphan = orphans[i]; - if (!prevs[orphan.hash]) - tips.push(orphan); - } - - tips.push(this.chain.tip); - - for (i = 0; i < tips.length; i++) { - entry = tips[i]; - fork = yield this._findFork(entry); - main = yield entry.isMainChain(); - result.push({ - height: entry.height, - hash: entry.rhash, - branchlen: entry.height - fork.height, - status: main ? 'active' : 'valid-headers' - }); - } - - return result; - }, this); -}; - -RPC.prototype._findFork = function _findFork(entry) { - return spawn(function *() { - while (entry) { - if (yield entry.isMainChain()) - return entry; - entry = yield entry.getPrevious(); - } - throw new Error('Fork not found.'); - }, this); -}; +RPC.prototype._findFork = spawn.co(function* _findFork(entry) { + while (entry) { + if (yield entry.isMainChain()) + return entry; + entry = yield entry.getPrevious(); + } + throw new Error('Fork not found.'); +}); RPC.prototype.getdifficulty = function getdifficulty(args) { if (args.help || args.length !== 0) @@ -1167,142 +1147,136 @@ RPC.prototype._entryToJSON = function _entryToJSON(entry) { }; }; -RPC.prototype.gettxout = function gettxout(args) { - return spawn(function *() { - var hash, index, mempool, coin; +RPC.prototype.gettxout = spawn.co(function* gettxout(args) { + var hash, index, mempool, coin; - if (args.help || args.length < 2 || args.length > 3) - throw new RPCError('gettxout "txid" n ( includemempool )'); + if (args.help || args.length < 2 || args.length > 3) + throw new RPCError('gettxout "txid" n ( includemempool )'); - if (this.chain.db.options.spv) - throw new RPCError('Cannot get coins in SPV mode.'); + if (this.chain.db.options.spv) + throw new RPCError('Cannot get coins in SPV mode.'); - if (this.chain.db.options.prune) - throw new RPCError('Cannot get coins when pruned.'); + if (this.chain.db.options.prune) + throw new RPCError('Cannot get coins when pruned.'); - hash = toHash(args[0]); - index = toNumber(args[1]); - mempool = true; + hash = toHash(args[0]); + index = toNumber(args[1]); + mempool = true; - if (args.length > 2) - mempool = toBool(args[2], true); + if (args.length > 2) + mempool = toBool(args[2], true); - if (!hash || index < 0) - throw new RPCError('Invalid parameter.'); + if (!hash || index < 0) + throw new RPCError('Invalid parameter.'); - if (mempool) - coin = yield this.node.getCoin(hash, index); - else - coin = yield this.chain.db.getCoin(hash, index); + if (mempool) + coin = yield this.node.getCoin(hash, index); + else + coin = yield this.chain.db.getCoin(hash, index); - if (!coin) - return null; + if (!coin) + return null; - return { - bestblock: utils.revHex(this.chain.tip.hash), - confirmations: coin.getConfirmations(this.chain.height), - value: +utils.btc(coin.value), - scriptPubKey: this._scriptToJSON(coin.script, true), - version: coin.version, - coinbase: coin.coinbase - }; - }, this); -}; + return { + bestblock: utils.revHex(this.chain.tip.hash), + confirmations: coin.getConfirmations(this.chain.height), + value: +utils.btc(coin.value), + scriptPubKey: this._scriptToJSON(coin.script, true), + version: coin.version, + coinbase: coin.coinbase + }; +}); -RPC.prototype.gettxoutproof = function gettxoutproof(args) { - return spawn(function *() { - var uniq = {}; - var i, txids, block, hash, last, tx, coins; +RPC.prototype.gettxoutproof = spawn.co(function* gettxoutproof(args) { + var uniq = {}; + var i, txids, block, hash, last, tx, coins; - if (args.help || (args.length !== 1 && args.length !== 2)) - throw new RPCError('gettxoutproof ["txid",...] ( blockhash )'); + if (args.help || (args.length !== 1 && args.length !== 2)) + throw new RPCError('gettxoutproof ["txid",...] ( blockhash )'); - if (this.chain.db.options.spv) - throw new RPCError('Cannot get coins in SPV mode.'); + if (this.chain.db.options.spv) + throw new RPCError('Cannot get coins in SPV mode.'); - if (this.chain.db.options.prune) - throw new RPCError('Cannot get coins when pruned.'); + if (this.chain.db.options.prune) + throw new RPCError('Cannot get coins when pruned.'); - txids = toArray(args[0]); - block = args[1]; + txids = toArray(args[0]); + block = args[1]; - if (!txids || txids.length === 0) - throw new RPCError('Invalid parameter.'); - - if (block) { - block = toHash(block); - if (!block) - throw new RPCError('Invalid parameter.'); - } - - for (i = 0; i < txids.length; i++) { - hash = toHash(txids[i]); - - if (!hash) - throw new RPCError('Invalid parameter.'); - - if (uniq[hash]) - throw new RPCError('Duplicate txid.'); - - uniq[hash] = true; - txids[i] = hash; - last = hash; - } - - if (hash) { - block = yield this.chain.db.getBlock(hash); - } else if (this.chain.options.indexTX) { - tx = yield this.chain.db.getTX(last); - if (!tx) - return; - block = yield this.chain.db.getBlock(tx.block); - } else { - coins = yield this.chain.db.getCoins(last); - if (!coins) - return; - block = yield this.chain.db.getBlock(coins.height); - } + if (!txids || txids.length === 0) + throw new RPCError('Invalid parameter.'); + if (block) { + block = toHash(block); if (!block) - throw new RPCError('Block not found.'); + throw new RPCError('Invalid parameter.'); + } - for (i = 0; i < txids.length; i++) { - if (!block.hasTX(txids[i])) - throw new RPCError('Block does not contain all txids.'); - } + for (i = 0; i < txids.length; i++) { + hash = toHash(txids[i]); - block = bcoin.merkleblock.fromHashes(block, txids); + if (!hash) + throw new RPCError('Invalid parameter.'); - return block.toRaw().toString('hex'); - }, this); -}; + if (uniq[hash]) + throw new RPCError('Duplicate txid.'); -RPC.prototype.verifytxoutproof = function verifytxoutproof(args) { - return spawn(function *() { - var res = []; - var i, block, hash, entry; + uniq[hash] = true; + txids[i] = hash; + last = hash; + } - if (args.help || args.length !== 1) - throw new RPCError('verifytxoutproof "proof"'); + if (hash) { + block = yield this.chain.db.getBlock(hash); + } else if (this.chain.options.indexTX) { + tx = yield this.chain.db.getTX(last); + if (!tx) + return; + block = yield this.chain.db.getBlock(tx.block); + } else { + coins = yield this.chain.db.getCoins(last); + if (!coins) + return; + block = yield this.chain.db.getBlock(coins.height); + } - block = bcoin.merkleblock.fromRaw(toString(args[0]), 'hex'); + if (!block) + throw new RPCError('Block not found.'); - if (!block.verify()) - return res; + for (i = 0; i < txids.length; i++) { + if (!block.hasTX(txids[i])) + throw new RPCError('Block does not contain all txids.'); + } - entry = yield this.chain.db.get(block.hash('hex')); + block = bcoin.merkleblock.fromHashes(block, txids); - if (!entry) - throw new RPCError('Block not found in chain.'); + return block.toRaw().toString('hex'); +}); - for (i = 0; i < block.matches.length; i++) { - hash = block.matches[i]; - res.push(utils.revHex(hash)); - } +RPC.prototype.verifytxoutproof = spawn.co(function* verifytxoutproof(args) { + var res = []; + var i, block, hash, entry; + if (args.help || args.length !== 1) + throw new RPCError('verifytxoutproof "proof"'); + + block = bcoin.merkleblock.fromRaw(toString(args[0]), 'hex'); + + if (!block.verify()) return res; - }, this); -}; + + entry = yield this.chain.db.get(block.hash('hex')); + + if (!entry) + throw new RPCError('Block not found in chain.'); + + for (i = 0; i < block.matches.length; i++) { + hash = block.matches[i]; + res.push(utils.revHex(hash)); + } + + return res; +}); RPC.prototype.gettxoutsetinfo = function gettxoutsetinfo(args) { if (args.help || args.length !== 0) @@ -1339,88 +1313,84 @@ RPC.prototype.verifychain = function verifychain(args) { * Mining */ -RPC.prototype._submitwork = function _submitwork(data) { - return spawn(function *() { - var attempt = this.attempt; - var block, header, cb, cur; +RPC.prototype._submitwork = spawn.co(function* _submitwork(data) { + var attempt = this.attempt; + var block, header, cb, cur; - if (data.length !== 128) - throw new RPCError('Invalid parameter.'); + if (data.length !== 128) + throw new RPCError('Invalid parameter.'); - if (!attempt) - return false; + if (!attempt) + return false; - data = data.slice(0, 80); + data = data.slice(0, 80); - reverseEndian(data); + reverseEndian(data); - header = bcoin.headers.fromAbbr(data); - block = attempt.block; + header = bcoin.headers.fromAbbr(data); + block = attempt.block; - if (header.prevBlock !== block.prevBlock - || header.bits !== block.bits) { - return false; - } + if (header.prevBlock !== block.prevBlock + || header.bits !== block.bits) { + return false; + } - if (!header.verify()) - return false; + if (!header.verify()) + return false; - cb = this.coinbase[header.merkleRoot]; + cb = this.coinbase[header.merkleRoot]; - if (!cb) - return false; + if (!cb) + return false; - cur = block.txs[0]; - block.txs[0] = cb; + cur = block.txs[0]; + block.txs[0] = cb; + attempt.updateMerkle(); + + if (header.merkleRoot !== block.merkleRoot) { + block.txs[0] = cur; attempt.updateMerkle(); + this.logger.warning('Bad calculated merkle root for submitted work.'); + return false; + } - if (header.merkleRoot !== block.merkleRoot) { - block.txs[0] = cur; - attempt.updateMerkle(); - this.logger.warning('Bad calculated merkle root for submitted work.'); + block.nonce = header.nonce; + block.ts = header.ts; + block.mutable = false; + block.txs[0].mutable = false; + + try { + yield this.chain.add(block); + } catch (err) { + if (err.type === 'VerifyError') return false; - } + throw err; + } - block.nonce = header.nonce; - block.ts = header.ts; - block.mutable = false; - block.txs[0].mutable = false; + return true; +}); - try { - yield this.chain.add(block); - } catch (err) { - if (err.type === 'VerifyError') - return false; - throw err; - } +RPC.prototype._getwork = spawn.co(function* _getwork() { + var attempt = yield this._getAttempt(true); + var data, abbr; - return true; - }, this); -}; + data = new Buffer(128); + data.fill(0); -RPC.prototype._getwork = function _getwork() { - return spawn(function *() { - var attempt = yield this._getAttempt(true); - var data, abbr; + abbr = attempt.block.abbr(); + abbr.copy(data, 0); - data = new Buffer(128); - data.fill(0); + data[80] = 0x80; + data.writeUInt32BE(80 * 8, data.length - 4, true); - abbr = attempt.block.abbr(); - abbr.copy(data, 0); + reverseEndian(data); - data[80] = 0x80; - data.writeUInt32BE(80 * 8, data.length - 4, true); - - reverseEndian(data); - - return { - data: data.toString('hex'), - target: attempt.target.toString('hex'), - height: attempt.height - }; - }, this); -}; + return { + data: data.toString('hex'), + target: attempt.target.toString('hex'), + height: attempt.height + }; +}); RPC.prototype.getworklp = function getworklp(args) { var self = this; @@ -1431,299 +1401,289 @@ RPC.prototype.getworklp = function getworklp(args) { }); }; -RPC.prototype.getwork = function getwork(args) { - return spawn(function *() { - var unlock = yield this.locker.lock(); - var data, result; +RPC.prototype.getwork = spawn.co(function* getwork(args) { + var unlock = yield this.locker.lock(); + var data, result; - if (args.length > 1) { + if (args.length > 1) { + unlock(); + throw new RPCError('getwork ( "data" )'); + } + + if (args.length === 1) { + if (!utils.isHex(args[0])) { unlock(); - throw new RPCError('getwork ( "data" )'); + throw new RPCError('Invalid parameter.'); } - if (args.length === 1) { - if (!utils.isHex(args[0])) { - unlock(); - throw new RPCError('Invalid parameter.'); - } - - data = new Buffer(args[0], 'hex'); - - try { - result = yield this._submitwork(data); - } catch (e) { - unlock(); - throw e; - } - - return result; - } + data = new Buffer(args[0], 'hex'); try { - result = yield this._getwork(); + result = yield this._submitwork(data); } catch (e) { unlock(); throw e; } - unlock(); return result; - }, this); -}; + } -RPC.prototype.submitblock = function submitblock(args) { - return spawn(function *() { - var unlock = yield this.locker.lock(); - var block; + try { + result = yield this._getwork(); + } catch (e) { + unlock(); + throw e; + } - if (args.help || args.length < 1 || args.length > 2) { - unlock(); - throw new RPCError('submitblock "hexdata" ( "jsonparametersobject" )'); + unlock(); + return result; +}); + +RPC.prototype.submitblock = spawn.co(function* submitblock(args) { + var unlock = yield this.locker.lock(); + var block; + + if (args.help || args.length < 1 || args.length > 2) { + unlock(); + throw new RPCError('submitblock "hexdata" ( "jsonparametersobject" )'); + } + + block = bcoin.block.fromRaw(toString(args[0]), 'hex'); + + return yield this._submitblock(block); +}); + +RPC.prototype._submitblock = spawn.co(function* submitblock(block) { + if (block.prevBlock !== this.chain.tip.hash) + return 'rejected: inconclusive-not-best-prevblk'; + + try { + yield this.chain.add(block); + } catch (err) { + if (err.type === 'VerifyError') + return 'rejected: ' + err.reason; + throw err; + } + + return null; +}); + +RPC.prototype.getblocktemplate = spawn.co(function* getblocktemplate(args) { + var mode = 'template'; + var version = -1; + var coinbase = true; + var i, opt, lpid, rules, cap, block; + var coinbasevalue, coinbasetxn; + + if (args.help || args.length > 1) + throw new RPCError('getblocktemplate ( "jsonrequestobject" )'); + + if (args.length === 1) { + opt = args[0] || {}; + + if (opt.mode != null) { + mode = opt.mode; + if (mode !== 'template' && mode !== 'proposal') + throw new RPCError('Invalid mode.'); } - block = bcoin.block.fromRaw(toString(args[0]), 'hex'); + lpid = opt.longpollid; - return yield this._submitblock(block); - }, this); -}; + if (mode === 'proposal') { + if (!utils.isHex(opt.data)) + throw new RPCError('Invalid parameter.'); -RPC.prototype._submitblock = function submitblock(block) { - return spawn(function *() { - if (block.prevBlock !== this.chain.tip.hash) - return 'rejected: inconclusive-not-best-prevblk'; + block = bcoin.block.fromRaw(opt.data, 'hex'); - try { - yield this.chain.add(block); - } catch (err) { - if (err.type === 'VerifyError') - return 'rejected: ' + err.reason; - throw err; + return yield this._submitblock(block); } - return null; - }, this); -}; + if (Array.isArray(opt.rules)) { + rules = []; + for (i = 0; i < opt.rules.length; i++) + rules.push(toString(opt.rules[i])); + } else if (utils.isNumber(opt.maxversion)) { + version = opt.maxversion; + } -RPC.prototype.getblocktemplate = function getblocktemplate(args) { - return spawn(function *() { - var mode = 'template'; - var version = -1; - var coinbase = true; - var i, opt, lpid, rules, cap, block; - var coinbasevalue, coinbasetxn; - - if (args.help || args.length > 1) - throw new RPCError('getblocktemplate ( "jsonrequestobject" )'); - - if (args.length === 1) { - opt = args[0] || {}; - - if (opt.mode != null) { - mode = opt.mode; - if (mode !== 'template' && mode !== 'proposal') - throw new RPCError('Invalid mode.'); + if (Array.isArray(opt.capabilities)) { + for (i = 0; i < opt.capabilities.length; i++) { + cap = toString(opt.capabilities[i]); + switch (cap) { + case 'coinbasetxn': + coinbasetxn = true; + break; + case 'coinbasevalue': + coinbasevalue = true; + break; + } } - lpid = opt.longpollid; + if (!coinbasetxn) + coinbase = false; + } + } - if (mode === 'proposal') { - if (!utils.isHex(opt.data)) - throw new RPCError('Invalid parameter.'); + if (!this.network.selfConnect) { + if (this.pool.peers.all.length === 0) + throw new RPCError('Bitcoin is not connected!'); - block = bcoin.block.fromRaw(opt.data, 'hex'); + if (!this.chain.isFull()) + throw new RPCError('Bitcoin is downloading blocks...'); + } - return yield this._submitblock(block); - } + yield this._poll(lpid); - if (Array.isArray(opt.rules)) { - rules = []; - for (i = 0; i < opt.rules.length; i++) - rules.push(toString(opt.rules[i])); - } else if (utils.isNumber(opt.maxversion)) { - version = opt.maxversion; - } + return yield this._tmpl(version, coinbase, rules); +}); - if (Array.isArray(opt.capabilities)) { - for (i = 0; i < opt.capabilities.length; i++) { - cap = toString(opt.capabilities[i]); - switch (cap) { - case 'coinbasetxn': - coinbasetxn = true; - break; - case 'coinbasevalue': - coinbasevalue = true; - break; +RPC.prototype._tmpl = spawn.co(function* _tmpl(version, coinbase, rules) { + var unlock = yield this.locker.lock(); + var txs = []; + var txIndex = {}; + var i, j, tx, deps, input, dep, block, output, raw, rwhash; + var keys, vbavailable, vbrules, mutable, template, attempt; + var id, deployment, state; + + try { + attempt = yield this._getAttempt(false); + } catch (e) { + unlock(); + throw e; + } + + block = attempt.block; + + for (i = 1; i < block.txs.length; i++) { + tx = block.txs[i]; + txIndex[tx.hash('hex')] = i; + deps = []; + + for (j = 0; j < tx.inputs.length; j++) { + input = tx.inputs[j]; + dep = txIndex[input.prevout.hash]; + if (dep != null) + deps.push(dep); + } + + txs.push({ + data: tx.toRaw().toString('hex'), + txid: tx.rhash, + hash: tx.rwhash, + depends: deps, + fee: tx.getFee(), + sigops: tx.getSigops(), + weight: tx.getWeight() + }); + } + + keys = Object.keys(this.network.deployments); + vbavailable = {}; + vbrules = []; + mutable = ['time', 'transactions', 'prevblock']; + + if (version >= 2) + mutable.push('version/force'); + + for (i = 0; i < keys.length; i++) { + id = keys[i]; + deployment = this.network.deployments[id]; + state = yield this.chain.getState(this.chain.tip, id); + + switch (state) { + case constants.thresholdStates.DEFINED: + case constants.thresholdStates.FAILED: + break; + case constants.thresholdStates.LOCKED_IN: + block.version |= 1 << deployment.bit; + case constants.thresholdStates.STARTED: + vbavailable[id] = deployment.bit; + if (rules) { + if (rules.indexOf(id) === -1 && !deployment.force) + block.version &= ~(1 << deployment.bit); + } + break; + case constants.thresholdStates.ACTIVE: + vbrules.push(id); + if (rules) { + if (rules.indexOf(id) === -1 && !deployment.force) { + unlock(); + throw new RPCError('Client must support ' + id + '.'); } } - - if (!coinbasetxn) - coinbase = false; - } + break; } + } - if (!this.network.selfConnect) { - if (this.pool.peers.all.length === 0) - throw new RPCError('Bitcoin is not connected!'); + block.version >>>= 0; - if (!this.chain.isFull()) - throw new RPCError('Bitcoin is downloading blocks...'); - } + template = { + capabilities: ['proposal'], + version: block.version, + rules: vbrules, + vbavailable: vbavailable, + vbrequired: 0, + previousblockhash: utils.revHex(block.prevBlock), + transactions: txs, + longpollid: this.chain.tip.rhash + utils.pad32(this._totalTX()), + target: utils.revHex(attempt.target.toString('hex')), + submitold: false, + mintime: block.ts, + maxtime: bcoin.now() + 2 * 60 * 60, + mutable: mutable, + noncerange: '00000000ffffffff', + sigoplimit: attempt.witness + ? constants.block.MAX_SIGOPS_WEIGHT + : constants.block.MAX_SIGOPS, + sizelimit: constants.block.MAX_SIZE, + weightlimit: constants.block.MAX_WEIGHT, + curtime: block.ts, + bits: utils.hex32(block.bits), + height: attempt.height + }; - yield this._poll(lpid); - - return yield this._tmpl(version, coinbase, rules); - }, this); -}; - -RPC.prototype._tmpl = function _tmpl(version, coinbase, rules) { - return spawn(function *() { - var unlock = yield this.locker.lock(); - var txs = []; - var txIndex = {}; - var i, j, tx, deps, input, dep, block, output, raw, rwhash; - var keys, vbavailable, vbrules, mutable, template, attempt; - var id, deployment, state; - - try { - attempt = yield this._getAttempt(false); - } catch (e) { - unlock(); - throw e; - } - - block = attempt.block; - - for (i = 1; i < block.txs.length; i++) { - tx = block.txs[i]; - txIndex[tx.hash('hex')] = i; - deps = []; - - for (j = 0; j < tx.inputs.length; j++) { - input = tx.inputs[j]; - dep = txIndex[input.prevout.hash]; - if (dep != null) - deps.push(dep); - } - - txs.push({ - data: tx.toRaw().toString('hex'), - txid: tx.rhash, - hash: tx.rwhash, - depends: deps, - fee: tx.getFee(), - sigops: tx.getSigops(), - weight: tx.getWeight() - }); - } - - keys = Object.keys(this.network.deployments); - vbavailable = {}; - vbrules = []; - mutable = ['time', 'transactions', 'prevblock']; - - if (version >= 2) - mutable.push('version/force'); - - for (i = 0; i < keys.length; i++) { - id = keys[i]; - deployment = this.network.deployments[id]; - state = yield this.chain.getState(this.chain.tip, id); - - switch (state) { - case constants.thresholdStates.DEFINED: - case constants.thresholdStates.FAILED: - break; - case constants.thresholdStates.LOCKED_IN: - block.version |= 1 << deployment.bit; - case constants.thresholdStates.STARTED: - vbavailable[id] = deployment.bit; - if (rules) { - if (rules.indexOf(id) === -1 && !deployment.force) - block.version &= ~(1 << deployment.bit); - } - break; - case constants.thresholdStates.ACTIVE: - vbrules.push(id); - if (rules) { - if (rules.indexOf(id) === -1 && !deployment.force) { - unlock(); - throw new RPCError('Client must support ' + id + '.'); - } - } - break; - } - } - - block.version >>>= 0; - - template = { - capabilities: ['proposal'], - version: block.version, - rules: vbrules, - vbavailable: vbavailable, - vbrequired: 0, - previousblockhash: utils.revHex(block.prevBlock), - transactions: txs, - longpollid: this.chain.tip.rhash + utils.pad32(this._totalTX()), - target: utils.revHex(attempt.target.toString('hex')), - submitold: false, - mintime: block.ts, - maxtime: bcoin.now() + 2 * 60 * 60, - mutable: mutable, - noncerange: '00000000ffffffff', - sigoplimit: attempt.witness - ? constants.block.MAX_SIGOPS_WEIGHT - : constants.block.MAX_SIGOPS, - sizelimit: constants.block.MAX_SIZE, - weightlimit: constants.block.MAX_WEIGHT, - curtime: block.ts, - bits: utils.hex32(block.bits), - height: attempt.height - }; - - if (coinbase) { - tx = attempt.coinbase; - - // We don't include the commitment - // output (see bip145). - if (attempt.witness) { - output = tx.outputs.pop(); - assert(output.script.isCommitment()); - raw = tx.toRaw(); - rwhash = tx.rwhash; - tx.outputs.push(output); - } else { - raw = tx.toRaw(); - rwhash = tx.rwhash; - } - - template.coinbasetxn = { - data: raw.toString('hex'), - txid: tx.rhash, - hash: rwhash, - depends: [], - fee: 0, - sigops: tx.getSigops(), - weight: tx.getWeight() - }; - } else { - template.coinbaseaux = { - flags: attempt.coinbaseFlags.toString('hex') - }; - template.coinbasevalue = attempt.coinbase.getOutputValue(); - } + if (coinbase) { + tx = attempt.coinbase; + // We don't include the commitment + // output (see bip145). if (attempt.witness) { - tx = attempt.coinbase; - output = tx.outputs[tx.outputs.length - 1]; + output = tx.outputs.pop(); assert(output.script.isCommitment()); - template.default_witness_commitment = output.script.toJSON(); + raw = tx.toRaw(); + rwhash = tx.rwhash; + tx.outputs.push(output); + } else { + raw = tx.toRaw(); + rwhash = tx.rwhash; } - unlock(); - return template; - }, this); -}; + template.coinbasetxn = { + data: raw.toString('hex'), + txid: tx.rhash, + hash: rwhash, + depends: [], + fee: 0, + sigops: tx.getSigops(), + weight: tx.getWeight() + }; + } else { + template.coinbaseaux = { + flags: attempt.coinbaseFlags.toString('hex') + }; + template.coinbasevalue = attempt.coinbase.getOutputValue(); + } + + if (attempt.witness) { + tx = attempt.coinbase; + output = tx.outputs[tx.outputs.length - 1]; + assert(output.script.isCommitment()); + template.default_witness_commitment = output.script.toJSON(); + } + + unlock(); + return template; +}); RPC.prototype._poll = function _poll(lpid) { var self = this; @@ -1785,63 +1745,59 @@ RPC.prototype._bindChain = function _bindChain() { }); }; -RPC.prototype._getAttempt = function _getAttempt(update) { - return spawn(function *() { - var attempt = this.attempt; +RPC.prototype._getAttempt = spawn.co(function* _getAttempt(update) { + var attempt = this.attempt; - this._bindChain(); + this._bindChain(); - if (attempt) { - if (update) { - attempt.updateNonce(); - this.coinbase[attempt.block.merkleRoot] = attempt.coinbase.clone(); - } - return attempt; + if (attempt) { + if (update) { + attempt.updateNonce(); + this.coinbase[attempt.block.merkleRoot] = attempt.coinbase.clone(); } - - attempt = yield this.miner.createBlock(); - - this.attempt = attempt; - this.start = utils.now(); - this.coinbase[attempt.block.merkleRoot] = attempt.coinbase.clone(); - return attempt; - }, this); -}; + } + + attempt = yield this.miner.createBlock(); + + this.attempt = attempt; + this.start = utils.now(); + this.coinbase[attempt.block.merkleRoot] = attempt.coinbase.clone(); + + return attempt; +}); RPC.prototype._totalTX = function _totalTX() { return this.mempool ? this.mempool.totalTX : 0; }; -RPC.prototype.getmininginfo = function getmininginfo(args) { - return spawn(function *() { - var block, hashps; +RPC.prototype.getmininginfo = spawn.co(function* getmininginfo(args) { + var block, hashps; - if (args.help || args.length !== 0) - throw new RPCError('getmininginfo'); + if (args.help || args.length !== 0) + throw new RPCError('getmininginfo'); - block = yield this.chain.db.getBlock(this.chain.tip.hash); + block = yield this.chain.db.getBlock(this.chain.tip.hash); - if (!block) - throw new RPCError('Block not found.'); + if (!block) + throw new RPCError('Block not found.'); - hashps = yield this._hashps(120, -1); + hashps = yield this._hashps(120, -1); - return { - blocks: this.chain.height, - currentblocksize: block.getSize(), - currentblocktx: block.txs.length, - difficulty: this._getDifficulty(), - errors: '', - genproclimit: this.proclimit, - networkhashps: hashps, - pooledtx: this._totalTX(), - testnet: this.network !== bcoin.network.main, - chain: 'main', - generate: this.mining - }; - }, this); -}; + return { + blocks: this.chain.height, + currentblocksize: block.getSize(), + currentblocktx: block.txs.length, + difficulty: this._getDifficulty(), + errors: '', + genproclimit: this.proclimit, + networkhashps: hashps, + pooledtx: this._totalTX(), + testnet: this.network !== bcoin.network.main, + chain: 'main', + generate: this.mining + }; +}); RPC.prototype.getnetworkhashps = function getnetworkhashps(args) { var lookup = 120; @@ -1897,50 +1853,48 @@ RPC.prototype.prioritisetransaction = function prioritisetransaction(args) { return Promise.resolve(true); }; -RPC.prototype._hashps = function _hashps(lookup, height) { - return spawn(function *() { - var i, minTime, maxTime, pb0, time; - var workDiff, timeDiff, ps, pb, entry; +RPC.prototype._hashps = spawn.co(function* _hashps(lookup, height) { + var i, minTime, maxTime, pb0, time; + var workDiff, timeDiff, ps, pb, entry; - pb = this.chain.tip; - if (height >= 0 && height < this.chain.tip.height) - pb = yield this.chain.db.get(height); + pb = this.chain.tip; + if (height >= 0 && height < this.chain.tip.height) + pb = yield this.chain.db.get(height); - if (!pb) - return 0; + if (!pb) + return 0; - if (lookup <= 0) - lookup = pb.height % this.network.pow.retargetInterval + 1; + if (lookup <= 0) + lookup = pb.height % this.network.pow.retargetInterval + 1; - if (lookup > pb.height) - lookup = pb.height; + if (lookup > pb.height) + lookup = pb.height; - minTime = pb.ts; - maxTime = minTime; - pb0 = pb; + minTime = pb.ts; + maxTime = minTime; + pb0 = pb; - for (i = 0; i < lookup; i++) { - entry = yield pb0.getPrevious(); + for (i = 0; i < lookup; i++) { + entry = yield pb0.getPrevious(); - if (!entry) - throw new RPCError('Not found.'); + if (!entry) + throw new RPCError('Not found.'); - pb0 = entry; - time = pb0.ts; - minTime = Math.min(time, minTime); - maxTime = Math.max(time, maxTime); - } + pb0 = entry; + time = pb0.ts; + minTime = Math.min(time, minTime); + maxTime = Math.max(time, maxTime); + } - if (minTime === maxTime) - return 0; + if (minTime === maxTime) + return 0; - workDiff = pb.chainwork.sub(pb0.chainwork); - timeDiff = maxTime - minTime; - ps = +workDiff.toString(10) / timeDiff; + workDiff = pb.chainwork.sub(pb0.chainwork); + timeDiff = maxTime - minTime; + ps = +workDiff.toString(10) / timeDiff; - return ps; - }, this); -}; + return ps; +}); /* * Coin generation @@ -1967,60 +1921,54 @@ RPC.prototype.setgenerate = function setgenerate(args) { return Promise.resolve(this.mining); }; -RPC.prototype.generate = function generate(args) { - return spawn(function *() { - var unlock = yield this.locker.lock(); - var numblocks; - - if (args.help || args.length < 1 || args.length > 2) { - unlock(); - throw new RPCError('generate numblocks ( maxtries )'); - } - - numblocks = toNumber(args[0], 1); - - return yield this._generate(numblocks); - }, this); -}; - -RPC.prototype._generate = function _generate(numblocks) { - return spawn(function *() { - var hashes = []; - var i, block; - - for (i = 0; i < numblocks; i++) { - block = yield this.miner.mineBlock(); - hashes.push(block.rhash); - yield this.chain.add(block); - } - - return hashes; - }, this); -}; - -RPC.prototype.generatetoaddress = function generatetoaddress(args) { - return spawn(function *() { - var unlock = yield this.locker.lock(); - var numblocks, address, hashes; - - if (args.help || args.length < 2 || args.length > 3) { - unlock(); - throw new RPCError('generatetoaddress numblocks address ( maxtries )'); - } - - numblocks = toNumber(args[0], 1); - address = this.miner.address; - - this.miner.address = bcoin.address.fromBase58(toString(args[1])); - - hashes = yield this._generate(numblocks); - - this.miner.address = address; +RPC.prototype.generate = spawn.co(function* generate(args) { + var unlock = yield this.locker.lock(); + var numblocks; + if (args.help || args.length < 1 || args.length > 2) { unlock(); - return hashes; - }, this); -}; + throw new RPCError('generate numblocks ( maxtries )'); + } + + numblocks = toNumber(args[0], 1); + + return yield this._generate(numblocks); +}); + +RPC.prototype._generate = spawn.co(function* _generate(numblocks) { + var hashes = []; + var i, block; + + for (i = 0; i < numblocks; i++) { + block = yield this.miner.mineBlock(); + hashes.push(block.rhash); + yield this.chain.add(block); + } + + return hashes; +}); + +RPC.prototype.generatetoaddress = spawn.co(function* generatetoaddress(args) { + var unlock = yield this.locker.lock(); + var numblocks, address, hashes; + + if (args.help || args.length < 2 || args.length > 3) { + unlock(); + throw new RPCError('generatetoaddress numblocks address ( maxtries )'); + } + + numblocks = toNumber(args[0], 1); + address = this.miner.address; + + this.miner.address = bcoin.address.fromBase58(toString(args[1])); + + hashes = yield this._generate(numblocks); + + this.miner.address = address; + + unlock(); + return hashes; +}); /* * Raw transactions @@ -2157,37 +2105,35 @@ RPC.prototype.decodescript = function decodescript(args) { return Promise.resolve(script); }; -RPC.prototype.getrawtransaction = function getrawtransaction(args) { - return spawn(function *() { - var hash, verbose, json, tx; +RPC.prototype.getrawtransaction = spawn.co(function* getrawtransaction(args) { + var hash, verbose, json, tx; - if (args.help || args.length < 1 || args.length > 2) - throw new RPCError('getrawtransaction "txid" ( verbose )'); + if (args.help || args.length < 1 || args.length > 2) + throw new RPCError('getrawtransaction "txid" ( verbose )'); - hash = toHash(args[0]); + hash = toHash(args[0]); - if (!hash) - throw new RPCError('Invalid parameter'); + if (!hash) + throw new RPCError('Invalid parameter'); - verbose = false; + verbose = false; - if (args.length > 1) - verbose = Boolean(args[1]); + if (args.length > 1) + verbose = Boolean(args[1]); - tx = yield this.node.getTX(hash); + tx = yield this.node.getTX(hash); - if (!tx) - throw new RPCError('Transaction not found.'); + if (!tx) + throw new RPCError('Transaction not found.'); - if (!verbose) - throw tx.toRaw().toString('hex'); + if (!verbose) + throw tx.toRaw().toString('hex'); - json = this._txToJSON(tx); - json.hex = tx.toRaw().toString('hex'); + json = this._txToJSON(tx); + json.hex = tx.toRaw().toString('hex'); - return json; - }, this); -}; + return json; +}); RPC.prototype.sendrawtransaction = function sendrawtransaction(args) { var tx; @@ -2207,36 +2153,34 @@ RPC.prototype.sendrawtransaction = function sendrawtransaction(args) { return tx.rhash; }; -RPC.prototype.signrawtransaction = function signrawtransaction(args) { - return spawn(function *() { - var raw, p, txs, merged; +RPC.prototype.signrawtransaction = spawn.co(function* signrawtransaction(args) { + var raw, p, txs, merged; - if (args.help || args.length < 1 || args.length > 4) { - throw new RPCError('signrawtransaction' - + ' "hexstring" (' - + ' [{"txid":"id","vout":n,"scriptPubKey":"hex",' - + 'redeemScript":"hex"},...] ["privatekey1",...]' - + ' sighashtype )'); - } + if (args.help || args.length < 1 || args.length > 4) { + throw new RPCError('signrawtransaction' + + ' "hexstring" (' + + ' [{"txid":"id","vout":n,"scriptPubKey":"hex",' + + 'redeemScript":"hex"},...] ["privatekey1",...]' + + ' sighashtype )'); + } - if (!utils.isHex(args[0])) - throw new RPCError('Invalid parameter'); + if (!utils.isHex(args[0])) + throw new RPCError('Invalid parameter'); - raw = new Buffer(args[0], 'hex'); - p = new bcoin.reader(raw); - txs = []; + raw = new Buffer(args[0], 'hex'); + p = new bcoin.reader(raw); + txs = []; - while (p.left()) - txs.push(bcoin.mtx.fromRaw(p)); + while (p.left()) + txs.push(bcoin.mtx.fromRaw(p)); - merged = txs[0]; + merged = txs[0]; - yield this._fillCoins(merged); - yield this.wallet.fillCoins(merged); + yield this._fillCoins(merged); + yield this.wallet.fillCoins(merged); - return yield this._signrawtransaction(merged, txs, args); - }, this); -}; + return yield this._signrawtransaction(merged, txs, args); +}); RPC.prototype._fillCoins = function _fillCoins(tx) { if (this.chain.db.options.spv) @@ -2245,228 +2189,220 @@ RPC.prototype._fillCoins = function _fillCoins(tx) { return this.node.fillCoins(tx); }; -RPC.prototype._signrawtransaction = function signrawtransaction(merged, txs, args) { - return spawn(function *() { - var keys = []; - var keyMap = {}; - var k, i, secret, key; - var coins, prevout, prev; - var hash, index, script, value; - var redeem, op, j; - var type, parts, tx; +RPC.prototype._signrawtransaction = spawn.co(function* signrawtransaction(merged, txs, args) { + var keys = []; + var keyMap = {}; + var k, i, secret, key; + var coins, prevout, prev; + var hash, index, script, value; + var redeem, op, j; + var type, parts, tx; - if (args.length > 2 && Array.isArray(args[2])) { - k = args[2]; - for (i = 0; i < k.length; i++) { - secret = k[i]; + if (args.length > 2 && Array.isArray(args[2])) { + k = args[2]; + for (i = 0; i < k.length; i++) { + secret = k[i]; - if (!utils.isBase58(secret)) - throw new RPCError('Invalid parameter'); + if (!utils.isBase58(secret)) + throw new RPCError('Invalid parameter'); - key = bcoin.keyring.fromSecret(secret); - keyMap[key.getPublicKey('hex')] = key; - keys.push(key); - } + key = bcoin.keyring.fromSecret(secret); + keyMap[key.getPublicKey('hex')] = key; + keys.push(key); } + } - coins = []; - if (args.length > 1 && Array.isArray(args[1])) { - prevout = args[1]; + coins = []; + if (args.length > 1 && Array.isArray(args[1])) { + prevout = args[1]; - for (i = 0; i < prevout.length; i++) { - prev = prevout[i]; + for (i = 0; i < prevout.length; i++) { + prev = prevout[i]; - if (!prev) - throw new RPCError('Invalid parameter'); + if (!prev) + throw new RPCError('Invalid parameter'); - hash = toHash(prev.txid); - index = prev.vout; - script = prev.scriptPubKey; - value = toSatoshi(prev.amount); + hash = toHash(prev.txid); + index = prev.vout; + script = prev.scriptPubKey; + value = toSatoshi(prev.amount); - if (!hash - || !utils.isNumber(index) - || index < 0 - || !utils.isHex(script)) { - throw new RPCError('Invalid parameter'); - } + if (!hash + || !utils.isNumber(index) + || index < 0 + || !utils.isHex(script)) { + throw new RPCError('Invalid parameter'); + } - script = bcoin.script.fromRaw(script, 'hex'); - coins.push(new bcoin.coin({ - hash: utils.revHex(hash), - index: index, - script: script, - value: value, - coinbase: false, - height: -1 - })); + script = bcoin.script.fromRaw(script, 'hex'); + coins.push(new bcoin.coin({ + hash: utils.revHex(hash), + index: index, + script: script, + value: value, + coinbase: false, + height: -1 + })); - if (keys.length === 0 || !utils.isHex(prev.redeemScript)) - continue; + if (keys.length === 0 || !utils.isHex(prev.redeemScript)) + continue; - if (script.isScripthash() || script.isWitnessScripthash()) { - redeem = bcoin.script.fromRaw(prev.redeemScript, 'hex'); - for (j = 0; j < redeem.length; j++) { - op = redeem.get(j); + if (script.isScripthash() || script.isWitnessScripthash()) { + redeem = bcoin.script.fromRaw(prev.redeemScript, 'hex'); + for (j = 0; j < redeem.length; j++) { + op = redeem.get(j); - if (!Buffer.isBuffer(op)) - continue; + if (!Buffer.isBuffer(op)) + continue; - key = keyMap[op.toString('hex')]; - if (key) { - key.script = redeem; - key.witness = script.isWitnessScripthash(); - break; - } + key = keyMap[op.toString('hex')]; + if (key) { + key.script = redeem; + key.witness = script.isWitnessScripthash(); + break; } } } - - tx.fillCoins(coins); } - type = constants.hashType.ALL; - if (args.length > 3 && typeof args[3] === 'string') { - parts = args[3].split('|'); - type = constants.hashType[parts[0]]; - if (type == null) + tx.fillCoins(coins); + } + + type = constants.hashType.ALL; + if (args.length > 3 && typeof args[3] === 'string') { + parts = args[3].split('|'); + type = constants.hashType[parts[0]]; + if (type == null) + throw new RPCError('Invalid parameter'); + if (parts.length > 2) + throw new RPCError('Invalid parameter'); + if (parts.length === 2) { + if (parts[1] !== 'ANYONECANPAY') throw new RPCError('Invalid parameter'); - if (parts.length > 2) - throw new RPCError('Invalid parameter'); - if (parts.length === 2) { - if (parts[1] !== 'ANYONECANPAY') - throw new RPCError('Invalid parameter'); - type |= constants.hashType.ANYONECANPAY; - } + type |= constants.hashType.ANYONECANPAY; } + } - for (i = 0; i < keys.length; i++) { - key = keys[i]; - merged.sign(key, type); - } + for (i = 0; i < keys.length; i++) { + key = keys[i]; + merged.sign(key, type); + } - yield this.wallet.sign(merged, { type: type }); + yield this.wallet.sign(merged, { type: type }); - // TODO: Merge with other txs here. + // TODO: Merge with other txs here. - return { - hex: merged.toRaw().toString('hex'), - complete: merged.isSigned() - }; - }, this); -}; + return { + hex: merged.toRaw().toString('hex'), + complete: merged.isSigned() + }; +}); -RPC.prototype.fundrawtransaction = function fundrawtransaction(args) { - return spawn(function *() { - var tx, options, changeAddress, feeRate; +RPC.prototype.fundrawtransaction = spawn.co(function* fundrawtransaction(args) { + var tx, options, changeAddress, feeRate; - if (args.help || args.length < 1 || args.length > 2) - throw new RPCError('fundrawtransaction "hexstring" ( options )'); + if (args.help || args.length < 1 || args.length > 2) + throw new RPCError('fundrawtransaction "hexstring" ( options )'); - tx = bcoin.mtx.fromRaw(toString(args[0]), 'hex'); + tx = bcoin.mtx.fromRaw(toString(args[0]), 'hex'); - if (tx.outputs.length === 0) - throw new RPCError('TX must have at least one output.'); + if (tx.outputs.length === 0) + throw new RPCError('TX must have at least one output.'); - if (args.length === 2 && args[1]) { - options = args[1]; - changeAddress = toString(options.changeAddress); + if (args.length === 2 && args[1]) { + options = args[1]; + changeAddress = toString(options.changeAddress); - if (changeAddress) - changeAddress = bcoin.address.fromBase58(changeAddress); + if (changeAddress) + changeAddress = bcoin.address.fromBase58(changeAddress); - feeRate = options.feeRate; + feeRate = options.feeRate; - if (feeRate != null) - feeRate = toSatoshi(feeRate); - } + if (feeRate != null) + feeRate = toSatoshi(feeRate); + } - options = { - rate: feeRate, - changeAddress: changeAddress - }; + options = { + rate: feeRate, + changeAddress: changeAddress + }; - yield this.wallet.fund(tx, options); + yield this.wallet.fund(tx, options); - return { - hex: tx.toRaw().toString('hex'), - changepos: tx.changeIndex, - fee: +utils.btc(tx.getFee()) - }; - }, this); -}; + return { + hex: tx.toRaw().toString('hex'), + changepos: tx.changeIndex, + fee: +utils.btc(tx.getFee()) + }; +}); -RPC.prototype._createRedeem = function _createRedeem(args) { - return spawn(function *() { - var i, m, n, keys, hash, script, key, ring; +RPC.prototype._createRedeem = spawn.co(function* _createRedeem(args) { + var i, m, n, keys, hash, script, key, ring; - if (!utils.isNumber(args[0]) - || !Array.isArray(args[1]) - || args[0] < 1 - || args[1].length < args[0] - || args[1].length > 16) { - throw new RPCError('Invalid parameter.'); - } + if (!utils.isNumber(args[0]) + || !Array.isArray(args[1]) + || args[0] < 1 + || args[1].length < args[0] + || args[1].length > 16) { + throw new RPCError('Invalid parameter.'); + } - m = args[0]; - n = args[1].length; - keys = args[1]; + m = args[0]; + n = args[1].length; + keys = args[1]; - for (i = 0; i < keys.length; i++) { - key = keys[i]; + for (i = 0; i < keys.length; i++) { + key = keys[i]; - if (!utils.isBase58(key)) { - if (!utils.isHex(key)) - throw new RPCError('Invalid key.'); - keys[i] = new Buffer(key, 'hex'); - continue; - } - - hash = bcoin.address.getHash(key, 'hex'); - - if (!hash) + if (!utils.isBase58(key)) { + if (!utils.isHex(key)) throw new RPCError('Invalid key.'); - - ring = yield this.node.wallet.getKeyRing(hash); - - if (!ring) - throw new RPCError('Invalid key.'); - - keys[i] = ring.publicKey; + keys[i] = new Buffer(key, 'hex'); + continue; } - try { - script = bcoin.script.fromMultisig(m, n, keys); - } catch (e) { - throw new RPCError('Invalid parameters.'); - } + hash = bcoin.address.getHash(key, 'hex'); - if (script.toRaw().length > constants.script.MAX_PUSH) - throw new RPCError('Redeem script exceeds size limit.'); + if (!hash) + throw new RPCError('Invalid key.'); - return script; - }, this); -}; + ring = yield this.node.wallet.getKeyRing(hash); + + if (!ring) + throw new RPCError('Invalid key.'); + + keys[i] = ring.publicKey; + } + + try { + script = bcoin.script.fromMultisig(m, n, keys); + } catch (e) { + throw new RPCError('Invalid parameters.'); + } + + if (script.toRaw().length > constants.script.MAX_PUSH) + throw new RPCError('Redeem script exceeds size limit.'); + + return script; +}); /* - * Utility functions + * Utility spawn.co(function* s */ RPC.prototype.createmultisig = function createmultisig(args) { - return spawn(function *() { - var script; + var script; - if (args.help || args.length < 2 || args.length > 2) - throw new RPCError('createmultisig nrequired ["key",...]'); + if (args.help || args.length < 2 || args.length > 2) + throw new RPCError('createmultisig nrequired ["key",...]'); - script = yield this._createRedeem(args); + script = yield this._createRedeem(args); - return { - address: script.getAddress().toBase58(this.network), - redeemScript: script.toJSON() - }; - }, this); -}; + return { + address: script.getAddress().toBase58(this.network), + redeemScript: script.toJSON() + }; +}); RPC.prototype._scriptForWitness = function scriptForWitness(script) { var hash; @@ -2501,42 +2437,40 @@ RPC.prototype.createwitnessaddress = function createwitnessaddress(args) { }); }; -RPC.prototype.validateaddress = function validateaddress(args) { - return spawn(function *() { - var b58, address, json, path; +RPC.prototype.validateaddress = spawn.co(function* validateaddress(args) { + var b58, address, json, path; - if (args.help || args.length !== 1) - throw new RPCError('validateaddress "bitcoinaddress"'); + if (args.help || args.length !== 1) + throw new RPCError('validateaddress "bitcoinaddress"'); - b58 = toString(args[0]); + b58 = toString(args[0]); - try { - address = bcoin.address.fromBase58(b58); - } catch (e) { - return { - isvalid: false - }; - } - - path = yield this.wallet.getPath(address.getHash('hex')); - - json = { - isvalid: true, - address: address.toBase58(this.network), - scriptPubKey: address.toScript().toJSON(), - ismine: path ? true : false, - iswatchonly: false + try { + address = bcoin.address.fromBase58(b58); + } catch (e) { + return { + isvalid: false }; + } - if (!path) - return json; + path = yield this.wallet.getPath(address.getHash('hex')); - json.account = path.name; - json.hdkeypath = path.toPath(); + json = { + isvalid: true, + address: address.toBase58(this.network), + scriptPubKey: address.toScript().toJSON(), + ismine: path ? true : false, + iswatchonly: false + }; + if (!path) return json; - }, this); -}; + + json.account = path.name; + json.hdkeypath = path.toPath(); + + return json; +}); RPC.magic = 'Bitcoin Signed Message:\n'; @@ -2736,24 +2670,22 @@ RPC.prototype.setmocktime = function setmocktime(args) { * Wallet */ -RPC.prototype.resendwallettransactions = function resendwallettransactions(args) { - return spawn(function *() { - var hashes = []; - var i, tx, txs; +RPC.prototype.resendwallettransactions = spawn.co(function* resendwallettransactions(args) { + var hashes = []; + var i, tx, txs; - if (args.help || args.length !== 0) - throw new RPCError('resendwallettransactions'); + if (args.help || args.length !== 0) + throw new RPCError('resendwallettransactions'); - txs = yield this.wallet.resend(); + txs = yield this.wallet.resend(); - for (i = 0; i < txs.length; i++) { - tx = txs[i]; - hashes.push(tx.rhash); - } + for (i = 0; i < txs.length; i++) { + tx = txs[i]; + hashes.push(tx.rhash); + } - return hashes; - }, this); -}; + return hashes; +}); RPC.prototype.addmultisigaddress = function addmultisigaddress(args) { if (args.help || args.length < 2 || args.length > 3) { @@ -2771,591 +2703,553 @@ RPC.prototype.addwitnessaddress = function addwitnessaddress(args) { Promise.reject(new Error('Not implemented.')); }; -RPC.prototype.backupwallet = function backupwallet(args) { - return spawn(function *() { - var dest; +RPC.prototype.backupwallet = spawn.co(function* backupwallet(args) { + var dest; - if (args.help || args.length !== 1) - throw new RPCError('backupwallet "destination"'); + if (args.help || args.length !== 1) + throw new RPCError('backupwallet "destination"'); - dest = toString(args[0]); + dest = toString(args[0]); - yield this.walletdb.backup(dest); - return null; - }, this); -}; + yield this.walletdb.backup(dest); + return null; +}); -RPC.prototype.dumpprivkey = function dumpprivkey(args) { - return spawn(function *() { - var hash, ring; +RPC.prototype.dumpprivkey = spawn.co(function* dumpprivkey(args) { + var hash, ring; - if (args.help || args.length !== 1) - throw new RPCError('dumpprivkey "bitcoinaddress"'); + if (args.help || args.length !== 1) + throw new RPCError('dumpprivkey "bitcoinaddress"'); - hash = bcoin.address.getHash(toString(args[0]), 'hex'); + hash = bcoin.address.getHash(toString(args[0]), 'hex'); - if (!hash) - throw new RPCError('Invalid address.'); + if (!hash) + throw new RPCError('Invalid address.'); + ring = yield this.wallet.getKeyRing(hash); + + if (!ring) + throw new RPCError('Key not found.'); + + if (!this.wallet.master.key) + throw new RPCError('Wallet is locked.'); + + return ring.toSecret(); +}); + +RPC.prototype.dumpwallet = spawn.co(function* dumpwallet(args) { + var i, file, time, address, fmt, str, out, hash, hashes, ring; + + if (args.help || args.length !== 1) + throw new RPCError('dumpwallet "filename"'); + + if (!args[0] || typeof args[0] !== 'string') + throw new RPCError('Invalid parameter.'); + + file = toString(args[0]); + time = utils.date(); + out = [ + utils.fmt('# Wallet Dump created by BCoin %s', constants.USER_VERSION), + utils.fmt('# * Created on %s', time), + utils.fmt('# * Best block at time of backup was %d (%s),', + this.chain.height, this.chain.tip.rhash), + utils.fmt('# mined on %s', utils.date(this.chain.tip.ts)), + utils.fmt('# * File: %s', file), + '' + ]; + + hashes = yield this.wallet.getAddressHashes(); + + for (i = 0; i < hashes.length; i++) { + hash = hashes[i]; ring = yield this.wallet.getKeyRing(hash); if (!ring) - throw new RPCError('Key not found.'); + continue; if (!this.wallet.master.key) throw new RPCError('Wallet is locked.'); - return ring.toSecret(); - }, this); -}; + address = ring.getAddress('base58'); + fmt = '%s %s label= addr=%s'; -RPC.prototype.dumpwallet = function dumpwallet(args) { - return spawn(function *() { - var i, file, time, address, fmt, str, out, hash, hashes, ring; + if (ring.change) + fmt = '%s %s change=1 addr=%s'; - if (args.help || args.length !== 1) - throw new RPCError('dumpwallet "filename"'); + str = utils.fmt(fmt, ring.toSecret(), time, address); - if (!args[0] || typeof args[0] !== 'string') - throw new RPCError('Invalid parameter.'); + out.push(str); + } - file = toString(args[0]); - time = utils.date(); - out = [ - utils.fmt('# Wallet Dump created by BCoin %s', constants.USER_VERSION), - utils.fmt('# * Created on %s', time), - utils.fmt('# * Best block at time of backup was %d (%s),', - this.chain.height, this.chain.tip.rhash), - utils.fmt('# mined on %s', utils.date(this.chain.tip.ts)), - utils.fmt('# * File: %s', file), - '' - ]; + out.push(''); + out.push('# End of dump'); + out.push(''); - hashes = yield this.wallet.getAddressHashes(); - - for (i = 0; i < hashes.length; i++) { - hash = hashes[i]; - ring = yield this.wallet.getKeyRing(hash); - - if (!ring) - continue; - - if (!this.wallet.master.key) - throw new RPCError('Wallet is locked.'); - - address = ring.getAddress('base58'); - fmt = '%s %s label= addr=%s'; - - if (ring.change) - fmt = '%s %s change=1 addr=%s'; - - str = utils.fmt(fmt, ring.toSecret(), time, address); - - out.push(str); - } - - out.push(''); - out.push('# End of dump'); - out.push(''); - - out = out.join('\n'); - - if (!fs) - return out; - - yield writeFile(file, out); + out = out.join('\n'); + if (!fs) return out; - }, this); -}; -RPC.prototype.encryptwallet = function encryptwallet(args) { - return spawn(function *() { - var passphrase; + yield writeFile(file, out); - if (!this.wallet.master.encrypted && (args.help || args.help !== 1)) - throw new RPCError('encryptwallet "passphrase"'); + return out; +}); - if (this.wallet.master.encrypted) - throw new RPCError('Already running with an encrypted wallet'); +RPC.prototype.encryptwallet = spawn.co(function* encryptwallet(args) { + var passphrase; - passphrase = toString(args[0]); + if (!this.wallet.master.encrypted && (args.help || args.help !== 1)) + throw new RPCError('encryptwallet "passphrase"'); - if (passphrase.length < 1) - throw new RPCError('encryptwallet "passphrase"'); + if (this.wallet.master.encrypted) + throw new RPCError('Already running with an encrypted wallet'); - yield this.wallet.setPassphrase(passphrase); + passphrase = toString(args[0]); - return 'wallet encrypted; we do not need to stop!'; - }, this); -}; + if (passphrase.length < 1) + throw new RPCError('encryptwallet "passphrase"'); -RPC.prototype.getaccountaddress = function getaccountaddress(args) { - return spawn(function *() { - var account; + yield this.wallet.setPassphrase(passphrase); - if (args.help || args.length !== 1) - throw new RPCError('getaccountaddress "account"'); + return 'wallet encrypted; we do not need to stop!'; +}); +RPC.prototype.getaccountaddress = spawn.co(function* getaccountaddress(args) { + var account; + + if (args.help || args.length !== 1) + throw new RPCError('getaccountaddress "account"'); + + account = toString(args[0]); + + if (!account) + account = 'default'; + + account = yield this.wallet.getAccount(account); + + if (!account) + return ''; + + return account.receiveAddress.getAddress('base58'); +}); + +RPC.prototype.getaccount = spawn.co(function* getaccount(args) { + var hash, path; + + if (args.help || args.length !== 1) + throw new RPCError('getaccount "bitcoinaddress"'); + + hash = bcoin.address.getHash(args[0], 'hex'); + + if (!hash) + throw new RPCError('Invalid address.'); + + path = yield this.wallet.getPath(hash); + + if (!path) + return ''; + + return path.name; +}); + +RPC.prototype.getaddressesbyaccount = spawn.co(function* getaddressesbyaccount(args) { + var i, path, account, addrs, paths; + + if (args.help || args.length !== 1) + throw new RPCError('getaddressesbyaccount "account"'); + + account = toString(args[0]); + + if (!account) + account = 'default'; + + addrs = []; + + paths = yield this.wallet.getPaths(account); + + for (i = 0; i < paths.length; i++) { + path = paths[i]; + addrs.push(path.toAddress().toBase58(this.network)); + } + + return addrs; +}); + +RPC.prototype.getbalance = spawn.co(function* getbalance(args) { + var minconf = 0; + var account, value, balance; + + if (args.help || args.length > 3) + throw new RPCError('getbalance ( "account" minconf includeWatchonly )'); + + if (args.length >= 1) { + account = toString(args[0]); + if (!account) + account = 'default'; + if (account === '*') + account = null; + } + + if (args.length >= 2) + minconf = toNumber(args[1], 0); + + balance = yield this.wallet.getBalance(account); + + if (minconf) + value = balance.confirmed; + else + value = balance.total; + + return +utils.btc(value); +}); + +RPC.prototype.getnewaddress = spawn.co(function* getnewaddress(args) { + var account, address; + + if (args.help || args.length > 1) + throw new RPCError('getnewaddress ( "account" )'); + + if (args.length === 1) account = toString(args[0]); - if (!account) - account = 'default'; + if (!account) + account = 'default'; - account = yield this.wallet.getAccount(account); + address = yield this.wallet.createReceive(account); - if (!account) - return ''; + return address.getAddress('base58'); +}); - return account.receiveAddress.getAddress('base58'); - }, this); -}; +RPC.prototype.getrawchangeaddress = spawn.co(function* getrawchangeaddress(args) { + var address; -RPC.prototype.getaccount = function getaccount(args) { - return spawn(function *() { - var hash, path; + if (args.help || args.length > 1) + throw new RPCError('getrawchangeaddress'); - if (args.help || args.length !== 1) - throw new RPCError('getaccount "bitcoinaddress"'); + address = yield this.wallet.createChange(); - hash = bcoin.address.getHash(args[0], 'hex'); + return address.getAddress('base58'); +}); - if (!hash) - throw new RPCError('Invalid address.'); +RPC.prototype.getreceivedbyaccount = spawn.co(function* getreceivedbyaccount(args) { + var minconf = 0; + var total = 0; + var filter = {}; + var lastConf = -1; + var i, j, path, tx, output, conf, hash, account, paths, txs; - path = yield this.wallet.getPath(hash); + if (args.help || args.length < 1 || args.length > 2) + throw new RPCError('getreceivedbyaccount "account" ( minconf )'); - if (!path) - return ''; + account = toString(args[0]); - return path.name; - }, this); -}; + if (!account) + account = 'default'; -RPC.prototype.getaddressesbyaccount = function getaddressesbyaccount(args) { - return spawn(function *() { - var i, path, account, addrs, paths; + if (args.length === 2) + minconf = toNumber(args[1], 0); - if (args.help || args.length !== 1) - throw new RPCError('getaddressesbyaccount "account"'); + paths = yield this.wallet.getPaths(account); - account = toString(args[0]); + for (i = 0; i < paths.length; i++) { + path = paths[i]; + filter[path.hash] = true; + } - if (!account) - account = 'default'; + txs = yield this.wallet.getHistory(account); - addrs = []; - - paths = yield this.wallet.getPaths(account); - - for (i = 0; i < paths.length; i++) { - path = paths[i]; - addrs.push(path.toAddress().toBase58(this.network)); - } - - return addrs; - }, this); -}; - -RPC.prototype.getbalance = function getbalance(args) { - return spawn(function *() { - var minconf = 0; - var account, value, balance; - - if (args.help || args.length > 3) - throw new RPCError('getbalance ( "account" minconf includeWatchonly )'); - - if (args.length >= 1) { - account = toString(args[0]); - if (!account) - account = 'default'; - if (account === '*') - account = null; - } - - if (args.length >= 2) - minconf = toNumber(args[1], 0); - - balance = yield this.wallet.getBalance(account); - - if (minconf) - value = balance.confirmed; - else - value = balance.total; - - return +utils.btc(value); - }, this); -}; - -RPC.prototype.getnewaddress = function getnewaddress(args) { - return spawn(function *() { - var account, address; - - if (args.help || args.length > 1) - throw new RPCError('getnewaddress ( "account" )'); - - if (args.length === 1) - account = toString(args[0]); - - if (!account) - account = 'default'; - - address = yield this.wallet.createReceive(account); - - return address.getAddress('base58'); - }, this); -}; - -RPC.prototype.getrawchangeaddress = function getrawchangeaddress(args) { - return spawn(function *() { - var address; - - if (args.help || args.length > 1) - throw new RPCError('getrawchangeaddress'); - - address = yield this.wallet.createChange(); - - return address.getAddress('base58'); - }, this); -}; - -RPC.prototype.getreceivedbyaccount = function getreceivedbyaccount(args) { - return spawn(function *() { - var minconf = 0; - var total = 0; - var filter = {}; - var lastConf = -1; - var i, j, path, tx, output, conf, hash, account, paths, txs; - - if (args.help || args.length < 1 || args.length > 2) - throw new RPCError('getreceivedbyaccount "account" ( minconf )'); - - account = toString(args[0]); - - if (!account) - account = 'default'; - - if (args.length === 2) - minconf = toNumber(args[1], 0); - - paths = yield this.wallet.getPaths(account); - - for (i = 0; i < paths.length; i++) { - path = paths[i]; - filter[path.hash] = true; - } - - txs = yield this.wallet.getHistory(account); - - for (i = 0; i < txs.length; i++) { - tx = txs[i]; - - if (minconf) { - if (tx.height === -1) - continue; - if (!(this.chain.height - tx.height + 1 >= minconf)) - continue; - } - - conf = tx.getConfirmations(this.chain.height); - - if (lastConf === -1 || conf < lastConf) - lastConf = conf; - - for (j = 0; j < tx.outputs.length; j++) { - output = tx.outputs[j]; - hash = output.getHash('hex'); - if (filter[hash]) - total += output.value; - } - } - - return +utils.btc(total); - }, this); -}; - -RPC.prototype.getreceivedbyaddress = function getreceivedbyaddress(args) { - return spawn(function *() { - var self = this; - var minconf = 0; - var total = 0; - var i, j, hash, tx, output, txs; - - if (args.help || args.length < 1 || args.length > 2) - throw new RPCError('getreceivedbyaddress "bitcoinaddress" ( minconf )'); - - hash = bcoin.address.getHash(toString(args[0]), 'hex'); - - if (!hash) - throw new RPCError('Invalid address'); - - if (args.length === 2) - minconf = toNumber(args[1], 0); - - txs = yield this.wallet.getHistory(); - - for (i = 0; i < txs.length; i++) { - tx = txs[i]; - if (minconf) { - if (tx.height === -1) - continue; - if (!(self.chain.height - tx.height + 1 >= minconf)) - continue; - } - for (j = 0; j < tx.outputs.length; j++) { - output = tx.outputs[j]; - if (output.getHash('hex') === hash) - total += output.value; - } - } - - return +utils.btc(total); - }, this); -}; - -RPC.prototype._toWalletTX = function _toWalletTX(tx) { - return spawn(function *() { - var i, det, receive, member, sent, received, json, details; - - details = yield this.wallet.toDetails(tx); - - if (!details) - throw new RPCError('TX not found.'); - - det = []; - sent = 0; - received = 0; - receive = true; - - for (i = 0; i < details.inputs.length; i++) { - member = details.inputs[i]; - if (member.path) { - receive = false; - break; - } - } - - for (i = 0; i < details.outputs.length; i++) { - member = details.outputs[i]; - - if (member.path) { - if (member.path.change === 1) - continue; - - det.push({ - account: member.path.name, - address: member.address.toBase58(this.network), - category: 'receive', - amount: +utils.btc(member.value), - label: member.path.name, - vout: i - }); - - received += member.value; + for (i = 0; i < txs.length; i++) { + tx = txs[i]; + if (minconf) { + if (tx.height === -1) continue; - } + if (!(this.chain.height - tx.height + 1 >= minconf)) + continue; + } - if (receive) + conf = tx.getConfirmations(this.chain.height); + + if (lastConf === -1 || conf < lastConf) + lastConf = conf; + + for (j = 0; j < tx.outputs.length; j++) { + output = tx.outputs[j]; + hash = output.getHash('hex'); + if (filter[hash]) + total += output.value; + } + } + + return +utils.btc(total); +}); + +RPC.prototype.getreceivedbyaddress = spawn.co(function* getreceivedbyaddress(args) { + var self = this; + var minconf = 0; + var total = 0; + var i, j, hash, tx, output, txs; + + if (args.help || args.length < 1 || args.length > 2) + throw new RPCError('getreceivedbyaddress "bitcoinaddress" ( minconf )'); + + hash = bcoin.address.getHash(toString(args[0]), 'hex'); + + if (!hash) + throw new RPCError('Invalid address'); + + if (args.length === 2) + minconf = toNumber(args[1], 0); + + txs = yield this.wallet.getHistory(); + + for (i = 0; i < txs.length; i++) { + tx = txs[i]; + if (minconf) { + if (tx.height === -1) + continue; + if (!(self.chain.height - tx.height + 1 >= minconf)) + continue; + } + for (j = 0; j < tx.outputs.length; j++) { + output = tx.outputs[j]; + if (output.getHash('hex') === hash) + total += output.value; + } + } + + return +utils.btc(total); +}); + +RPC.prototype._toWalletTX = spawn.co(function* _toWalletTX(tx) { + var i, det, receive, member, sent, received, json, details; + + details = yield this.wallet.toDetails(tx); + + if (!details) + throw new RPCError('TX not found.'); + + det = []; + sent = 0; + received = 0; + receive = true; + + for (i = 0; i < details.inputs.length; i++) { + member = details.inputs[i]; + if (member.path) { + receive = false; + break; + } + } + + for (i = 0; i < details.outputs.length; i++) { + member = details.outputs[i]; + + if (member.path) { + if (member.path.change === 1) continue; det.push({ - account: '', - address: member.address - ? member.address.toBase58(this.network) - : null, - category: 'send', - amount: -(+utils.btc(member.value)), - fee: -(+utils.btc(details.fee)), + account: member.path.name, + address: member.address.toBase58(this.network), + category: 'receive', + amount: +utils.btc(member.value), + label: member.path.name, vout: i }); - sent += member.value; + received += member.value; + + continue; } - json = { - amount: +utils.btc(receive ? received : -sent), - confirmations: details.confirmations, - blockhash: details.block ? utils.revHex(details.block) : null, - blockindex: details.index, - blocktime: details.ts, - txid: utils.revHex(details.hash), - walletconflicts: [], - time: details.ps, - timereceived: details.ps, - 'bip125-replaceable': 'no', - details: det, - hex: details.tx.toRaw().toString('hex') - }; + if (receive) + continue; - return json; - }, this); -}; + det.push({ + account: '', + address: member.address + ? member.address.toBase58(this.network) + : null, + category: 'send', + amount: -(+utils.btc(member.value)), + fee: -(+utils.btc(details.fee)), + vout: i + }); -RPC.prototype.gettransaction = function gettransaction(args) { - return spawn(function *() { - var hash, tx; + sent += member.value; + } - if (args.help || args.length < 1 || args.length > 2) - throw new RPCError('gettransaction "txid" ( includeWatchonly )'); + json = { + amount: +utils.btc(receive ? received : -sent), + confirmations: details.confirmations, + blockhash: details.block ? utils.revHex(details.block) : null, + blockindex: details.index, + blocktime: details.ts, + txid: utils.revHex(details.hash), + walletconflicts: [], + time: details.ps, + timereceived: details.ps, + 'bip125-replaceable': 'no', + details: det, + hex: details.tx.toRaw().toString('hex') + }; - hash = toHash(args[0]); + return json; +}); - if (!hash) - throw new RPCError('Invalid parameter'); +RPC.prototype.gettransaction = spawn.co(function* gettransaction(args) { + var hash, tx; - tx = yield this.wallet.getTX(hash); + if (args.help || args.length < 1 || args.length > 2) + throw new RPCError('gettransaction "txid" ( includeWatchonly )'); - if (!tx) - throw new RPCError('TX not found.'); + hash = toHash(args[0]); - return yield this._toWalletTX(tx); - }, this); -}; + if (!hash) + throw new RPCError('Invalid parameter'); -RPC.prototype.abandontransaction = function abandontransaction(args) { - return spawn(function *() { - var hash, result; + tx = yield this.wallet.getTX(hash); - if (args.help || args.length !== 1) - throw new RPCError('abandontransaction "txid"'); + if (!tx) + throw new RPCError('TX not found.'); - hash = toHash(args[0]); + return yield this._toWalletTX(tx); +}); - if (!hash) - throw new RPCError('Invalid parameter.'); +RPC.prototype.abandontransaction = spawn.co(function* abandontransaction(args) { + var hash, result; - result = yield this.wallet.abandon(hash); + if (args.help || args.length !== 1) + throw new RPCError('abandontransaction "txid"'); - if (!result) - throw new RPCError('Transaction not in wallet.'); + hash = toHash(args[0]); + if (!hash) + throw new RPCError('Invalid parameter.'); + + result = yield this.wallet.abandon(hash); + + if (!result) + throw new RPCError('Transaction not in wallet.'); + + return null; +}); + +RPC.prototype.getunconfirmedbalance = spawn.co(function* getunconfirmedbalance(args) { + var balance; + + if (args.help || args.length > 0) + throw new RPCError('getunconfirmedbalance'); + + balance = yield this.wallet.getBalance(); + + return +utils.btc(balance.unconfirmed); +}); + +RPC.prototype.getwalletinfo = spawn.co(function* getwalletinfo(args) { + var balance, hashes; + + if (args.help || args.length !== 0) + throw new RPCError('getwalletinfo'); + + balance = yield this.wallet.getBalance(); + + hashes = yield this.wallet.tx.getHistoryHashes(this.wallet.id); + + return { + walletversion: 0, + balance: +utils.btc(balance.total), + unconfirmed_balance: +utils.btc(balance.unconfirmed), + txcount: hashes.length, + keypoololdest: 0, + keypoolsize: 0, + unlocked_until: this.wallet.master.until, + paytxfee: this.feeRate != null + ? +utils.btc(this.feeRate) + : +utils.btc(0) + }; +}); + +RPC.prototype.importprivkey = spawn.co(function* importprivkey(args) { + var secret, label, rescan, key; + + if (args.help || args.length < 1 || args.length > 3) + throw new RPCError('importprivkey "bitcoinprivkey" ( "label" rescan )'); + + secret = toString(args[0]); + + if (args.length > 1) + label = toString(args[1]); + + if (args.length > 2) + rescan = toBool(args[2]); + + if (rescan && this.chain.db.options.prune) + throw new RPCError('Cannot rescan when pruned.'); + + key = bcoin.keyring.fromSecret(secret); + + yield this.wallet.importKey(0, key, null); + + if (!rescan) return null; - }, this); -}; -RPC.prototype.getunconfirmedbalance = function getunconfirmedbalance(args) { - return spawn(function *() { - var balance; + yield this.walletdb.rescan(this.chain.db, 0); - if (args.help || args.length > 0) - throw new RPCError('getunconfirmedbalance'); + return null; +}); - balance = yield this.wallet.getBalance(); +RPC.prototype.importwallet = spawn.co(function* importwallet(args) { + var file, keys, lines, line, parts; + var i, secret, time, label, addr; + var data, key; - return +utils.btc(balance.unconfirmed); - }, this); -}; + if (args.help || args.length !== 1) + throw new RPCError('importwallet "filename"'); -RPC.prototype.getwalletinfo = function getwalletinfo(args) { - return spawn(function *() { - var balance, hashes; + file = toString(args[0]); - if (args.help || args.length !== 0) - throw new RPCError('getwalletinfo'); + if (!fs) + throw new RPCError('FS not available.'); - balance = yield this.wallet.getBalance(); + data = yield readFile(file, 'utf8'); - hashes = yield this.wallet.tx.getHistoryHashes(this.wallet.id); + lines = data.split(/\n+/); + keys = []; - return { - walletversion: 0, - balance: +utils.btc(balance.total), - unconfirmed_balance: +utils.btc(balance.unconfirmed), - txcount: hashes.length, - keypoololdest: 0, - keypoolsize: 0, - unlocked_until: this.wallet.master.until, - paytxfee: this.feeRate != null - ? +utils.btc(this.feeRate) - : +utils.btc(0) - }; - }, this); -}; + for (i = 0; i < lines.length; i++) { + line = lines[i].trim(); -RPC.prototype.importprivkey = function importprivkey(args) { - return spawn(function *() { - var secret, label, rescan, key; + if (line.length === 0) + continue; - if (args.help || args.length < 1 || args.length > 3) - throw new RPCError('importprivkey "bitcoinprivkey" ( "label" rescan )'); + if (/^\s*#/.test(line)) + continue; - secret = toString(args[0]); + parts = line.split(/\s+/); - if (args.length > 1) - label = toString(args[1]); + if (parts.length < 4) + throw new RPCError('Malformed wallet.'); - if (args.length > 2) - rescan = toBool(args[2]); + secret = bcoin.keyring.fromSecret(parts[0]); - if (rescan && this.chain.db.options.prune) - throw new RPCError('Cannot rescan when pruned.'); + time = +parts[1]; + label = parts[2]; + addr = parts[3]; - key = bcoin.keyring.fromSecret(secret); + keys.push(secret); + } + for (i = 0; i < keys.length; i++) { + key = keys[i]; yield this.wallet.importKey(0, key, null); + } - if (!rescan) - return null; + yield this.walletdb.rescan(this.chain.db, 0); - yield this.walletdb.rescan(this.chain.db, 0); - - return null; - }, this); -}; - -RPC.prototype.importwallet = function importwallet(args) { - return spawn(function *() { - var file, keys, lines, line, parts; - var i, secret, time, label, addr; - var data, key; - - if (args.help || args.length !== 1) - throw new RPCError('importwallet "filename"'); - - file = toString(args[0]); - - if (!fs) - throw new RPCError('FS not available.'); - - data = yield readFile(file, 'utf8'); - - lines = data.split(/\n+/); - keys = []; - - for (i = 0; i < lines.length; i++) { - line = lines[i].trim(); - - if (line.length === 0) - continue; - - if (/^\s*#/.test(line)) - continue; - - parts = line.split(/\s+/); - - if (parts.length < 4) - throw new RPCError('Malformed wallet.'); - - secret = bcoin.keyring.fromSecret(parts[0]); - - time = +parts[1]; - label = parts[2]; - addr = parts[3]; - - keys.push(secret); - } - - for (i = 0; i < keys.length; i++) { - key = keys[i]; - yield this.wallet.importKey(0, key, null); - } - - yield this.walletdb.rescan(this.chain.db, 0); - - return null; - }, this); -}; + return null; +}); RPC.prototype.importaddress = function importaddress(args) { if (args.help || args.length < 1 || args.length > 4) { @@ -3366,41 +3260,39 @@ RPC.prototype.importaddress = function importaddress(args) { return Promise.reject(new Error('Not implemented.')); }; -RPC.prototype.importpubkey = function importpubkey(args) { - return spawn(function *() { - var pubkey, label, rescan, key; +RPC.prototype.importpubkey = spawn.co(function* importpubkey(args) { + var pubkey, label, rescan, key; - if (args.help || args.length < 1 || args.length > 4) - throw new RPCError('importpubkey "pubkey" ( "label" rescan )'); + if (args.help || args.length < 1 || args.length > 4) + throw new RPCError('importpubkey "pubkey" ( "label" rescan )'); - pubkey = toString(args[0]); + pubkey = toString(args[0]); - if (!utils.isHex(pubkey)) - throw new RPCError('Invalid paremeter.'); + if (!utils.isHex(pubkey)) + throw new RPCError('Invalid paremeter.'); - if (args.length > 1) - label = toString(args[1]); + if (args.length > 1) + label = toString(args[1]); - if (args.length > 2) - rescan = toBool(args[2]); + if (args.length > 2) + rescan = toBool(args[2]); - if (rescan && this.chain.db.options.prune) - throw new RPCError('Cannot rescan when pruned.'); + if (rescan && this.chain.db.options.prune) + throw new RPCError('Cannot rescan when pruned.'); - pubkey = new Buffer(pubkey, 'hex'); + pubkey = new Buffer(pubkey, 'hex'); - key = bcoin.keyring.fromPublic(pubkey, this.network); + key = bcoin.keyring.fromPublic(pubkey, this.network); - yield this.wallet.importKey(0, key, null); - - if (!rescan) - return null; - - yield this.walletdb.rescan(this.chain.db, 0); + yield this.wallet.importKey(0, key, null); + if (!rescan) return null; - }, this); -}; + + yield this.walletdb.rescan(this.chain.db, 0); + + return null; +}); RPC.prototype.keypoolrefill = function keypoolrefill(args) { if (args.help || args.length > 1) @@ -3408,25 +3300,23 @@ RPC.prototype.keypoolrefill = function keypoolrefill(args) { return Promise.resolve(null); }; -RPC.prototype.listaccounts = function listaccounts(args) { - return spawn(function *() { - var i, map, accounts, account, balance; +RPC.prototype.listaccounts = spawn.co(function* listaccounts(args) { + var i, map, accounts, account, balance; - if (args.help || args.length > 2) - throw new RPCError('listaccounts ( minconf includeWatchonly)'); + if (args.help || args.length > 2) + throw new RPCError('listaccounts ( minconf includeWatchonly)'); - map = {}; - accounts = yield this.wallet.getAccounts(); + map = {}; + accounts = yield this.wallet.getAccounts(); - for (i = 0; i < accounts.length; i++) { - account = accounts[i]; - balance = yield this.wallet.getBalance(account); - map[account] = +utils.btc(balance.total); - } + for (i = 0; i < accounts.length; i++) { + account = accounts[i]; + balance = yield this.wallet.getBalance(account); + map[account] = +utils.btc(balance.total); + } - return map; - }, this); -}; + return map; +}); RPC.prototype.listaddressgroupings = function listaddressgroupings(args) { if (args.help) @@ -3490,355 +3380,345 @@ RPC.prototype.listreceivedbyaddress = function listreceivedbyaddress(args) { return this._listReceived(minconf, includeEmpty, false); }; -RPC.prototype._listReceived = function _listReceived(minconf, empty, account) { - return spawn(function *() { - var out = []; - var result = []; - var map = {}; - var i, j, path, tx, output, conf, hash; - var entry, address, keys, key, item, paths, txs; +RPC.prototype._listReceived = spawn.co(function* _listReceived(minconf, empty, account) { + var out = []; + var result = []; + var map = {}; + var i, j, path, tx, output, conf, hash; + var entry, address, keys, key, item, paths, txs; - paths = yield this.wallet.getPaths(); + paths = yield this.wallet.getPaths(); - for (i = 0; i < paths.length; i++) { - path = paths[i]; - map[path.hash] = { - involvesWatchonly: false, - address: path.toAddress().toBase58(this.network), - account: path.name, - amount: 0, - confirmations: -1, - label: '', - }; + for (i = 0; i < paths.length; i++) { + path = paths[i]; + map[path.hash] = { + involvesWatchonly: false, + address: path.toAddress().toBase58(this.network), + account: path.name, + amount: 0, + confirmations: -1, + label: '', + }; + } + + txs = yield this.wallet.getHistory(); + + for (i = 0; i < txs.length; i++) { + tx = txs[i]; + + if (minconf) { + if (tx.height === -1) + continue; + if (!(this.chain.height - tx.height + 1 >= minconf)) + continue; } - txs = yield this.wallet.getHistory(); + conf = tx.getConfirmations(this.chain.height); - for (i = 0; i < txs.length; i++) { - tx = txs[i]; - - if (minconf) { - if (tx.height === -1) - continue; - if (!(this.chain.height - tx.height + 1 >= minconf)) - continue; - } - - conf = tx.getConfirmations(this.chain.height); - - for (j = 0; j < tx.outputs.length; j++) { - output = tx.outputs[j]; - address = output.getAddress(); - if (!address) - continue; - hash = address.getHash('hex'); - entry = map[hash]; - if (entry) { - if (entry.confirmations === -1 || conf < entry.confirmations) - entry.confirmations = conf; - entry.address = address.toBase58(this.network); - entry.amount += output.value; - } + for (j = 0; j < tx.outputs.length; j++) { + output = tx.outputs[j]; + address = output.getAddress(); + if (!address) + continue; + hash = address.getHash('hex'); + entry = map[hash]; + if (entry) { + if (entry.confirmations === -1 || conf < entry.confirmations) + entry.confirmations = conf; + entry.address = address.toBase58(this.network); + entry.amount += output.value; } } + } + keys = Object.keys(map); + for (i = 0; i < keys.length; i++) { + key = keys[i]; + entry = map[key]; + out.push(entry); + } + + if (account) { + map = {}; + for (i = 0; i < out.length; i++) { + entry = out[i]; + item = map[entry.account]; + if (!item) { + map[entry.account] = entry; + entry.address = undefined; + continue; + } + item.amount += entry.amount; + } + out = []; keys = Object.keys(map); for (i = 0; i < keys.length; i++) { key = keys[i]; entry = map[key]; out.push(entry); } + } - if (account) { - map = {}; - for (i = 0; i < out.length; i++) { - entry = out[i]; - item = map[entry.account]; - if (!item) { - map[entry.account] = entry; - entry.address = undefined; - continue; - } - item.amount += entry.amount; - } - out = []; - keys = Object.keys(map); - for (i = 0; i < keys.length; i++) { - key = keys[i]; - entry = map[key]; - out.push(entry); - } + for (i = 0; i < out.length; i++) { + entry = out[i]; + if (!empty && entry.amount === 0) + continue; + if (entry.confirmations === -1) + entry.confirmations = 0; + entry.amount = +utils.btc(entry.amount); + result.push(entry); + } + + return result; +}); + +RPC.prototype.listsinceblock = spawn.co(function* listsinceblock(args) { + var block, conf, out, highest; + var i, height, txs, tx, json; + + if (args.help) { + throw new RPCError('listsinceblock' + + ' ( "blockhash" target-confirmations includeWatchonly)'); + } + + if (args.length > 0) { + block = toHash(args[0]); + if (!block) + throw new RPCError('Invalid parameter.'); + } + + conf = 0; + + if (args.length > 1) + conf = toNumber(args[1], 0); + + out = []; + + height = yield this.chain.db.getHeight(block); + + if (height === -1) + height = this.chain.height; + + txs = yield this.wallet.getHistory(); + + for (i = 0; i < txs.length; i++) { + tx = txs[i]; + + if (tx.height < height) + continue; + + if (tx.getConfirmations(this.chain.height) < conf) + continue; + + if (!highest || tx.height > highest) + highest = tx; + + json = yield this._toListTX(tx); + + out.push(json); + } + + return { + transactions: out, + lastblock: highest && highest.block + ? utils.revHex(highest.block) + : constants.NULL_HASH + }; +}); + +RPC.prototype._toListTX = spawn.co(function* _toListTX(tx) { + var i, receive, member, det, sent, received, index; + var sendMember, recMember, sendIndex, recIndex, json; + var details; + + details = yield this.wallet.toDetails(tx); + + if (!details) + throw new RPCError('TX not found.'); + + det = []; + sent = 0; + received = 0; + receive = true; + + for (i = 0; i < details.inputs.length; i++) { + member = details.inputs[i]; + if (member.path) { + receive = false; + break; } + } - for (i = 0; i < out.length; i++) { - entry = out[i]; - if (!empty && entry.amount === 0) + for (i = 0; i < details.outputs.length; i++) { + member = details.outputs[i]; + + if (member.path) { + if (member.path.change === 1) continue; - if (entry.confirmations === -1) - entry.confirmations = 0; - entry.amount = +utils.btc(entry.amount); - result.push(entry); + received += member.value; + recMember = member; + recIndex = i; + continue; } - return result; - }, this); -}; + sent += member.value; + sendMember = member; + sendIndex = i; + } -RPC.prototype.listsinceblock = function listsinceblock(args) { - return spawn(function *() { - var block, conf, out, highest; - var i, height, txs, tx, json; + if (receive) { + member = recMember; + index = recIndex; + } else { + member = sendMember; + index = sendIndex; + } - if (args.help) { - throw new RPCError('listsinceblock' - + ' ( "blockhash" target-confirmations includeWatchonly)'); - } + // In the odd case where we send to ourselves. + if (!member) { + assert(!receive); + member = recMember; + index = recIndex; + } - if (args.length > 0) { - block = toHash(args[0]); - if (!block) - throw new RPCError('Invalid parameter.'); - } + json = { + account: member.path ? member.path.name : '', + address: member.address + ? member.address.toBase58(this.network) + : null, + category: receive ? 'receive' : 'send', + amount: +utils.btc(receive ? received : -sent), + label: member.path ? member.path.name : undefined, + vout: index, + confirmations: details.confirmations, + blockhash: details.block ? utils.revHex(details.block) : null, + blockindex: details.index, + blocktime: details.ts, + txid: utils.revHex(details.hash), + walletconflicts: [], + time: details.ps, + timereceived: details.ps, + 'bip125-replaceable': 'no' + }; - conf = 0; + return json; +}); - if (args.length > 1) - conf = toNumber(args[1], 0); +RPC.prototype.listtransactions = spawn.co(function* listtransactions(args) { + var i, account, count, txs, tx, json; - out = []; + if (args.help || args.length > 4) { + throw new RPCError( + 'listtransactions ( "account" count from includeWatchonly)'); + } - height = yield this.chain.db.getHeight(block); + account = null; - if (height === -1) - height = this.chain.height; + if (args.length > 0) { + account = toString(args[0]); + if (!account) + account = 'default'; + } - txs = yield this.wallet.getHistory(); + count = 10; - for (i = 0; i < txs.length; i++) { - tx = txs[i]; - - if (tx.height < height) - continue; - - if (tx.getConfirmations(this.chain.height) < conf) - continue; - - if (!highest || tx.height > highest) - highest = tx; - - json = yield this._toListTX(tx); - - out.push(json); - } - - return { - transactions: out, - lastblock: highest && highest.block - ? utils.revHex(highest.block) - : constants.NULL_HASH - }; - }, this); -}; - -RPC.prototype._toListTX = function _toListTX(tx) { - return spawn(function *() { - var i, receive, member, det, sent, received, index; - var sendMember, recMember, sendIndex, recIndex, json; - var details; - - details = yield this.wallet.toDetails(tx); - - if (!details) - throw new RPCError('TX not found.'); - - det = []; - sent = 0; - received = 0; - receive = true; - - for (i = 0; i < details.inputs.length; i++) { - member = details.inputs[i]; - if (member.path) { - receive = false; - break; - } - } - - for (i = 0; i < details.outputs.length; i++) { - member = details.outputs[i]; - - if (member.path) { - if (member.path.change === 1) - continue; - received += member.value; - recMember = member; - recIndex = i; - continue; - } - - sent += member.value; - sendMember = member; - sendIndex = i; - } - - if (receive) { - member = recMember; - index = recIndex; - } else { - member = sendMember; - index = sendIndex; - } - - // In the odd case where we send to ourselves. - if (!member) { - assert(!receive); - member = recMember; - index = recIndex; - } - - json = { - account: member.path ? member.path.name : '', - address: member.address - ? member.address.toBase58(this.network) - : null, - category: receive ? 'receive' : 'send', - amount: +utils.btc(receive ? received : -sent), - label: member.path ? member.path.name : undefined, - vout: index, - confirmations: details.confirmations, - blockhash: details.block ? utils.revHex(details.block) : null, - blockindex: details.index, - blocktime: details.ts, - txid: utils.revHex(details.hash), - walletconflicts: [], - time: details.ps, - timereceived: details.ps, - 'bip125-replaceable': 'no' - }; - - return json; - }, this); -}; - -RPC.prototype.listtransactions = function listtransactions(args) { - return spawn(function *() { - var i, account, count, txs, tx, json; - - if (args.help || args.length > 4) { - throw new RPCError( - 'listtransactions ( "account" count from includeWatchonly)'); - } - - account = null; - - if (args.length > 0) { - account = toString(args[0]); - if (!account) - account = 'default'; - } + if (args.length > 1) + count = toNumber(args[1], 10); + if (count < 0) count = 10; - if (args.length > 1) - count = toNumber(args[1], 10); + txs = yield this.wallet.getHistory(); - if (count < 0) - count = 10; + for (i = 0; i < txs.length; i++) { + tx = txs[i]; + json = yield this._toListTX(tx); + txs[i] = json; + } - txs = yield this.wallet.getHistory(); + return txs; +}); - for (i = 0; i < txs.length; i++) { - tx = txs[i]; - json = yield this._toListTX(tx); - txs[i] = json; +RPC.prototype.listunspent = spawn.co(function* listunspent(args) { + var minDepth = 1; + var maxDepth = 9999999; + var out = []; + var i, addresses, addrs, depth, address, hash, coins, coin, ring; + + if (args.help || args.length > 3) { + throw new RPCError('listunspent' + + ' ( minconf maxconf ["address",...] )'); + } + + if (args.length > 0) + minDepth = toNumber(args[0], 1); + + if (args.length > 1) + maxDepth = toNumber(args[1], maxDepth); + + if (args.length > 2) + addrs = toArray(args[2]); + + if (addrs) { + addresses = {}; + for (i = 0; i < addrs.length; i++) { + address = toString(addrs[i]); + hash = bcoin.address.getHash(address, 'hex'); + + if (!hash) + throw new RPCError('Invalid address.'); + + if (addresses[hash]) + throw new RPCError('Duplicate address.'); + + addresses[hash] = true; } + } - return txs; - }, this); -}; + coins = yield this.wallet.getCoins(); -RPC.prototype.listunspent = function listunspent(args) { - return spawn(function *() { - var minDepth = 1; - var maxDepth = 9999999; - var out = []; - var i, addresses, addrs, depth, address, hash, coins, coin, ring; + for (i = 0; i < coins.length; i++ ) { + coin = coins[i]; - if (args.help || args.length > 3) { - throw new RPCError('listunspent' - + ' ( minconf maxconf ["address",...] )'); - } + depth = coin.height !== -1 + ? this.chain.height - coin.height + 1 + : 0; - if (args.length > 0) - minDepth = toNumber(args[0], 1); + if (!(depth >= minDepth && depth <= maxDepth)) + continue; - if (args.length > 1) - maxDepth = toNumber(args[1], maxDepth); + address = coin.getAddress(); - if (args.length > 2) - addrs = toArray(args[2]); + if (!address) + continue; - if (addrs) { - addresses = {}; - for (i = 0; i < addrs.length; i++) { - address = toString(addrs[i]); - hash = bcoin.address.getHash(address, 'hex'); + hash = coin.getHash('hex'); - if (!hash) - throw new RPCError('Invalid address.'); - - if (addresses[hash]) - throw new RPCError('Duplicate address.'); - - addresses[hash] = true; - } - } - - coins = yield this.wallet.getCoins(); - - for (i = 0; i < coins.length; i++ ) { - coin = coins[i]; - - depth = coin.height !== -1 - ? this.chain.height - coin.height + 1 - : 0; - - if (!(depth >= minDepth && depth <= maxDepth)) + if (addresses) { + if (!hash || !addresses[hash]) continue; - - address = coin.getAddress(); - - if (!address) - continue; - - hash = coin.getHash('hex'); - - if (addresses) { - if (!hash || !addresses[hash]) - continue; - } - - ring = yield this.wallet.getKeyRing(hash); - - out.push({ - txid: utils.revHex(coin.hash), - vout: coin.index, - address: address ? address.toBase58(this.network) : null, - account: ring ? ring.path.name : undefined, - redeemScript: ring && ring.script - ? ring.script.toJSON() - : undefined, - scriptPubKey: coin.script.toJSON(), - amount: +utils.btc(coin.value), - confirmations: depth, - spendable: !this.wallet.tx.isLocked(coin), - solvable: true - }); } - return out; - }, this); -}; + ring = yield this.wallet.getKeyRing(hash); + + out.push({ + txid: utils.revHex(coin.hash), + vout: coin.index, + address: address ? address.toBase58(this.network) : null, + account: ring ? ring.path.name : undefined, + redeemScript: ring && ring.script + ? ring.script.toJSON() + : undefined, + scriptPubKey: coin.script.toJSON(), + amount: +utils.btc(coin.value), + confirmations: depth, + spendable: !this.wallet.tx.isLocked(coin), + solvable: true + }); + } + + return out; +}); RPC.prototype.lockunspent = function lockunspent(args) { var i, unlock, outputs, output, outpoint; @@ -3891,25 +3771,23 @@ RPC.prototype.move = function move(args) { Promise.reject(new Error('Not implemented.')); }; -RPC.prototype._send = function _send(account, address, amount, subtractFee) { - return spawn(function *() { - var tx, options; +RPC.prototype._send = spawn.co(function* _send(account, address, amount, subtractFee) { + var tx, options; - options = { - account: account, - subtractFee: subtractFee, - rate: this.feeRate, - outputs: [{ - address: address, - value: amount - }] - }; + options = { + account: account, + subtractFee: subtractFee, + rate: this.feeRate, + outputs: [{ + address: address, + value: amount + }] + }; - tx = yield this.wallet.send(options); + tx = yield this.wallet.send(options); - return tx.rhash; - }, this); -}; + return tx.rhash; +}); RPC.prototype.sendfrom = function sendfrom(args) { var account, address, amount; @@ -3930,70 +3808,68 @@ RPC.prototype.sendfrom = function sendfrom(args) { return this._send(account, address, amount, false); }; -RPC.prototype.sendmany = function sendmany(args) { - return spawn(function *() { - var account, sendTo, minDepth, comment, subtractFee; - var i, outputs, keys, uniq, tx; - var key, value, address, hash, output, options; +RPC.prototype.sendmany = spawn.co(function* sendmany(args) { + var account, sendTo, minDepth, comment, subtractFee; + var i, outputs, keys, uniq, tx; + var key, value, address, hash, output, options; - if (args.help || args.length < 2 || args.length > 5) { - return Promise.reject(new RPCError('sendmany' - + ' "fromaccount" {"address":amount,...}' - + ' ( minconf "comment" ["address",...] )')); - } + if (args.help || args.length < 2 || args.length > 5) { + return Promise.reject(new RPCError('sendmany' + + ' "fromaccount" {"address":amount,...}' + + ' ( minconf "comment" ["address",...] )')); + } - account = toString(args[0]); - sendTo = toObject(args[1]); - minDepth = 1; + account = toString(args[0]); + sendTo = toObject(args[1]); + minDepth = 1; - if (!account) - account = 'default'; + if (!account) + account = 'default'; - if (!sendTo) + if (!sendTo) + throw new RPCError('Invalid parameter.'); + + if (args.length > 2) + minDepth = toNumber(args[2], 1); + + if (args.length > 3) + comment = toString(args[3]); + + if (args.length > 4) + subtractFee = toArray(args[4]); + + outputs = []; + keys = Object.keys(sendTo); + uniq = {}; + + for (i = 0; i < keys.length; i++) { + key = keys[i]; + value = toSatoshi(sendTo[key]); + address = bcoin.address.fromBase58(key); + hash = address.getHash('hex'); + + if (uniq[hash]) throw new RPCError('Invalid parameter.'); - if (args.length > 2) - minDepth = toNumber(args[2], 1); + uniq[hash] = true; - if (args.length > 3) - comment = toString(args[3]); + output = new bcoin.output(); + output.value = value; + output.script.fromAddress(address); + outputs.push(output); + } - if (args.length > 4) - subtractFee = toArray(args[4]); + options = { + outputs: outputs, + subtractFee: subtractFee, + account: account, + confirmations: minDepth + }; - outputs = []; - keys = Object.keys(sendTo); - uniq = {}; + tx = yield this.wallet.send(options); - for (i = 0; i < keys.length; i++) { - key = keys[i]; - value = toSatoshi(sendTo[key]); - address = bcoin.address.fromBase58(key); - hash = address.getHash('hex'); - - if (uniq[hash]) - throw new RPCError('Invalid parameter.'); - - uniq[hash] = true; - - output = new bcoin.output(); - output.value = value; - output.script.fromAddress(address); - outputs.push(output); - } - - options = { - outputs: outputs, - subtractFee: subtractFee, - account: account, - confirmations: minDepth - }; - - tx = yield this.wallet.send(options); - - return tx.rhash; - }, this); -}; + return tx.rhash; +}); RPC.prototype.sendtoaddress = function sendtoaddress(args) { var address, amount, subtractFee; @@ -4030,37 +3906,35 @@ RPC.prototype.settxfee = function settxfee(args) { return Promise.resolve(true); }; -RPC.prototype.signmessage = function signmessage(args) { - return spawn(function *() { - var address, msg, sig, ring; +RPC.prototype.signmessage = spawn.co(function* signmessage(args) { + var address, msg, sig, ring; - if (args.help || args.length !== 2) - throw new RPCError('signmessage "bitcoinaddress" "message"'); + if (args.help || args.length !== 2) + throw new RPCError('signmessage "bitcoinaddress" "message"'); - address = toString(args[0]); - msg = toString(args[1]); + address = toString(args[0]); + msg = toString(args[1]); - address = bcoin.address.getHash(address, 'hex'); + address = bcoin.address.getHash(address, 'hex'); - if (!address) - throw new RPCError('Invalid address.'); + if (!address) + throw new RPCError('Invalid address.'); - ring = yield this.wallet.getKeyRing(address); + ring = yield this.wallet.getKeyRing(address); - if (!ring) - throw new RPCError('Address not found.'); + if (!ring) + throw new RPCError('Address not found.'); - if (!this.wallet.master.key) - throw new RPCError('Wallet is locked.'); + if (!this.wallet.master.key) + throw new RPCError('Wallet is locked.'); - msg = new Buffer(RPC.magic + msg, 'utf8'); - msg = crypto.hash256(msg); + msg = new Buffer(RPC.magic + msg, 'utf8'); + msg = crypto.hash256(msg); - sig = ring.sign(msg); + sig = ring.sign(msg); - return sig.toString('base64'); - }, this); -}; + return sig.toString('base64'); +}); RPC.prototype.walletlock = function walletlock(args) { if (args.help || (this.wallet.master.encrypted && args.length !== 0)) @@ -4074,121 +3948,113 @@ RPC.prototype.walletlock = function walletlock(args) { return null; }; -RPC.prototype.walletpassphrasechange = function walletpassphrasechange(args) { - return spawn(function *() { - var old, new_; +RPC.prototype.walletpassphrasechange = spawn.co(function* walletpassphrasechange(args) { + var old, new_; - if (args.help || (this.wallet.master.encrypted && args.length !== 2)) { - throw new RPCError('walletpassphrasechange' - + ' "oldpassphrase" "newpassphrase"'); - } + if (args.help || (this.wallet.master.encrypted && args.length !== 2)) { + throw new RPCError('walletpassphrasechange' + + ' "oldpassphrase" "newpassphrase"'); + } - if (!this.wallet.master.encrypted) - throw new RPCError('Wallet is not encrypted.'); + if (!this.wallet.master.encrypted) + throw new RPCError('Wallet is not encrypted.'); - old = toString(args[0]); - new_ = toString(args[1]); + old = toString(args[0]); + new_ = toString(args[1]); - if (old.length < 1 || new_.length < 1) - throw new RPCError('Invalid parameter'); + if (old.length < 1 || new_.length < 1) + throw new RPCError('Invalid parameter'); - yield this.wallet.setPassphrase(old, new_); + yield this.wallet.setPassphrase(old, new_); - return null; - }, this); -}; + return null; +}); -RPC.prototype.walletpassphrase = function walletpassphrase(args) { - return spawn(function *() { - var passphrase, timeout; +RPC.prototype.walletpassphrase = spawn.co(function* walletpassphrase(args) { + var passphrase, timeout; - if (args.help || (this.wallet.master.encrypted && args.length !== 2)) - throw new RPCError('walletpassphrase "passphrase" timeout'); + if (args.help || (this.wallet.master.encrypted && args.length !== 2)) + throw new RPCError('walletpassphrase "passphrase" timeout'); - if (!this.wallet.master.encrypted) - throw new RPCError('Wallet is not encrypted.'); + if (!this.wallet.master.encrypted) + throw new RPCError('Wallet is not encrypted.'); - passphrase = toString(args[0]); - timeout = toNumber(args[1]); + passphrase = toString(args[0]); + timeout = toNumber(args[1]); - if (passphrase.length < 1) - throw new RPCError('Invalid parameter'); + if (passphrase.length < 1) + throw new RPCError('Invalid parameter'); - if (timeout < 0) - throw new RPCError('Invalid parameter'); + if (timeout < 0) + throw new RPCError('Invalid parameter'); - yield this.wallet.unlock(passphrase, timeout); + yield this.wallet.unlock(passphrase, timeout); - return null; - }, this); -}; + return null; +}); -RPC.prototype.importprunedfunds = function importprunedfunds(args) { - return spawn(function *() { - var tx, block, label, height, added; +RPC.prototype.importprunedfunds = spawn.co(function* importprunedfunds(args) { + var tx, block, label, height, added; - if (args.help || args.length < 2 || args.length > 3) { - throw new RPCError('importprunedfunds' - + ' "rawtransaction" "txoutproof" ( "label" )'); - } + if (args.help || args.length < 2 || args.length > 3) { + throw new RPCError('importprunedfunds' + + ' "rawtransaction" "txoutproof" ( "label" )'); + } - tx = args[0]; - block = args[1]; + tx = args[0]; + block = args[1]; - if (!utils.isHex(tx) || !utils.isHex(block)) - throw new RPCError('Invalid parameter.'); + if (!utils.isHex(tx) || !utils.isHex(block)) + throw new RPCError('Invalid parameter.'); - tx = bcoin.tx.fromRaw(tx, 'hex'); - block = bcoin.merkleblock.fromRaw(block, 'hex'); + tx = bcoin.tx.fromRaw(tx, 'hex'); + block = bcoin.merkleblock.fromRaw(block, 'hex'); - if (args.length === 3) - label = toString(args[2]); + if (args.length === 3) + label = toString(args[2]); - if (!block.verify()) - throw new RPCError('Invalid proof.'); + if (!block.verify()) + throw new RPCError('Invalid proof.'); - if (!block.hasTX(tx)) - throw new RPCError('Invalid proof.'); + if (!block.hasTX(tx)) + throw new RPCError('Invalid proof.'); - height = yield this.chain.db.getHeight(block.hash('hex')); + height = yield this.chain.db.getHeight(block.hash('hex')); - if (height === -1) - throw new RPCError('Invalid proof.'); + if (height === -1) + throw new RPCError('Invalid proof.'); - tx.index = block.indexOf(tx); - tx.block = block.hash('hex'); - tx.ts = block.ts; - tx.height = height; + tx.index = block.indexOf(tx); + tx.block = block.hash('hex'); + tx.ts = block.ts; + tx.height = height; - added = yield this.wallet.addTX(tx); + added = yield this.wallet.addTX(tx); - if (!added) - throw new RPCError('No tracked address for TX.'); + if (!added) + throw new RPCError('No tracked address for TX.'); - return null; - }, this); -}; + return null; +}); -RPC.prototype.removeprunedfunds = function removeprunedfunds(args) { - return spawn(function *() { - var hash, removed; +RPC.prototype.removeprunedfunds = spawn.co(function* removeprunedfunds(args) { + var hash, removed; - if (args.help || args.length !== 1) - throw new RPCError('removeprunedfunds "txid"'); + if (args.help || args.length !== 1) + throw new RPCError('removeprunedfunds "txid"'); - hash = toHash(args[0]); + hash = toHash(args[0]); - if (!hash) - throw new RPCError('Invalid parameter.'); + if (!hash) + throw new RPCError('Invalid parameter.'); - removed = yield this.wallet.tx.remove(hash); + removed = yield this.wallet.tx.remove(hash); - if (!removed) - throw new RPCError('Transaction not in wallet.'); + if (!removed) + throw new RPCError('Transaction not in wallet.'); - return null; - }, this); -}; + return null; +}); RPC.prototype.getmemory = function getmemory(args) { var mem; diff --git a/lib/http/rpcclient.js b/lib/http/rpcclient.js index f8b2952c..a0d90e02 100644 --- a/lib/http/rpcclient.js +++ b/lib/http/rpcclient.js @@ -44,38 +44,36 @@ function RPCClient(options) { * @param {Function} callback - Returns [Error, Object?]. */ -RPCClient.prototype.call = function call(method, params) { - return spawn(function *() { - var res = yield request.promise({ - method: 'POST', - uri: this.uri, - json: { - method: method, - params: params, - id: this.id++ - }, - auth: { - username: 'bitcoinrpc', - password: this.apiKey || '' - }, - expect: 'json' - }); +RPCClient.prototype.call = spawn.co(function* call(method, params) { + var res = yield request.promise({ + method: 'POST', + uri: this.uri, + json: { + method: method, + params: params, + id: this.id++ + }, + auth: { + username: 'bitcoinrpc', + password: this.apiKey || '' + }, + expect: 'json' + }); - if (!res.body) - return; - - if (res.statusCode === 400) - return res.body.result; - - if (res.statusCode !== 200) { - if (res.body.error) - throw new Error(res.body.error.message); - throw new Error('Status code: ' + res.statusCode); - } + if (!res.body) + return; + if (res.statusCode === 400) return res.body.result; - }, this); -}; + + if (res.statusCode !== 200) { + if (res.body.error) + throw new Error(res.body.error.message); + throw new Error('Status code: ' + res.statusCode); + } + + return res.body.result; +}); /* * Expose diff --git a/lib/http/server.js b/lib/http/server.js index a62dc1ce..80e0150b 100644 --- a/lib/http/server.js +++ b/lib/http/server.js @@ -1062,20 +1062,18 @@ HTTPServer.prototype._initIO = function _initIO() { * @param {Function} callback */ -HTTPServer.prototype.open = function open() { - return spawn(function *() { - yield this.server.open(); +HTTPServer.prototype.open = spawn.co(function* open() { + yield this.server.open(); - this.logger.info('HTTP server loaded.'); + this.logger.info('HTTP server loaded.'); - if (this.apiKey) { - this.logger.info('HTTP API key: %s', this.apiKey); - this.apiKey = null; - } else if (!this.apiHash) { - this.logger.warning('WARNING: Your http server is open to the world.'); - } - }, this); -}; + if (this.apiKey) { + this.logger.info('HTTP API key: %s', this.apiKey); + this.apiKey = null; + } else if (!this.apiHash) { + this.logger.warning('WARNING: Your http server is open to the world.'); + } +}); /** * Close the server, wait for server socket to close. diff --git a/lib/http/wallet.js b/lib/http/wallet.js index 0e16387c..3292cb6b 100644 --- a/lib/http/wallet.js +++ b/lib/http/wallet.js @@ -89,28 +89,26 @@ HTTPWallet.prototype._init = function _init() { * @param {Function} callback */ -HTTPWallet.prototype.open = function open(options) { - return spawn(function *() { - var wallet; +HTTPWallet.prototype.open = spawn.co(function* open(options) { + var wallet; - this.id = options.id; + this.id = options.id; - if (options.token) { - this.token = options.token; - if (Buffer.isBuffer(this.token)) - this.token = this.token.toString('hex'); - this.client.token = this.token; - } + if (options.token) { + this.token = options.token; + if (Buffer.isBuffer(this.token)) + this.token = this.token.toString('hex'); + this.client.token = this.token; + } - yield this.client.open(); + yield this.client.open(); - wallet = yield this.client.getWallet(this.id); + wallet = yield this.client.getWallet(this.id); - yield this.client.join(this.id, wallet.token); + yield this.client.join(this.id, wallet.token); - return wallet; - }, this); -}; + return wallet; +}); /** * Open the client and create a wallet. @@ -118,17 +116,15 @@ HTTPWallet.prototype.open = function open(options) { * @param {Function} callback */ -HTTPWallet.prototype.create = function create(options) { - return spawn(function *() { - var wallet; - yield this.client.open(); - wallet = yield this.client.createWallet(options); - return yield this.open({ - id: wallet.id, - token: wallet.token - }); - }, this); -}; +HTTPWallet.prototype.create = spawn.co(function* create(options) { + var wallet; + yield this.client.open(); + wallet = yield this.client.createWallet(options); + return yield this.open({ + id: wallet.id, + token: wallet.token + }); +}); /** * Close the client, wait for the socket to close. @@ -296,16 +292,14 @@ HTTPWallet.prototype.setPassphrase = function setPassphrase(old, new_) { * @see Wallet#retoken */ -HTTPWallet.prototype.retoken = function retoken(passphrase) { - return spawn(function *() { - var token = yield this.client.retoken(this.id, passphrase); +HTTPWallet.prototype.retoken = spawn.co(function* retoken(passphrase) { + var token = yield this.client.retoken(this.id, passphrase); - this.token = token; - this.client.token = token; + this.token = token; + this.client.token = token; - return token; - }, this); -}; + return token; +}); /* * Expose diff --git a/lib/mempool/mempool.js b/lib/mempool/mempool.js index 5a282bb4..62280f74 100644 --- a/lib/mempool/mempool.js +++ b/lib/mempool/mempool.js @@ -119,13 +119,11 @@ utils.inherits(Mempool, AsyncObject); * @param {Function} callback */ -Mempool.prototype._open = function open() { - return spawn(function *() { - var size = (this.maxSize / 1024).toFixed(2); - yield this.chain.open(); - this.logger.info('Mempool loaded (maxsize=%dkb).', size); - }, this); -}; +Mempool.prototype._open = spawn.co(function* open() { + var size = (this.maxSize / 1024).toFixed(2); + yield this.chain.open(); + this.logger.info('Mempool loaded (maxsize=%dkb).', size); +}); /** * Close the chain, wait for the database to close. @@ -155,46 +153,44 @@ Mempool.prototype._lock = function _lock(tx, force) { * @param {Function} callback */ -Mempool.prototype.addBlock = function addBlock(block) { - return spawn(function *() { - var unlock = yield this._lock(); - var entries = []; - var i, entry, tx, hash; +Mempool.prototype.addBlock = spawn.co(function* addBlock(block) { + var unlock = yield this._lock(); + var entries = []; + var i, entry, tx, hash; - for (i = block.txs.length - 1; i >= 0; i--) { - tx = block.txs[i]; - hash = tx.hash('hex'); + for (i = block.txs.length - 1; i >= 0; i--) { + tx = block.txs[i]; + hash = tx.hash('hex'); - if (tx.isCoinbase()) - continue; + if (tx.isCoinbase()) + continue; - entry = this.getEntry(hash); + entry = this.getEntry(hash); - if (!entry) { - this.removeOrphan(hash); - continue; - } - - this.removeUnchecked(entry); - this.emit('confirmed', tx, block); - - entries.push(entry); + if (!entry) { + this.removeOrphan(hash); + continue; } - this.blockSinceBump = true; - this.lastFeeUpdate = utils.now(); + this.removeUnchecked(entry); + this.emit('confirmed', tx, block); - if (this.fees) - this.fees.processBlock(block.height, entries, this.chain.isFull()); + entries.push(entry); + } - // We need to reset the rejects filter periodically. - // There may be a locktime in a TX that is now valid. - this.rejects.reset(); + this.blockSinceBump = true; + this.lastFeeUpdate = utils.now(); - yield utils.wait(); - unlock(); - }, this); -}; + if (this.fees) + this.fees.processBlock(block.height, entries, this.chain.isFull()); + + // We need to reset the rejects filter periodically. + // There may be a locktime in a TX that is now valid. + this.rejects.reset(); + + yield utils.wait(); + unlock(); +}); /** * Notify the mempool that a block has been disconnected @@ -203,38 +199,36 @@ Mempool.prototype.addBlock = function addBlock(block) { * @param {Function} callback */ -Mempool.prototype.removeBlock = function removeBlock(block) { - return spawn(function *() { - var unlock = yield this.lock(); - var i, entry, tx, hash; +Mempool.prototype.removeBlock = spawn.co(function* removeBlock(block) { + var unlock = yield this.lock(); + var i, entry, tx, hash; - for (i = 0; i < block.txs.length; i++) { - tx = block.txs[i]; - hash = tx.hash('hex'); + for (i = 0; i < block.txs.length; i++) { + tx = block.txs[i]; + hash = tx.hash('hex'); - if (tx.isCoinbase()) - continue; + if (tx.isCoinbase()) + continue; - if (this.hasTX(hash)) - continue; + if (this.hasTX(hash)) + continue; - entry = MempoolEntry.fromTX(tx, block.height); + entry = MempoolEntry.fromTX(tx, block.height); - try { - yield this.addUnchecked(entry, true); - } catch (e) { - unlock(); - throw e; - } - - this.emit('unconfirmed', tx, block); + try { + yield this.addUnchecked(entry, true); + } catch (e) { + unlock(); + throw e; } - this.rejects.reset(); + this.emit('unconfirmed', tx, block); + } - unlock(); - }, this); -}; + this.rejects.reset(); + + unlock(); +}); /** * Ensure the size of the mempool stays below 300mb. @@ -530,26 +524,24 @@ Mempool.prototype.hasReject = function hasReject(hash) { * @param {Function} callback - Returns [{@link VerifyError}]. */ -Mempool.prototype.addTX = function addTX(tx) { - return spawn(function *() { - var unlock = yield this._lock(tx); - var missing; +Mempool.prototype.addTX = spawn.co(function* addTX(tx) { + var unlock = yield this._lock(tx); + var missing; - try { - missing = yield this._addTX(tx); - } catch (err) { - if (err.type === 'VerifyError') { - if (!tx.hasWitness() && !err.malleated) - this.rejects.add(tx.hash()); - } - unlock(); - throw err; + try { + missing = yield this._addTX(tx); + } catch (err) { + if (err.type === 'VerifyError') { + if (!tx.hasWitness() && !err.malleated) + this.rejects.add(tx.hash()); } - unlock(); - return missing; - }, this); -}; + throw err; + } + + unlock(); + return missing; +}); /** * Add a transaction to the mempool. @@ -558,117 +550,115 @@ Mempool.prototype.addTX = function addTX(tx) { * @param {Function} callback - Returns [{@link VerifyError}]. */ -Mempool.prototype._addTX = function _addTX(tx) { - return spawn(function *() { - var lockFlags = constants.flags.STANDARD_LOCKTIME_FLAGS; - var hash = tx.hash('hex'); - var ret, entry, missing; - var result, exists; +Mempool.prototype._addTX = spawn.co(function* _addTX(tx) { + var lockFlags = constants.flags.STANDARD_LOCKTIME_FLAGS; + var hash = tx.hash('hex'); + var ret, entry, missing; + var result, exists; - assert(!tx.mutable, 'Cannot add mutable TX to mempool.'); + assert(!tx.mutable, 'Cannot add mutable TX to mempool.'); - ret = new VerifyResult(); + ret = new VerifyResult(); - if (tx.ts !== 0) { + if (tx.ts !== 0) { + throw new VerifyError(tx, + 'alreadyknown', + 'txn-already-known', + 0); + } + + if (!tx.isSane(ret)) { + throw new VerifyError(tx, + 'invalid', + ret.reason, + ret.score); + } + + if (tx.isCoinbase()) { + throw new VerifyError(tx, + 'invalid', + 'coinbase', + 100); + } + + if (this.requireStandard) { + if (!this.chain.state.hasCSV() && tx.version >= 2) { throw new VerifyError(tx, - 'alreadyknown', - 'txn-already-known', + 'nonstandard', + 'premature-version2-tx', 0); } + } - if (!tx.isSane(ret)) { + if (!this.chain.state.hasWitness() && !this.prematureWitness) { + if (tx.hasWitness()) { throw new VerifyError(tx, - 'invalid', + 'nonstandard', + 'no-witness-yet', + 0); + } + } + + if (this.requireStandard) { + if (!tx.isStandard(ret)) { + throw new VerifyError(tx, + 'nonstandard', ret.reason, ret.score); } + } - if (tx.isCoinbase()) { - throw new VerifyError(tx, - 'invalid', - 'coinbase', - 100); - } + result = yield this.chain.checkFinal(this.chain.tip, tx, lockFlags); - if (this.requireStandard) { - if (!this.chain.state.hasCSV() && tx.version >= 2) { - throw new VerifyError(tx, - 'nonstandard', - 'premature-version2-tx', - 0); - } - } + if (!result) { + throw new VerifyError(tx, + 'nonstandard', + 'non-final', + 0); + } - if (!this.chain.state.hasWitness() && !this.prematureWitness) { - if (tx.hasWitness()) { - throw new VerifyError(tx, - 'nonstandard', - 'no-witness-yet', - 0); - } - } + if (this.has(hash)) { + throw new VerifyError(tx, + 'alreadyknown', + 'txn-already-in-mempool', + 0); + } - if (this.requireStandard) { - if (!tx.isStandard(ret)) { - throw new VerifyError(tx, - 'nonstandard', - ret.reason, - ret.score); - } - } + exists = yield this.chain.db.hasCoins(hash); - result = yield this.chain.checkFinal(this.chain.tip, tx, lockFlags); + if (exists) { + throw new VerifyError(tx, + 'alreadyknown', + 'txn-already-known', + 0); + } - if (!result) { - throw new VerifyError(tx, - 'nonstandard', - 'non-final', - 0); - } + if (this.isDoubleSpend(tx)) { + throw new VerifyError(tx, + 'duplicate', + 'bad-txns-inputs-spent', + 0); + } - if (this.has(hash)) { - throw new VerifyError(tx, - 'alreadyknown', - 'txn-already-in-mempool', - 0); - } + yield this.fillAllCoins(tx); - exists = yield this.chain.db.hasCoins(hash); + if (!tx.hasCoins()) { + missing = this.storeOrphan(tx); + return missing; + } - if (exists) { - throw new VerifyError(tx, - 'alreadyknown', - 'txn-already-known', - 0); - } + entry = MempoolEntry.fromTX(tx, this.chain.height); - if (this.isDoubleSpend(tx)) { - throw new VerifyError(tx, - 'duplicate', - 'bad-txns-inputs-spent', - 0); - } + yield this.verify(entry); + yield this.addUnchecked(entry, true); - yield this.fillAllCoins(tx); - - if (!tx.hasCoins()) { - missing = this.storeOrphan(tx); - return missing; - } - - entry = MempoolEntry.fromTX(tx, this.chain.height); - - yield this.verify(entry); - yield this.addUnchecked(entry, true); - - if (this.limitMempoolSize(hash)) { - throw new VerifyError(tx, - 'insufficientfee', - 'mempool full', - 0); - } - }, this); -}; + if (this.limitMempoolSize(hash)) { + throw new VerifyError(tx, + 'insufficientfee', + 'mempool full', + 0); + } +}); /** * Add a transaction to the mempool without performing any @@ -680,57 +670,55 @@ Mempool.prototype._addTX = function _addTX(tx) { * @param {Function} callback - Returns [{@link VerifyError}]. */ -Mempool.prototype.addUnchecked = function addUnchecked(entry, force) { - return spawn(function *() { - var unlock = yield this._lock(null, force); - var i, resolved, tx, orphan; +Mempool.prototype.addUnchecked = spawn.co(function* addUnchecked(entry, force) { + var unlock = yield this._lock(null, force); + var i, resolved, tx, orphan; - this.trackEntry(entry); + this.trackEntry(entry); - this.emit('tx', entry.tx); - this.emit('add tx', entry.tx); + this.emit('tx', entry.tx); + this.emit('add tx', entry.tx); - if (this.fees) - this.fees.processTX(entry, this.chain.isFull()); + if (this.fees) + this.fees.processTX(entry, this.chain.isFull()); - this.logger.debug('Added tx %s to mempool.', entry.tx.rhash); + this.logger.debug('Added tx %s to mempool.', entry.tx.rhash); - resolved = this.resolveOrphans(entry.tx); + resolved = this.resolveOrphans(entry.tx); - for (i = 0; i < resolved.length; i++) { - tx = resolved[i]; - orphan = MempoolEntry.fromTX(tx, this.chain.height); + for (i = 0; i < resolved.length; i++) { + tx = resolved[i]; + orphan = MempoolEntry.fromTX(tx, this.chain.height); - try { - yield this.verify(orphan); - } catch (err) { - if (err.type === 'VerifyError') { - this.logger.debug('Could not resolve orphan %s: %s.', - tx.rhash, - err.message); + try { + yield this.verify(orphan); + } catch (err) { + if (err.type === 'VerifyError') { + this.logger.debug('Could not resolve orphan %s: %s.', + tx.rhash, + err.message); - if (!tx.hasWitness() && !err.malleated) - this.rejects.add(tx.hash()); + if (!tx.hasWitness() && !err.malleated) + this.rejects.add(tx.hash()); - continue; - } - this.emit('error', err); continue; } - - try { - yield this.addUnchecked(orphan, true); - } catch (err) { - this.emit('error', err); - continue; - } - - this.logger.spam('Resolved orphan %s in mempool.', orphan.tx.rhash); + this.emit('error', err); + continue; } - unlock(); - }, this); -}; + try { + yield this.addUnchecked(orphan, true); + } catch (err) { + this.emit('error', err); + continue; + } + + this.logger.spam('Resolved orphan %s in mempool.', orphan.tx.rhash); + } + + unlock(); +}); /** * Remove a transaction from the mempool. Generally @@ -816,155 +804,153 @@ Mempool.prototype.getMinRate = function getMinRate() { * @param {Function} callback - Returns [{@link VerifyError}]. */ -Mempool.prototype.verify = function verify(entry) { - return spawn(function *() { - var height = this.chain.height + 1; - var lockFlags = flags.STANDARD_LOCKTIME_FLAGS; - var flags1 = flags.STANDARD_VERIFY_FLAGS; - var flags2 = flags1 & ~(flags.VERIFY_WITNESS | flags.VERIFY_CLEANSTACK); - var flags3 = flags1 & ~flags.VERIFY_CLEANSTACK; - var mandatory = flags.MANDATORY_VERIFY_FLAGS; - var tx = entry.tx; - var ret = new VerifyResult(); - var fee, modFee, now, size, minRate; - var rejectFee, minRelayFee, count, result; +Mempool.prototype.verify = spawn.co(function* verify(entry) { + var height = this.chain.height + 1; + var lockFlags = flags.STANDARD_LOCKTIME_FLAGS; + var flags1 = flags.STANDARD_VERIFY_FLAGS; + var flags2 = flags1 & ~(flags.VERIFY_WITNESS | flags.VERIFY_CLEANSTACK); + var flags3 = flags1 & ~flags.VERIFY_CLEANSTACK; + var mandatory = flags.MANDATORY_VERIFY_FLAGS; + var tx = entry.tx; + var ret = new VerifyResult(); + var fee, modFee, now, size, minRate; + var rejectFee, minRelayFee, count, result; - result = yield this.checkLocks(tx, lockFlags); + result = yield this.checkLocks(tx, lockFlags); - if (!result) { + if (!result) { + throw new VerifyError(tx, + 'nonstandard', + 'non-BIP68-final', + 0); + } + + if (this.requireStandard) { + if (!tx.hasStandardInputs()) { throw new VerifyError(tx, 'nonstandard', - 'non-BIP68-final', + 'bad-txns-nonstandard-inputs', 0); } - - if (this.requireStandard) { - if (!tx.hasStandardInputs()) { - throw new VerifyError(tx, + if (this.chain.state.hasWitness()) { + if (!tx.hasStandardWitness(ret)) { + ret = new VerifyError(tx, 'nonstandard', - 'bad-txns-nonstandard-inputs', - 0); - } - if (this.chain.state.hasWitness()) { - if (!tx.hasStandardWitness(ret)) { - ret = new VerifyError(tx, - 'nonstandard', - ret.reason, - ret.score); - ret.malleated = ret.score > 0; - throw ret; - } + ret.reason, + ret.score); + ret.malleated = ret.score > 0; + throw ret; } } + } - if (tx.getSigopsWeight(flags) > constants.tx.MAX_SIGOPS_WEIGHT) { - throw new VerifyError(tx, - 'nonstandard', - 'bad-txns-too-many-sigops', - 0); - } + if (tx.getSigopsWeight(flags) > constants.tx.MAX_SIGOPS_WEIGHT) { + throw new VerifyError(tx, + 'nonstandard', + 'bad-txns-too-many-sigops', + 0); + } - fee = tx.getFee(); - modFee = entry.fees; - size = entry.size; - minRate = this.getMinRate(); + fee = tx.getFee(); + modFee = entry.fees; + size = entry.size; + minRate = this.getMinRate(); - if (minRate > this.minRelayFee) - this.network.updateMinRelay(minRate); + if (minRate > this.minRelayFee) + this.network.updateMinRelay(minRate); - rejectFee = tx.getMinFee(size, minRate); - minRelayFee = tx.getMinFee(size, this.minRelayFee); + rejectFee = tx.getMinFee(size, minRate); + minRelayFee = tx.getMinFee(size, this.minRelayFee); - if (rejectFee > 0 && modFee < rejectFee) { + if (rejectFee > 0 && modFee < rejectFee) { + throw new VerifyError(tx, + 'insufficientfee', + 'mempool min fee not met', + 0); + } + + if (this.relayPriority && modFee < minRelayFee) { + if (!entry.isFree(height)) { throw new VerifyError(tx, 'insufficientfee', - 'mempool min fee not met', + 'insufficient priority', 0); } + } - if (this.relayPriority && modFee < minRelayFee) { - if (!entry.isFree(height)) { - throw new VerifyError(tx, - 'insufficientfee', - 'insufficient priority', - 0); - } - } + // Continuously rate-limit free (really, very-low-fee) + // transactions. This mitigates 'penny-flooding'. i.e. + // sending thousands of free transactions just to be + // annoying or make others' transactions take longer + // to confirm. + if (this.limitFree && modFee < minRelayFee) { + now = utils.now(); - // Continuously rate-limit free (really, very-low-fee) - // transactions. This mitigates 'penny-flooding'. i.e. - // sending thousands of free transactions just to be - // annoying or make others' transactions take longer - // to confirm. - if (this.limitFree && modFee < minRelayFee) { - now = utils.now(); + // Use an exponentially decaying ~10-minute window: + this.freeCount *= Math.pow(1 - 1 / 600, now - this.lastTime); + this.lastTime = now; - // Use an exponentially decaying ~10-minute window: - this.freeCount *= Math.pow(1 - 1 / 600, now - this.lastTime); - this.lastTime = now; - - // The limitFreeRelay unit is thousand-bytes-per-minute - // At default rate it would take over a month to fill 1GB - if (this.freeCount > this.limitFreeRelay * 10 * 1000) { - throw new VerifyError(tx, - 'insufficientfee', - 'rate limited free transaction', - 0); - } - - this.freeCount += size; - } - - if (this.rejectAbsurdFees && fee > minRelayFee * 10000) - throw new VerifyError(tx, 'highfee', 'absurdly-high-fee', 0); - - count = this.countAncestors(tx); - - if (count > constants.mempool.ANCESTOR_LIMIT) { + // The limitFreeRelay unit is thousand-bytes-per-minute + // At default rate it would take over a month to fill 1GB + if (this.freeCount > this.limitFreeRelay * 10 * 1000) { throw new VerifyError(tx, - 'nonstandard', - 'too-long-mempool-chain', + 'insufficientfee', + 'rate limited free transaction', 0); } - if (!tx.checkInputs(height, ret)) - throw new VerifyError(tx, 'invalid', ret.reason, ret.score); + this.freeCount += size; + } - // Standard verification - try { - yield this.checkInputs(tx, flags1); - } catch (error) { - if (tx.hasWitness()) - throw error; + if (this.rejectAbsurdFees && fee > minRelayFee * 10000) + throw new VerifyError(tx, 'highfee', 'absurdly-high-fee', 0); - // Try without segwit and cleanstack. - result = yield this.checkResult(tx, flags2); + count = this.countAncestors(tx); - // If it failed, the first verification - // was the only result we needed. - if (!result) - throw error; + if (count > constants.mempool.ANCESTOR_LIMIT) { + throw new VerifyError(tx, + 'nonstandard', + 'too-long-mempool-chain', + 0); + } - // If it succeeded, segwit may be causing the - // failure. Try with segwit but without cleanstack. - result = yield this.checkResult(tx, flags3); + if (!tx.checkInputs(height, ret)) + throw new VerifyError(tx, 'invalid', ret.reason, ret.score); - // Cleanstack was causing the failure. - if (result) - throw error; - - // Do not insert into reject cache. - error.malleated = true; + // Standard verification + try { + yield this.checkInputs(tx, flags1); + } catch (error) { + if (tx.hasWitness()) throw error; - } - // Paranoid checks. - if (this.paranoid) { - result = yield this.checkResult(tx, mandatory); - assert(result, 'BUG: Verify failed for mandatory but not standard.'); - } - }, this); -}; + // Try without segwit and cleanstack. + result = yield this.checkResult(tx, flags2); + + // If it failed, the first verification + // was the only result we needed. + if (!result) + throw error; + + // If it succeeded, segwit may be causing the + // failure. Try with segwit but without cleanstack. + result = yield this.checkResult(tx, flags3); + + // Cleanstack was causing the failure. + if (result) + throw error; + + // Do not insert into reject cache. + error.malleated = true; + throw error; + } + + // Paranoid checks. + if (this.paranoid) { + result = yield this.checkResult(tx, mandatory); + assert(result, 'BUG: Verify failed for mandatory but not standard.'); + } +}); /** * Verify inputs, return a boolean @@ -974,18 +960,16 @@ Mempool.prototype.verify = function verify(entry) { * @param {Function} callback */ -Mempool.prototype.checkResult = function checkResult(tx, flags) { - return spawn(function *() { - try { - yield this.checkInputs(tx, flags); - } catch (err) { - if (err.type === 'VerifyError') - return false; - throw err; - } - return true; - }, this); -}; +Mempool.prototype.checkResult = spawn.co(function* checkResult(tx, flags) { + try { + yield this.checkInputs(tx, flags); + } catch (err) { + if (err.type === 'VerifyError') + return false; + throw err; + } + return true; +}); /** * Verify inputs for standard @@ -995,36 +979,34 @@ Mempool.prototype.checkResult = function checkResult(tx, flags) { * @param {Function} callback */ -Mempool.prototype.checkInputs = function checkInputs(tx, flags) { - return spawn(function *() { - var result = yield tx.verifyAsync(flags); - if (result) - return; - - if (!(flags & constants.flags.UNSTANDARD_VERIFY_FLAGS)) { - throw new VerifyError(tx, - 'nonstandard', - 'non-mandatory-script-verify-flag', - 0); - } - - flags &= ~constants.flags.UNSTANDARD_VERIFY_FLAGS; - - result = yield tx.verifyAsync(flags); - - if (result) { - throw new VerifyError(tx, - 'nonstandard', - 'non-mandatory-script-verify-flag', - 0); - } +Mempool.prototype.checkInputs = spawn.co(function* checkInputs(tx, flags) { + var result = yield tx.verifyAsync(flags); + if (result) + return; + if (!(flags & constants.flags.UNSTANDARD_VERIFY_FLAGS)) { throw new VerifyError(tx, 'nonstandard', - 'mandatory-script-verify-flag', - 100); - }, this); -}; + 'non-mandatory-script-verify-flag', + 0); + } + + flags &= ~constants.flags.UNSTANDARD_VERIFY_FLAGS; + + result = yield tx.verifyAsync(flags); + + if (result) { + throw new VerifyError(tx, + 'nonstandard', + 'non-mandatory-script-verify-flag', + 0); + } + + throw new VerifyError(tx, + 'nonstandard', + 'mandatory-script-verify-flag', + 100); +}); /** * Count the highest number of @@ -1405,32 +1387,30 @@ Mempool.prototype.fillAllHistory = function fillAllHistory(tx) { * @param {Function} callback - Returns [Error, {@link TX}]. */ -Mempool.prototype.fillAllCoins = function fillAllCoins(tx) { - return spawn(function *() { - var i, input, hash, index, coin; +Mempool.prototype.fillAllCoins = spawn.co(function* fillAllCoins(tx) { + var i, input, hash, index, coin; - this.fillCoins(tx); - - if (tx.hasCoins()) - return tx; - - for (i = 0; i < tx.inputs.length; i++) { - input = tx.inputs[i]; - hash = input.prevout.hash; - index = input.prevout.index; - - if (this.isSpent(hash, index)) - continue; - - coin = yield this.chain.db.getCoin(hash, index); - - if (coin) - input.coin = coin; - } + this.fillCoins(tx); + if (tx.hasCoins()) return tx; - }, this); -}; + + for (i = 0; i < tx.inputs.length; i++) { + input = tx.inputs[i]; + hash = input.prevout.hash; + index = input.prevout.index; + + if (this.isSpent(hash, index)) + continue; + + coin = yield this.chain.db.getCoin(hash, index); + + if (coin) + input.coin = coin; + } + + return tx; +}); /** * Get a snapshot of all transaction hashes in the mempool. Used @@ -1483,38 +1463,36 @@ Mempool.prototype.isDoubleSpend = function isDoubleSpend(tx) { * @param {Function} callback - Returns [Error, Number]. */ -Mempool.prototype.getConfidence = function getConfidence(hash) { - return spawn(function *() { - var tx, result; +Mempool.prototype.getConfidence = spawn.co(function* getConfidence(hash) { + var tx, result; - if (hash instanceof bcoin.tx) { - tx = hash; - hash = hash.hash('hex'); - } else { - tx = this.getTX(hash); - } + if (hash instanceof bcoin.tx) { + tx = hash; + hash = hash.hash('hex'); + } else { + tx = this.getTX(hash); + } - if (this.hasTX(hash)) - return constants.confidence.PENDING; + if (this.hasTX(hash)) + return constants.confidence.PENDING; - if (tx && this.isDoubleSpend(tx)) - return constants.confidence.INCONFLICT; - - if (tx && tx.block) { - result = yield this.chain.db.isMainChain(tx.block); - if (result) - return constants.confidence.BUILDING; - return constants.confidence.DEAD; - } - - result = yield this.chain.db.hasCoins(hash); + if (tx && this.isDoubleSpend(tx)) + return constants.confidence.INCONFLICT; + if (tx && tx.block) { + result = yield this.chain.db.isMainChain(tx.block); if (result) return constants.confidence.BUILDING; + return constants.confidence.DEAD; + } - return constants.confidence.UNKNOWN; - }, this); -}; + result = yield this.chain.db.hasCoins(hash); + + if (result) + return constants.confidence.BUILDING; + + return constants.confidence.UNKNOWN; +}); /** * Map a transaction to the mempool. diff --git a/lib/miner/miner.js b/lib/miner/miner.js index 0bcff0e3..6b713ead 100644 --- a/lib/miner/miner.js +++ b/lib/miner/miner.js @@ -134,17 +134,15 @@ Miner.prototype._init = function _init() { * @param {Function} callback */ -Miner.prototype._open = function open() { - return spawn(function *() { - if (this.mempool) - yield this.mempool.open(); - else - yield this.chain.open(); +Miner.prototype._open = spawn.co(function* open() { + if (this.mempool) + yield this.mempool.open(); + else + yield this.chain.open(); - this.logger.info('Miner loaded (flags=%s).', - this.coinbaseFlags.toString('utf8')); - }, this); -}; + this.logger.info('Miner loaded (flags=%s).', + this.coinbaseFlags.toString('utf8')); +}); /** * Close the miner. @@ -242,29 +240,28 @@ Miner.prototype.stop = function stop() { * @param {Function} callback - Returns [Error, {@link MinerBlock}]. */ -Miner.prototype.createBlock = function createBlock(tip) { - return spawn(function *() { - var i, ts, attempt, txs, tx, target, version; +Miner.prototype.createBlock = spawn.co(function* createBlock(tip) { + var i, ts, attempt, txs, tx, target, version; - if (!this.loaded) - yield this.open(); + if (!this.loaded) + yield this.open(); - if (!tip) - tip = this.chain.tip; + if (!tip) + tip = this.chain.tip; - assert(tip); + assert(tip); - ts = Math.max(bcoin.now(), tip.ts + 1); + ts = Math.max(bcoin.now(), tip.ts + 1); - // Find target - target = yield this.chain.getTargetAsync(ts, tip); + // Find target + target = yield this.chain.getTargetAsync(ts, tip); if (this.version != null) { version = this.version; } else { - // Calculate version with versionbits + // Calculate version with versionbits version = yield this.chain.computeBlockVersion(tip); - } + } attempt = new MinerBlock({ workerPool: this.workerPool, @@ -289,8 +286,7 @@ Miner.prototype.createBlock = function createBlock(tip) { } return attempt; - }, this); -}; +}); /** * Mine a single block. @@ -298,13 +294,11 @@ Miner.prototype.createBlock = function createBlock(tip) { * @param {Function} callback - Returns [Error, [{@link Block}]]. */ -Miner.prototype.mineBlock = function mineBlock(tip) { - return spawn(function *() { +Miner.prototype.mineBlock = spawn.co(function* mineBlock(tip) { // Create a new block and start hashing var attempt = yield this.createBlock(tip); - return yield attempt.mineAsync(); - }, this); -}; + return yield attempt.mineAsync(); +}); /* * Expose diff --git a/lib/miner/minerblock.js b/lib/miner/minerblock.js index 570ac2a5..04388551 100644 --- a/lib/miner/minerblock.js +++ b/lib/miner/minerblock.js @@ -349,19 +349,17 @@ MinerBlock.prototype.sendStatus = function sendStatus() { * @param {Function} callback - Returns [Error, {@link Block}]. */ -MinerBlock.prototype.mine = function mine() { - return spawn(function *() { - yield this.wait(100); +MinerBlock.prototype.mine = spawn.co(function* mine() { + yield this.wait(100); - // Try to find a block: do one iteration of extraNonce - if (!this.findNonce()) { - yield this.mine(); - return; - } + // Try to find a block: do one iteration of extraNonce + if (!this.findNonce()) { + yield this.mine(); + return; + } - return this.block; - }, this); -}; + return this.block; +}); /** * Wait for a timeout. @@ -393,20 +391,18 @@ MinerBlock.prototype.mineSync = function mineSync() { * @param {Function} callback - Returns [Error, {@link Block}]. */ -MinerBlock.prototype.mineAsync = function mineAsync() { - return spawn(function *() { - var block; +MinerBlock.prototype.mineAsync = spawn.co(function* mineAsync() { + var block; - if (!this.workerPool) - return yield this.mine(); + if (!this.workerPool) + return yield this.mine(); - block = yield this.workerPool.mine(this); + block = yield this.workerPool.mine(this); - this.workerPool.destroy(); + this.workerPool.destroy(); - return block; - }, this); -}; + return block; +}); /** * Destroy the minerblock. Stop mining. Clear timeout. diff --git a/lib/net/peer.js b/lib/net/peer.js index 2a8892c9..2e49f5a2 100644 --- a/lib/net/peer.js +++ b/lib/net/peer.js @@ -1430,50 +1430,48 @@ Peer.prototype._handleMempool = function _handleMempool(packet) { * [Error, {@link Block}|{@link MempoolEntry}]. */ -Peer.prototype._getItem = function _getItem(item) { - return spawn(function *() { - var entry = this.pool.invMap[item.hash]; +Peer.prototype._getItem = spawn.co(function* _getItem(item) { + var entry = this.pool.invMap[item.hash]; - if (entry) { - this.logger.debug( - 'Peer requested %s %s as a %s packet (%s).', - entry.type === constants.inv.TX ? 'tx' : 'block', - utils.revHex(entry.hash), - item.hasWitness() ? 'witness' : 'normal', - this.hostname); + if (entry) { + this.logger.debug( + 'Peer requested %s %s as a %s packet (%s).', + entry.type === constants.inv.TX ? 'tx' : 'block', + utils.revHex(entry.hash), + item.hasWitness() ? 'witness' : 'normal', + this.hostname); - entry.ack(this); + entry.ack(this); - if (entry.msg) { - if (item.isTX()) { - if (entry.type === constants.inv.TX) - return entry.msg; - } else { - if (entry.type === constants.inv.BLOCK) - return entry.msg; - } - return; + if (entry.msg) { + if (item.isTX()) { + if (entry.type === constants.inv.TX) + return entry.msg; + } else { + if (entry.type === constants.inv.BLOCK) + return entry.msg; } + return; } + } - if (this.options.selfish) + if (this.options.selfish) + return; + + if (item.isTX()) { + if (!this.mempool) return; + return this.mempool.getTX(item.hash); + } - if (item.isTX()) { - if (!this.mempool) - return; - return this.mempool.getTX(item.hash); - } + if (this.chain.db.options.spv) + return; - if (this.chain.db.options.spv) - return; + if (this.chain.db.options.prune) + return; - if (this.chain.db.options.prune) - return; - - return yield this.chain.db.getBlock(item.hash); - }, this); -}; + return yield this.chain.db.getBlock(item.hash); +}); /** * Handle `getdata` packet. @@ -2351,24 +2349,22 @@ Peer.prototype.reject = function reject(obj, code, reason, score) { * @param {Function} callback */ -Peer.prototype.resolveOrphan = function resolveOrphan(tip, orphan) { - return spawn(function *() { - var root, locator; +Peer.prototype.resolveOrphan = spawn.co(function* resolveOrphan(tip, orphan) { + var root, locator; - assert(orphan); + assert(orphan); - locator = yield this.chain.getLocator(tip); - root = this.chain.getOrphanRoot(orphan); + locator = yield this.chain.getLocator(tip); + root = this.chain.getOrphanRoot(orphan); - // Was probably resolved. - if (!root) { - this.logger.debug('Orphan root was already resolved.'); - return; - } + // Was probably resolved. + if (!root) { + this.logger.debug('Orphan root was already resolved.'); + return; + } - this.sendGetBlocks(locator, root); - }, this); -}; + this.sendGetBlocks(locator, root); +}); /** * Send `getheaders` to peer after building locator. @@ -2377,12 +2373,10 @@ Peer.prototype.resolveOrphan = function resolveOrphan(tip, orphan) { * @param {Function} callback */ -Peer.prototype.getHeaders = function getHeaders(tip, stop) { - return spawn(function *() { - var locator = yield this.chain.getLocator(tip); - this.sendGetHeaders(locator, stop); - }, this); -}; +Peer.prototype.getHeaders = spawn.co(function* getHeaders(tip, stop) { + var locator = yield this.chain.getLocator(tip); + this.sendGetHeaders(locator, stop); +}); /** * Send `getblocks` to peer after building locator. @@ -2391,12 +2385,10 @@ Peer.prototype.getHeaders = function getHeaders(tip, stop) { * @param {Function} callback */ -Peer.prototype.getBlocks = function getBlocks(tip, stop) { - return spawn(function *() { - var locator = yield this.chain.getLocator(tip); - this.sendGetBlocks(locator, stop); - }, this); -}; +Peer.prototype.getBlocks = spawn.co(function* getBlocks(tip, stop) { + var locator = yield this.chain.getLocator(tip); + this.sendGetBlocks(locator, stop); +}); /** * Start syncing from peer. diff --git a/lib/net/pool.js b/lib/net/pool.js index 4303777f..e056bc84 100644 --- a/lib/net/pool.js +++ b/lib/net/pool.js @@ -290,40 +290,38 @@ Pool.prototype._lock = function _lock(force) { * @param {Function} callback */ -Pool.prototype._open = function _open() { - return spawn(function *() { - var ip, key; +Pool.prototype._open = spawn.co(function* _open() { + var ip, key; - try { - ip = yield this.getIP(); - } catch (e) { - this.logger.error(e); - } + try { + ip = yield this.getIP(); + } catch (e) { + this.logger.error(e); + } - if (ip) { - this.address.setHost(ip); - this.logger.info('External IP found: %s.', ip); - } + if (ip) { + this.address.setHost(ip); + this.logger.info('External IP found: %s.', ip); + } - if (this.mempool) - yield this.mempool.open(); - else - yield this.chain.open(); + if (this.mempool) + yield this.mempool.open(); + else + yield this.chain.open(); - this.logger.info('Pool loaded (maxpeers=%d).', this.maxPeers); + this.logger.info('Pool loaded (maxpeers=%d).', this.maxPeers); - if (this.identityKey) { - key = bcoin.ec.publicKeyCreate(this.identityKey, true); - this.logger.info('Identity public key: %s.', key.toString('hex')); - this.logger.info('Identity address: %s.', bcoin.bip150.address(key)); - } + if (this.identityKey) { + key = bcoin.ec.publicKeyCreate(this.identityKey, true); + this.logger.info('Identity public key: %s.', key.toString('hex')); + this.logger.info('Identity address: %s.', bcoin.bip150.address(key)); + } - if (!this.options.listen) - return; + if (!this.options.listen) + return; - yield this.listen(); - }, this); -}; + yield this.listen(); +}); /** * Close and destroy the pool. @@ -331,37 +329,35 @@ Pool.prototype._open = function _open() { * @param {Function} callback */ -Pool.prototype._close = function close() { - return spawn(function *() { - var i, items, hashes, hash; +Pool.prototype._close = spawn.co(function* close() { + var i, items, hashes, hash; - this.stopSync(); + this.stopSync(); - items = this.invItems.slice(); + items = this.invItems.slice(); - for (i = 0; i < items.length; i++) - items[i].finish(); + for (i = 0; i < items.length; i++) + items[i].finish(); - hashes = Object.keys(this.requestMap); + hashes = Object.keys(this.requestMap); - for (i = 0; i < hashes.length; i++) { - hash = hashes[i]; - this.requestMap[hash].finish(new Error('Pool closed.')); - } + for (i = 0; i < hashes.length; i++) { + hash = hashes[i]; + this.requestMap[hash].finish(new Error('Pool closed.')); + } - this.peers.destroy(); + this.peers.destroy(); - this.stopInterval(); - this.stopTimeout(); + this.stopInterval(); + this.stopTimeout(); - if (this.pendingWatch != null) { - clearTimeout(this.pendingWatch); - this.pendingWatch = null; - } + if (this.pendingWatch != null) { + clearTimeout(this.pendingWatch); + this.pendingWatch = null; + } - yield this.unlisten(); - }, this); -}; + yield this.unlisten(); +}); /** * Connect to the network. @@ -724,68 +720,66 @@ Pool.prototype.stopSync = function stopSync() { * @param {Function} callback */ -Pool.prototype._handleHeaders = function _handleHeaders(headers, peer) { - return spawn(function *() { - var i, unlock, ret, header, hash, last; +Pool.prototype._handleHeaders = spawn.co(function* _handleHeaders(headers, peer) { + var i, unlock, ret, header, hash, last; - if (!this.options.headers) - return; + if (!this.options.headers) + return; - unlock = yield this._lock(); + unlock = yield this._lock(); - ret = new VerifyResult(); + ret = new VerifyResult(); - this.logger.debug( - 'Received %s headers from peer (%s).', - headers.length, - peer.hostname); + this.logger.debug( + 'Received %s headers from peer (%s).', + headers.length, + peer.hostname); - this.emit('headers', headers); + this.emit('headers', headers); - if (peer.isLoader()) { - // Reset interval to avoid stall behavior. - this.startInterval(); - // Reset timeout to avoid killing the loader. - this.startTimeout(); + if (peer.isLoader()) { + // Reset interval to avoid stall behavior. + this.startInterval(); + // Reset timeout to avoid killing the loader. + this.startTimeout(); + } + + for (i = 0; i < headers.length; i++) { + header = headers[i]; + hash = header.hash('hex'); + + if (last && header.prevBlock !== last) { + peer.setMisbehavior(100); + unlock(); + throw new Error('Bad header chain.'); } - for (i = 0; i < headers.length; i++) { - header = headers[i]; - hash = header.hash('hex'); - - if (last && header.prevBlock !== last) { - peer.setMisbehavior(100); - unlock(); - throw new Error('Bad header chain.'); - } - - if (!header.verify(ret)) { - peer.reject(header, 'invalid', ret.reason, 100); - unlock(); - throw new Error('Invalid header.'); - } - - last = hash; - - yield this.getData(peer, this.blockType, hash); + if (!header.verify(ret)) { + peer.reject(header, 'invalid', ret.reason, 100); + unlock(); + throw new Error('Invalid header.'); } - // Schedule the getdata's we just added. - this.scheduleRequests(peer); + last = hash; - // Restart the getheaders process - // Technically `last` is not indexed yet so - // the locator hashes will not be entirely - // accurate. However, it shouldn't matter - // that much since FindForkInGlobalIndex - // simply tries to find the latest block in - // the peer's chain. - if (last && headers.length === 2000) - yield peer.getHeaders(last, null); + yield this.getData(peer, this.blockType, hash); + } - unlock(); - }, this); -}; + // Schedule the getdata's we just added. + this.scheduleRequests(peer); + + // Restart the getheaders process + // Technically `last` is not indexed yet so + // the locator hashes will not be entirely + // accurate. However, it shouldn't matter + // that much since FindForkInGlobalIndex + // simply tries to find the latest block in + // the peer's chain. + if (last && headers.length === 2000) + yield peer.getHeaders(last, null); + + unlock(); +}); /** * Handle `inv` packet from peer (containing only BLOCK types). @@ -795,66 +789,64 @@ Pool.prototype._handleHeaders = function _handleHeaders(headers, peer) { * @param {Function} callback */ -Pool.prototype._handleBlocks = function _handleBlocks(hashes, peer) { - return spawn(function *() { - var i, hash, exists; +Pool.prototype._handleBlocks = spawn.co(function* _handleBlocks(hashes, peer) { + var i, hash, exists; - assert(!this.options.headers); + assert(!this.options.headers); - this.logger.debug( - 'Received %s block hashes from peer (%s).', - hashes.length, - peer.hostname); + this.logger.debug( + 'Received %s block hashes from peer (%s).', + hashes.length, + peer.hostname); - this.emit('blocks', hashes); + this.emit('blocks', hashes); - if (peer.isLoader()) { - // Reset interval to avoid stall behavior. - this.startInterval(); - // Reset timeout to avoid killing the loader. - this.startTimeout(); + if (peer.isLoader()) { + // Reset interval to avoid stall behavior. + this.startInterval(); + // Reset timeout to avoid killing the loader. + this.startTimeout(); + } + + for (i = 0; i < hashes.length; i++) { + hash = hashes[i]; + + // Resolve orphan chain. + if (this.chain.hasOrphan(hash)) { + // There is a possible race condition here. + // The orphan may get resolved by the time + // we create the locator. In that case, we + // should probably actually move to the + // `exists` clause below if it is the last + // hash. + this.logger.debug('Received known orphan hash (%s).', peer.hostname); + yield peer.resolveOrphan(null, hash); + continue; } - for (i = 0; i < hashes.length; i++) { - hash = hashes[i]; + exists = yield this.getData(peer, this.blockType, hash); - // Resolve orphan chain. - if (this.chain.hasOrphan(hash)) { - // There is a possible race condition here. - // The orphan may get resolved by the time - // we create the locator. In that case, we - // should probably actually move to the - // `exists` clause below if it is the last - // hash. - this.logger.debug('Received known orphan hash (%s).', peer.hostname); - yield peer.resolveOrphan(null, hash); + // Normally we request the hashContinue. + // In the odd case where we already have + // it, we can do one of two things: either + // force re-downloading of the block to + // continue the sync, or do a getblocks + // from the last hash (this will reset + // the hashContinue on the remote node). + if (exists && i === hashes.length - 1) { + // Make sure we _actually_ have this block. + if (!this.requestMap[hash]) { + this.logger.debug('Received existing hash (%s).', peer.hostname); + yield peer.getBlocks(hash, null); continue; } - - exists = yield this.getData(peer, this.blockType, hash); - - // Normally we request the hashContinue. - // In the odd case where we already have - // it, we can do one of two things: either - // force re-downloading of the block to - // continue the sync, or do a getblocks - // from the last hash (this will reset - // the hashContinue on the remote node). - if (exists && i === hashes.length - 1) { - // Make sure we _actually_ have this block. - if (!this.requestMap[hash]) { - this.logger.debug('Received existing hash (%s).', peer.hostname); - yield peer.getBlocks(hash, null); - continue; - } - // Otherwise, we're still requesting it. Ignore. - this.logger.debug('Received requested hash (%s).', peer.hostname); - } + // Otherwise, we're still requesting it. Ignore. + this.logger.debug('Received requested hash (%s).', peer.hostname); } + } - this.scheduleRequests(peer); - }, this); -}; + this.scheduleRequests(peer); +}); /** * Handle `inv` packet from peer (containing only BLOCK types). @@ -865,30 +857,28 @@ Pool.prototype._handleBlocks = function _handleBlocks(hashes, peer) { * @param {Function} callback */ -Pool.prototype._handleInv = function _handleInv(hashes, peer) { - return spawn(function *() { - var unlock = yield this._lock(); - var i, hash; +Pool.prototype._handleInv = spawn.co(function* _handleInv(hashes, peer) { + var unlock = yield this._lock(); + var i, hash; - // Ignore for now if we're still syncing - if (!this.chain.synced && !peer.isLoader()) - return; + // Ignore for now if we're still syncing + if (!this.chain.synced && !peer.isLoader()) + return; - if (!this.options.headers) { - yield this._handleBlocks(hashes, peer); - unlock(); - return; - } - - for (i = 0; i < hashes.length; i++) { - hash = hashes[i]; - yield peer.getHeaders(null, hash); - } - - this.scheduleRequests(peer); + if (!this.options.headers) { + yield this._handleBlocks(hashes, peer); unlock(); - }, this); -}; + return; + } + + for (i = 0; i < hashes.length; i++) { + hash = hashes[i]; + yield peer.getHeaders(null, hash); + } + + this.scheduleRequests(peer); + unlock(); +}); /** * Handle `block` packet. Attempt to add to chain. @@ -898,81 +888,79 @@ Pool.prototype._handleInv = function _handleInv(hashes, peer) { * @param {Function} callback */ -Pool.prototype._handleBlock = function _handleBlock(block, peer) { - return spawn(function *() { - var requested; +Pool.prototype._handleBlock = spawn.co(function* _handleBlock(block, peer) { + var requested; - // Fulfill the load request. - requested = this.fulfill(block); + // Fulfill the load request. + requested = this.fulfill(block); - // Someone is sending us blocks without - // us requesting them. - if (!requested) { - peer.invFilter.add(block.hash()); - this.logger.warning( - 'Received unrequested block: %s (%s).', - block.rhash, peer.hostname); - return yield utils.wait(); + // Someone is sending us blocks without + // us requesting them. + if (!requested) { + peer.invFilter.add(block.hash()); + this.logger.warning( + 'Received unrequested block: %s (%s).', + block.rhash, peer.hostname); + return yield utils.wait(); + } + + try { + yield this.chain.add(block); + } catch (err) { + if (err.type !== 'VerifyError') { + this.scheduleRequests(peer); + throw err; } - try { - yield this.chain.add(block); - } catch (err) { - if (err.type !== 'VerifyError') { - this.scheduleRequests(peer); + if (err.score !== -1) + peer.reject(block, err.code, err.reason, err.score); + + if (err.reason === 'bad-prevblk') { + if (this.options.headers) { + peer.setMisbehavior(10); throw err; } - - if (err.score !== -1) - peer.reject(block, err.code, err.reason, err.score); - - if (err.reason === 'bad-prevblk') { - if (this.options.headers) { - peer.setMisbehavior(10); - throw err; - } - this.logger.debug('Peer sent an orphan block. Resolving.'); - yield peer.resolveOrphan(null, block.hash('hex')); - this.scheduleRequests(peer); - throw err; - } - + this.logger.debug('Peer sent an orphan block. Resolving.'); + yield peer.resolveOrphan(null, block.hash('hex')); this.scheduleRequests(peer); throw err; } this.scheduleRequests(peer); + throw err; + } - this.emit('chain-progress', this.chain.getProgress(), peer); + this.scheduleRequests(peer); - if (this.logger.level >= 4 && this.chain.total % 20 === 0) { - this.logger.debug('Status:' - + ' ts=%s height=%d highest=%d progress=%s' - + ' blocks=%d orphans=%d active=%d' - + ' queue=%d target=%s peers=%d' - + ' pending=%d jobs=%d', - utils.date(block.ts), - this.chain.height, - this.chain.bestHeight, - (this.chain.getProgress() * 100).toFixed(2) + '%', - this.chain.total, - this.chain.orphan.count, - this.activeBlocks, - peer.queueBlock.length, - block.bits, - this.peers.all.length, - this.chain.locker.pending.length, - this.chain.locker.jobs.length); - } + this.emit('chain-progress', this.chain.getProgress(), peer); - if (this.chain.total % 2000 === 0) { - this.logger.info( - 'Received 2000 more blocks (height=%d, hash=%s).', - this.chain.height, - block.rhash); - } - }, this); -}; + if (this.logger.level >= 4 && this.chain.total % 20 === 0) { + this.logger.debug('Status:' + + ' ts=%s height=%d highest=%d progress=%s' + + ' blocks=%d orphans=%d active=%d' + + ' queue=%d target=%s peers=%d' + + ' pending=%d jobs=%d', + utils.date(block.ts), + this.chain.height, + this.chain.bestHeight, + (this.chain.getProgress() * 100).toFixed(2) + '%', + this.chain.total, + this.chain.orphan.count, + this.activeBlocks, + peer.queueBlock.length, + block.bits, + this.peers.all.length, + this.chain.locker.pending.length, + this.chain.locker.jobs.length); + } + + if (this.chain.total % 2000 === 0) { + this.logger.info( + 'Received 2000 more blocks (height=%d, hash=%s).', + this.chain.height, + block.rhash); + } +}); /** * Send `mempool` to all peers. @@ -1304,54 +1292,52 @@ Pool.prototype.hasReject = function hasReject(hash) { * @param {Function} callback */ -Pool.prototype._handleTX = function _handleTX(tx, peer) { - return spawn(function *() { - var i, requested, missing; +Pool.prototype._handleTX = spawn.co(function* _handleTX(tx, peer) { + var i, requested, missing; - // Fulfill the load request. - requested = this.fulfill(tx); + // Fulfill the load request. + requested = this.fulfill(tx); - if (!requested) { - peer.invFilter.add(tx.hash()); + if (!requested) { + peer.invFilter.add(tx.hash()); - if (!this.mempool) - this.txFilter.add(tx.hash()); + if (!this.mempool) + this.txFilter.add(tx.hash()); - this.logger.warning('Peer sent unrequested tx: %s (%s).', - tx.rhash, peer.hostname); + this.logger.warning('Peer sent unrequested tx: %s (%s).', + tx.rhash, peer.hostname); - if (this.hasReject(tx.hash())) { - throw new VerifyError(tx, - 'alreadyknown', - 'txn-already-in-mempool', - 0); - } + if (this.hasReject(tx.hash())) { + throw new VerifyError(tx, + 'alreadyknown', + 'txn-already-in-mempool', + 0); } + } - if (!this.mempool) { - this.emit('tx', tx, peer); - return; - } + if (!this.mempool) { + this.emit('tx', tx, peer); + return; + } - try { - missing = yield this.mempool.addTX(tx); - } catch (err) { - if (err.type === 'VerifyError') { - if (err.score !== -1) - peer.reject(tx, err.code, err.reason, err.score); - throw err; - } + try { + missing = yield this.mempool.addTX(tx); + } catch (err) { + if (err.type === 'VerifyError') { + if (err.score !== -1) + peer.reject(tx, err.code, err.reason, err.score); throw err; } + throw err; + } - if (missing) { - for (i = 0; i < missing.length; i++) - yield this.getData(peer, this.txType, missing[i]); - } + if (missing) { + for (i = 0; i < missing.length; i++) + yield this.getData(peer, this.txType, missing[i]); + } - this.emit('tx', tx, peer); - }, this); -}; + this.emit('tx', tx, peer); +}); /** * Create a leech peer from an existing socket. @@ -1517,45 +1503,43 @@ Pool.prototype.watchAddress = function watchAddress(address) { * @param {Function} callback */ -Pool.prototype.getData = function getData(peer, type, hash) { - return spawn(function *() { - var self = this; - var item, exists; +Pool.prototype.getData = spawn.co(function* getData(peer, type, hash) { + var self = this; + var item, exists; - if (!this.loaded) - return; + if (!this.loaded) + return; - exists = yield this.has(peer, type, hash); + exists = yield this.has(peer, type, hash); - if (exists) - return true; + if (exists) + return true; - item = new LoadRequest(this, peer, type, hash); + item = new LoadRequest(this, peer, type, hash); - if (type === this.txType) { - if (peer.queueTX.length === 0) { - utils.nextTick(function() { - self.logger.debug( - 'Requesting %d/%d txs from peer with getdata (%s).', - peer.queueTX.length, - self.activeTX, - peer.hostname); + if (type === this.txType) { + if (peer.queueTX.length === 0) { + utils.nextTick(function() { + self.logger.debug( + 'Requesting %d/%d txs from peer with getdata (%s).', + peer.queueTX.length, + self.activeTX, + peer.hostname); - peer.getData(peer.queueTX); - peer.queueTX.length = 0; - }); - } - - peer.queueTX.push(item.start()); - - return false; + peer.getData(peer.queueTX); + peer.queueTX.length = 0; + }); } - peer.queueBlock.push(item); + peer.queueTX.push(item.start()); return false; - }, this); -}; + } + + peer.queueBlock.push(item); + + return false; +}); /** * Queue a `getdata` request to be sent. Promise @@ -1581,31 +1565,29 @@ Pool.prototype.getDataSync = function getDataSync(peer, type, hash) { * @param {Function} callback - Returns [Error, Boolean]. */ -Pool.prototype.has = function has(peer, type, hash) { - return spawn(function *() { - var exists = yield this.exists(type, hash); +Pool.prototype.has = spawn.co(function* has(peer, type, hash) { + var exists = yield this.exists(type, hash); - if (exists) - return true; + if (exists) + return true; - // Check the pending requests. - if (this.requestMap[hash]) - return true; - - if (type !== this.txType) - return false; - - // If we recently rejected this item. Ignore. - if (this.hasReject(hash)) { - this.logger.spam( - 'Peer sent a known reject of %s (%s).', - utils.revHex(hash), peer.hostname); - return true; - } + // Check the pending requests. + if (this.requestMap[hash]) + return true; + if (type !== this.txType) return false; - }, this); -}; + + // If we recently rejected this item. Ignore. + if (this.hasReject(hash)) { + this.logger.spam( + 'Peer sent a known reject of %s (%s).', + utils.revHex(hash), peer.hostname); + return true; + } + + return false; +}); /** * Test whether the chain or mempool has seen an item. @@ -1873,64 +1855,60 @@ Pool.prototype.isIgnored = function isIgnored(addr) { * @param {Function} callback */ -Pool.prototype.getIP = function getIP() { - return spawn(function *() { - var request, res, ip; +Pool.prototype.getIP = spawn.co(function* getIP() { + var request, res, ip; - if (utils.isBrowser) - throw new Error('Could not find IP.'); + if (utils.isBrowser) + throw new Error('Could not find IP.'); - request = require('../http/request'); + request = require('../http/request'); - try { - res = yield request.promise({ - method: 'GET', - uri: 'http://icanhazip.com', - expect: 'text', - timeout: 3000 - }); - } catch (e) { - return yield this.getIP2(); - } + try { + res = yield request.promise({ + method: 'GET', + uri: 'http://icanhazip.com', + expect: 'text', + timeout: 3000 + }); + } catch (e) { + return yield this.getIP2(); + } - ip = res.body.trim(); + ip = res.body.trim(); - if (IP.version(ip) === -1) - return yield this.getIP2(); + if (IP.version(ip) === -1) + return yield this.getIP2(); - return IP.normalize(ip); - }, this); -}; + return IP.normalize(ip); +}); /** * Attempt to retrieve external IP from dyndns.org. * @param {Function} callback */ -Pool.prototype.getIP2 = function getIP2() { - return spawn(function *() { - var request, res, ip; +Pool.prototype.getIP2 = spawn.co(function* getIP2() { + var request, res, ip; - if (utils.isBrowser) - throw new Error('Could not find IP.'); + if (utils.isBrowser) + throw new Error('Could not find IP.'); - request = require('../http/request'); + request = require('../http/request'); - res = yield request.promise({ - method: 'GET', - uri: 'http://checkip.dyndns.org', - expect: 'html', - timeout: 3000 - }); + res = yield request.promise({ + method: 'GET', + uri: 'http://checkip.dyndns.org', + expect: 'html', + timeout: 3000 + }); - ip = /IP Address:\s*([0-9a-f.:]+)/i.exec(res.body); + ip = /IP Address:\s*([0-9a-f.:]+)/i.exec(res.body); - if (!ip || IP.version(ip[1]) === -1) - throw new Error('Could not find IP.'); + if (!ip || IP.version(ip[1]) === -1) + throw new Error('Could not find IP.'); - return IP.normalize(ip[1]); - }, this); -}; + return IP.normalize(ip[1]); +}); /** * Peer List diff --git a/lib/node/fullnode.js b/lib/node/fullnode.js index ffb678c0..d6a21ed6 100644 --- a/lib/node/fullnode.js +++ b/lib/node/fullnode.js @@ -223,29 +223,27 @@ Fullnode.prototype._init = function _init() { * @param {Function} callback */ -Fullnode.prototype._open = function open() { - return spawn(function *() { - yield this.chain.open(); - yield this.mempool.open(); - yield this.miner.open(); - yield this.pool.open(); - yield this.walletdb.open(); +Fullnode.prototype._open = spawn.co(function* open() { + yield this.chain.open(); + yield this.mempool.open(); + yield this.miner.open(); + yield this.pool.open(); + yield this.walletdb.open(); - // Ensure primary wallet. - yield this.openWallet(); + // Ensure primary wallet. + yield this.openWallet(); - // Rescan for any missed transactions. - yield this.rescan(); + // Rescan for any missed transactions. + yield this.rescan(); - // Rebroadcast pending transactions. - yield this.resend(); + // Rebroadcast pending transactions. + yield this.resend(); - if (this.http) - yield this.http.open(); + if (this.http) + yield this.http.open(); - this.logger.info('Node is loaded.'); - }, this); -}; + this.logger.info('Node is loaded.'); +}); /** * Close the node, wait for the database to close. @@ -253,22 +251,20 @@ Fullnode.prototype._open = function open() { * @param {Function} callback */ -Fullnode.prototype._close = function close() { - return spawn(function *() { - this.wallet = null; +Fullnode.prototype._close = spawn.co(function* close() { + this.wallet = null; - if (this.http) - yield this.http.close(); + if (this.http) + yield this.http.close(); - this.walletdb.close(); - this.pool.close(); - this.miner.close(); - this.mempool.close(); - this.chain.close(); + this.walletdb.close(); + this.pool.close(); + this.miner.close(); + this.mempool.close(); + this.chain.close(); - this.logger.info('Node is closed.'); - }, this); -}; + this.logger.info('Node is closed.'); +}); /** * Rescan for any missed transactions. @@ -309,26 +305,24 @@ Fullnode.prototype.broadcast = function broadcast(item, callback) { * @param {TX} tx */ -Fullnode.prototype.sendTX = function sendTX(tx) { - return spawn(function *() { - try { - yield this.mempool.addTX(tx); - } catch (err) { - if (err.type === 'VerifyError') { - this._error(err); - this.logger.warning('Verification failed for tx: %s.', tx.rhash); - this.logger.warning('Attempting to broadcast anyway...'); - return this.pool.broadcast(tx); - } - throw err; +Fullnode.prototype.sendTX = spawn.co(function* sendTX(tx) { + try { + yield this.mempool.addTX(tx); + } catch (err) { + if (err.type === 'VerifyError') { + this._error(err); + this.logger.warning('Verification failed for tx: %s.', tx.rhash); + this.logger.warning('Attempting to broadcast anyway...'); + return this.pool.broadcast(tx); } + throw err; + } - if (!this.options.selfish) - tx = tx.toInv(); + if (!this.options.selfish) + tx = tx.toInv(); - return this.pool.broadcast(tx); - }, this); -}; + return this.pool.broadcast(tx); +}); /** * Listen on a server socket on @@ -410,24 +404,22 @@ Fullnode.prototype.getCoin = function getCoin(hash, index) { * @param {Function} callback - Returns [Error, {@link Coin}[]]. */ -Fullnode.prototype.getCoinsByAddress = function getCoinsByAddress(addresses) { - return spawn(function *() { - var coins = this.mempool.getCoinsByAddress(addresses); - var i, blockCoins, coin, spent; +Fullnode.prototype.getCoinsByAddress = spawn.co(function* getCoinsByAddress(addresses) { + var coins = this.mempool.getCoinsByAddress(addresses); + var i, blockCoins, coin, spent; - blockCoins = yield this.chain.db.getCoinsByAddress(addresses); + blockCoins = yield this.chain.db.getCoinsByAddress(addresses); - for (i = 0; i < blockCoins.length; i++) { - coin = blockCoins[i]; - spent = this.mempool.isSpent(coin.hash, coin.index); + for (i = 0; i < blockCoins.length; i++) { + coin = blockCoins[i]; + spent = this.mempool.isSpent(coin.hash, coin.index); - if (!spent) - coins.push(coin); - } + if (!spent) + coins.push(coin); + } - return coins; - }, this); -}; + return coins; +}); /** * Retrieve transactions pertaining to an @@ -436,13 +428,11 @@ Fullnode.prototype.getCoinsByAddress = function getCoinsByAddress(addresses) { * @param {Function} callback - Returns [Error, {@link TX}[]]. */ -Fullnode.prototype.getTXByAddress = function getTXByAddress(addresses) { - return spawn(function *() { - var mempool = this.mempool.getTXByAddress(addresses); - var txs = yield this.chain.db.getTXByAddress(addresses); - return mempool.concat(txs); - }, this); -}; +Fullnode.prototype.getTXByAddress = spawn.co(function* getTXByAddress(addresses) { + var mempool = this.mempool.getTXByAddress(addresses); + var txs = yield this.chain.db.getTXByAddress(addresses); + return mempool.concat(txs); +}); /** * Retrieve a transaction from the mempool or chain database. diff --git a/lib/node/node.js b/lib/node/node.js index cfafde0b..7a9ec16b 100644 --- a/lib/node/node.js +++ b/lib/node/node.js @@ -233,33 +233,31 @@ Node.prototype.location = function location(name) { * @param {Function} callback */ -Node.prototype.openWallet = function openWallet() { - return spawn(function *() { - var options, wallet; +Node.prototype.openWallet = spawn.co(function* openWallet() { + var options, wallet; - assert(!this.wallet); + assert(!this.wallet); - options = { - id: 'primary', - passphrase: this.options.passphrase - }; + options = { + id: 'primary', + passphrase: this.options.passphrase + }; - wallet = yield this.walletdb.ensure(options); + wallet = yield this.walletdb.ensure(options); - this.logger.info( - 'Loaded wallet with id=%s wid=%d address=%s', - wallet.id, wallet.wid, wallet.getAddress()); + this.logger.info( + 'Loaded wallet with id=%s wid=%d address=%s', + wallet.id, wallet.wid, wallet.getAddress()); - // Set the miner payout address if the - // programmer didn't pass one in. - if (this.miner) { - if (!this.options.payoutAddress) - this.miner.address = wallet.getAddress(); - } + // Set the miner payout address if the + // programmer didn't pass one in. + if (this.miner) { + if (!this.options.payoutAddress) + this.miner.address = wallet.getAddress(); + } - this.wallet = wallet; - }, this); -}; + this.wallet = wallet; +}); /** * Resend all pending transactions. diff --git a/lib/node/spvnode.js b/lib/node/spvnode.js index 9f694485..8d42ee96 100644 --- a/lib/node/spvnode.js +++ b/lib/node/spvnode.js @@ -147,30 +147,28 @@ SPVNode.prototype._init = function _init() { * @param {Function} callback */ -SPVNode.prototype._open = function open(callback) { - return spawn(function *() { - yield this.chain.open(); - yield this.pool.open(); - yield this.walletdb.open(); +SPVNode.prototype._open = spawn.co(function* open(callback) { + yield this.chain.open(); + yield this.pool.open(); + yield this.walletdb.open(); - // Ensure primary wallet. - yield this.openWallet(); + // Ensure primary wallet. + yield this.openWallet(); - // Load bloom filter. - yield this.openFilter(); + // Load bloom filter. + yield this.openFilter(); - // Rescan for any missed transactions. - yield this.rescan(); + // Rescan for any missed transactions. + yield this.rescan(); - // Rebroadcast pending transactions. - yield this.resend(); + // Rebroadcast pending transactions. + yield this.resend(); - if (this.http) - yield this.http.open(); + if (this.http) + yield this.http.open(); - this.logger.info('Node is loaded.'); - }, this); -}; + this.logger.info('Node is loaded.'); +}); /** * Close the node, wait for the database to close. @@ -178,34 +176,30 @@ SPVNode.prototype._open = function open(callback) { * @param {Function} callback */ -SPVNode.prototype._close = function close() { - return spawn(function *() { - this.wallet = null; - if (this.http) - yield this.http.close(); - yield this.walletdb.close(); - yield this.pool.close(); - yield this.chain.close(); - }, this); -}; +SPVNode.prototype._close = spawn.co(function* close() { + this.wallet = null; + if (this.http) + yield this.http.close(); + yield this.walletdb.close(); + yield this.pool.close(); + yield this.chain.close(); +}); /** * Initialize p2p bloom filter for address watching. * @param {Function} callback */ -SPVNode.prototype.openFilter = function openFilter() { - return spawn(function *() { - var hashes = yield this.walletdb.getAddressHashes(); - var i; +SPVNode.prototype.openFilter = spawn.co(function* openFilter() { + var hashes = yield this.walletdb.getAddressHashes(); + var i; - if (hashes.length > 0) - this.logger.info('Adding %d addresses to filter.', hashes.length); + if (hashes.length > 0) + this.logger.info('Adding %d addresses to filter.', hashes.length); - for (i = 0; i < hashes.length; i++) - this.pool.watch(hashes[i], 'hex'); - }, this); -}; + for (i = 0; i < hashes.length; i++) + this.pool.watch(hashes[i], 'hex'); +}); /** * Rescan for any missed transactions. diff --git a/lib/utils/async.js b/lib/utils/async.js index e62a4b03..29087ed2 100644 --- a/lib/utils/async.js +++ b/lib/utils/async.js @@ -53,98 +53,94 @@ AsyncObject.prototype._onClose = function _onClose() { }); }; -AsyncObject.prototype.open = function open() { - return spawn(function *() { - var err, unlock; +AsyncObject.prototype.open = spawn.co(function* open() { + var err, unlock; - assert(!this.closing, 'Cannot open while closing.'); + assert(!this.closing, 'Cannot open while closing.'); - if (this.loaded) - return yield wait(); + if (this.loaded) + return yield wait(); - if (this.loading) - return yield this._onOpen(); + if (this.loading) + return yield this._onOpen(); - if (this.locker) - unlock = yield this.locker.lock(); + if (this.locker) + unlock = yield this.locker.lock(); - this.emit('preopen'); + this.emit('preopen'); - this.loading = true; + this.loading = true; - try { - yield this._open(); - } catch (e) { - err = e; - } + try { + yield this._open(); + } catch (e) { + err = e; + } - yield wait(); - - if (err) { - this.loading = false; - this._error('open', err); - if (unlock) - unlock(); - throw err; - } + yield wait(); + if (err) { this.loading = false; - this.loaded = true; - this.emit('open'); - + this._error('open', err); if (unlock) unlock(); - }, this); -}; + throw err; + } + + this.loading = false; + this.loaded = true; + this.emit('open'); + + if (unlock) + unlock(); +}); /** * Close the object (recallable). * @param {Function} callback */ -AsyncObject.prototype.close = function close() { - return spawn(function *() { - var unlock, err; +AsyncObject.prototype.close = spawn.co(function* close() { + var unlock, err; - assert(!this.loading, 'Cannot close while loading.'); + assert(!this.loading, 'Cannot close while loading.'); - if (!this.loaded) - return yield wait(); + if (!this.loaded) + return yield wait(); - if (this.closing) - return yield this._onClose(); + if (this.closing) + return yield this._onClose(); - if (this.locker) - unlock = yield this.locker.lock(); + if (this.locker) + unlock = yield this.locker.lock(); - this.emit('preclose'); + this.emit('preclose'); - this.closing = true; - this.loaded = false; + this.closing = true; + this.loaded = false; - try { - yield this._close(); - } catch (e) { - err = e; - } + try { + yield this._close(); + } catch (e) { + err = e; + } - yield wait(); - - if (err) { - this.closing = false; - this._error('close', err); - if (unlock) - unlock(); - throw err; - } + yield wait(); + if (err) { this.closing = false; - this.emit('close'); - + this._error('close', err); if (unlock) unlock(); - }, this); -}; + throw err; + } + + this.closing = false; + this.emit('close'); + + if (unlock) + unlock(); +}); /** * Close the object (recallable). diff --git a/lib/utils/spawn.js b/lib/utils/spawn.js index 5c4121bd..4cdff405 100644 --- a/lib/utils/spawn.js +++ b/lib/utils/spawn.js @@ -1,11 +1,7 @@ 'use strict'; -// See: https://github.com/yoursnetwork/asink - -function spawn(generator, self) { +function exec(gen) { return new Promise(function(resolve, reject) { - var gen = generator.call(self); - function step(value, rejection) { var next; @@ -38,4 +34,18 @@ function spawn(generator, self) { }); } +function spawn(generator, self) { + var gen = generator.call(self); + return exec(gen); +} + +function co(generator) { + return function() { + var gen = generator.apply(this, arguments); + return exec(gen); + }; +} + +spawn.co = co; + module.exports = spawn; diff --git a/lib/wallet/account.js b/lib/wallet/account.js index 79e60478..efedd974 100644 --- a/lib/wallet/account.js +++ b/lib/wallet/account.js @@ -204,22 +204,20 @@ Account.MAX_LOOKAHEAD = 5; * @param {Function} callback */ -Account.prototype.init = function init() { - return spawn(function *() { - // Waiting for more keys. - if (this.keys.length !== this.n - 1) { - assert(!this.initialized); - this.save(); - return; - } +Account.prototype.init = spawn.co(function* init() { + // Waiting for more keys. + if (this.keys.length !== this.n - 1) { + assert(!this.initialized); + this.save(); + return; + } - assert(this.receiveDepth === 0); - assert(this.changeDepth === 0); + assert(this.receiveDepth === 0); + assert(this.changeDepth === 0); - this.initialized = true; - yield this.setDepth(1, 1); - }, this); -}; + this.initialized = true; + yield this.setDepth(1, 1); +}); /** * Open the account (done after retrieval). @@ -306,30 +304,28 @@ Account.prototype.spliceKey = function spliceKey(key) { * @param {Function} callback */ -Account.prototype.addKey = function addKey(key) { - return spawn(function *() { - var result = false; - var exists; +Account.prototype.addKey = spawn.co(function* addKey(key) { + var result = false; + var exists; - try { - result = this.pushKey(key); - } catch (e) { - throw e; - } + try { + result = this.pushKey(key); + } catch (e) { + throw e; + } - exists = yield this._checkKeys(); + exists = yield this._checkKeys(); - if (exists) { - this.spliceKey(key); - throw new Error('Cannot add a key from another account.'); - } + if (exists) { + this.spliceKey(key); + throw new Error('Cannot add a key from another account.'); + } - // Try to initialize again. - yield this.init(); + // Try to initialize again. + yield this.init(); - return result; - }, this); -}; + return result; +}); /** * Ensure accounts are not sharing keys. @@ -337,27 +333,25 @@ Account.prototype.addKey = function addKey(key) { * @param {Function} callback */ -Account.prototype._checkKeys = function _checkKeys() { - return spawn(function *() { - var ring, hash, paths; +Account.prototype._checkKeys = spawn.co(function* _checkKeys() { + var ring, hash, paths; - if (this.initialized || this.type !== Account.types.MULTISIG) - return false; + if (this.initialized || this.type !== Account.types.MULTISIG) + return false; - if (this.keys.length !== this.n - 1) - return false; + if (this.keys.length !== this.n - 1) + return false; - ring = this.deriveReceive(0); - hash = ring.getScriptHash('hex'); + ring = this.deriveReceive(0); + hash = ring.getScriptHash('hex'); - paths = yield this.db.getAddressPaths(hash); + paths = yield this.db.getAddressPaths(hash); - if (!paths) - return false; + if (!paths) + return false; - return paths[this.wid] != null; - }, this); -}; + return paths[this.wid] != null; +}); /** * Remove a public account key from the account (multisig). @@ -404,29 +398,27 @@ Account.prototype.createChange = function createChange() { * @param {Function} callback - Returns [Error, {@link KeyRing}]. */ -Account.prototype.createAddress = function createAddress(change) { - return spawn(function *() { - var ring, lookahead; +Account.prototype.createAddress = spawn.co(function* createAddress(change) { + var ring, lookahead; - if (change) { - ring = this.deriveChange(this.changeDepth); - lookahead = this.deriveChange(this.changeDepth + this.lookahead); - this.changeDepth++; - this.changeAddress = ring; - } else { - ring = this.deriveReceive(this.receiveDepth); - lookahead = this.deriveReceive(this.receiveDepth + this.lookahead); - this.receiveDepth++; - this.receiveAddress = ring; - } + if (change) { + ring = this.deriveChange(this.changeDepth); + lookahead = this.deriveChange(this.changeDepth + this.lookahead); + this.changeDepth++; + this.changeAddress = ring; + } else { + ring = this.deriveReceive(this.receiveDepth); + lookahead = this.deriveReceive(this.receiveDepth + this.lookahead); + this.receiveDepth++; + this.receiveAddress = ring; + } - yield this.saveAddress([ring, lookahead]); + yield this.saveAddress([ring, lookahead]); - this.save(); + this.save(); - return ring; - }, this); -}; + return ring; +}); /** * Derive a receiving address at `index`. Do not increment depth. @@ -568,47 +560,45 @@ Account.prototype.saveAddress = function saveAddress(rings) { * @param {Function} callback - Returns [Error, {@link KeyRing}, {@link KeyRing}]. */ -Account.prototype.setDepth = function setDepth(receiveDepth, changeDepth) { - return spawn(function *() { - var rings = []; - var i, receive, change; +Account.prototype.setDepth = spawn.co(function* setDepth(receiveDepth, changeDepth) { + var rings = []; + var i, receive, change; - if (receiveDepth > this.receiveDepth) { - for (i = this.receiveDepth; i < receiveDepth; i++) { - receive = this.deriveReceive(i); - rings.push(receive); - } - - for (i = receiveDepth; i < receiveDepth + this.lookahead; i++) - rings.push(this.deriveReceive(i)); - - this.receiveAddress = receive; - this.receiveDepth = receiveDepth; + if (receiveDepth > this.receiveDepth) { + for (i = this.receiveDepth; i < receiveDepth; i++) { + receive = this.deriveReceive(i); + rings.push(receive); } - if (changeDepth > this.changeDepth) { - for (i = this.changeDepth; i < changeDepth; i++) { - change = this.deriveChange(i); - rings.push(change); - } + for (i = receiveDepth; i < receiveDepth + this.lookahead; i++) + rings.push(this.deriveReceive(i)); - for (i = changeDepth; i < changeDepth + this.lookahead; i++) - rings.push(this.deriveChange(i)); + this.receiveAddress = receive; + this.receiveDepth = receiveDepth; + } - this.changeAddress = change; - this.changeDepth = changeDepth; + if (changeDepth > this.changeDepth) { + for (i = this.changeDepth; i < changeDepth; i++) { + change = this.deriveChange(i); + rings.push(change); } - if (rings.length === 0) - return []; + for (i = changeDepth; i < changeDepth + this.lookahead; i++) + rings.push(this.deriveChange(i)); - yield this.saveAddress(rings); + this.changeAddress = change; + this.changeDepth = changeDepth; + } - this.save(); + if (rings.length === 0) + return []; - return [receive, change]; - }, this); -}; + yield this.saveAddress(rings); + + this.save(); + + return [receive, change]; +}); /** * Convert the account to a more inspection-friendly object. diff --git a/lib/wallet/txdb.js b/lib/wallet/txdb.js index 2decc64f..e4e036dc 100644 --- a/lib/wallet/txdb.js +++ b/lib/wallet/txdb.js @@ -229,17 +229,15 @@ TXDB.layout = layout; * @param {Function} callback */ -TXDB.prototype.open = function open() { - return spawn(function *() { - this.balance = yield this.getBalance(); - this.logger.info('TXDB loaded for %s.', this.wallet.id); - this.logger.info( - 'Balance: unconfirmed=%s confirmed=%s total=%s.', - utils.btc(this.balance.unconfirmed), - utils.btc(this.balance.confirmed), - utils.btc(this.balance.total)); - }, this); -}; +TXDB.prototype.open = spawn.co(function* open() { + this.balance = yield this.getBalance(); + this.logger.info('TXDB loaded for %s.', this.wallet.id); + this.logger.info( + 'Balance: unconfirmed=%s confirmed=%s total=%s.', + utils.btc(this.balance.unconfirmed), + utils.btc(this.balance.confirmed), + utils.btc(this.balance.total)); +}); /** * Emit transaction event. @@ -374,18 +372,16 @@ TXDB.prototype.iterate = function iterate(options) { * @param {Function} callback */ -TXDB.prototype.commit = function commit() { - return spawn(function *() { - assert(this.current); - try { - yield this.current.write(); - } catch (e) { - this.current = null; - throw e; - } +TXDB.prototype.commit = spawn.co(function* commit() { + assert(this.current); + try { + yield this.current.write(); + } catch (e) { this.current = null; - }, this); -}; + throw e; + } + this.current = null; +}); /** * Map a transactions' addresses to wallet IDs. @@ -406,20 +402,18 @@ TXDB.prototype.getInfo = function getInfo(tx) { * @param {Function} callback - Returns [Error, Buffer]. */ -TXDB.prototype._addOrphan = function _addOrphan(prevout, spender) { - return spawn(function *() { - var p = new BufferWriter(); - var key = layout.o(prevout.hash, prevout.index); - var data = yield this.get(key); +TXDB.prototype._addOrphan = spawn.co(function* _addOrphan(prevout, spender) { + var p = new BufferWriter(); + var key = layout.o(prevout.hash, prevout.index); + var data = yield this.get(key); - if (data) - p.writeBytes(data); + if (data) + p.writeBytes(data); - p.writeBytes(spender); + p.writeBytes(spender); - this.put(key, p.render()); - }, this); -}; + this.put(key, p.render()); +}); /** * Retrieve orphan list by coin ID. @@ -429,33 +423,31 @@ TXDB.prototype._addOrphan = function _addOrphan(prevout, spender) { * @param {Function} callback - Returns [Error, {@link Orphan}]. */ -TXDB.prototype._getOrphans = function _getOrphans(hash, index) { - return spawn(function *() { - var items = []; - var i, orphans, orphan, tx; +TXDB.prototype._getOrphans = spawn.co(function* _getOrphans(hash, index) { + var items = []; + var i, orphans, orphan, tx; - orphans = yield this.fetch(layout.o(hash, index), function(data) { - var p = new BufferReader(data); - var orphans = []; + orphans = yield this.fetch(layout.o(hash, index), function(data) { + var p = new BufferReader(data); + var orphans = []; - while (p.left()) - orphans.push(bcoin.outpoint.fromRaw(p)); + while (p.left()) + orphans.push(bcoin.outpoint.fromRaw(p)); - return orphans; - }); + return orphans; + }); - if (!orphans) - return; + if (!orphans) + return; - for (i = 0; i < orphans.length; i++) { - orphan = orphans[i]; - tx = yield this.getTX(orphan.hash); - items.push([orphan, tx]); - } + for (i = 0; i < orphans.length; i++) { + orphan = orphans[i]; + tx = yield this.getTX(orphan.hash); + items.push([orphan, tx]); + } - return items; - }, this); -}; + return items; +}); /** * Retrieve coins for own inputs, remove @@ -466,52 +458,26 @@ TXDB.prototype._getOrphans = function _getOrphans(hash, index) { * @param {Function} callback - Returns [Error]. */ -TXDB.prototype._verify = function _verify(tx, info) { - return spawn(function *() { - var i, input, prevout, address, coin, spent, rtx, rinfo, result; +TXDB.prototype._verify = spawn.co(function* _verify(tx, info) { + var i, input, prevout, address, coin, spent, rtx, rinfo, result; - for (i = 0; i < tx.inputs.length; i++) { - input = tx.inputs[i]; - prevout = input.prevout; + for (i = 0; i < tx.inputs.length; i++) { + input = tx.inputs[i]; + prevout = input.prevout; - if (tx.isCoinbase()) - continue; + if (tx.isCoinbase()) + continue; - address = input.getHash('hex'); + address = input.getHash('hex'); - // Only bother if this input is ours. - if (!info.hasPath(address)) - continue; + // Only bother if this input is ours. + if (!info.hasPath(address)) + continue; - coin = yield this.getCoin(prevout.hash, prevout.index); - - if (coin) { - // Add TX to inputs and spend money - input.coin = coin; - - // Skip invalid transactions - if (this.options.verify) { - if (!tx.verifyInput(i)) - return false; - } - - continue; - } - - input.coin = null; - - spent = yield this.isSpent(prevout.hash, prevout.index); - - // Are we double-spending? - // Replace older txs with newer ones. - if (!spent) - continue; - - coin = yield this.getSpentCoin(spent, prevout); - - if (!coin) - throw new Error('Could not find double-spent coin.'); + coin = yield this.getCoin(prevout.hash, prevout.index); + if (coin) { + // Add TX to inputs and spend money input.coin = coin; // Skip invalid transactions @@ -520,26 +486,50 @@ TXDB.prototype._verify = function _verify(tx, info) { return false; } - this.logger.warning('Removing conflicting tx: %s.', - utils.revHex(spent.hash)); - - result = yield this._removeConflict(spent.hash, tx); - - // Spender was not removed, the current - // transaction is not elligible to be added. - if (!result) - return false; - - rtx = result[0]; - rinfo = result[1]; - - // Emit the _removed_ transaction. - this.emit('conflict', rtx, rinfo); + continue; } - return true; - }, this); -}; + input.coin = null; + + spent = yield this.isSpent(prevout.hash, prevout.index); + + // Are we double-spending? + // Replace older txs with newer ones. + if (!spent) + continue; + + coin = yield this.getSpentCoin(spent, prevout); + + if (!coin) + throw new Error('Could not find double-spent coin.'); + + input.coin = coin; + + // Skip invalid transactions + if (this.options.verify) { + if (!tx.verifyInput(i)) + return false; + } + + this.logger.warning('Removing conflicting tx: %s.', + utils.revHex(spent.hash)); + + result = yield this._removeConflict(spent.hash, tx); + + // Spender was not removed, the current + // transaction is not elligible to be added. + if (!result) + return false; + + rtx = result[0]; + rinfo = result[1]; + + // Emit the _removed_ transaction. + this.emit('conflict', rtx, rinfo); + } + + return true; +}); /** * Attempt to resolve orphans for an output. @@ -549,51 +539,49 @@ TXDB.prototype._verify = function _verify(tx, info) { * @param {Function} callback */ -TXDB.prototype._resolveOrphans = function _resolveOrphans(tx, index) { - return spawn(function *() { - var hash = tx.hash('hex'); - var i, orphans, coin, item, input, orphan; +TXDB.prototype._resolveOrphans = spawn.co(function* _resolveOrphans(tx, index) { + var hash = tx.hash('hex'); + var i, orphans, coin, item, input, orphan; - orphans = yield this._getOrphans(hash, index); + orphans = yield this._getOrphans(hash, index); - if (!orphans) - return false; + if (!orphans) + return false; - this.del(layout.o(hash, index)); + this.del(layout.o(hash, index)); - coin = bcoin.coin.fromTX(tx, index); + coin = bcoin.coin.fromTX(tx, index); - // Add input to orphan - for (i = 0; i < orphans.length; i++) { - item = orphans[i]; - input = item[0]; - orphan = item[1]; + // Add input to orphan + for (i = 0; i < orphans.length; i++) { + item = orphans[i]; + input = item[0]; + orphan = item[1]; - // Probably removed by some other means. - if (!orphan) - continue; + // Probably removed by some other means. + if (!orphan) + continue; - orphan.inputs[input.index].coin = coin; + orphan.inputs[input.index].coin = coin; - assert(orphan.inputs[input.index].prevout.hash === hash); - assert(orphan.inputs[input.index].prevout.index === index); + assert(orphan.inputs[input.index].prevout.hash === hash); + assert(orphan.inputs[input.index].prevout.index === index); - // Verify that input script is correct, if not - add - // output to unspent and remove orphan from storage - if (!this.options.verify || orphan.verifyInput(input.index)) { - this.put(layout.d(input.hash, input.index), coin.toRaw()); - return true; - } - - yield this._lazyRemove(orphan); + // Verify that input script is correct, if not - add + // output to unspent and remove orphan from storage + if (!this.options.verify || orphan.verifyInput(input.index)) { + this.put(layout.d(input.hash, input.index), coin.toRaw()); + return true; } - // Just going to be added again outside. - this.balance.sub(coin); + yield this._lazyRemove(orphan); + } - return false; - }, this); -}; + // Just going to be added again outside. + this.balance.sub(coin); + + return false; +}); /** * Add transaction, runs _confirm (separate batch) and @@ -604,146 +592,144 @@ TXDB.prototype._resolveOrphans = function _resolveOrphans(tx, index) { * @param {Function} callback */ -TXDB.prototype.add = function add(tx, info) { - return spawn(function *() { - var unlock = yield this._lock(); - var hash, path, account; - var i, result, input, output, coin; - var prevout, key, address, spender, orphans; +TXDB.prototype.add = spawn.co(function* add(tx, info) { + var unlock = yield this._lock(); + var hash, path, account; + var i, result, input, output, coin; + var prevout, key, address, spender, orphans; - assert(!tx.mutable, 'Cannot add mutable TX to wallet.'); + assert(!tx.mutable, 'Cannot add mutable TX to wallet.'); - // Attempt to confirm tx before adding it. - result = yield this._confirm(tx, info); + // Attempt to confirm tx before adding it. + result = yield this._confirm(tx, info); - // Ignore if we already have this tx. - if (result) { - unlock(); - return true; - } + // Ignore if we already have this tx. + if (result) { + unlock(); + return true; + } - result = yield this._verify(tx, info); + result = yield this._verify(tx, info); - if (!result) { - unlock(); - return false; - } + if (!result) { + unlock(); + return false; + } - hash = tx.hash('hex'); + hash = tx.hash('hex'); - this.start(); - this.put(layout.t(hash), tx.toExtended()); + this.start(); + this.put(layout.t(hash), tx.toExtended()); + if (tx.ts === 0) + this.put(layout.p(hash), DUMMY); + else + this.put(layout.h(tx.height, hash), DUMMY); + + this.put(layout.m(tx.ps, hash), DUMMY); + + for (i = 0; i < info.accounts.length; i++) { + account = info.accounts[i]; + this.put(layout.T(account, hash), DUMMY); if (tx.ts === 0) - this.put(layout.p(hash), DUMMY); + this.put(layout.P(account, hash), DUMMY); else - this.put(layout.h(tx.height, hash), DUMMY); + this.put(layout.H(account, tx.height, hash), DUMMY); + this.put(layout.M(account, tx.ps, hash), DUMMY); + } - this.put(layout.m(tx.ps, hash), DUMMY); + // Consume unspent money or add orphans + for (i = 0; i < tx.inputs.length; i++) { + input = tx.inputs[i]; + prevout = input.prevout; - for (i = 0; i < info.accounts.length; i++) { - account = info.accounts[i]; - this.put(layout.T(account, hash), DUMMY); - if (tx.ts === 0) - this.put(layout.P(account, hash), DUMMY); - else - this.put(layout.H(account, tx.height, hash), DUMMY); - this.put(layout.M(account, tx.ps, hash), DUMMY); - } + if (tx.isCoinbase()) + continue; - // Consume unspent money or add orphans - for (i = 0; i < tx.inputs.length; i++) { - input = tx.inputs[i]; - prevout = input.prevout; + address = input.getHash('hex'); + path = info.getPath(address); - if (tx.isCoinbase()) - continue; + // Only bother if this input is ours. + if (!path) + continue; - address = input.getHash('hex'); - path = info.getPath(address); + key = prevout.hash + prevout.index; - // Only bother if this input is ours. - if (!path) - continue; - - key = prevout.hash + prevout.index; - - // s/[outpoint-key] -> [spender-hash]|[spender-input-index] - spender = bcoin.outpoint.fromTX(tx, i).toRaw(); - this.put(layout.s(prevout.hash, prevout.index), spender); - - // Add orphan, if no parent transaction is yet known - if (!input.coin) { - try { - yield this._addOrphan(prevout, spender); - } catch (e) { - this.drop(); - unlock(); - throw e; - } - continue; - } - - this.del(layout.c(prevout.hash, prevout.index)); - this.del(layout.C(path.account, prevout.hash, prevout.index)); - this.put(layout.d(hash, i), input.coin.toRaw()); - this.balance.sub(input.coin); - - this.coinCache.remove(key); - } - - // Add unspent outputs or resolve orphans - for (i = 0; i < tx.outputs.length; i++) { - output = tx.outputs[i]; - address = output.getHash('hex'); - key = hash + i; - - path = info.getPath(address); - - // Do not add unspents for outputs that aren't ours. - if (!path) - continue; + // s/[outpoint-key] -> [spender-hash]|[spender-input-index] + spender = bcoin.outpoint.fromTX(tx, i).toRaw(); + this.put(layout.s(prevout.hash, prevout.index), spender); + // Add orphan, if no parent transaction is yet known + if (!input.coin) { try { - orphans = yield this._resolveOrphans(tx, i); + yield this._addOrphan(prevout, spender); } catch (e) { this.drop(); unlock(); throw e; } - - if (orphans) - continue; - - coin = bcoin.coin.fromTX(tx, i); - this.balance.add(coin); - coin = coin.toRaw(); - - this.put(layout.c(hash, i), coin); - this.put(layout.C(path.account, hash, i), DUMMY); - - this.coinCache.set(key, coin); + continue; } + this.del(layout.c(prevout.hash, prevout.index)); + this.del(layout.C(path.account, prevout.hash, prevout.index)); + this.put(layout.d(hash, i), input.coin.toRaw()); + this.balance.sub(input.coin); + + this.coinCache.remove(key); + } + + // Add unspent outputs or resolve orphans + for (i = 0; i < tx.outputs.length; i++) { + output = tx.outputs[i]; + address = output.getHash('hex'); + key = hash + i; + + path = info.getPath(address); + + // Do not add unspents for outputs that aren't ours. + if (!path) + continue; + try { - yield this.commit(); + orphans = yield this._resolveOrphans(tx, i); } catch (e) { + this.drop(); unlock(); throw e; } - // Clear any locked coins to free up memory. - this.unlockTX(tx); + if (orphans) + continue; - this.emit('tx', tx, info); + coin = bcoin.coin.fromTX(tx, i); + this.balance.add(coin); + coin = coin.toRaw(); - if (tx.ts !== 0) - this.emit('confirmed', tx, info); + this.put(layout.c(hash, i), coin); + this.put(layout.C(path.account, hash, i), DUMMY); + this.coinCache.set(key, coin); + } + + try { + yield this.commit(); + } catch (e) { unlock(); - return true; - }, this); -}; + throw e; + } + + // Clear any locked coins to free up memory. + this.unlockTX(tx); + + this.emit('tx', tx, info); + + if (tx.ts !== 0) + this.emit('confirmed', tx, info); + + unlock(); + return true; +}); /** * Remove spenders that have not been confirmed. We do this in the @@ -757,41 +743,39 @@ TXDB.prototype.add = function add(tx, info) { * @param {Function} callback - Returns [Error, Boolean]. */ -TXDB.prototype._removeConflict = function _removeConflict(hash, ref) { - return spawn(function *() { - var tx = yield this.getTX(hash); - var info; +TXDB.prototype._removeConflict = spawn.co(function* _removeConflict(hash, ref) { + var tx = yield this.getTX(hash); + var info; - if (!tx) - throw new Error('Could not find spender.'); + if (!tx) + throw new Error('Could not find spender.'); - if (tx.ts !== 0) { - // If spender is confirmed and replacement - // is not confirmed, do nothing. - if (ref.ts === 0) - return; + if (tx.ts !== 0) { + // If spender is confirmed and replacement + // is not confirmed, do nothing. + if (ref.ts === 0) + return; - // If both are confirmed but replacement - // is older than spender, do nothing. - if (ref.ts < tx.ts) - return; - } else { - // If spender is unconfirmed and replacement - // is confirmed, do nothing. - if (ref.ts !== 0) - return; + // If both are confirmed but replacement + // is older than spender, do nothing. + if (ref.ts < tx.ts) + return; + } else { + // If spender is unconfirmed and replacement + // is confirmed, do nothing. + if (ref.ts !== 0) + return; - // If both are unconfirmed but replacement - // is older than spender, do nothing. - if (ref.ps < tx.ps) - return; - } + // If both are unconfirmed but replacement + // is older than spender, do nothing. + if (ref.ps < tx.ps) + return; + } - info = yield this._removeRecursive(tx); + info = yield this._removeRecursive(tx); - return [tx, info]; - }, this); -}; + return [tx, info]; +}); /** * Remove a transaction and recursively @@ -801,45 +785,43 @@ TXDB.prototype._removeConflict = function _removeConflict(hash, ref) { * @param {Function} callback - Returns [Error, Boolean]. */ -TXDB.prototype._removeRecursive = function _removeRecursive(tx) { - return spawn(function *() { - var hash = tx.hash('hex'); - var i, spent, stx, info; +TXDB.prototype._removeRecursive = spawn.co(function* _removeRecursive(tx) { + var hash = tx.hash('hex'); + var i, spent, stx, info; - for (i = 0; i < tx.outputs.length; i++) { - spent = yield this.isSpent(hash, i); - if (!spent) - continue; + for (i = 0; i < tx.outputs.length; i++) { + spent = yield this.isSpent(hash, i); + if (!spent) + continue; - // Remove all of the spender's spenders first. - stx = yield this.getTX(spent.hash); + // Remove all of the spender's spenders first. + stx = yield this.getTX(spent.hash); - if (!stx) - throw new Error('Could not find spender.'); + if (!stx) + throw new Error('Could not find spender.'); - yield this._removeRecursive(stx); - } + yield this._removeRecursive(stx); + } - this.start(); + this.start(); - // Remove the spender. - try { - info = yield this._lazyRemove(tx); - } catch (e) { - this.drop(); - throw e; - } + // Remove the spender. + try { + info = yield this._lazyRemove(tx); + } catch (e) { + this.drop(); + throw e; + } - if (!info) { - this.drop(); - throw new Error('Cannot remove spender.'); - } + if (!info) { + this.drop(); + throw new Error('Cannot remove spender.'); + } - yield this.commit(); + yield this.commit(); - return info; - }, this); -}; + return info; +}); /** * Test an entire transaction to see @@ -848,21 +830,19 @@ TXDB.prototype._removeRecursive = function _removeRecursive(tx) { * @param {Function} callback - Returns [Error, Boolean]. */ -TXDB.prototype.isDoubleSpend = function isDoubleSpend(tx) { - return spawn(function *() { - var i, input, prevout, spent; +TXDB.prototype.isDoubleSpend = spawn.co(function* isDoubleSpend(tx) { + var i, input, prevout, spent; - for (i = 0; i < tx.inputs.length; i++) { - input = tx.inputs[i]; - prevout = input.prevout; - spent = yield this.isSpent(prevout.hash, prevout.index); - if (spent) - return true; - } + for (i = 0; i < tx.inputs.length; i++) { + input = tx.inputs[i]; + prevout = input.prevout; + spent = yield this.isSpent(prevout.hash, prevout.index); + if (spent) + return true; + } - return false; - }, this); -}; + return false; +}); /** * Test a whether a coin has been spent. @@ -888,95 +868,93 @@ TXDB.prototype.isSpent = function isSpent(hash, index) { * transaction was confirmed, or should be ignored. */ -TXDB.prototype._confirm = function _confirm(tx, info) { - return spawn(function *() { - var hash = tx.hash('hex'); - var i, account, existing, output, coin; - var address, key; +TXDB.prototype._confirm = spawn.co(function* _confirm(tx, info) { + var hash = tx.hash('hex'); + var i, account, existing, output, coin; + var address, key; - existing = yield this.getTX(hash); + existing = yield this.getTX(hash); - // Haven't seen this tx before, add it. - if (!existing) - return false; + // Haven't seen this tx before, add it. + if (!existing) + return false; - // Existing tx is already confirmed. Ignore. - if (existing.ts !== 0) - return true; + // Existing tx is already confirmed. Ignore. + if (existing.ts !== 0) + return true; - // The incoming tx won't confirm the - // existing one anyway. Ignore. - if (tx.ts === 0) - return true; + // The incoming tx won't confirm the + // existing one anyway. Ignore. + if (tx.ts === 0) + return true; - // Tricky - update the tx and coin in storage, - // and remove pending flag to mark as confirmed. - assert(tx.height >= 0); + // Tricky - update the tx and coin in storage, + // and remove pending flag to mark as confirmed. + assert(tx.height >= 0); - // Clear any locked coins to free up memory. - this.unlockTX(tx); + // Clear any locked coins to free up memory. + this.unlockTX(tx); - // Save the original received time. - tx.ps = existing.ps; + // Save the original received time. + tx.ps = existing.ps; - this.start(); + this.start(); - this.put(layout.t(hash), tx.toExtended()); + this.put(layout.t(hash), tx.toExtended()); - this.del(layout.p(hash)); - this.put(layout.h(tx.height, hash), DUMMY); + this.del(layout.p(hash)); + this.put(layout.h(tx.height, hash), DUMMY); - for (i = 0; i < info.accounts.length; i++) { - account = info.accounts[i]; - this.del(layout.P(account, hash)); - this.put(layout.H(account, tx.height, hash), DUMMY); + for (i = 0; i < info.accounts.length; i++) { + account = info.accounts[i]; + this.del(layout.P(account, hash)); + this.put(layout.H(account, tx.height, hash), DUMMY); + } + + for (i = 0; i < tx.outputs.length; i++) { + output = tx.outputs[i]; + address = output.getHash('hex'); + key = hash + i; + + // Only update coins if this output is ours. + if (!info.hasPath(address)) + continue; + + try { + coin = yield this.getCoin(hash, i); + } catch (e) { + this.drop(); + throw e; } - for (i = 0; i < tx.outputs.length; i++) { - output = tx.outputs[i]; - address = output.getHash('hex'); - key = hash + i; - - // Only update coins if this output is ours. - if (!info.hasPath(address)) - continue; - + // Update spent coin. + if (!coin) { try { - coin = yield this.getCoin(hash, i); + yield this.updateSpentCoin(tx, i); } catch (e) { this.drop(); throw e; } - - // Update spent coin. - if (!coin) { - try { - yield this.updateSpentCoin(tx, i); - } catch (e) { - this.drop(); - throw e; - } - continue; - } - - this.balance.confirm(coin.value); - - coin.height = tx.height; - coin = coin.toRaw(); - - this.put(layout.c(hash, i), coin); - - this.coinCache.set(key, coin); + continue; } - yield this.commit(); + this.balance.confirm(coin.value); - this.emit('tx', tx, info); - this.emit('confirmed', tx, info); + coin.height = tx.height; + coin = coin.toRaw(); - return true; - }, this); -}; + this.put(layout.c(hash, i), coin); + + this.coinCache.set(key, coin); + } + + yield this.commit(); + + this.emit('tx', tx, info); + this.emit('confirmed', tx, info); + + return true; +}); /** * Remove a transaction from the database. Disconnect inputs. @@ -984,19 +962,17 @@ TXDB.prototype._confirm = function _confirm(tx, info) { * @param {Function} callback - Returns [Error]. */ -TXDB.prototype.remove = function remove(hash, force) { - return spawn(function *() { - var unlock = yield this._lock(force); - var info = yield this._removeRecursive(); +TXDB.prototype.remove = spawn.co(function* remove(hash, force) { + var unlock = yield this._lock(force); + var info = yield this._removeRecursive(); - unlock(); + unlock(); - if (!info) - return; + if (!info) + return; - return info; - }, this); -}; + return info; +}); /** * Remove a transaction from the database, but do not @@ -1006,15 +982,13 @@ TXDB.prototype.remove = function remove(hash, force) { * @param {Function} callback - Returns [Error]. */ -TXDB.prototype._lazyRemove = function lazyRemove(tx) { - return spawn(function *() { - var info = yield this.getInfo(tx); - if (!info) - return; +TXDB.prototype._lazyRemove = spawn.co(function* lazyRemove(tx) { + var info = yield this.getInfo(tx); + if (!info) + return; - return yield this._remove(tx, info); - }, this); -}; + return yield this._remove(tx, info); +}); /** * Remove a transaction from the database. Disconnect inputs. @@ -1024,88 +998,86 @@ TXDB.prototype._lazyRemove = function lazyRemove(tx) { * @param {Function} callback - Returns [Error]. */ -TXDB.prototype._remove = function remove(tx, info) { - return spawn(function *() { - var hash = tx.hash('hex'); - var i, path, account, key, prevout; - var address, input, output, coin; +TXDB.prototype._remove = spawn.co(function* remove(tx, info) { + var hash = tx.hash('hex'); + var i, path, account, key, prevout; + var address, input, output, coin; - this.del(layout.t(hash)); + this.del(layout.t(hash)); + if (tx.ts === 0) + this.del(layout.p(hash)); + else + this.del(layout.h(tx.height, hash)); + + this.del(layout.m(tx.ps, hash)); + + for (i = 0; i < info.accounts.length; i++) { + account = info.accounts[i]; + this.del(layout.T(account, hash)); if (tx.ts === 0) - this.del(layout.p(hash)); + this.del(layout.P(account, hash)); else - this.del(layout.h(tx.height, hash)); + this.del(layout.H(account, tx.height, hash)); + this.del(layout.M(account, tx.ps, hash)); + } - this.del(layout.m(tx.ps, hash)); + yield this.fillHistory(tx); - for (i = 0; i < info.accounts.length; i++) { - account = info.accounts[i]; - this.del(layout.T(account, hash)); - if (tx.ts === 0) - this.del(layout.P(account, hash)); - else - this.del(layout.H(account, tx.height, hash)); - this.del(layout.M(account, tx.ps, hash)); - } + for (i = 0; i < tx.inputs.length; i++) { + input = tx.inputs[i]; + key = input.prevout.hash + input.prevout.index; + prevout = input.prevout; + address = input.getHash('hex'); - yield this.fillHistory(tx); + if (tx.isCoinbase()) + break; - for (i = 0; i < tx.inputs.length; i++) { - input = tx.inputs[i]; - key = input.prevout.hash + input.prevout.index; - prevout = input.prevout; - address = input.getHash('hex'); + if (!input.coin) + continue; - if (tx.isCoinbase()) - break; + path = info.getPath(address); - if (!input.coin) - continue; + if (!path) + continue; - path = info.getPath(address); + this.balance.add(input.coin); - if (!path) - continue; + coin = input.coin.toRaw(); - this.balance.add(input.coin); + this.put(layout.c(prevout.hash, prevout.index), coin); + this.put(layout.C(path.account, prevout.hash, prevout.index), DUMMY); + this.del(layout.d(hash, i)); + this.del(layout.s(prevout.hash, prevout.index)); + this.del(layout.o(prevout.hash, prevout.index)); - coin = input.coin.toRaw(); + this.coinCache.set(key, coin); + } - this.put(layout.c(prevout.hash, prevout.index), coin); - this.put(layout.C(path.account, prevout.hash, prevout.index), DUMMY); - this.del(layout.d(hash, i)); - this.del(layout.s(prevout.hash, prevout.index)); - this.del(layout.o(prevout.hash, prevout.index)); + for (i = 0; i < tx.outputs.length; i++) { + output = tx.outputs[i]; + key = hash + i; + address = output.getHash('hex'); - this.coinCache.set(key, coin); - } + path = info.getPath(address); - for (i = 0; i < tx.outputs.length; i++) { - output = tx.outputs[i]; - key = hash + i; - address = output.getHash('hex'); + if (!path) + continue; - path = info.getPath(address); + coin = bcoin.coin.fromTX(tx, i); - if (!path) - continue; + this.balance.sub(coin); - coin = bcoin.coin.fromTX(tx, i); + this.del(layout.c(hash, i)); + this.del(layout.C(path.account, hash, i)); - this.balance.sub(coin); + this.coinCache.remove(key); + } - this.del(layout.c(hash, i)); - this.del(layout.C(path.account, hash, i)); + this.emit('remove tx', tx, info); - this.coinCache.remove(key); - } - - this.emit('remove tx', tx, info); - - return info; - }, this); -}; + return info; +}); /** * Unconfirm a transaction. This is usually necessary after a reorg. @@ -1113,56 +1085,54 @@ TXDB.prototype._remove = function remove(tx, info) { * @param {Function} callback */ -TXDB.prototype.unconfirm = function unconfirm(hash, force) { - return spawn(function *() { - var unlock = yield this._lock(force); - var tx, info, result; - - try { - tx = yield this.getTX(hash); - } catch (e) { - unlock(); - throw e; - } - - if (!tx) { - unlock(); - return false; - } - - try { - info = yield this.getInfo(tx); - } catch (e) { - unlock(); - throw e; - } - - if (!info) { - unlock(); - return false; - } - - this.start(); - - try { - result = yield this._unconfirm(tx, info); - } catch (e) { - this.drop(); - unlock(); - throw e; - } - - try { - yield this.commit(); - } catch (e) { - unlock(); - throw e; - } +TXDB.prototype.unconfirm = spawn.co(function* unconfirm(hash, force) { + var unlock = yield this._lock(force); + var tx, info, result; + try { + tx = yield this.getTX(hash); + } catch (e) { unlock(); - return result; - }, this); -}; + throw e; + } + + if (!tx) { + unlock(); + return false; + } + + try { + info = yield this.getInfo(tx); + } catch (e) { + unlock(); + throw e; + } + + if (!info) { + unlock(); + return false; + } + + this.start(); + + try { + result = yield this._unconfirm(tx, info); + } catch (e) { + this.drop(); + unlock(); + throw e; + } + + try { + yield this.commit(); + } catch (e) { + unlock(); + throw e; + } + + unlock(); + return result; +}); /** * Unconfirm a transaction. This is usually necessary after a reorg. @@ -1171,56 +1141,54 @@ TXDB.prototype.unconfirm = function unconfirm(hash, force) { * @param {Function} callback */ -TXDB.prototype._unconfirm = function unconfirm(tx, info) { - return spawn(function *() { - var hash = tx.hash('hex'); - var height = tx.height; - var i, account, output, key, coin; +TXDB.prototype._unconfirm = spawn.co(function* unconfirm(tx, info) { + var hash = tx.hash('hex'); + var height = tx.height; + var i, account, output, key, coin; - if (height === -1) - return; + if (height === -1) + return; - tx.height = -1; - tx.ts = 0; - tx.index = -1; - tx.block = null; + tx.height = -1; + tx.ts = 0; + tx.index = -1; + tx.block = null; - this.put(layout.t(hash), tx.toExtended()); + this.put(layout.t(hash), tx.toExtended()); - this.put(layout.p(hash), DUMMY); - this.del(layout.h(height, hash)); + this.put(layout.p(hash), DUMMY); + this.del(layout.h(height, hash)); - for (i = 0; i < info.accounts.length; i++) { - account = info.accounts[i]; - this.put(layout.P(account, hash), DUMMY); - this.del(layout.H(account, height, hash)); + for (i = 0; i < info.accounts.length; i++) { + account = info.accounts[i]; + this.put(layout.P(account, hash), DUMMY); + this.del(layout.H(account, height, hash)); + } + + for (i = 0; i < tx.outputs.length; i++) { + output = tx.outputs[i]; + key = hash + i; + coin = yield this.getCoin(hash, i); + + // Update spent coin. + if (!coin) { + yield this.updateSpentCoin(tx, i); + continue; } - for (i = 0; i < tx.outputs.length; i++) { - output = tx.outputs[i]; - key = hash + i; - coin = yield this.getCoin(hash, i); + this.balance.unconfirm(coin.value); + coin.height = tx.height; + coin = coin.toRaw(); - // Update spent coin. - if (!coin) { - yield this.updateSpentCoin(tx, i); - continue; - } + this.put(layout.c(hash, i), coin); - this.balance.unconfirm(coin.value); - coin.height = tx.height; - coin = coin.toRaw(); + this.coinCache.set(key, coin); + } - this.put(layout.c(hash, i), coin); + this.emit('unconfirmed', tx, info); - this.coinCache.set(key, coin); - } - - this.emit('unconfirmed', tx, info); - - return info; - }, this); -}; + return info; +}); /** * Lock all coins in a transaction. @@ -1507,31 +1475,29 @@ TXDB.prototype.getRangeHashes = function getRangeHashes(account, options) { * @param {Function} callback - Returns [Error, {@link TX}[]]. */ -TXDB.prototype.getRange = function getRange(account, options) { - return spawn(function *() { - var txs = []; - var i, hashes, hash, tx; +TXDB.prototype.getRange = spawn.co(function* getRange(account, options) { + var txs = []; + var i, hashes, hash, tx; - if (account && typeof account === 'object') { - options = account; - account = null; - } + if (account && typeof account === 'object') { + options = account; + account = null; + } - hashes = yield this.getRangeHashes(account, options); + hashes = yield this.getRangeHashes(account, options); - for (i = 0; i < hashes.length; i++) { - hash = hashes[i]; - tx = yield this.getTX(hash); + for (i = 0; i < hashes.length; i++) { + hash = hashes[i]; + tx = yield this.getTX(hash); - if (!tx) - continue; + if (!tx) + continue; - txs.push(tx); - } + txs.push(tx); + } - return txs; - }, this); -}; + return txs; +}); /** * Get last N transactions. @@ -1578,26 +1544,24 @@ TXDB.prototype.getHistory = function getHistory(account) { * @param {Function} callback - Returns [Error, {@link TX}[]]. */ -TXDB.prototype.getAccountHistory = function getAccountHistory(account) { - return spawn(function *() { - var txs = []; - var i, hashes, hash, tx; +TXDB.prototype.getAccountHistory = spawn.co(function* getAccountHistory(account) { + var txs = []; + var i, hashes, hash, tx; - hashes = yield this.getHistoryHashes(account); + hashes = yield this.getHistoryHashes(account); - for (i = 0; i < hashes.length; i++) { - hash = hashes[i]; - tx = yield this.getTX(hash); + for (i = 0; i < hashes.length; i++) { + hash = hashes[i]; + tx = yield this.getTX(hash); - if (!tx) - continue; + if (!tx) + continue; - txs.push(tx); - } + txs.push(tx); + } - return sortTX(txs); - }, this); -}; + return sortTX(txs); +}); /** * Get unconfirmed transactions. @@ -1605,26 +1569,24 @@ TXDB.prototype.getAccountHistory = function getAccountHistory(account) { * @param {Function} callback - Returns [Error, {@link TX}[]]. */ -TXDB.prototype.getUnconfirmed = function getUnconfirmed(account) { - return spawn(function *() { - var txs = []; - var i, hashes, hash, tx; +TXDB.prototype.getUnconfirmed = spawn.co(function* getUnconfirmed(account) { + var txs = []; + var i, hashes, hash, tx; - hashes = yield this.getUnconfirmedHashes(account); + hashes = yield this.getUnconfirmedHashes(account); - for (i = 0; i < hashes.length; i++) { - hash = hashes[i]; - tx = yield this.getTX(hash); + for (i = 0; i < hashes.length; i++) { + hash = hashes[i]; + tx = yield this.getTX(hash); - if (!tx) - continue; + if (!tx) + continue; - txs.push(tx); - } + txs.push(tx); + } - return sortTX(txs); - }, this); -}; + return sortTX(txs); +}); /** * Get coins. @@ -1664,24 +1626,22 @@ TXDB.prototype.getCoins = function getCoins(account) { * @param {Function} callback - Returns [Error, {@link Coin}[]]. */ -TXDB.prototype.getAccountCoins = function getCoins(account) { - return spawn(function *() { - var coins = []; - var i, hashes, key, coin; +TXDB.prototype.getAccountCoins = spawn.co(function* getCoins(account) { + var coins = []; + var i, hashes, key, coin; - hashes = yield this.getCoinHashes(account); + hashes = yield this.getCoinHashes(account); - for (i = 0; i < hashes.length; i++) { - key = hashes[i]; - coin = yield this.getCoin(key[0], key[1]); - if (!coin) - continue; - coins.push(coin); - } + for (i = 0; i < hashes.length; i++) { + key = hashes[i]; + coin = yield this.getCoin(key[0], key[1]); + if (!coin) + continue; + coins.push(coin); + } - return coins; - }, this); -}; + return coins; +}); /** * Fill a transaction with coins (all historical coins). @@ -1718,29 +1678,27 @@ TXDB.prototype.fillHistory = function fillHistory(tx) { * @param {Function} callback - Returns [Error, {@link TX}]. */ -TXDB.prototype.fillCoins = function fillCoins(tx) { - return spawn(function *() { - var i, input, prevout, coin; - - if (tx.isCoinbase()) - return tx; - - for (i = 0; i < tx.inputs.length; i++) { - input = tx.inputs[i]; - prevout = input.prevout; - - if (input.coin) - continue; - - coin = yield this.getCoin(prevout.hash, prevout.index); - - if (coin) - input.coin = coin; - } +TXDB.prototype.fillCoins = spawn.co(function* fillCoins(tx) { + var i, input, prevout, coin; + if (tx.isCoinbase()) return tx; - }, this); -}; + + for (i = 0; i < tx.inputs.length; i++) { + input = tx.inputs[i]; + prevout = input.prevout; + + if (input.coin) + continue; + + coin = yield this.getCoin(prevout.hash, prevout.index); + + if (coin) + input.coin = coin; + } + + return tx; +}); /** * Get transaction. @@ -1760,16 +1718,14 @@ TXDB.prototype.getTX = function getTX(hash) { * @param {Function} callback - Returns [Error, {@link TXDetails}]. */ -TXDB.prototype.getDetails = function getDetails(hash) { - return spawn(function *() { - var tx = yield this.getTX(hash); +TXDB.prototype.getDetails = spawn.co(function* getDetails(hash) { + var tx = yield this.getTX(hash); - if (!tx) - return; + if (!tx) + return; - return yield this.toDetails(tx); - }, this); -}; + return yield this.toDetails(tx); +}); /** * Convert transaction to transaction details. @@ -1777,37 +1733,35 @@ TXDB.prototype.getDetails = function getDetails(hash) { * @param {Function} callback */ -TXDB.prototype.toDetails = function toDetails(tx) { - return spawn(function *() { - var i, out, txs, details, info; +TXDB.prototype.toDetails = spawn.co(function* toDetails(tx) { + var i, out, txs, details, info; - if (Array.isArray(tx)) { - out = []; - txs = tx; + if (Array.isArray(tx)) { + out = []; + txs = tx; - for (i = 0; i < txs.length; i++) { - tx = txs[i]; - details = yield this.toDetails(tx); + for (i = 0; i < txs.length; i++) { + tx = txs[i]; + details = yield this.toDetails(tx); - if (!details) - continue; + if (!details) + continue; - out.push(details); - } - - return out; + out.push(details); } - yield this.fillHistory(tx); + return out; + } - info = yield this.getInfo(tx); + yield this.fillHistory(tx); - if (!info) - throw new Error('Info not found.'); + info = yield this.getInfo(tx); - return info.toDetails(); - }, this); -}; + if (!info) + throw new Error('Info not found.'); + + return info.toDetails(); +}); /** * Test whether the database has a transaction. @@ -1874,25 +1828,23 @@ TXDB.prototype.getSpentCoin = function getSpentCoin(spent, prevout) { * @param {Function} callback */ -TXDB.prototype.updateSpentCoin = function updateSpentCoin(tx, i) { - return spawn(function *() { - var prevout = bcoin.outpoint.fromTX(tx, i); - var spent = yield this.isSpent(prevout.hash, prevout.index); - var coin; +TXDB.prototype.updateSpentCoin = spawn.co(function* updateSpentCoin(tx, i) { + var prevout = bcoin.outpoint.fromTX(tx, i); + var spent = yield this.isSpent(prevout.hash, prevout.index); + var coin; - if (!spent) - return; + if (!spent) + return; - coin = yield this.getSpentCoin(spent, prevout); + coin = yield this.getSpentCoin(spent, prevout); - if (!coin) - return; + if (!coin) + return; - coin.height = tx.height; + coin.height = tx.height; - this.put(layout.d(spent.hash, spent.index), coin.toRaw()); - }, this); -}; + this.put(layout.d(spent.hash, spent.index), coin.toRaw()); +}); /** * Test whether the database has a transaction. @@ -1915,39 +1867,37 @@ TXDB.prototype.hasCoin = function hasCoin(hash, index) { * @param {Function} callback - Returns [Error, {@link Balance}]. */ -TXDB.prototype.getBalance = function getBalance(account) { - return spawn(function *() { - var self = this; - var balance; +TXDB.prototype.getBalance = spawn.co(function* getBalance(account) { + var self = this; + var balance; - // Slow case - if (account != null) - return yield this.getAccountBalance(account); + // Slow case + if (account != null) + return yield this.getAccountBalance(account); - // Really fast case - if (this.balance) - return this.balance; + // Really fast case + if (this.balance) + return this.balance; - // Fast case - balance = new Balance(this.wallet); + // Fast case + balance = new Balance(this.wallet); - yield this.iterate({ - gte: layout.c(constants.NULL_HASH, 0), - lte: layout.c(constants.HIGH_HASH, 0xffffffff), - values: true, - parse: function(key, data) { - var parts = layout.cc(key); - var hash = parts[0]; - var index = parts[1]; - var ckey = hash + index; - balance.addRaw(data); - self.coinCache.set(ckey, data); - } - }); + yield this.iterate({ + gte: layout.c(constants.NULL_HASH, 0), + lte: layout.c(constants.HIGH_HASH, 0xffffffff), + values: true, + parse: function(key, data) { + var parts = layout.cc(key); + var hash = parts[0]; + var index = parts[1]; + var ckey = hash + index; + balance.addRaw(data); + self.coinCache.set(ckey, data); + } + }); - return balance; - }, this); -}; + return balance; +}); /** * Calculate balance by account. @@ -1955,36 +1905,34 @@ TXDB.prototype.getBalance = function getBalance(account) { * @param {Function} callback - Returns [Error, {@link Balance}]. */ -TXDB.prototype.getAccountBalance = function getBalance(account) { - return spawn(function *() { - var balance = new Balance(this.wallet); - var i, key, coin, hashes, hash, data; +TXDB.prototype.getAccountBalance = spawn.co(function* getBalance(account) { + var balance = new Balance(this.wallet); + var i, key, coin, hashes, hash, data; - hashes = yield this.getCoinHashes(account); + hashes = yield this.getCoinHashes(account); - for (i = 0; i < hashes.length; i++) { - hash = hashes[i]; - key = hash[0] + hash[1]; - coin = this.coinCache.get(key); + for (i = 0; i < hashes.length; i++) { + hash = hashes[i]; + key = hash[0] + hash[1]; + coin = this.coinCache.get(key); - if (coin) { - balance.addRaw(coin); - continue; - } - - data = yield this.get(layout.c(hash[0], hash[1])); - - if (!data) - continue; - - balance.addRaw(data); - - this.coinCache.set(key, data); + if (coin) { + balance.addRaw(coin); + continue; } - return balance; - }, this); -}; + data = yield this.get(layout.c(hash[0], hash[1])); + + if (!data) + continue; + + balance.addRaw(data); + + this.coinCache.set(key, data); + } + + return balance; +}); /** * @param {Number?} account @@ -1992,37 +1940,35 @@ TXDB.prototype.getAccountBalance = function getBalance(account) { * @param {Function} callback */ -TXDB.prototype.zap = function zap(account, age) { - return spawn(function *() { - var unlock = yield this._lock(); - var i, txs, tx, hash; +TXDB.prototype.zap = spawn.co(function* zap(account, age) { + var unlock = yield this._lock(); + var i, txs, tx, hash; - if (!utils.isUInt32(age)) - throw new Error('Age must be a number.'); + if (!utils.isUInt32(age)) + throw new Error('Age must be a number.'); - txs = yield this.getRange(account, { - start: 0, - end: bcoin.now() - age - }); + txs = yield this.getRange(account, { + start: 0, + end: bcoin.now() - age + }); - for (i = 0; i < txs.length; i++) { - tx = txs[i]; - hash = tx.hash('hex'); + for (i = 0; i < txs.length; i++) { + tx = txs[i]; + hash = tx.hash('hex'); - if (tx.ts !== 0) - continue; + if (tx.ts !== 0) + continue; - try { - yield this.remove(hash, true); - } catch (e) { - unlock(); - throw e; - } + try { + yield this.remove(hash, true); + } catch (e) { + unlock(); + throw e; } + } - unlock(); - }, this); -}; + unlock(); +}); /** * Abandon transaction. @@ -2030,14 +1976,12 @@ TXDB.prototype.zap = function zap(account, age) { * @param {Function} callback */ -TXDB.prototype.abandon = function abandon(hash) { - return spawn(function *() { - var result = yield this.has(layout.p(hash)); - if (!result) - throw new Error('TX not eligible.'); - return yield this.remove(hash); - }, this); -}; +TXDB.prototype.abandon = spawn.co(function* abandon(hash) { + var result = yield this.has(layout.p(hash)); + if (!result) + throw new Error('TX not eligible.'); + return yield this.remove(hash); +}); /* * Balance diff --git a/lib/wallet/wallet.js b/lib/wallet/wallet.js index ff2dd7aa..e6f63cc9 100644 --- a/lib/wallet/wallet.js +++ b/lib/wallet/wallet.js @@ -179,50 +179,46 @@ Wallet.fromOptions = function fromOptions(db, options) { * @param {Function} callback */ -Wallet.prototype.init = function init(options) { - return spawn(function *() { - var account; +Wallet.prototype.init = spawn.co(function* init(options) { + var account; - assert(!this.initialized); - this.initialized = true; + assert(!this.initialized); + this.initialized = true; - if (options.passphrase) - yield this.master.encrypt(options.passphrase); + if (options.passphrase) + yield this.master.encrypt(options.passphrase); - account = yield this.createAccount(options); - assert(account); + account = yield this.createAccount(options); + assert(account); - this.account = account; + this.account = account; - this.logger.info('Wallet initialized (%s).', this.id); + this.logger.info('Wallet initialized (%s).', this.id); - yield this.tx.open(); - }, this); -}; + yield this.tx.open(); +}); /** * Open wallet (done after retrieval). * @param {Function} callback */ -Wallet.prototype.open = function open() { - return spawn(function *() { - var account; +Wallet.prototype.open = spawn.co(function* open() { + var account; - assert(this.initialized); + assert(this.initialized); - account = yield this.getAccount(0); + account = yield this.getAccount(0); - if (!account) - throw new Error('Default account not found.'); + if (!account) + throw new Error('Default account not found.'); - this.account = account; + this.account = account; - this.logger.info('Wallet opened (%s).', this.id); + this.logger.info('Wallet opened (%s).', this.id); - yield this.tx.open(); - }, this); -}; + yield this.tx.open(); +}); /** * Close the wallet, unregister with the database. @@ -248,42 +244,40 @@ Wallet.prototype.destroy = function destroy() { * @param {Function} callback */ -Wallet.prototype.addKey = function addKey(account, key) { - return spawn(function *() { - var unlock = yield this._lockWrite(); - var result; +Wallet.prototype.addKey = spawn.co(function* addKey(account, key) { + var unlock = yield this._lockWrite(); + var result; - if (!key) { - key = account; - account = null; - } + if (!key) { + key = account; + account = null; + } - if (account == null) - account = 0; + if (account == null) + account = 0; - account = yield this.getAccount(account); - - if (!account) { - unlock(); - throw new Error('Account not found.'); - } - - this.start(); - - try { - result = yield account.addKey(key, true); - } catch (e) { - this.drop(); - unlock(); - throw e; - } - - yield this.commit(); + account = yield this.getAccount(account); + if (!account) { unlock(); - return result; - }, this); -}; + throw new Error('Account not found.'); + } + + this.start(); + + try { + result = yield account.addKey(key, true); + } catch (e) { + this.drop(); + unlock(); + throw e; + } + + yield this.commit(); + + unlock(); + return result; +}); /** * Remove a public account key from the wallet (multisig). @@ -292,42 +286,40 @@ Wallet.prototype.addKey = function addKey(account, key) { * @param {Function} callback */ -Wallet.prototype.removeKey = function removeKey(account, key) { - return spawn(function *() { - var unlock = yield this._lockWrite(); - var result; +Wallet.prototype.removeKey = spawn.co(function* removeKey(account, key) { + var unlock = yield this._lockWrite(); + var result; - if (!key) { - key = account; - account = null; - } + if (!key) { + key = account; + account = null; + } - if (account == null) - account = 0; + if (account == null) + account = 0; - account = yield this.getAccount(account); - - if (!account) { - unlock(); - throw new Error('Account not found.'); - } - - this.start(); - - try { - result = yield account.removeKey(key, true); - } catch (e) { - this.drop(); - unlock(); - throw e; - } - - yield this.commit(); + account = yield this.getAccount(account); + if (!account) { unlock(); - return result; - }, this); -}; + throw new Error('Account not found.'); + } + + this.start(); + + try { + result = yield account.removeKey(key, true); + } catch (e) { + this.drop(); + unlock(); + throw e; + } + + yield this.commit(); + + unlock(); + return result; +}); /** * Change or set master key's passphrase. @@ -336,31 +328,29 @@ Wallet.prototype.removeKey = function removeKey(account, key) { * @param {Function} callback */ -Wallet.prototype.setPassphrase = function setPassphrase(old, new_) { - return spawn(function *() { - var unlock = yield this._lockWrite(); +Wallet.prototype.setPassphrase = spawn.co(function* setPassphrase(old, new_) { + var unlock = yield this._lockWrite(); - if (!new_) { - new_ = old; - old = null; - } + if (!new_) { + new_ = old; + old = null; + } - try { - if (old) - yield this.master.decrypt(old); - if (new_) - yield this.master.encrypt(new_); - } catch (e) { - unlock(); - throw e; - } - - this.start(); - this.save(); - yield this.commit(); + try { + if (old) + yield this.master.decrypt(old); + if (new_) + yield this.master.encrypt(new_); + } catch (e) { unlock(); - }, this); -}; + throw e; + } + + this.start(); + this.save(); + yield this.commit(); + unlock(); +}); /** * Generate a new token. @@ -368,29 +358,27 @@ Wallet.prototype.setPassphrase = function setPassphrase(old, new_) { * @param {Function} callback */ -Wallet.prototype.retoken = function retoken(passphrase) { - return spawn(function *() { - var unlock = yield this._lockWrite(); - var master; - - try { - master = yield this.unlock(passphrase); - } catch (e) { - unlock(); - throw e; - } - - this.tokenDepth++; - this.token = this.getToken(master, this.tokenDepth); - - this.start(); - this.save(); - yield this.commit(); +Wallet.prototype.retoken = spawn.co(function* retoken(passphrase) { + var unlock = yield this._lockWrite(); + var master; + try { + master = yield this.unlock(passphrase); + } catch (e) { unlock(); - return this.token; - }, this); -}; + throw e; + } + + this.tokenDepth++; + this.token = this.getToken(master, this.tokenDepth); + + this.start(); + this.save(); + yield this.commit(); + + unlock(); + return this.token; +}); /** * Lock the wallet, destroy decrypted key. @@ -471,61 +459,59 @@ Wallet.prototype.getToken = function getToken(master, nonce) { * @param {Function} callback - Returns [Error, {@link Account}]. */ -Wallet.prototype.createAccount = function createAccount(options) { - return spawn(function *() { - var unlock = yield this._lockWrite(); - var passphrase = options.passphrase; - var timeout = options.timeout; - var name = options.name; - var key, master, account; +Wallet.prototype.createAccount = spawn.co(function* createAccount(options) { + var unlock = yield this._lockWrite(); + var passphrase = options.passphrase; + var timeout = options.timeout; + var name = options.name; + var key, master, account; - if (typeof options.account === 'string') - name = options.account; + if (typeof options.account === 'string') + name = options.account; - if (!name) - name = this.accountDepth + ''; + if (!name) + name = this.accountDepth + ''; - try { - master = yield this.unlock(passphrase, timeout); - } catch (e) { - unlock(); - throw e; - } - - key = master.deriveAccount44(this.accountDepth); - - options = { - network: this.network, - wid: this.wid, - id: this.id, - name: this.accountDepth === 0 ? 'default' : name, - witness: options.witness, - accountKey: key.hdPublicKey, - accountIndex: this.accountDepth, - type: options.type, - keys: options.keys, - m: options.m, - n: options.n - }; - - this.start(); - - try { - account = yield this.db.createAccount(options); - } catch (e) { - this.drop(); - unlock(); - throw e; - } - - this.accountDepth++; - this.save(); - yield this.commit(); + try { + master = yield this.unlock(passphrase, timeout); + } catch (e) { unlock(); + throw e; + } - return account; - }, this); -}; + key = master.deriveAccount44(this.accountDepth); + + options = { + network: this.network, + wid: this.wid, + id: this.id, + name: this.accountDepth === 0 ? 'default' : name, + witness: options.witness, + accountKey: key.hdPublicKey, + accountIndex: this.accountDepth, + type: options.type, + keys: options.keys, + m: options.m, + n: options.n + }; + + this.start(); + + try { + account = yield this.db.createAccount(options); + } catch (e) { + this.drop(); + unlock(); + throw e; + } + + this.accountDepth++; + this.save(); + yield this.commit(); + unlock(); + + return account; +}); /** * Ensure an account. Requires passphrase if master key is encrypted. @@ -533,22 +519,20 @@ Wallet.prototype.createAccount = function createAccount(options) { * @param {Function} callback - Returns [Error, {@link Account}]. */ -Wallet.prototype.ensureAccount = function ensureAccount(options) { - return spawn(function *() { - var account = options.account; - var exists; +Wallet.prototype.ensureAccount = spawn.co(function* ensureAccount(options) { + var account = options.account; + var exists; - if (typeof options.name === 'string') - account = options.name; + if (typeof options.name === 'string') + account = options.name; - exists = yield this.hasAccount(account); + exists = yield this.hasAccount(account); - if (exists) - return yield this.getAccount(account); + if (exists) + return yield this.getAccount(account); - return this.createAccount(options); - }, this); -}; + return this.createAccount(options); +}); /** * List account names and indexes from the db. @@ -574,24 +558,22 @@ Wallet.prototype.getAddressHashes = function getAddressHashes() { * @param {Function} callback - Returns [Error, {@link Account}]. */ -Wallet.prototype.getAccount = function getAccount(account) { - return spawn(function *() { - if (this.account) { - if (account === 0 || account === 'default') - return this.account; - } +Wallet.prototype.getAccount = spawn.co(function* getAccount(account) { + if (this.account) { + if (account === 0 || account === 'default') + return this.account; + } - account = yield this.db.getAccount(this.wid, account); + account = yield this.db.getAccount(this.wid, account); - if (!account) - return; + if (!account) + return; - account.wid = this.wid; - account.id = this.id; + account.wid = this.wid; + account.id = this.id; - return account; - }, this); -}; + return account; +}); /** * Test whether an account exists. @@ -630,47 +612,45 @@ Wallet.prototype.createChange = function createChange(account) { * @param {Function} callback - Returns [Error, {@link KeyRing}]. */ -Wallet.prototype.createAddress = function createAddress(account, change) { - return spawn(function *() { - var unlock = yield this._lockWrite(); - var result; +Wallet.prototype.createAddress = spawn.co(function* createAddress(account, change) { + var unlock = yield this._lockWrite(); + var result; - if (typeof account === 'boolean') { - change = account; - account = null; - } + if (typeof account === 'boolean') { + change = account; + account = null; + } - if (account == null) - account = 0; + if (account == null) + account = 0; - try { - account = yield this.getAccount(account); - } catch (e) { - unlock(); - throw e; - } - - if (!account) { - unlock(); - throw new Error('Account not found.'); - } - - this.start(); - - try { - result = yield account.createAddress(change, true); - } catch (e) { - this.drop(); - unlock(); - throw e; - } - - yield this.commit(); + try { + account = yield this.getAccount(account); + } catch (e) { unlock(); + throw e; + } - return result; - }, this); -}; + if (!account) { + unlock(); + throw new Error('Account not found.'); + } + + this.start(); + + try { + result = yield account.createAddress(change, true); + } catch (e) { + this.drop(); + unlock(); + throw e; + } + + yield this.commit(); + unlock(); + + return result; +}); /** * Save the wallet to the database. Necessary @@ -728,24 +708,22 @@ Wallet.prototype.hasAddress = function hasAddress(address) { * @param {Function} callback - Returns [Error, {@link Path}]. */ -Wallet.prototype.getPath = function getPath(address) { - return spawn(function *() { - var hash = bcoin.address.getHash(address, 'hex'); - var path; +Wallet.prototype.getPath = spawn.co(function* getPath(address) { + var hash = bcoin.address.getHash(address, 'hex'); + var path; - if (!hash) - return; + if (!hash) + return; - path = yield this.db.getAddressPath(this.wid, hash); + path = yield this.db.getAddressPath(this.wid, hash); - if (!path) - return; + if (!path) + return; - path.id = this.id; + path.id = this.id; - return path; - }, this); -}; + return path; +}); /** * Get all wallet paths. @@ -753,25 +731,23 @@ Wallet.prototype.getPath = function getPath(address) { * @param {Function} callback - Returns [Error, {@link Path}]. */ -Wallet.prototype.getPaths = function getPaths(account) { - return spawn(function *() { - var out = []; - var i, account, paths, path; +Wallet.prototype.getPaths = spawn.co(function* getPaths(account) { + var out = []; + var i, account, paths, path; - account = yield this._getIndex(account); - paths = yield this.db.getWalletPaths(this.wid); + account = yield this._getIndex(account); + paths = yield this.db.getWalletPaths(this.wid); - for (i = 0; i < paths.length; i++) { - path = paths[i]; - if (!account || path.account === account) { - path.id = this.id; - out.push(path); - } + for (i = 0; i < paths.length; i++) { + path = paths[i]; + if (!account || path.account === account) { + path.id = this.id; + out.push(path); } + } - return out; - }, this); -}; + return out; +}); /** * Import a keyring (will not exist on derivation chain). @@ -782,77 +758,75 @@ Wallet.prototype.getPaths = function getPaths(account) { * @param {Function} callback */ -Wallet.prototype.importKey = function importKey(account, ring, passphrase) { - return spawn(function *() { - var unlock = yield this._lockWrite(); - var exists, account, raw, path; +Wallet.prototype.importKey = spawn.co(function* importKey(account, ring, passphrase) { + var unlock = yield this._lockWrite(); + var exists, account, raw, path; - if (account && typeof account === 'object') { - passphrase = ring; - ring = account; - account = null; - } + if (account && typeof account === 'object') { + passphrase = ring; + ring = account; + account = null; + } - if (account == null) - account = 0; + if (account == null) + account = 0; - try { - exists = yield this.getPath(ring.getHash('hex')); - } catch (e) { - unlock(); - throw e; - } - - if (exists) { - unlock(); - throw new Error('Key already exists.'); - } - - account = yield this.getAccount(account); - - if (!account) { - unlock(); - throw new Error('Account not found.'); - } - - if (account.type !== bcoin.account.types.PUBKEYHASH) { - unlock(); - throw new Error('Cannot import into non-pkh account.'); - } - - try { - yield this.unlock(passphrase); - } catch (e) { - unlock(); - throw e; - } - - raw = ring.toRaw(); - path = Path.fromAccount(account, ring); - - if (this.master.encrypted) { - raw = this.master.encipher(raw, path.hash); - assert(raw); - path.encrypted = true; - } - - path.imported = raw; - ring.path = path; - - this.start(); - - try { - yield account.saveAddress([ring], true); - } catch (e) { - this.drop(); - unlock(); - throw e; - } - - yield this.commit(); + try { + exists = yield this.getPath(ring.getHash('hex')); + } catch (e) { unlock(); - }, this); -}; + throw e; + } + + if (exists) { + unlock(); + throw new Error('Key already exists.'); + } + + account = yield this.getAccount(account); + + if (!account) { + unlock(); + throw new Error('Account not found.'); + } + + if (account.type !== bcoin.account.types.PUBKEYHASH) { + unlock(); + throw new Error('Cannot import into non-pkh account.'); + } + + try { + yield this.unlock(passphrase); + } catch (e) { + unlock(); + throw e; + } + + raw = ring.toRaw(); + path = Path.fromAccount(account, ring); + + if (this.master.encrypted) { + raw = this.master.encipher(raw, path.hash); + assert(raw); + path.encrypted = true; + } + + path.imported = raw; + ring.path = path; + + this.start(); + + try { + yield account.saveAddress([ring], true); + } catch (e) { + this.drop(); + unlock(); + throw e; + } + + yield this.commit(); + unlock(); +}); /** * Fill a transaction with inputs, estimate @@ -878,70 +852,68 @@ Wallet.prototype.importKey = function importKey(account, ring, passphrase) { * fee from existing outputs rather than adding more inputs. */ -Wallet.prototype.fund = function fund(tx, options, force) { - return spawn(function *() { - var unlock = yield this._lockFund(force); - var rate, account, coins; +Wallet.prototype.fund = spawn.co(function* fund(tx, options, force) { + var unlock = yield this._lockFund(force); + var rate, account, coins; - if (!options) - options = {}; + if (!options) + options = {}; - if (!this.initialized) { + if (!this.initialized) { + unlock(); + throw new Error('Wallet is not initialized.'); + } + + if (options.account != null) { + account = yield this.getAccount(options.account); + if (!account) { unlock(); - throw new Error('Wallet is not initialized.'); + throw new Error('Account not found.'); } + } else { + account = this.account; + } - if (options.account != null) { - account = yield this.getAccount(options.account); - if (!account) { - unlock(); - throw new Error('Account not found.'); - } - } else { - account = this.account; - } + if (!account.initialized) { + unlock(); + throw new Error('Account is not initialized.'); + } - if (!account.initialized) { - unlock(); - throw new Error('Account is not initialized.'); - } + coins = yield this.getCoins(options.account); - coins = yield this.getCoins(options.account); + rate = options.rate; - rate = options.rate; + if (rate == null) { + if (this.db.fees) + rate = this.db.fees.estimateFee(); + else + rate = this.network.getRate(); + } - if (rate == null) { - if (this.db.fees) - rate = this.db.fees.estimateFee(); - else - rate = this.network.getRate(); - } + // Don't use any locked coins. + coins = this.tx.filterLocked(coins); - // Don't use any locked coins. - coins = this.tx.filterLocked(coins); - - try { - tx.fund(coins, { - selection: options.selection, - round: options.round, - confirmations: options.confirmations, - free: options.free, - hardFee: options.hardFee, - subtractFee: options.subtractFee, - changeAddress: account.changeAddress.getAddress(), - height: this.db.height, - rate: rate, - maxFee: options.maxFee, - m: account.m, - n: account.n, - witness: account.witness, - script: account.receiveAddress.script - }); - } finally { - unlock(); - } - }, this); -}; + try { + tx.fund(coins, { + selection: options.selection, + round: options.round, + confirmations: options.confirmations, + free: options.free, + hardFee: options.hardFee, + subtractFee: options.subtractFee, + changeAddress: account.changeAddress.getAddress(), + height: this.db.height, + rate: rate, + maxFee: options.maxFee, + m: account.m, + n: account.n, + witness: account.witness, + script: account.receiveAddress.script + }); + } finally { + unlock(); + } +}); /** * Build a transaction, fill it with outputs and inputs, @@ -952,48 +924,46 @@ Wallet.prototype.fund = function fund(tx, options, force) { * @param {Function} callback - Returns [Error, {@link MTX}]. */ -Wallet.prototype.createTX = function createTX(options, force) { - return spawn(function *() { - var outputs = options.outputs; - var i, tx, total; +Wallet.prototype.createTX = spawn.co(function* createTX(options, force) { + var outputs = options.outputs; + var i, tx, total; - if (!Array.isArray(outputs) || outputs.length === 0) - throw new Error('No outputs.'); + if (!Array.isArray(outputs) || outputs.length === 0) + throw new Error('No outputs.'); - // Create mutable tx - tx = bcoin.mtx(); + // Create mutable tx + tx = bcoin.mtx(); - // Add the outputs - for (i = 0; i < outputs.length; i++) - tx.addOutput(outputs[i]); + // Add the outputs + for (i = 0; i < outputs.length; i++) + tx.addOutput(outputs[i]); - // Fill the inputs with unspents - yield this.fund(tx, options, force); + // Fill the inputs with unspents + yield this.fund(tx, options, force); - // Sort members a la BIP69 - tx.sortMembers(); + // Sort members a la BIP69 + tx.sortMembers(); - // Set the locktime to target value or - // `height - whatever` to avoid fee sniping. - // if (options.locktime != null) - // tx.setLocktime(options.locktime); - // else - // tx.avoidFeeSniping(this.db.height); + // Set the locktime to target value or + // `height - whatever` to avoid fee sniping. + // if (options.locktime != null) + // tx.setLocktime(options.locktime); + // else + // tx.avoidFeeSniping(this.db.height); - if (!tx.isSane()) - throw new Error('CheckTransaction failed.'); + if (!tx.isSane()) + throw new Error('CheckTransaction failed.'); - if (!tx.checkInputs(this.db.height)) - throw new Error('CheckInputs failed.'); + if (!tx.checkInputs(this.db.height)) + throw new Error('CheckInputs failed.'); - total = yield this.template(tx); + total = yield this.template(tx); - if (total === 0) - throw new Error('template failed.'); + if (total === 0) + throw new Error('template failed.'); - return tx; - }, this); -}; + return tx; +}); /** * Build a transaction, fill it with outputs and inputs, @@ -1005,61 +975,57 @@ Wallet.prototype.createTX = function createTX(options, force) { * @param {Function} callback - Returns [Error, {@link TX}]. */ -Wallet.prototype.send = function send(options) { - return spawn(function *() { - var unlock = yield this._lockFund(); - var tx; - - try { - tx = yield this.createTX(options, true); - } catch (e) { - unlock(); - throw e; - } - - try { - yield this.sign(tx); - } catch (e) { - unlock(); - throw e; - } - - if (!tx.isSigned()) { - unlock(); - throw new Error('TX could not be fully signed.'); - } - - tx = tx.toTX(); - - yield this.addTX(tx); - - this.logger.debug('Sending wallet tx (%s): %s', this.id, tx.rhash); - this.db.emit('send', tx); +Wallet.prototype.send = spawn.co(function* send(options) { + var unlock = yield this._lockFund(); + var tx; + try { + tx = yield this.createTX(options, true); + } catch (e) { unlock(); - return tx; - }, this); -}; + throw e; + } + + try { + yield this.sign(tx); + } catch (e) { + unlock(); + throw e; + } + + if (!tx.isSigned()) { + unlock(); + throw new Error('TX could not be fully signed.'); + } + + tx = tx.toTX(); + + yield this.addTX(tx); + + this.logger.debug('Sending wallet tx (%s): %s', this.id, tx.rhash); + this.db.emit('send', tx); + + unlock(); + return tx; +}); /** * Resend pending wallet transactions. * @param {Function} callback */ -Wallet.prototype.resend = function resend() { - return spawn(function *() { - var txs = yield this.getUnconfirmed(); - var i; +Wallet.prototype.resend = spawn.co(function* resend() { + var txs = yield this.getUnconfirmed(); + var i; - if (txs.length > 0) - this.logger.info('Rebroadcasting %d transactions.', txs.length); + if (txs.length > 0) + this.logger.info('Rebroadcasting %d transactions.', txs.length); - for (i = 0; i < txs.length; i++) - this.db.emit('send', txs[i]); + for (i = 0; i < txs.length; i++) + this.db.emit('send', txs[i]); - return txs; - }, this); -}; + return txs; +}); /** * Derive necessary addresses for signing a transaction. @@ -1068,29 +1034,27 @@ Wallet.prototype.resend = function resend() { * @param {Function} callback - Returns [Error, {@link KeyRing}[]]. */ -Wallet.prototype.deriveInputs = function deriveInputs(tx) { - return spawn(function *() { - var rings = []; - var i, paths, path, account, ring; +Wallet.prototype.deriveInputs = spawn.co(function* deriveInputs(tx) { + var rings = []; + var i, paths, path, account, ring; - paths = yield this.getInputPaths(tx); + paths = yield this.getInputPaths(tx); - for (i = 0; i < paths.length; i++) { - path = paths[i]; - account = yield this.getAccount(path.account); + for (i = 0; i < paths.length; i++) { + path = paths[i]; + account = yield this.getAccount(path.account); - if (!account) - continue; + if (!account) + continue; - ring = account.derivePath(path, this.master); + ring = account.derivePath(path, this.master); - if (ring) - rings.push(ring); - } + if (ring) + rings.push(ring); + } - return rings; - }, this); -}; + return rings; +}); /** * Retrieve a single keyring by address. @@ -1098,27 +1062,25 @@ Wallet.prototype.deriveInputs = function deriveInputs(tx) { * @param {Function} callback */ -Wallet.prototype.getKeyRing = function getKeyRing(address) { - return spawn(function *() { - var hash = bcoin.address.getHash(address, 'hex'); - var path, account; +Wallet.prototype.getKeyRing = spawn.co(function* getKeyRing(address) { + var hash = bcoin.address.getHash(address, 'hex'); + var path, account; - if (!hash) - return; + if (!hash) + return; - path = yield this.getPath(hash); + path = yield this.getPath(hash); - if (!path) - return; + if (!path) + return; - account = yield this.getAccount(path.account); + account = yield this.getAccount(path.account); - if (!account) - return; + if (!account) + return; - return account.derivePath(path, this.master); - }, this); -}; + return account.derivePath(path, this.master); +}); /** * Map input addresses to paths. @@ -1126,39 +1088,37 @@ Wallet.prototype.getKeyRing = function getKeyRing(address) { * @param {Function} callback - Returns [Error, {@link Path}[]]. */ -Wallet.prototype.getInputPaths = function getInputPaths(tx) { - return spawn(function *() { - var paths = []; - var hashes = []; - var i, hash, path; +Wallet.prototype.getInputPaths = spawn.co(function* getInputPaths(tx) { + var paths = []; + var hashes = []; + var i, hash, path; - if (tx instanceof bcoin.input) { - if (!tx.coin) - throw new Error('Not all coins available.'); + if (tx instanceof bcoin.input) { + if (!tx.coin) + throw new Error('Not all coins available.'); - hash = tx.coin.getHash('hex'); + hash = tx.coin.getHash('hex'); - if (hash) - hashes.push(hash); - } else { - yield this.fillCoins(tx); + if (hash) + hashes.push(hash); + } else { + yield this.fillCoins(tx); - if (!tx.hasCoins()) - throw new Error('Not all coins available.'); + if (!tx.hasCoins()) + throw new Error('Not all coins available.'); - hashes = tx.getInputHashes('hex'); - } + hashes = tx.getInputHashes('hex'); + } - for (i = 0; i < hashes.length; i++) { - hash = hashes[i]; - path = yield this.getPath(hash); - if (path) - paths.push(path); - } + for (i = 0; i < hashes.length; i++) { + hash = hashes[i]; + path = yield this.getPath(hash); + if (path) + paths.push(path); + } - return paths; - }, this); -}; + return paths; +}); /** * Map output addresses to paths. @@ -1166,30 +1126,28 @@ Wallet.prototype.getInputPaths = function getInputPaths(tx) { * @param {Function} callback - Returns [Error, {@link Path}[]]. */ -Wallet.prototype.getOutputPaths = function getOutputPaths(tx) { - return spawn(function *() { - var paths = []; - var hashes = []; - var i, hash, path; +Wallet.prototype.getOutputPaths = spawn.co(function* getOutputPaths(tx) { + var paths = []; + var hashes = []; + var i, hash, path; - if (tx instanceof bcoin.output) { - hash = tx.getHash('hex'); - if (hash) - hashes.push(hash); - } else { - hashes = tx.getOutputHashes('hex'); - } + if (tx instanceof bcoin.output) { + hash = tx.getHash('hex'); + if (hash) + hashes.push(hash); + } else { + hashes = tx.getOutputHashes('hex'); + } - for (i = 0; i < hashes.length; i++) { - hash = hashes[i]; - path = yield this.getPath(hash); - if (path) - paths.push(path); - } + for (i = 0; i < hashes.length; i++) { + hash = hashes[i]; + path = yield this.getPath(hash); + if (path) + paths.push(path); + } - return paths; - }, this); -}; + return paths; +}); /** * Sync address depths based on a transaction's outputs. @@ -1200,87 +1158,85 @@ Wallet.prototype.getOutputPaths = function getOutputPaths(tx) { * (true if new addresses were allocated). */ -Wallet.prototype.syncOutputDepth = function syncOutputDepth(info) { - return spawn(function *() { - var unlock = yield this._lockWrite(); - var receive = []; - var accounts = {}; - var i, j, path, paths, account; - var receiveDepth, changeDepth; - var ret, rcv, chng; +Wallet.prototype.syncOutputDepth = spawn.co(function* syncOutputDepth(info) { + var unlock = yield this._lockWrite(); + var receive = []; + var accounts = {}; + var i, j, path, paths, account; + var receiveDepth, changeDepth; + var ret, rcv, chng; - this.start(); + this.start(); - for (i = 0; i < info.paths.length; i++) { - path = info.paths[i]; + for (i = 0; i < info.paths.length; i++) { + path = info.paths[i]; - if (path.index === -1) - continue; + if (path.index === -1) + continue; - if (!accounts[path.account]) - accounts[path.account] = []; + if (!accounts[path.account]) + accounts[path.account] = []; - accounts[path.account].push(path); + accounts[path.account].push(path); + } + + accounts = utils.values(accounts); + + for (i = 0; i < accounts.length; i++) { + paths = accounts[i]; + account = paths[0].account; + receiveDepth = -1; + changeDepth = -1; + + for (j = 0; j < paths.length; j++) { + path = paths[j]; + + if (path.change) { + if (path.index > changeDepth) + changeDepth = path.index; + } else { + if (path.index > receiveDepth) + receiveDepth = path.index; + } } - accounts = utils.values(accounts); + receiveDepth += 2; + changeDepth += 2; - for (i = 0; i < accounts.length; i++) { - paths = accounts[i]; - account = paths[0].account; - receiveDepth = -1; - changeDepth = -1; - - for (j = 0; j < paths.length; j++) { - path = paths[j]; - - if (path.change) { - if (path.index > changeDepth) - changeDepth = path.index; - } else { - if (path.index > receiveDepth) - receiveDepth = path.index; - } - } - - receiveDepth += 2; - changeDepth += 2; - - try { - account = yield this.getAccount(account); - } catch (e) { - unlock(); - throw e; - } - - if (!account) - continue; - - try { - ret = yield account.setDepth(receiveDepth, changeDepth); - } catch (e) { - unlock(); - throw e; - } - - rcv = ret[0]; - chng = ret[1]; - - if (rcv) - receive.push(rcv); + try { + account = yield this.getAccount(account); + } catch (e) { + unlock(); + throw e; } - if (receive.length > 0) { - this.db.emit('address', this.id, receive); - this.emit('address', receive); + if (!account) + continue; + + try { + ret = yield account.setDepth(receiveDepth, changeDepth); + } catch (e) { + unlock(); + throw e; } - yield this.commit(); + rcv = ret[0]; + chng = ret[1]; - unlock(); - return receive; - }, this); -}; + if (rcv) + receive.push(rcv); + } + + if (receive.length > 0) { + this.db.emit('address', this.id, receive); + this.emit('address', receive); + } + + yield this.commit(); + + unlock(); + return receive; +}); /** * Emit balance events after a tx is saved. @@ -1290,21 +1246,19 @@ Wallet.prototype.syncOutputDepth = function syncOutputDepth(info) { * @param {Function} callback */ -Wallet.prototype.updateBalances = function updateBalances() { - return spawn(function *() { - var balance; +Wallet.prototype.updateBalances = spawn.co(function* updateBalances() { + var balance; - if (this.db.listeners('balance').length === 0 - && this.listeners('balance').length === 0) { - return; - } + if (this.db.listeners('balance').length === 0 + && this.listeners('balance').length === 0) { + return; + } - balance = yield this.getBalance(); + balance = yield this.getBalance(); - this.db.emit('balance', this.id, balance); - this.emit('balance', balance); - }, this); -}; + this.db.emit('balance', this.id, balance); + this.emit('balance', balance); +}); /** * Derive new addresses and emit balance. @@ -1314,12 +1268,10 @@ Wallet.prototype.updateBalances = function updateBalances() { * @param {Function} callback */ -Wallet.prototype.handleTX = function handleTX(info) { - return spawn(function *() { - yield this.syncOutputDepth(info); - yield this.updateBalances(); - }, this); -}; +Wallet.prototype.handleTX = spawn.co(function* handleTX(info) { + yield this.syncOutputDepth(info); + yield this.updateBalances(); +}); /** * Get a redeem script or witness script by hash. @@ -1327,21 +1279,19 @@ Wallet.prototype.handleTX = function handleTX(info) { * @returns {Script} */ -Wallet.prototype.getRedeem = function getRedeem(hash) { - return spawn(function *() { - var ring; +Wallet.prototype.getRedeem = spawn.co(function* getRedeem(hash) { + var ring; - if (typeof hash === 'string') - hash = new Buffer(hash, 'hex'); + if (typeof hash === 'string') + hash = new Buffer(hash, 'hex'); - ring = yield this.getKeyRing(hash.toString('hex')); + ring = yield this.getKeyRing(hash.toString('hex')); - if (!ring) - return; + if (!ring) + return; - return ring.getRedeem(hash); - }, this); -}; + return ring.getRedeem(hash); +}); /** * Build input scripts templates for a transaction (does not @@ -1352,21 +1302,19 @@ Wallet.prototype.getRedeem = function getRedeem(hash) { * (total number of scripts built). */ -Wallet.prototype.template = function template(tx) { - return spawn(function *() { - var total = 0; - var i, rings, ring; +Wallet.prototype.template = spawn.co(function* template(tx) { + var total = 0; + var i, rings, ring; - rings = yield this.deriveInputs(tx); + rings = yield this.deriveInputs(tx); - for (i = 0; i < rings.length; i++) { - ring = rings[i]; - total += tx.template(ring); - } + for (i = 0; i < rings.length; i++) { + ring = rings[i]; + total += tx.template(ring); + } - return total; - }, this); -}; + return total; +}); /** * Build input scripts and sign inputs for a transaction. Only attempts @@ -1377,25 +1325,23 @@ Wallet.prototype.template = function template(tx) { * of inputs scripts built and signed). */ -Wallet.prototype.sign = function sign(tx, options) { - return spawn(function *() { - var passphrase, timeout, master, rings; +Wallet.prototype.sign = spawn.co(function* sign(tx, options) { + var passphrase, timeout, master, rings; - if (!options) - options = {}; + if (!options) + options = {}; - if (typeof options === 'string' || Buffer.isBuffer(options)) - options = { passphrase: options }; + if (typeof options === 'string' || Buffer.isBuffer(options)) + options = { passphrase: options }; - passphrase = options.passphrase; - timeout = options.timeout; + passphrase = options.passphrase; + timeout = options.timeout; - master = yield this.unlock(passphrase, timeout); - rings = yield this.deriveInputs(tx); + master = yield this.unlock(passphrase, timeout); + rings = yield this.deriveInputs(tx); - return yield this.signAsync(rings, tx); - }, this); -}; + return yield this.signAsync(rings, tx); +}); /** * Sign a transaction asynchronously. @@ -1497,12 +1443,10 @@ Wallet.prototype.addTX = function addTX(tx) { * @param {Function} callback - Returns [Error, {@link TX}[]]. */ -Wallet.prototype.getHistory = function getHistory(account) { - return spawn(function *() { - account = yield this._getIndex(account); - return this.tx.getHistory(account); - }, this); -}; +Wallet.prototype.getHistory = spawn.co(function* getHistory(account) { + account = yield this._getIndex(account); + return this.tx.getHistory(account); +}); /** * Get all available coins (accesses db). @@ -1510,12 +1454,10 @@ Wallet.prototype.getHistory = function getHistory(account) { * @param {Function} callback - Returns [Error, {@link Coin}[]]. */ -Wallet.prototype.getCoins = function getCoins(account) { - return spawn(function *() { - account = yield this._getIndex(account); - return yield this.tx.getCoins(account); - }, this); -}; +Wallet.prototype.getCoins = spawn.co(function* getCoins(account) { + account = yield this._getIndex(account); + return yield this.tx.getCoins(account); +}); /** * Get all pending/unconfirmed transactions (accesses db). @@ -1523,12 +1465,10 @@ Wallet.prototype.getCoins = function getCoins(account) { * @param {Function} callback - Returns [Error, {@link TX}[]]. */ -Wallet.prototype.getUnconfirmed = function getUnconfirmed(account) { - return spawn(function *() { - account = yield this._getIndex(account); - return yield this.tx.getUnconfirmed(account); - }, this); -}; +Wallet.prototype.getUnconfirmed = spawn.co(function* getUnconfirmed(account) { + account = yield this._getIndex(account); + return yield this.tx.getUnconfirmed(account); +}); /** * Get wallet balance (accesses db). @@ -1536,12 +1476,10 @@ Wallet.prototype.getUnconfirmed = function getUnconfirmed(account) { * @param {Function} callback - Returns [Error, {@link Balance}]. */ -Wallet.prototype.getBalance = function getBalance(account) { - return spawn(function *() { - account = yield this._getIndex(account); - return yield this.tx.getBalance(account); - }, this); -}; +Wallet.prototype.getBalance = spawn.co(function* getBalance(account) { + account = yield this._getIndex(account); + return yield this.tx.getBalance(account); +}); /** * Get a range of transactions between two timestamps (accesses db). @@ -1552,16 +1490,14 @@ Wallet.prototype.getBalance = function getBalance(account) { * @param {Function} callback - Returns [Error, {@link TX}[]]. */ -Wallet.prototype.getRange = function getRange(account, options) { - return spawn(function *() { - if (account && typeof account === 'object') { - options = account; - account = null; - } - account = yield this._getIndex(account); - return yield this.tx.getRange(account, options); - }, this); -}; +Wallet.prototype.getRange = spawn.co(function* getRange(account, options) { + if (account && typeof account === 'object') { + options = account; + account = null; + } + account = yield this._getIndex(account); + return yield this.tx.getRange(account, options); +}); /** * Get the last N transactions (accesses db). @@ -1570,12 +1506,10 @@ Wallet.prototype.getRange = function getRange(account, options) { * @param {Function} callback - Returns [Error, {@link TX}[]]. */ -Wallet.prototype.getLast = function getLast(account, limit) { - return spawn(function *() { - account = yield this._getIndex(account); - return yield this.tx.getLast(account, limit); - }, this); -}; +Wallet.prototype.getLast = spawn.co(function* getLast(account, limit) { + account = yield this._getIndex(account); + return yield this.tx.getLast(account, limit); +}); /** * Zap stale TXs from wallet (accesses db). @@ -1584,12 +1518,10 @@ Wallet.prototype.getLast = function getLast(account, limit) { * @param {Function} callback - Returns [Error]. */ -Wallet.prototype.zap = function zap(account, age) { - return spawn(function *() { - account = yield this._getIndex(account); - return yield this.tx.zap(account, age); - }, this); -}; +Wallet.prototype.zap = spawn.co(function* zap(account, age) { + account = yield this._getIndex(account); + return yield this.tx.zap(account, age); +}); /** * Abandon transaction (accesses db). @@ -1609,21 +1541,19 @@ Wallet.prototype.abandon = function abandon(hash) { * @param {Function} callback */ -Wallet.prototype._getIndex = function _getIndex(account) { - return spawn(function *() { - var index; +Wallet.prototype._getIndex = spawn.co(function* _getIndex(account) { + var index; - if (account == null) - return null; + if (account == null) + return null; - index = yield this.db.getAccountIndex(this.wid, account); + index = yield this.db.getAccountIndex(this.wid, account); - if (index === -1) - throw new Error('Account not found.'); + if (index === -1) + throw new Error('Account not found.'); - return index; - }, this); -}; + return index; +}); /** * Get public key for current receiving address. @@ -2087,41 +2017,39 @@ MasterKey.prototype._lock = function _lock(force) { * @param {Function} callback - Returns [Error, {@link HDPrivateKey}]. */ -MasterKey.prototype.unlock = function _unlock(passphrase, timeout) { - return spawn(function *() { - var unlock = yield this._lock(); - var data, key; - - if (this.key) { - unlock(); - return this.key; - } - - if (!passphrase) { - unlock(); - throw new Error('No passphrase.'); - } - - assert(this.encrypted); - - try { - key = yield crypto.derive(passphrase); - data = crypto.decipher(this.ciphertext, key, this.iv); - } catch (e) { - unlock(); - throw e; - } - - this.key = bcoin.hd.fromExtended(data); - - this.start(timeout); - - this.aesKey = key; +MasterKey.prototype.unlock = spawn.co(function* _unlock(passphrase, timeout) { + var unlock = yield this._lock(); + var data, key; + if (this.key) { unlock(); return this.key; - }, this); -}; + } + + if (!passphrase) { + unlock(); + throw new Error('No passphrase.'); + } + + assert(this.encrypted); + + try { + key = yield crypto.derive(passphrase); + data = crypto.decipher(this.ciphertext, key, this.iv); + } catch (e) { + unlock(); + throw e; + } + + this.key = bcoin.hd.fromExtended(data); + + this.start(timeout); + + this.aesKey = key; + + unlock(); + return this.key; +}); /** * Start the destroy timer. @@ -2207,42 +2135,40 @@ MasterKey.prototype.destroy = function destroy() { * @param {Function} callback */ -MasterKey.prototype.decrypt = function decrypt(passphrase) { - return spawn(function *() { - var unlock = yield this._lock(); - var data; +MasterKey.prototype.decrypt = spawn.co(function* decrypt(passphrase) { + var unlock = yield this._lock(); + var data; - if (!this.encrypted) { - assert(this.key); - return unlock(); - } + if (!this.encrypted) { + assert(this.key); + return unlock(); + } - if (!passphrase) - return unlock(); + if (!passphrase) + return unlock(); - this.destroy(); - - try { - data = yield crypto.decrypt(this.ciphertext, passphrase, this.iv); - } catch (e) { - unlock(); - throw e; - } - - try { - this.key = bcoin.hd.fromExtended(data); - } catch (e) { - unlock(); - throw e; - } - - this.encrypted = false; - this.iv = null; - this.ciphertext = null; + this.destroy(); + try { + data = yield crypto.decrypt(this.ciphertext, passphrase, this.iv); + } catch (e) { unlock(); - }, this); -}; + throw e; + } + + try { + this.key = bcoin.hd.fromExtended(data); + } catch (e) { + unlock(); + throw e; + } + + this.encrypted = false; + this.iv = null; + this.ciphertext = null; + + unlock(); +}); /** * Encrypt the key permanently. @@ -2250,37 +2176,35 @@ MasterKey.prototype.decrypt = function decrypt(passphrase) { * @param {Function} callback */ -MasterKey.prototype.encrypt = function encrypt(passphrase) { - return spawn(function *() { - var unlock = yield this._lock(); - var data, iv; +MasterKey.prototype.encrypt = spawn.co(function* encrypt(passphrase) { + var unlock = yield this._lock(); + var data, iv; - if (this.encrypted) - return unlock(); + if (this.encrypted) + return unlock(); - if (!passphrase) - return unlock(); + if (!passphrase) + return unlock(); - data = this.key.toExtended(); - iv = crypto.randomBytes(16); + data = this.key.toExtended(); + iv = crypto.randomBytes(16); - this.stop(); - - try { - data = yield crypto.encrypt(data, passphrase, iv); - } catch (e) { - unlock(); - throw e; - } - - this.key = null; - this.encrypted = true; - this.iv = iv; - this.ciphertext = data; + this.stop(); + try { + data = yield crypto.encrypt(data, passphrase, iv); + } catch (e) { unlock(); - }, this); -}; + throw e; + } + + this.key = null; + this.encrypted = true; + this.iv = iv; + this.ciphertext = data; + + unlock(); +}); /** * Serialize the key in the form of: diff --git a/lib/wallet/walletdb.js b/lib/wallet/walletdb.js index 3a84fdff..c716f7dc 100644 --- a/lib/wallet/walletdb.js +++ b/lib/wallet/walletdb.js @@ -225,21 +225,19 @@ WalletDB.prototype._lockTX = function _lockTX(force) { * @param {Function} callback */ -WalletDB.prototype._open = function open() { - return spawn(function *() { - yield this.db.open(); - yield this.db.checkVersion('V', 2); - yield this.writeGenesis(); +WalletDB.prototype._open = spawn.co(function* open() { + yield this.db.open(); + yield this.db.checkVersion('V', 2); + yield this.writeGenesis(); - this.depth = yield this.getDepth(); + this.depth = yield this.getDepth(); - this.logger.info( - 'WalletDB loaded (depth=%d, height=%d).', - this.depth, this.height); + this.logger.info( + 'WalletDB loaded (depth=%d, height=%d).', + this.depth, this.height); - yield this.loadFilter(); - }, this); -}; + yield this.loadFilter(); +}); /** * Close the walletdb, wait for the database to close. @@ -247,20 +245,18 @@ WalletDB.prototype._open = function open() { * @param {Function} callback */ -WalletDB.prototype._close = function close() { - return spawn(function *() { - var keys = Object.keys(this.wallets); - var i, key, wallet; +WalletDB.prototype._close = spawn.co(function* close() { + var keys = Object.keys(this.wallets); + var i, key, wallet; - for (i = 0; i < keys.length; i++) { - key = keys[i]; - wallet = this.wallets[key]; - yield wallet.destroy(); - } + for (i = 0; i < keys.length; i++) { + key = keys[i]; + wallet = this.wallets[key]; + yield wallet.destroy(); + } - yield this.db.close(); - }, this); -}; + yield this.db.close(); +}); /** * Backup the wallet db. @@ -278,37 +274,35 @@ WalletDB.prototype.backup = function backup(path) { * @param {Function} callback */ -WalletDB.prototype.getDepth = function getDepth() { - return spawn(function *() { - var kv, iter, depth; +WalletDB.prototype.getDepth = spawn.co(function* getDepth() { + var kv, iter, depth; - // This may seem like a strange way to do - // this, but updating a global state when - // creating a new wallet is actually pretty - // damn tricky. There would be major atomicity - // issues if updating a global state inside - // a "scoped" state. So, we avoid all the - // nonsense of adding a global lock to - // walletdb.create by simply seeking to the - // highest wallet wid. - iter = this.db.iterator({ - gte: layout.w(0x00000000), - lte: layout.w(0xffffffff), - reverse: true - }); + // This may seem like a strange way to do + // this, but updating a global state when + // creating a new wallet is actually pretty + // damn tricky. There would be major atomicity + // issues if updating a global state inside + // a "scoped" state. So, we avoid all the + // nonsense of adding a global lock to + // walletdb.create by simply seeking to the + // highest wallet wid. + iter = this.db.iterator({ + gte: layout.w(0x00000000), + lte: layout.w(0xffffffff), + reverse: true + }); - kv = yield iter.next(); + kv = yield iter.next(); - if (!kv) - return 1; + if (!kv) + return 1; - yield iter.end(); + yield iter.end(); - depth = layout.ww(kv[0]); + depth = layout.ww(kv[0]); - return depth + 1; - }, this); -}; + return depth + 1; +}); /** * Start batch. @@ -410,20 +404,18 @@ WalletDB.prototype.testFilter = function testFilter(hashes) { * @param {Function} callback - Returns [Error, Object]. */ -WalletDB.prototype.dump = function dump() { - return spawn(function *() { - var records = {}; - yield this.db.iterate({ - gte: ' ', - lte: '~', - values: true, - parse: function(key, value) { - records[key] = value; - } - }); - return records; - }, this); -}; +WalletDB.prototype.dump = spawn.co(function* dump() { + var records = {}; + yield this.db.iterate({ + gte: ' ', + lte: '~', + values: true, + parse: function(key, value) { + records[key] = value; + } + }); + return records; +}); /** * Register an object with the walletdb. @@ -480,48 +472,46 @@ WalletDB.prototype.getWalletID = function getWalletID(id) { * @param {Function} callback - Returns [Error, {@link Wallet}]. */ -WalletDB.prototype.get = function get(wid) { - return spawn(function *() { - var self = this; - var wallet, unlock; +WalletDB.prototype.get = spawn.co(function* get(wid) { + var self = this; + var wallet, unlock; - wid = yield this.getWalletID(wid); - if (!wid) - return; + wid = yield this.getWalletID(wid); + if (!wid) + return; - wallet = this.wallets[wid]; - if (wallet) - return wallet; - - // NOTE: Lock must start here! - unlock = yield this._lockRead(wid); - - try { - wallet = yield this.db.fetch(layout.w(wid), function(data) { - return bcoin.wallet.fromRaw(self, data); - }); - } catch (e) { - unlock(); - throw e; - } - - if (!wallet) { - unlock(); - return; - } - - try { - this.register(wallet); - yield wallet.open(); - } catch (e) { - unlock(); - throw e; - } - - unlock(); + wallet = this.wallets[wid]; + if (wallet) return wallet; - }, this); -}; + + // NOTE: Lock must start here! + unlock = yield this._lockRead(wid); + + try { + wallet = yield this.db.fetch(layout.w(wid), function(data) { + return bcoin.wallet.fromRaw(self, data); + }); + } catch (e) { + unlock(); + throw e; + } + + if (!wallet) { + unlock(); + return; + } + + try { + this.register(wallet); + yield wallet.open(); + } catch (e) { + unlock(); + throw e; + } + + unlock(); + return wallet; +}); /** * Save a wallet to the database. @@ -545,25 +535,23 @@ WalletDB.prototype.save = function save(wallet) { * @param {Function} callback */ -WalletDB.prototype.auth = function auth(wid, token) { - return spawn(function *() { - var wallet = yield this.get(wid); - if (!wallet) - return; +WalletDB.prototype.auth = spawn.co(function* auth(wid, token) { + var wallet = yield this.get(wid); + if (!wallet) + return; - if (typeof token === 'string') { - if (!utils.isHex(token)) - throw new Error('Authentication error.'); - token = new Buffer(token, 'hex'); - } - - // Compare in constant time: - if (!crypto.ccmp(token, wallet.token)) + if (typeof token === 'string') { + if (!utils.isHex(token)) throw new Error('Authentication error.'); + token = new Buffer(token, 'hex'); + } - return wallet; - }, this); -}; + // Compare in constant time: + if (!crypto.ccmp(token, wallet.token)) + throw new Error('Authentication error.'); + + return wallet; +}); /** * Create a new wallet, save to database, setup watcher. @@ -571,38 +559,36 @@ WalletDB.prototype.auth = function auth(wid, token) { * @param {Function} callback - Returns [Error, {@link Wallet}]. */ -WalletDB.prototype.create = function create(options) { - return spawn(function *() { - var unlock, wallet, exists; +WalletDB.prototype.create = spawn.co(function* create(options) { + var unlock, wallet, exists; - if (!options) - options = {}; + if (!options) + options = {}; - unlock = yield this._lockWrite(options.id); + unlock = yield this._lockWrite(options.id); - exists = yield this.has(options.id); - - if (exists) { - unlock(); - throw new Error('Wallet already exists.'); - } - - try { - wallet = bcoin.wallet.fromOptions(this, options); - wallet.wid = this.depth++; - this.register(wallet); - yield wallet.init(options); - } catch (e) { - unlock(); - throw e; - } - - this.logger.info('Created wallet %s.', wallet.id); + exists = yield this.has(options.id); + if (exists) { unlock(); - return wallet; - }, this); -}; + throw new Error('Wallet already exists.'); + } + + try { + wallet = bcoin.wallet.fromOptions(this, options); + wallet.wid = this.depth++; + this.register(wallet); + yield wallet.init(options); + } catch (e) { + unlock(); + throw e; + } + + this.logger.info('Created wallet %s.', wallet.id); + + unlock(); + return wallet; +}); /** * Test for the existence of a wallet. @@ -610,12 +596,10 @@ WalletDB.prototype.create = function create(options) { * @param {Function} callback */ -WalletDB.prototype.has = function has(id) { - return spawn(function *() { - var wid = yield this.getWalletID(id); - return wid != null; - }, this); -}; +WalletDB.prototype.has = spawn.co(function* has(id) { + var wid = yield this.getWalletID(id); + return wid != null; +}); /** * Attempt to create wallet, return wallet if already exists. @@ -623,14 +607,12 @@ WalletDB.prototype.has = function has(id) { * @param {Function} callback */ -WalletDB.prototype.ensure = function ensure(options) { - return spawn(function *() { - var wallet = yield this.get(options.id); - if (wallet) - return wallet; - return yield this.create(options); - }, this); -}; +WalletDB.prototype.ensure = spawn.co(function* ensure(options) { + var wallet = yield this.get(options.id); + if (wallet) + return wallet; + return yield this.create(options); +}); /** * Get an account from the database. @@ -639,23 +621,21 @@ WalletDB.prototype.ensure = function ensure(options) { * @param {Function} callback - Returns [Error, {@link Wallet}]. */ -WalletDB.prototype.getAccount = function getAccount(wid, name) { - return spawn(function *() { - var index = yield this.getAccountIndex(wid, name); - var account; +WalletDB.prototype.getAccount = spawn.co(function* getAccount(wid, name) { + var index = yield this.getAccountIndex(wid, name); + var account; - if (index === -1) - return; + if (index === -1) + return; - account = yield this._getAccount(wid, index); + account = yield this._getAccount(wid, index); - if (!account) - return; + if (!account) + return; - yield account.open(); - return account; - }, this); -}; + yield account.open(); + return account; +}); /** * Get an account from the database. Do not setup watcher. @@ -686,33 +666,31 @@ WalletDB.prototype._getAccount = function getAccount(wid, index) { * @param {Function} callback - Returns [Error, Array]. */ -WalletDB.prototype.getAccounts = function getAccounts(wid) { - return spawn(function *() { - var map = []; - var i, accounts; +WalletDB.prototype.getAccounts = spawn.co(function* getAccounts(wid) { + var map = []; + var i, accounts; - yield this.db.iterate({ - gte: layout.i(wid, ''), - lte: layout.i(wid, MAX_POINT), - values: true, - parse: function(key, value) { - var name = layout.ii(key)[1]; - var index = value.readUInt32LE(0, true); - map[index] = name; - } - }); - - // Get it out of hash table mode. - accounts = []; - - for (i = 0; i < map.length; i++) { - assert(map[i] != null); - accounts.push(map[i]); + yield this.db.iterate({ + gte: layout.i(wid, ''), + lte: layout.i(wid, MAX_POINT), + values: true, + parse: function(key, value) { + var name = layout.ii(key)[1]; + var index = value.readUInt32LE(0, true); + map[index] = name; } + }); - return accounts; - }, this); -}; + // Get it out of hash table mode. + accounts = []; + + for (i = 0; i < map.length; i++) { + assert(map[i] != null); + accounts.push(map[i]); + } + + return accounts; +}); /** * Lookup the corresponding account name's index. @@ -721,27 +699,25 @@ WalletDB.prototype.getAccounts = function getAccounts(wid) { * @param {Function} callback - Returns [Error, Number]. */ -WalletDB.prototype.getAccountIndex = function getAccountIndex(wid, name) { - return spawn(function *() { - var index; +WalletDB.prototype.getAccountIndex = spawn.co(function* getAccountIndex(wid, name) { + var index; - if (!wid) - return -1; + if (!wid) + return -1; - if (name == null) - return -1; + if (name == null) + return -1; - if (typeof name === 'number') - return name; + if (typeof name === 'number') + return name; - index = yield this.db.get(layout.i(wid, name)); + index = yield this.db.get(layout.i(wid, name)); - if (!index) - return -1; + if (!index) + return -1; - return index.readUInt32LE(0, true); - }, this); -}; + return index.readUInt32LE(0, true); +}); /** * Save an account to the database. @@ -768,26 +744,24 @@ WalletDB.prototype.saveAccount = function saveAccount(account) { * @param {Function} callback - Returns [Error, {@link Account}]. */ -WalletDB.prototype.createAccount = function createAccount(options) { - return spawn(function *() { - var exists = yield this.hasAccount(options.wid, options.name); - var account; +WalletDB.prototype.createAccount = spawn.co(function* createAccount(options) { + var exists = yield this.hasAccount(options.wid, options.name); + var account; - if (exists) - throw new Error('Account already exists.'); + if (exists) + throw new Error('Account already exists.'); - account = bcoin.account.fromOptions(this, options); + account = bcoin.account.fromOptions(this, options); - yield account.init(); + yield account.init(); - this.logger.info('Created account %s/%s/%d.', - account.id, - account.name, - account.accountIndex); + this.logger.info('Created account %s/%s/%d.', + account.id, + account.name, + account.accountIndex); - return account; - }, this); -}; + return account; +}); /** * Test for the existence of an account. @@ -796,26 +770,24 @@ WalletDB.prototype.createAccount = function createAccount(options) { * @param {Function} callback - Returns [Error, Boolean]. */ -WalletDB.prototype.hasAccount = function hasAccount(wid, account) { - return spawn(function *() { - var index, key; +WalletDB.prototype.hasAccount = spawn.co(function* hasAccount(wid, account) { + var index, key; - if (!wid) - return false; + if (!wid) + return false; - index = yield this.getAccountIndex(wid, account); + index = yield this.getAccountIndex(wid, account); - if (index === -1) - return false; + if (index === -1) + return false; - key = wid + '/' + index; + key = wid + '/' + index; - if (this.accountCache.has(key)) - return true; + if (this.accountCache.has(key)) + return true; - return yield this.db.has(layout.a(wid, index)); - }, this); -}; + return yield this.db.has(layout.a(wid, index)); +}); /** * Save addresses to the path map. @@ -826,24 +798,22 @@ WalletDB.prototype.hasAccount = function hasAccount(wid, account) { * @param {Function} callback */ -WalletDB.prototype.saveAddress = function saveAddress(wid, rings) { - return spawn(function *() { - var i, ring, path; +WalletDB.prototype.saveAddress = spawn.co(function* saveAddress(wid, rings) { + var i, ring, path; - for (i = 0; i < rings.length; i++) { - ring = rings[i]; - path = ring.path; + for (i = 0; i < rings.length; i++) { + ring = rings[i]; + path = ring.path; - yield this.writeAddress(wid, ring.getAddress(), path); + yield this.writeAddress(wid, ring.getAddress(), path); - if (ring.witness) { - path = path.clone(); - path.hash = ring.getProgramHash('hex'); - yield this.writeAddress(wid, ring.getProgramAddress(), path); - } + if (ring.witness) { + path = path.clone(); + path.hash = ring.getProgramHash('hex'); + yield this.writeAddress(wid, ring.getProgramAddress(), path); } - }, this); -}; + } +}); /** * Save a single address to the path map. @@ -853,32 +823,30 @@ WalletDB.prototype.saveAddress = function saveAddress(wid, rings) { * @param {Function} callback */ -WalletDB.prototype.writeAddress = function writeAddress(wid, address, path) { - return spawn(function *() { - var hash = address.getHash('hex'); - var batch = this.batch(wid); - var paths; +WalletDB.prototype.writeAddress = spawn.co(function* writeAddress(wid, address, path) { + var hash = address.getHash('hex'); + var batch = this.batch(wid); + var paths; - if (this.filter) - this.filter.add(hash, 'hex'); + if (this.filter) + this.filter.add(hash, 'hex'); - this.emit('save address', address, path); + this.emit('save address', address, path); - paths = yield this.getAddressPaths(hash); + paths = yield this.getAddressPaths(hash); - if (!paths) - paths = {}; + if (!paths) + paths = {}; - if (paths[wid]) - return; + if (paths[wid]) + return; - paths[wid] = path; + paths[wid] = path; - this.pathCache.set(hash, paths); + this.pathCache.set(hash, paths); - batch.put(layout.p(hash), serializePaths(paths)); - }, this); -}; + batch.put(layout.p(hash), serializePaths(paths)); +}); /** * Retrieve paths by hash. @@ -886,30 +854,28 @@ WalletDB.prototype.writeAddress = function writeAddress(wid, address, path) { * @param {Function} callback */ -WalletDB.prototype.getAddressPaths = function getAddressPaths(hash) { - return spawn(function *() { - var paths; +WalletDB.prototype.getAddressPaths = spawn.co(function* getAddressPaths(hash) { + var paths; - if (!hash) - return; + if (!hash) + return; - paths = this.pathCache.get(hash); - - if (paths) - return paths; - - paths = yield this.db.fetch(layout.p(hash), function(value) { - return parsePaths(value, hash); - }); - - if (!paths) - return; - - this.pathCache.set(hash, paths); + paths = this.pathCache.get(hash); + if (paths) return paths; - }, this); -}; + + paths = yield this.db.fetch(layout.p(hash), function(value) { + return parsePaths(value, hash); + }); + + if (!paths) + return; + + this.pathCache.set(hash, paths); + + return paths; +}); /** * Test whether an address hash exists in the @@ -919,16 +885,14 @@ WalletDB.prototype.getAddressPaths = function getAddressPaths(hash) { * @param {Function} callback */ -WalletDB.prototype.hasAddress = function hasAddress(wid, hash) { - return spawn(function *() { - var paths = yield this.getAddressPaths(hash); +WalletDB.prototype.hasAddress = spawn.co(function* hasAddress(wid, hash) { + var paths = yield this.getAddressPaths(hash); - if (!paths || !paths[wid]) - return false; + if (!paths || !paths[wid]) + return false; - return true; - }, this); -}; + return true; +}); /** * Get all address hashes. @@ -997,31 +961,29 @@ WalletDB.prototype.getWallets = function getWallets() { * @param {Function} callback */ -WalletDB.prototype.rescan = function rescan(chaindb, height) { - return spawn(function *() { - var self = this; - var unlock = yield this._lockTX(); - var hashes; +WalletDB.prototype.rescan = spawn.co(function* rescan(chaindb, height) { + var self = this; + var unlock = yield this._lockTX(); + var hashes; - if (height == null) - height = this.height; + if (height == null) + height = this.height; - hashes = yield this.getAddressHashes(); + hashes = yield this.getAddressHashes(); - this.logger.info('Scanning for %d addresses.', hashes.length); - - try { - yield chaindb.scan(height, hashes, function *(block, txs) { - yield self.addBlock(block, txs, true); - }); - } catch (e) { - unlock(); - throw e; - } + this.logger.info('Scanning for %d addresses.', hashes.length); + try { + yield chaindb.scan(height, hashes, function *(block, txs) { + yield self.addBlock(block, txs, true); + }); + } catch (e) { unlock(); - }, this); -}; + throw e; + } + + unlock(); +}); /** * Get keys of all pending transactions @@ -1062,27 +1024,25 @@ WalletDB.prototype.getPendingKeys = function getPendingKeys() { * @param {Function} callback */ -WalletDB.prototype.resend = function resend() { - return spawn(function *() { - var self = this; - var i, keys, key, tx; +WalletDB.prototype.resend = spawn.co(function* resend() { + var self = this; + var i, keys, key, tx; - keys = yield this.getPendingKeys(); + keys = yield this.getPendingKeys(); - if (keys.length > 0) - this.logger.info('Rebroadcasting %d transactions.', keys.length); + if (keys.length > 0) + this.logger.info('Rebroadcasting %d transactions.', keys.length); - for (i = 0; i < keys.length; i++) { - key = keys[i]; - tx = yield self.db.fetch(key, function(data) { - return bcoin.tx.fromExtended(data); - }); - if (!tx) - continue; - this.emit('send', tx); - } - }, this); -}; + for (i = 0; i < keys.length; i++) { + key = keys[i]; + tx = yield self.db.fetch(key, function(data) { + return bcoin.tx.fromExtended(data); + }); + if (!tx) + continue; + this.emit('send', tx); + } +}); /** * Map a transactions' addresses to wallet IDs. @@ -1090,22 +1050,20 @@ WalletDB.prototype.resend = function resend() { * @param {Function} callback - Returns [Error, {@link PathInfo[]}]. */ -WalletDB.prototype.mapWallets = function mapWallets(tx) { - return spawn(function *() { - var hashes = tx.getHashes('hex'); - var table; +WalletDB.prototype.mapWallets = spawn.co(function* mapWallets(tx) { + var hashes = tx.getHashes('hex'); + var table; - if (!this.testFilter(hashes)) - return; + if (!this.testFilter(hashes)) + return; - table = yield this.getTable(hashes); + table = yield this.getTable(hashes); - if (!table) - return; + if (!table) + return; - return PathInfo.map(this, tx, table); - }, this); -}; + return PathInfo.map(this, tx, table); +}); /** * Map a transactions' addresses to wallet IDs. @@ -1113,21 +1071,19 @@ WalletDB.prototype.mapWallets = function mapWallets(tx) { * @param {Function} callback - Returns [Error, {@link PathInfo}]. */ -WalletDB.prototype.getPathInfo = function getPathInfo(wallet, tx) { - return spawn(function *() { - var hashes = tx.getHashes('hex'); - var table = yield this.getTable(hashes); - var info; +WalletDB.prototype.getPathInfo = spawn.co(function* getPathInfo(wallet, tx) { + var hashes = tx.getHashes('hex'); + var table = yield this.getTable(hashes); + var info; - if (!table) - return; + if (!table) + return; - info = new PathInfo(this, wallet.wid, tx, table); - info.id = wallet.id; + info = new PathInfo(this, wallet.wid, tx, table); + info.id = wallet.id; - return info; - }, this); -}; + return info; +}); /** * Map address hashes to paths. @@ -1135,56 +1091,52 @@ WalletDB.prototype.getPathInfo = function getPathInfo(wallet, tx) { * @param {Function} callback - Returns [Error, {@link AddressTable}]. */ -WalletDB.prototype.getTable = function getTable(hashes) { - return spawn(function *() { - var table = {}; - var count = 0; - var i, j, keys, values, hash, paths; +WalletDB.prototype.getTable = spawn.co(function* getTable(hashes) { + var table = {}; + var count = 0; + var i, j, keys, values, hash, paths; - for (i = 0; i < hashes.length; i++) { - hash = hashes[i]; - paths = yield this.getAddressPaths(hash); - - if (!paths) { - assert(!table[hash]); - table[hash] = []; - continue; - } - - keys = Object.keys(paths); - values = []; - - for (j = 0; j < keys.length; j++) - values.push(paths[keys[j]]); + for (i = 0; i < hashes.length; i++) { + hash = hashes[i]; + paths = yield this.getAddressPaths(hash); + if (!paths) { assert(!table[hash]); - table[hash] = values; - count += values.length; + table[hash] = []; + continue; } - if (count === 0) - return; + keys = Object.keys(paths); + values = []; - return table; - }, this); -}; + for (j = 0; j < keys.length; j++) + values.push(paths[keys[j]]); + + assert(!table[hash]); + table[hash] = values; + count += values.length; + } + + if (count === 0) + return; + + return table; +}); /** * Write the genesis block as the best hash. * @param {Function} callback */ -WalletDB.prototype.writeGenesis = function writeGenesis() { - return spawn(function *() { - var block = yield this.getTip(); - if (block) { - this.tip = block.hash; - this.height = block.height; - return; - } - yield this.setTip(this.network.genesis.hash, 0); - }, this); -}; +WalletDB.prototype.writeGenesis = spawn.co(function* writeGenesis() { + var block = yield this.getTip(); + if (block) { + this.tip = block.hash; + this.height = block.height; + return; + } + yield this.setTip(this.network.genesis.hash, 0); +}); /** * Get the best block hash. @@ -1204,16 +1156,14 @@ WalletDB.prototype.getTip = function getTip() { * @param {Function} callback */ -WalletDB.prototype.setTip = function setTip(hash, height) { - return spawn(function *() { - var block = new WalletBlock(hash, height); +WalletDB.prototype.setTip = spawn.co(function* setTip(hash, height) { + var block = new WalletBlock(hash, height); - yield this.db.put(layout.R, block.toTip()); + yield this.db.put(layout.R, block.toTip()); - this.tip = block.hash; - this.height = block.height; - }, this); -}; + this.tip = block.hash; + this.height = block.height; +}); /** * Connect a block. @@ -1284,68 +1234,66 @@ WalletDB.prototype.getWalletsByTX = function getWalletsByTX(hash) { * @param {Function} callback */ -WalletDB.prototype.addBlock = function addBlock(entry, txs, force) { - return spawn(function *() { - var unlock = yield this._lockTX(force); - var i, block, matches, hash, tx, wallets; - - if (this.options.useCheckpoints) { - if (entry.height <= this.network.checkpoints.lastHeight) { - try { - yield this.setTip(entry.hash, entry.height); - } catch (e) { - unlock(); - throw e; - } - unlock(); - return; - } - } - - block = WalletBlock.fromEntry(entry); - matches = []; - - // Update these early so transactions - // get correct confirmation calculations. - this.tip = block.hash; - this.height = block.height; - - // NOTE: Atomicity doesn't matter here. If we crash - // during this loop, the automatic rescan will get - // the database back into the correct state. - for (i = 0; i < txs.length; i++) { - tx = txs[i]; +WalletDB.prototype.addBlock = spawn.co(function* addBlock(entry, txs, force) { + var unlock = yield this._lockTX(force); + var i, block, matches, hash, tx, wallets; + if (this.options.useCheckpoints) { + if (entry.height <= this.network.checkpoints.lastHeight) { try { - wallets = yield this.addTX(tx, true); + yield this.setTip(entry.hash, entry.height); } catch (e) { unlock(); throw e; } - - if (!wallets) - continue; - - hash = tx.hash('hex'); - block.hashes.push(hash); - matches.push(wallets); + unlock(); + return; } + } - if (block.hashes.length > 0) { - this.logger.info('Connecting block %s (%d txs).', - utils.revHex(block.hash), block.hashes.length); - } + block = WalletBlock.fromEntry(entry); + matches = []; + + // Update these early so transactions + // get correct confirmation calculations. + this.tip = block.hash; + this.height = block.height; + + // NOTE: Atomicity doesn't matter here. If we crash + // during this loop, the automatic rescan will get + // the database back into the correct state. + for (i = 0; i < txs.length; i++) { + tx = txs[i]; try { - yield this.writeBlock(block, matches); + wallets = yield this.addTX(tx, true); } catch (e) { unlock(); throw e; } + if (!wallets) + continue; + + hash = tx.hash('hex'); + block.hashes.push(hash); + matches.push(wallets); + } + + if (block.hashes.length > 0) { + this.logger.info('Connecting block %s (%d txs).', + utils.revHex(block.hash), block.hashes.length); + } + + try { + yield this.writeBlock(block, matches); + } catch (e) { unlock(); - }, this); -}; + throw e; + } + + unlock(); +}); /** * Unconfirm a block's transactions @@ -1354,57 +1302,55 @@ WalletDB.prototype.addBlock = function addBlock(entry, txs, force) { * @param {Function} callback */ -WalletDB.prototype.removeBlock = function removeBlock(entry) { - return spawn(function *() { - var unlock = yield this._lockTX(); - var i, j, block, data, hash, wallets, wid, wallet; +WalletDB.prototype.removeBlock = spawn.co(function* removeBlock(entry) { + var unlock = yield this._lockTX(); + var i, j, block, data, hash, wallets, wid, wallet; - block = WalletBlock.fromEntry(entry); + block = WalletBlock.fromEntry(entry); - // Note: - // If we crash during a reorg, there's not much to do. - // Reorgs cannot be rescanned. The database will be - // in an odd state, with some txs being confirmed - // when they shouldn't be. That being said, this - // should eventually resolve itself when a new block - // comes in. - data = yield this.getBlock(block.hash); + // Note: + // If we crash during a reorg, there's not much to do. + // Reorgs cannot be rescanned. The database will be + // in an odd state, with some txs being confirmed + // when they shouldn't be. That being said, this + // should eventually resolve itself when a new block + // comes in. + data = yield this.getBlock(block.hash); - if (data) - block.hashes = data.hashes; + if (data) + block.hashes = data.hashes; - if (block.hashes.length > 0) { - this.logger.warning('Disconnecting block %s (%d txs).', - utils.revHex(block.hash), block.hashes.length); - } + if (block.hashes.length > 0) { + this.logger.warning('Disconnecting block %s (%d txs).', + utils.revHex(block.hash), block.hashes.length); + } - // Unwrite the tip as fast as we can. - yield this.unwriteBlock(block); + // Unwrite the tip as fast as we can. + yield this.unwriteBlock(block); - for (i = 0; i < block.hashes.length; i++) { - hash = block.hashes[i]; - wallets = yield this.getWalletsByTX(hash); + for (i = 0; i < block.hashes.length; i++) { + hash = block.hashes[i]; + wallets = yield this.getWalletsByTX(hash); - if (!wallets) + if (!wallets) + continue; + + for (j = 0; j < wallets.length; j++) { + wid = wallets[j]; + wallet = yield this.get(wid); + + if (!wallet) continue; - for (j = 0; j < wallets.length; j++) { - wid = wallets[j]; - wallet = yield this.get(wid); - - if (!wallet) - continue; - - yield wallet.tx.unconfirm(hash); - } + yield wallet.tx.unconfirm(hash); } + } - this.tip = block.hash; - this.height = block.height; + this.tip = block.hash; + this.height = block.height; - unlock(); - }, this); -}; + unlock(); +}); /** * Add a transaction to the database, map addresses @@ -1414,57 +1360,55 @@ WalletDB.prototype.removeBlock = function removeBlock(entry) { * @param {Function} callback - Returns [Error]. */ -WalletDB.prototype.addTX = function addTX(tx, force) { - return spawn(function *() { - var unlock = yield this._lockTX(force); - var i, wallets, info, wallet; +WalletDB.prototype.addTX = spawn.co(function* addTX(tx, force) { + var unlock = yield this._lockTX(force); + var i, wallets, info, wallet; - assert(!tx.mutable, 'Cannot add mutable TX to wallet.'); + assert(!tx.mutable, 'Cannot add mutable TX to wallet.'); + + // Note: + // Atomicity doesn't matter here. If we crash, + // the automatic rescan will get the database + // back in the correct state. + try { + wallets = yield this.mapWallets(tx); + } catch (e) { + unlock(); + throw e; + } + + if (!wallets) { + unlock(); + return; + } + + this.logger.info( + 'Incoming transaction for %d wallets (%s).', + wallets.length, tx.rhash); + + for (i = 0; i < wallets.length; i++) { + info = wallets[i]; + wallet = yield this.get(info.wid); + + if (!wallet) + continue; + + this.logger.debug('Adding tx to wallet: %s', wallet.id); + + info.id = wallet.id; - // Note: - // Atomicity doesn't matter here. If we crash, - // the automatic rescan will get the database - // back in the correct state. try { - wallets = yield this.mapWallets(tx); + yield wallet.tx.add(tx, info); + yield wallet.handleTX(info); } catch (e) { unlock(); throw e; } + } - if (!wallets) { - unlock(); - return; - } - - this.logger.info( - 'Incoming transaction for %d wallets (%s).', - wallets.length, tx.rhash); - - for (i = 0; i < wallets.length; i++) { - info = wallets[i]; - wallet = yield this.get(info.wid); - - if (!wallet) - continue; - - this.logger.debug('Adding tx to wallet: %s', wallet.id); - - info.id = wallet.id; - - try { - yield wallet.tx.add(tx, info); - yield wallet.handleTX(info); - } catch (e) { - unlock(); - throw e; - } - } - - unlock(); - return wallets; - }, this); -}; + unlock(); + return wallets; +}); /** * Get the corresponding path for an address hash. @@ -1473,22 +1417,20 @@ WalletDB.prototype.addTX = function addTX(tx, force) { * @param {Function} callback */ -WalletDB.prototype.getAddressPath = function getAddressPath(wid, hash) { - return spawn(function *() { - var paths = yield this.getAddressPaths(hash); - var path; +WalletDB.prototype.getAddressPath = spawn.co(function* getAddressPath(wid, hash) { + var paths = yield this.getAddressPaths(hash); + var path; - if (!paths) - return; + if (!paths) + return; - path = paths[wid]; + path = paths[wid]; - if (!path) - return; + if (!path) + return; - return path; - }, this); -}; + return path; +}); /** * Path Info diff --git a/lib/workers/workers.js b/lib/workers/workers.js index 2f1f11e1..8055598f 100644 --- a/lib/workers/workers.js +++ b/lib/workers/workers.js @@ -247,25 +247,23 @@ Workers.prototype.verify = function verify(tx, flags) { * @param {Function} callback */ -Workers.prototype.sign = function sign(tx, ring, type) { - return spawn(function *() { - var i, result, input, sig, sigs, total; +Workers.prototype.sign = spawn.co(function* sign(tx, ring, type) { + var i, result, input, sig, sigs, total; - result = yield this.execute('sign', [tx, ring, type], -1); + result = yield this.execute('sign', [tx, ring, type], -1); - sigs = result[0]; - total = result[1]; + sigs = result[0]; + total = result[1]; - for (i = 0; i < sigs.length; i++) { - sig = sigs[i]; - input = tx.inputs[i]; - input.script = sig[0]; - input.witness = sig[1]; - } + for (i = 0; i < sigs.length; i++) { + sig = sigs[i]; + input = tx.inputs[i]; + input.script = sig[0]; + input.witness = sig[1]; + } - return total; - }, this); -}; + return total; +}); /** * Execute the mining job (no timeout).