wip
This commit is contained in:
parent
9c943cc959
commit
a488c3f083
@ -63,6 +63,7 @@ AddressService.prototype.getAddressBalance = function(addresses, options, callba
|
||||
var addresses = self._normalizeAddressArg(addressArg);
|
||||
var cacheKey = addresses.join('');
|
||||
var balance = self.balanceCache.get(cacheKey);
|
||||
|
||||
if (balance) {
|
||||
return setImmediate(function() {
|
||||
callback(null, balance);
|
||||
@ -80,8 +81,8 @@ AddressService.prototype.getAddressBalance = function(addresses, options, callba
|
||||
};
|
||||
|
||||
AddressService.prototype.getAddressHistory = function(addresses, options, callback) {
|
||||
var self = this;
|
||||
|
||||
var self = this;
|
||||
var txids = [];
|
||||
|
||||
async.eachLimit(addresses, 4, function(address, next) {
|
||||
@ -361,7 +362,7 @@ AddressService.prototype._processTransaction = function(opts, tx) {
|
||||
|
||||
};
|
||||
|
||||
AddressService.prototype._onBlock = function(block, connect) {
|
||||
AddressService.prototype.onBlock = function(block, connect) {
|
||||
|
||||
var self = this;
|
||||
|
||||
|
||||
@ -6,12 +6,11 @@ var Block = require('bitcore-lib').Block;
|
||||
function Encoding(servicePrefix) {
|
||||
this.servicePrefix = servicePrefix;
|
||||
this.blockPrefix = new Buffer('00', 'hex');
|
||||
this.hashPrefix = new Buffer('01', 'hex');
|
||||
this.heightPrefix = new Buffer('02', 'hex');
|
||||
this.metaPrefix = new Buffer('01', 'hex');
|
||||
}
|
||||
|
||||
|
||||
// ---- hash --> block
|
||||
// ---- hash --> rawblock
|
||||
Encoding.prototype.encodeBlockKey = function(hash) {
|
||||
return Buffer.concat([ this.servicePrefix, this.blockPrefix, new Buffer(hash, 'hex') ]);
|
||||
};
|
||||
@ -28,38 +27,27 @@ Encoding.prototype.decodeBlockValue = function(buffer) {
|
||||
return Block.fromBuffer(buffer);
|
||||
};
|
||||
|
||||
|
||||
// ---- hash --> header
|
||||
Encoding.prototype.encodeHashKey = function(hash) {
|
||||
var hashBuf = new Buffer(hash, 'hex');
|
||||
return Buffer.concat([ this.servicePrefix, this.hashPrefix, hashBuf ]);
|
||||
};
|
||||
|
||||
Encoding.prototype.decodeHashKey = function(buffer) {
|
||||
return buffer.slice(3).toString('hex');
|
||||
};
|
||||
|
||||
Encoding.prototype.encodeHeaderValue = function(header) {
|
||||
return new Buffer(JSON.stringify(header), 'utf8');
|
||||
};
|
||||
|
||||
Encoding.prototype.decodeHeaderValue = function(buffer) {
|
||||
return JSON.parse(buffer.toString('utf8'));
|
||||
};
|
||||
|
||||
|
||||
// ---- height --> header
|
||||
Encoding.prototype.encodeHeightKey = function(height) {
|
||||
|
||||
var heightBuf = new Buffer(4);
|
||||
// ---- height --> hash, chainwork
|
||||
Encoding.prototype.encodeMetaKey = function(height) {
|
||||
var heigthtBuf = new Buffer(4);
|
||||
heightBuf.writeUInt32BE(height);
|
||||
return Buffer.concat([ this.servicePrefix, this.heightPrefix, heightBuf ]);
|
||||
|
||||
return Buffer.concat([ this.blockPrefix, this.metaPrefix, heightBuf ]);
|
||||
};
|
||||
|
||||
Encoding.prototype.decodeHeightKey = function(buffer) {
|
||||
return buffer.readUInt32BE(3);
|
||||
Encoding.prototype.decodeMetaKey = function(buffer) {
|
||||
return heightBuf.readUInt32BE(3);
|
||||
};
|
||||
|
||||
Encoding.prototype.encodeMetaValue = function(value) {
|
||||
// { chainwork: hex-string, hash: hex-string }
|
||||
return Buffer([ new Buffer(value.hash, 'hex'), new Buffer(value.chainwork, 'hex') ]);
|
||||
};
|
||||
|
||||
Encoding.prototype.decodeMetaValue = function(buffer) {
|
||||
return {
|
||||
hash: buffer.slice(0, 32).toString('hex'),
|
||||
chainwork: buffer.slice(32).toString('hex')
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = Encoding;
|
||||
|
||||
@ -14,17 +14,23 @@ var consensus = require('bcoin').consensus;
|
||||
var constants = require('../../constants');
|
||||
|
||||
var BlockService = function(options) {
|
||||
|
||||
BaseService.call(this, options);
|
||||
|
||||
this._tip = null;
|
||||
this._p2p = this.node.services.p2p;
|
||||
this._db = this.node.services.db;
|
||||
|
||||
this._subscriptions = {};
|
||||
this._subscriptions.block = [];
|
||||
this._subscriptions.reorg = [];
|
||||
|
||||
// meta is [{ chainwork: chainwork, hash: hash }]
|
||||
this._meta = []; // properties that apply to blocks that are not already stored on the blocks, yet we still needed, i.e. chainwork, height
|
||||
// properties that apply to blocks that are not already stored on the blocks,
|
||||
// i.e. chainwork, height
|
||||
this._meta = [];
|
||||
|
||||
// this is the in-memory full/raw block cache
|
||||
this._blockQueue = LRU({
|
||||
max: 50 * (1 * 1024 * 1024), // 50 MB of blocks,
|
||||
length: function(n) {
|
||||
@ -32,6 +38,9 @@ var BlockService = function(options) {
|
||||
}
|
||||
}); // hash -> block
|
||||
|
||||
// keep track of out-of-order blocks, this is a list of chains (which are lists themselves)
|
||||
// e.g. [ [ block1, block5 ], [ block2, block6 ] ];
|
||||
this._incompleteChains = [];
|
||||
this._chainTips = []; // list of all chain tips, including main chain and any chains that were orphaned after a reorg
|
||||
this._blockCount = 0;
|
||||
this.GENESIS_HASH = constants.BITCOIN_GENESIS_HASH[this.node.getNetworkName()];
|
||||
@ -56,11 +65,18 @@ BlockService.prototype.start = function(callback) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
self.prefix = prefix;
|
||||
self._encoding = new Encoding(self.prefix);
|
||||
self._loadMeta();
|
||||
self._setListeners();
|
||||
callback();
|
||||
self._db.getServiceTip('block', function(err, tip) {
|
||||
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
self.prefix = prefix;
|
||||
self._encoding = new Encoding(self.prefix);
|
||||
self._loadMeta();
|
||||
self._setListeners();
|
||||
self._onTipBlock(tip);
|
||||
callback();
|
||||
});
|
||||
});
|
||||
|
||||
};
|
||||
@ -177,8 +193,8 @@ BlockService.prototype._computeChainwork = function(bits, prev) {
|
||||
|
||||
BlockService.prototype._determineBlockState = function(block) {
|
||||
|
||||
if (this._isOrphanBlock(block)) {
|
||||
return 'orphaned';
|
||||
if (this._isOutOfOrder(block)) {
|
||||
return 'outoforder';
|
||||
}
|
||||
|
||||
if (this._isChainReorganizing(block)) {
|
||||
@ -330,26 +346,27 @@ BlockService.prototype._handleReorg = function(block) {
|
||||
|
||||
BlockService.prototype._isChainReorganizing = function(block) {
|
||||
|
||||
// if we aren't an out of order block, then is this block's prev hash our tip?
|
||||
var prevHash = utils.reverseBufferToString(block.header.prevHash);
|
||||
|
||||
return prevHash !== this._tip.hash;
|
||||
|
||||
};
|
||||
|
||||
BlockService.prototype._isOrphanBlock = function(block) {
|
||||
BlockService.prototype._isOutOfOrder = function(block) {
|
||||
|
||||
// so this should fail - "is orphan"
|
||||
// is this block the direct child of one of our chain tips? If so, not an out of order block
|
||||
var prevHash = utils.reverseBufferToString(block.header.prevHash);
|
||||
|
||||
// we'll consult our metadata
|
||||
var i = this._meta.length - 1;
|
||||
for(; i >= 0; --i) {
|
||||
// if we find this block's prev hash in our meta collection, then we know all of its ancestors are there too
|
||||
if (block.hash === this._meta[i].hash) {
|
||||
for(var i = 0; i < this._chainTips.length; i++) {
|
||||
var chainTip = this._chainTips[i];
|
||||
if (chainTip === prevHash) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
};
|
||||
|
||||
BlockService.prototype._loadMeta = function() {
|
||||
@ -359,20 +376,10 @@ BlockService.prototype._loadMeta = function() {
|
||||
}
|
||||
};
|
||||
|
||||
BlockService.prototype._loadTip = function() {
|
||||
var self = this;
|
||||
self._db.getServiceTip('block');
|
||||
};
|
||||
|
||||
BlockService.prototype._onBestHeight = function(height) {
|
||||
|
||||
var self = this;
|
||||
self._bestHeight = height;
|
||||
|
||||
// once we have best height, we know the p2p network is ready to go
|
||||
self._db.once('tip-block', self._onTipBlock.bind(self));
|
||||
|
||||
self._loadTip();
|
||||
};
|
||||
|
||||
BlockService.prototype._onBlock = function(block) {
|
||||
@ -388,14 +395,11 @@ BlockService.prototype._onBlock = function(block) {
|
||||
// 3. store the block for safe keeping
|
||||
this._cacheBlock(block);
|
||||
|
||||
// 4. add block to meta
|
||||
this._updateMeta(block);
|
||||
|
||||
// 5. determine block state, reorg, orphaned, normal
|
||||
// 4. determine block state, reorg, outoforder, normal
|
||||
var blockState = this._determineBlockState(block);
|
||||
|
||||
// 6. add block to chainTips
|
||||
this._updateChainTips(block, blockState);
|
||||
// 5. update internal data structures depending on blockstate
|
||||
this._updateChainInfo(block, blockState);
|
||||
|
||||
// 7. react to state of block
|
||||
switch (blockState) {
|
||||
@ -407,6 +411,7 @@ BlockService.prototype._onBlock = function(block) {
|
||||
break;
|
||||
default:
|
||||
// send all unsent blocks now that we have a complete chain
|
||||
this._updateMeta(block);
|
||||
this._sendDelta();
|
||||
break;
|
||||
}
|
||||
@ -419,23 +424,10 @@ BlockService.prototype._onDbError = function(err) {
|
||||
|
||||
};
|
||||
|
||||
// --- mark 1
|
||||
BlockService.prototype._onTipBlock = function(tip) {
|
||||
|
||||
var self = this;
|
||||
if (tip) {
|
||||
|
||||
self._tip = tip;
|
||||
|
||||
} else {
|
||||
|
||||
self._tip = {
|
||||
height: 0,
|
||||
hash: self.GENESIS_HASH
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
self._tip = tip;
|
||||
self._chainTips.push(self._tip.hash);
|
||||
self._startSubscriptions();
|
||||
self._startSync();
|
||||
@ -588,31 +580,63 @@ BlockService.prototype._sync = function() {
|
||||
|
||||
};
|
||||
|
||||
BlockService.prototype._updateChainTips = function(block, state) {
|
||||
BlockService.prototype._updateNormalStateChainInfo = function(block, prevHash) {
|
||||
var index = this._chainTips.indexOf(prevHash);
|
||||
assert(index > -1, 'Block state is normal, ' +
|
||||
'yet the previous block hash is missing from the chain tips collection.');
|
||||
|
||||
this._chainTips.push(block.hash);
|
||||
this._chainTips.splice(index, 1);
|
||||
};
|
||||
|
||||
BlockService.prototype._updateReorgStateChainInfo = function(block) {
|
||||
this._chainTips.push(block.hash);
|
||||
};
|
||||
|
||||
BlockService.prototype._updateOutOfOrderStateChainInfo = function(block, prevHash) {
|
||||
// add this block to the incomplete chains
|
||||
// are we the parent of some other block in here?
|
||||
// are we the child of some other block in here?
|
||||
// block chains in here go newest to oldest block.
|
||||
// When the oldest block's prevHash is one of the chainTips, the
|
||||
// incomplete chain is now complete.
|
||||
for(var i = 0; i < this._incompleteChains.length; i++) {
|
||||
var chain = this._incompleteChains[i];
|
||||
// does the last block in this chain have a prevHash that equal's our block's hash?
|
||||
// if so, this block should be pushed on the end
|
||||
if (chain.length > 0) {
|
||||
|
||||
if (utils.reverseBufferToString(chain[chain.length - 1].header.prevHash) === block.hash) {
|
||||
chain.push(block);
|
||||
}
|
||||
|
||||
// is our block's prevHash equal to the first block's hash?
|
||||
if (chain[0].hash === prevHash) {
|
||||
chain.unshift(block);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
BlockService.prototype._updateChainInfo = function(block, state) {
|
||||
|
||||
var prevHash = utils.reverseBufferToString(block.header.prevHash);
|
||||
|
||||
// otherwise we are not an orphan, so we could be a reorg or normal
|
||||
// if this is a normal state, then we are just adding ourselves as the tip on the main chain
|
||||
if (state === 'normal') {
|
||||
|
||||
var index = this._chainTips.indexOf(prevHash);
|
||||
assert(index > -1, 'Block state is normal, ' +
|
||||
'yet the previous block hash is missing from the chain tips collection.');
|
||||
|
||||
this._chainTips.push(block.hash);
|
||||
this._chainTips.splice(index, 1);
|
||||
this._updateNormalStateChainInfo(block, prevHash);
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
// if this is a reorg state, then a new chain tip will be added to the list
|
||||
if (state === 'reorg') {
|
||||
|
||||
this._chainTips.push(block.hash);
|
||||
|
||||
this._updateReorgStateChainInfo(block);
|
||||
return;
|
||||
}
|
||||
|
||||
this._updateOutOfOrderStateChainInfo(block, prevHash);
|
||||
|
||||
|
||||
};
|
||||
|
||||
BlockService.prototype._updateMeta = function(block) {
|
||||
|
||||
@ -37,6 +37,7 @@ function DB(options) {
|
||||
this.subscriptions = {};
|
||||
|
||||
this._operationsQueue = 0;
|
||||
this.GENESIS_HASH = constants.BITCOIN_GENESIS_HASH[this.node.getNetworkName()];
|
||||
}
|
||||
|
||||
util.inherits(DB, Service);
|
||||
@ -260,27 +261,37 @@ DB.prototype.getPublishEvents = function() {
|
||||
return [];
|
||||
};
|
||||
|
||||
DB.prototype.getServiceTip = function(serviceName) {
|
||||
DB.prototype.getServiceTip = function(serviceName, callback) {
|
||||
|
||||
var keyBuf = Buffer.concat([ this.dbPrefix, new Buffer('tip-' + serviceName, 'utf8') ]);
|
||||
|
||||
var self = this;
|
||||
self.get(keyBuf, function(err, tipBuf) {
|
||||
|
||||
if (err) {
|
||||
this.emit('error', err);
|
||||
return;
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
var tip = null;
|
||||
var tip;
|
||||
if (tipBuf) {
|
||||
|
||||
var heightBuf = new Buffer(4);
|
||||
|
||||
tip = {
|
||||
height: heightBuf.readUInt32BE(),
|
||||
hash: tipBuf.slice(4)
|
||||
};
|
||||
|
||||
} else {
|
||||
|
||||
tip = {
|
||||
height: 0,
|
||||
hash: self.GENESIS_HASH
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
self.emit('tip-' + serviceName, tip);
|
||||
callback(null, tip);
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
@ -35,11 +35,12 @@ P2P.prototype.clearInventoryCache = function() {
|
||||
|
||||
P2P.prototype.getAPIMethods = function() {
|
||||
var methods = [
|
||||
['getInfo', this, this.getInfo, 0],
|
||||
['getHeaders', this, this.getHeaders, 1],
|
||||
['getMempool', this, this.getMempool, 0],
|
||||
['clearInventoryCache', this, this.clearInventoryCache, 0],
|
||||
['getBlocks', this, this.getBlocks, 1],
|
||||
['clearInventoryCache', this, this.clearInventoryCache, 0]
|
||||
['getHeaders', this, this.getHeaders, 1],
|
||||
['getInfo', this, this.getInfo, 0],
|
||||
['getMempool', this, this.getMempool, 0],
|
||||
['sendTransaction', this, this.sendTransaction, 1]
|
||||
];
|
||||
return methods;
|
||||
};
|
||||
@ -94,6 +95,11 @@ P2P.prototype.getPublishEvents = function() {
|
||||
};
|
||||
|
||||
|
||||
P2P.prototype.sendTransaction = function(tx, callback) {
|
||||
p2p.sendMessage(this.messages.Inventory(tx));
|
||||
};
|
||||
|
||||
|
||||
P2P.prototype.start = function(callback) {
|
||||
|
||||
var self = this;
|
||||
|
||||
@ -7,17 +7,9 @@ var inherits = require('util').inherits;
|
||||
var Encoding = require('./encoding');
|
||||
var levelup = require('levelup');
|
||||
|
||||
/**
|
||||
* The Transaction Service builds upon the Database Service and the Bitcoin Service to add additional
|
||||
* functionality for getting information by bitcoin transaction hash/id. This includes the current
|
||||
* bitcoin memory pool as validated by a trusted bitcoind instance.
|
||||
* @param {Object} options
|
||||
* @param {Node} options.node - An instance of the node
|
||||
* @param {String} options.name - An optional name of the service
|
||||
*/
|
||||
function TransactionService(options) {
|
||||
BaseService.call(this, options);
|
||||
this.concurrency = options.concurrency || 20;
|
||||
this._db = this.node.services.db;
|
||||
this.currentTransactions = {};
|
||||
}
|
||||
|
||||
@ -40,26 +32,59 @@ TransactionService.prototype.getAPIMethods = function() {
|
||||
];
|
||||
};
|
||||
|
||||
TransactionService.prototype.start = function(callback) {
|
||||
var self = this;
|
||||
|
||||
self.db = this.node.services.db;
|
||||
|
||||
self.node.services.db.getPrefix(self.name, function(err, prefix) {
|
||||
if(err) {
|
||||
TransactionService.prototype.getRawTransaction = function(txid, callback) {
|
||||
this.getTransaction(txid, function(err, tx) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
self.prefix = prefix;
|
||||
self.encoding = new Encoding(self.prefix);
|
||||
callback();
|
||||
return tx.serialize();
|
||||
});
|
||||
};
|
||||
|
||||
TransactionService.prototype.stop = function(callback) {
|
||||
setImmediate(callback);
|
||||
TransactionService.prototype.getDetailedTransaction = TransactionService.prototype.getTransaction = function(txid, options, callback) {
|
||||
|
||||
var self = this;
|
||||
|
||||
assert(txid.length === 64, 'Transaction, Txid: ' + txid + ' with length: ' + txid.length + ' does not resemble a txid.');
|
||||
|
||||
if(self.currentTransactions[txid]) {
|
||||
return setImmediate(function() {
|
||||
callback(null, self.currentTransactions[txid]);
|
||||
});
|
||||
}
|
||||
|
||||
var key = self.encoding.encodeTransactionKey(txid);
|
||||
|
||||
async.waterfall([
|
||||
function(next) {
|
||||
self.node.services.db.get(key, function(err, buffer) {
|
||||
if (err instanceof levelup.errors.NotFoundError) {
|
||||
return next(null, false);
|
||||
} else if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
var tx = self.encoding.decodeTransactionValue(buffer);
|
||||
next(null, tx);
|
||||
});
|
||||
}, function(tx, next) {
|
||||
if (tx) {
|
||||
return next(null, tx);
|
||||
}
|
||||
if (!options || !options.queryMempool) {
|
||||
return next(new Error('Transaction: ' + txid + ' not found in index'));
|
||||
}
|
||||
self.node.services.mempool.getTransaction(txid, function(err, tx) {
|
||||
if (err instanceof levelup.errors.NotFoundError) {
|
||||
return callback(new Error('Transaction: ' + txid + ' not found in index or mempool'));
|
||||
} else if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
self._getMissingInputValues(tx, next);
|
||||
});
|
||||
}], callback);
|
||||
};
|
||||
|
||||
TransactionService.prototype.blockHandler = function(block, connectBlock, callback) {
|
||||
TransactionService.prototype.onBlock = function(block, connectBlock, callback) {
|
||||
var self = this;
|
||||
var action = 'put';
|
||||
var reverseAction = 'del';
|
||||
@ -114,7 +139,45 @@ TransactionService.prototype.blockHandler = function(block, connectBlock, callba
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
TransactionService.prototype.sendTransaction = function(tx, callback) {
|
||||
this._p2p.sendTransaction(tx, callback);
|
||||
};
|
||||
|
||||
TransactionService.prototype.start = function(callback) {
|
||||
var self = this;
|
||||
|
||||
self._db.getPrefix(self.name, function(err, prefix) {
|
||||
|
||||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
self._db.getServiceTip('transaction', function(err, tip) {
|
||||
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
self._tip = tip;
|
||||
self.prefix = prefix;
|
||||
self.encoding = new Encoding(self.prefix);
|
||||
callback();
|
||||
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
TransactionService.prototype.stop = function(callback) {
|
||||
setImmediate(callback);
|
||||
};
|
||||
|
||||
TransactionService.prototype.syncPercentage = function(callback) {
|
||||
|
||||
};
|
||||
|
||||
TransactionService.prototype._getMissingInputValues = function(tx, callback) {
|
||||
|
||||
var self = this;
|
||||
|
||||
if (tx.isCoinbase()) {
|
||||
@ -140,6 +203,7 @@ TransactionService.prototype._getMissingInputValues = function(tx, callback) {
|
||||
next();
|
||||
});
|
||||
}, callback);
|
||||
|
||||
};
|
||||
|
||||
TransactionService.prototype._getInputValues = function(tx, callback) {
|
||||
@ -163,47 +227,4 @@ TransactionService.prototype._getInputValues = function(tx, callback) {
|
||||
}, callback);
|
||||
};
|
||||
|
||||
TransactionService.prototype.getTransaction = function(txid, options, callback) {
|
||||
var self = this;
|
||||
|
||||
assert(txid.length === 64, 'Transaction, Txid: ' + txid + ' with length: ' + txid.length + ' does not resemble a txid.');
|
||||
|
||||
if(self.currentTransactions[txid]) {
|
||||
return setImmediate(function() {
|
||||
callback(null, self.currentTransactions[txid]);
|
||||
});
|
||||
}
|
||||
|
||||
var key = self.encoding.encodeTransactionKey(txid);
|
||||
|
||||
async.waterfall([
|
||||
function(next) {
|
||||
self.node.services.db.get(key, function(err, buffer) {
|
||||
if (err instanceof levelup.errors.NotFoundError) {
|
||||
return next(null, false);
|
||||
} else if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
var tx = self.encoding.decodeTransactionValue(buffer);
|
||||
next(null, tx);
|
||||
});
|
||||
}, function(tx, next) {
|
||||
if (tx) {
|
||||
return next(null, tx);
|
||||
}
|
||||
if (!options || !options.queryMempool) {
|
||||
return next(new Error('Transaction: ' + txid + ' not found in index'));
|
||||
}
|
||||
self.node.services.mempool.getTransaction(txid, function(err, tx) {
|
||||
if (err instanceof levelup.errors.NotFoundError) {
|
||||
return callback(new Error('Transaction: ' + txid + ' not found in index or mempool'));
|
||||
} else if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
self._getMissingInputValues(tx, next);
|
||||
});
|
||||
}], callback);
|
||||
};
|
||||
|
||||
|
||||
module.exports = TransactionService;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user