diff --git a/Transaction.js b/Transaction.js index 9b40217..ac82be1 100644 --- a/Transaction.js +++ b/Transaction.js @@ -211,148 +211,6 @@ Transaction.prototype.inputs = function inputs() { return res; }; -/** - * Load and cache transaction inputs. - * - * This function will try to load the inputs for a transaction. - * - * @param {BlockChain} blockChain A reference to the BlockChain object. - * @param {TransactionMap|null} txStore Additional transactions to consider. - * @param {Boolean} wait Whether to keep trying until the dependencies are - * met (or a timeout occurs.) - * @param {Function} callback Function to call on completion. - */ -Transaction.prototype.cacheInputs = - function cacheInputs(blockChain, txStore, wait, callback) { - var self = this; - - var txCache = new TransactionInputsCache(this); - txCache.buffer(blockChain, txStore, wait, callback); -}; - -Transaction.prototype.verify = function verify(txCache, blockChain, callback) { - var self = this; - - var txIndex = txCache.txIndex; - - var outpoints = []; - - var valueIn = bignum(0); - var valueOut = bignum(0); - - function getTxOut(txin, n) { - var outHash = txin.getOutpointHash(); - var outIndex = txin.getOutpointIndex(); - var outHashBase64 = outHash.toString('base64'); - var fromTxOuts = txIndex[outHashBase64]; - - if (!fromTxOuts) { - throw new MissingSourceError( - "Source tx " + util.formatHash(outHash) + - " for inputs " + n + " not found", - // We store the hash of the missing tx in the error - // so that the txStore can watch out for it. - outHash.toString('base64') - ); - } - - var txout = fromTxOuts[outIndex]; - - if (!txout) { - throw new Error("Source output index " + outIndex + - " for input " + n + " out of bounds"); - } - - return txout; - } - - Step( - function verifyInputs(opts) { - var group = this.group(); - - if (self.isCoinBase()) { - throw new Error("Coinbase tx are invalid unless part of a block"); - } - - self.ins.forEach(function(txin, n) { - var txout = getTxOut(txin, n); - - // TODO: Verify coinbase maturity - - valueIn = valueIn.add(util.valueToBigInt(txout.v)); - - outpoints.push(txin.o); - - self.verifyInput(n, txout.getScript(), opts, group()); - }); - }, - - function verifyInputsResults(err, results) { - if (err) throw err; - - for (var i = 0, l = results.length; i < l; i++) { - if (!results[i]) { - var txout = getTxOut(self.ins[i]); - log.debug('Script evaluated to false'); - log.debug('|- scriptSig', "" + self.ins[i].getScript()); - log.debug('`- scriptPubKey', "" + txout.getScript()); - throw new VerificationError('Script for input ' + i + ' evaluated to false'); - } - } - - this(); - }, - - function queryConflicts(err) { - if (err) throw err; - - // Make sure there are no other transactions spending the same outs - blockChain.countConflictingTransactions(outpoints, this); - }, - function checkConflicts(err, count) { - if (err) throw err; - - self.outs.forEach(function(txout) { - valueOut = valueOut.add(util.valueToBigInt(txout.v)); - }); - - if (valueIn.cmp(valueOut) < 0) { - var outValue = util.formatValue(valueOut); - var inValue = util.formatValue(valueIn); - throw new Error("Tx output value (BTC " + outValue + ") " + - "exceeds input value (BTC " + inValue + ")"); - } - - var fees = valueIn.sub(valueOut); - - if (count) { - // Spent output detected, retrieve transaction that spends it - blockChain.getConflictingTransactions(outpoints, function(err, results) { - if (results.length) { - if (buffertools.compare(results[0].getHash(), self.getHash()) === 0) { - log.warn("Detected tx re-add (recoverable db corruption): " + util.formatHashAlt(results[0].getHash())); - // TODO: Needs to return an error for the memory pool case? - callback(null, fees); - } else { - callback(new Error("At least one referenced output has" + " already been spent in tx " + util.formatHashAlt(results[0].getHash()))); - } - } else { - callback(new Error("Outputs of this transaction are spent, but " + - "the transaction(s) that spend them are not " + - "available. This probably means you need to " + - "reset your database.")); - } - }); - return; - } - - // Success - this(null, fees); - }, - callback - ); -}; - Transaction.prototype.verifyInput = function verifyInput(n, scriptPubKey, opts, callback) { var scriptSig = this.ins[n].getScript(); return ScriptInterpreter.verifyFull( @@ -1110,132 +968,4 @@ Transaction.createAndSign = function(utxos, outs, keys, opts) { return ret; }; -var TransactionInputsCache = exports.TransactionInputsCache = - function TransactionInputsCache(tx) { - var txList = []; - var txList64 = []; - var reqOuts = {}; - - // Get list of transactions required for verification - tx.ins.forEach(function(txin) { - if (txin.isCoinBase()) return; - - var hash = txin.o.slice(0, 32); - var hash64 = hash.toString('base64'); - if (txList64.indexOf(hash64) == -1) { - txList.push(hash); - txList64.push(hash64); - } - if (!reqOuts[hash64]) { - reqOuts[hash64] = []; - } - reqOuts[hash64][txin.getOutpointIndex()] = true; - }); - - this.tx = tx; - this.txList = txList; - this.txList64 = txList64; - this.txIndex = {}; - this.requiredOuts = reqOuts; - this.callbacks = []; -}; - -TransactionInputsCache.prototype.buffer = function buffer(blockChain, txStore, wait, callback) { - var self = this; - - var complete = false; - - if ("function" === typeof callback) { - self.callbacks.push(callback); - } - - var missingTx = {}; - self.txList64.forEach(function(hash64) { - missingTx[hash64] = true; - }); - - // A utility function to create the index object from the txs result lists - function indexTxs(err, txs) { - if (err) throw err; - - // Index memory transactions - txs.forEach(function(tx) { - var hash64 = tx.getHash().toString('base64'); - var obj = {}; - Object.keys(self.requiredOuts[hash64]).forEach(function(o) { - obj[+o] = tx.outs[+o]; - }); - self.txIndex[hash64] = obj; - delete missingTx[hash64]; - }); - - this(null); - }; - - Step( - // First find and index memory transactions (if a txStore was provided) - function findMemTx() { - if (txStore) { - txStore.find(self.txList64, this); - } else { - this(null, []); - } - }, - indexTxs, - // Second find and index persistent transactions - function findBlockChainTx(err) { - if (err) throw err; - - // TODO: Major speedup should be possible if we load only the outs and not - // whole transactions. - var callback = this; - blockChain.getOutputsByHashes(self.txList, function(err, result) { - callback(err, result); - }); - }, - indexTxs, - function saveTxCache(err) { - if (err) throw err; - - var missingTxDbg = ''; - if (Object.keys(missingTx).length) { - missingTxDbg = Object.keys(missingTx).map(function(hash64) { - return util.formatHash(new Buffer(hash64, 'base64')); - }).join(','); - } - - if (wait && Object.keys(missingTx).length) { - // TODO: This might no longer be needed now that saveTransactions uses - // the safe=true option. - setTimeout(function() { - var missingHashes = Object.keys(missingTx); - if (missingHashes.length) { - self.callback(new Error('Missing inputs (timeout while searching): ' + missingTxDbg)); - } else if (!complete) { - self.callback(new Error('Callback failed to trigger')); - } - }, 10000); - } else { - complete = true; - this(null, self); - } - }, - self.callback.bind(self) - ); -}; - - -TransactionInputsCache.prototype.callback = function callback(err) { - var args = Array.prototype.slice.apply(arguments); - - // Empty the callback array first (because downstream functions could add new - // callbacks or otherwise interfere if were not in a consistent state.) - var cbs = this.callbacks; - this.callbacks = []; - - cbs.forEach(function(cb) { - cb.apply(null, args); - }); -}; - module.exports = require('soop')(Transaction);