This commit is contained in:
Chris Kleeschulte 2017-07-15 15:50:52 -04:00
parent 98ea052405
commit b471857bf0
6 changed files with 439 additions and 287 deletions

View File

@ -7,7 +7,7 @@ function Encoding(servicePrefix) {
this.servicePrefix = servicePrefix; this.servicePrefix = servicePrefix;
} }
Encoding.prototype.encodeAddressIndexKey = function(address, height, txid) { Encoding.prototype.encodeAddressIndexKey = function(address, height, txid, index, input) {
var prefix = new Buffer('00', 'hex'); var prefix = new Buffer('00', 'hex');
var buffers = [this.servicePrefix, prefix]; var buffers = [this.servicePrefix, prefix];
@ -25,6 +25,15 @@ Encoding.prototype.encodeAddressIndexKey = function(address, height, txid) {
var txidBuffer = new Buffer(txid || Array(65).join('0'), 'hex'); var txidBuffer = new Buffer(txid || Array(65).join('0'), 'hex');
buffers.push(txidBuffer); buffers.push(txidBuffer);
var indexBuffer = new Buffer(4);
indexBuffer.writeUInt32BE(index || 0);
buffers.push(indexBuffer);
// this is whether the address appears in an input (1) or output (0)
var inputBuffer = new Buffer(1);
inputBuffer.writeUInt8(input || 0);
buffers.push(inputBuffer);
return Buffer.concat(buffers); return Buffer.concat(buffers);
}; };
@ -36,10 +45,14 @@ Encoding.prototype.decodeAddressIndexKey = function(buffer) {
var address = reader.read(addressSize).toString('utf8'); var address = reader.read(addressSize).toString('utf8');
var height = reader.readUInt32BE(); var height = reader.readUInt32BE();
var txid = reader.read(32).toString('hex'); var txid = reader.read(32).toString('hex');
var index = reader.readUInt32BE();
var input = reader.readUInt8();
return { return {
address: address, address: address,
height: height, height: height,
txid: txid, txid: txid,
index: index,
input: input
}; };
}; };

View File

@ -7,10 +7,33 @@ var index = require('../../');
var log = index.log; var log = index.log;
var errors = index.errors; var errors = index.errors;
var bitcore = require('bitcore-lib'); var bitcore = require('bitcore-lib');
var Unit = bitcore.Unit;
var _ = bitcore.deps._; var _ = bitcore.deps._;
var Address = bitcore.Address;
var Encoding = require('./encoding'); var Encoding = require('./encoding');
var utils = require('../../utils'); var utils = require('../../utils');
var Transform = require('stream').Transform;
/*
1. getAddressSummary
2. getAddressUnspentOutputs
3. bitcoind.height
4. getBlockHeader
5. getDetailedTransaction
6. getTransaction
7. sendTransaction
8. getInfo
9. bitcoind.tiphash
10. getBestBlockHash
11. isSynced
12. getAddressHistory
13. getBlock
14. getRawBlock
15. getBlockHashesByTimestamp
16. estimateFee
17. getBlockOverview
18. syncPercentage
*/
var AddressService = function(options) { var AddressService = function(options) {
BaseService.call(this, options); BaseService.call(this, options);
@ -28,25 +51,6 @@ AddressService.dependencies = [
]; ];
// ---- public function prototypes // ---- public function prototypes
AddressService.prototype.getBalance = function(address, queryMempool, callback) {
this.getUtxos(address, queryMempool, function(err, outputs) {
if(err) {
return callback(err);
}
var satoshis = outputs.map(function(output) {
return output.satoshis;
});
var sum = satoshis.reduce(function(a, b) {
return a + b;
}, 0);
return callback(null, sum);
});
};
AddressService.prototype.getUtxos = function(addresses, queryMempool, callback) { AddressService.prototype.getUtxos = function(addresses, queryMempool, callback) {
var self = this; var self = this;
@ -127,266 +131,275 @@ AddressService.prototype.stop = function(callback) {
AddressService.prototype.getAPIMethods = function() { AddressService.prototype.getAPIMethods = function() {
return [ return [
['getAddressBalance', this, this.getAddressBalance, 2],
['getAddressHistory', this, this.getAddressHistory, 2], ['getAddressHistory', this, this.getAddressHistory, 2],
['getAddressSummary', this, this.getAddressSummary, 1], ['getAddressSummary', this, this.getAddressSummary, 1],
['getAddressTxids', this, this.getAddressTxids, 2],
['getAddressUnspentOutputs', this, this.getAddressUnspentOutputs, 1], ['getAddressUnspentOutputs', this, this.getAddressUnspentOutputs, 1],
['syncPercentage', this, this.syncPercentage, 0] ['syncPercentage', this, this.syncPercentage, 0]
]; ];
}; };
AddressService.prototype.getAddressBalance = function(addresses, options, callback) { AddressService.prototype.getAddressHistory = function(addresses, options, callback) {
var self = this; var self = this;
addresses = utils.normalizeAddressArg(addresses);
var balance = 0;
async.eachLimit(addresses, 4, function(address, next) { options = options || {};
var from = options.from || 0;
var to = options.to || 0xffffffff;
var start = self._encoding.encodeUtxoIndexKey(address); async.mapLimit(addresses, 4, function(address, next) {
var criteria = {
gte: start, self._getAddressHistory(address, next);
lte: Buffer.concat([ start.slice(-36), new Buffer(new Array(73).join('f'), 'hex') ])
}, function(err, res) {
if(err) {
return callback(err);
}
var results = {
totalItems: res.length,
from: from,
to: to,
items: res
}; };
var stream = this._db.createReadStream(criteria); callback(null, results);
stream.on('data', function(data) {
});
stream.on('error', function(err) {
});
stream.on('end', function() {
});
}); });
}; };
AddressService.prototype.getAddressHistory = function(addresses, options, callback) { AddressService.prototype._getAddressHistory = function(address, options, callback) {
var self = this; var self = this;
var txids = [];
async.eachLimit(addresses, 4, function(address, next) { var results = [];
self.getAddressTxids(address, options, function(err, tmpTxids) { var start = self._encoding.encodeAddressIndexKey(address);
var criteria = {
gte: start,
lte: utils.getTerminalKey(start)
};
// txid stream
var txidStream = self._db.createKeyStream(criteria);
txidStream.on('close', function() {
txidStream.unpipe();
});
// tx stream
var txStream = new Transform({ objectMode: true, highWaterMark: 1000 });
var streamErr;
txStream.on('end', function() {
if (streamErr) {
return callback(streamErr);
}
callback(null, results);
});
// pipe txids into tx stream for processing
txidStream.pipe(txStream);
txStream._transform = function(chunk, enc, callback) {
var key = self._encoding.decodeWalletTransactionKey(chunk);
self._tx.getDetailedTransaction(key.txid, options, function(err, tx) {
if(err) { if(err) {
return next(err); log.error(err);
txStream.emit('error', err);
return callback();
} }
txids = _.union(txids, tmpTxids); if (!tx) {
return next(); log.error('Could not find tx for txid: ' + key.txid + '. This should not be possible, check indexes.');
return callback();
}
results.push(tx);
callback();
}); });
}, function() {
async.mapLimit(txids, 4, function(txid, next) {
self.node.services.transaction.getTransaction(txid.toString('hex'), options, function(err, tx) {
if(err) {
return next(err);
}
var txObj = tx.toObject(); };
for(var i = 0; i < txObj.inputs.length; i++) {
txObj.inputs[i].satoshis = tx.__inputValues[i];
}
next(null, txObj); txStream.on('error', function(err) {
}); log.error(err);
}, callback); txStream.unpipe();
}); });
txStream._flush = function(callback) {
txStream.emit('end');
callback();
};
}; };
AddressService.prototype.getAddressSummary = function(addressArg, options, callback) { AddressService.prototype.getAddressSummary = function(address, options, callback) {
var self = this; var self = this;
var startTime = new Date();
var address = new Address(addressArg);
if (_.isUndefined(options.queryMempool)) { if (_.isUndefined(options.queryMempool)) {
options.queryMempool = true; options.queryMempool = true;
} }
async.waterfall([ var result = {
function(next) { addrStr: address,
self._getAddressConfirmedSummary(address, options, next); balance: 0,
}, balanceSat: 0,
function(result, next) { totalReceived: 0,
self._getAddressMempoolSummary(address, options, result, next); totalReceivedSat: 0,
}, totalSent: 0,
function(result, next) { totalSentSat: 0,
self._setAndSortTxidsFromAppearanceIds(result, next); unconfirmedBalance: 0,
} unconfirmedBalanceSat: 0,
], function(err, result) { unconfirmedTxApperances: 0,
if (err) { txApperances: 0,
return callback(err); transactions: []
} };
var summary = self._transformAddressSummaryFromResult(result, options); // txid criteria
var start = self._encoding.encodeAddressIndexKey(address);
var timeDelta = new Date() - startTime; var criteria = {
if (timeDelta > 5000) {
var seconds = Math.round(timeDelta / 1000);
log.warn('Slow (' + seconds + 's) getAddressSummary request for address: ' + address.toString());
}
callback(null, summary);
});
};
AddressService.prototype.getAddressTxids = function(address, options, callback) {
var self = this;
var opts = options || { start: 0, end: 0xffffffff, txid: new Array(65).join('0') };
var txids = {};
var start = self._encoding.encodeAddressIndexKey(address, opts.start, opts.txid);
var end = self._encoding.encodeAddressIndexKey(address, opts.end, opts.txid);
var stream = self.db.createKeyStream({
gte: start, gte: start,
lt: end lte: utils.getTerminalKey(start)
};
// txid stream
var txidStream = self._db.createKeyStream(criteria);
txidStream.on('close', function() {
txidStream.unpipe();
}); });
var streamErr = null; // tx stream
stream.on('close', function() { var txStream = new Transform({ objectMode: true, highWaterMark: 1000 });
txStream.on('end', function() {
result.balance = Unit.fromSatoshis(result.balanceSat).toBTC();
result.totalReceived = Unit.fromSatoshis(result.totalReceivedSat).toBTC();
result.totalSent = Unit.fromSatoshis(result.totalSentSat).toBTC();
result.unconfirmedBalance = Unit.fromSatoshis(result.unconfirmedBalanceSat).toBTC();
callback(null, result);
}); });
stream.on('data', function(buffer) { // pipe txids into tx stream for processing
var key = self._encoding.decodeAddressIndexKey(buffer); txidStream.pipe(txStream);
txids[key.txid] = true;
txStream._transform = function(chunk, enc, callback) {
var key = self._encoding.decodeWalletTransactionKey(chunk);
self._tx.getTransaction(key.txid, options, function(err, res) {
if(err) {
log.error(err);
txStream.emit('error', err);
return callback();
}
if (!res) {
log.error('Could not find tx for txid: ' + key.txid + '. This should not be possible, check indexes.');
return callback();
}
var tx = res.tx;
result.transactions.push(tx.id);
result.txApperances++;
if (key.input) {
result.balanceSat -= tx.inputValues[key.index];
result.totalSentSat += tx.inputValues[key.index];
if (res.confirmations === 0) {
result.unconfirmedBalanceSat -= tx.inputValues[key.index];
result.unconfirmedTxApperances++;
}
} else {
result.balanceSat += tx.outputs[key.index].satoshis;
result.totalReceivedSat += tx.outputs[key.index].satoshis;
if (res.confirmations === 0) {
result.unconfirmedBalanceSat += tx.inputValues[key.index];
result.unconfirmedTxApperances++;
}
}
callback();
});
};
txStream.on('error', function(err) {
log.error(err);
txStream.unpipe();
}); });
stream.on('end', function() { txStream._flush = function(callback) {
callback(streamErr, Object.keys(txids)); txStream.emit('end');
}); callback();
};
stream.on('error', function(err) {
streamErr = err;
});
}; };
AddressService.prototype.getAddressUnspentOutputs = function(address, options, callback) { AddressService.prototype.getAddressUnspentOutputs = function(address, options, callback) {
var queryMempool = _.isUndefined(options.queryMempool) ? true : options.queryMempool;
var addresses = utils._normalizeAddressArg(address);
var cacheKey = addresses.join('');
var utxos = this.utxosCache.get(cacheKey);
function transformUnspentOutput(delta) {
var script = bitcore.Script.fromAddress(delta.address);
return {
address: delta.address,
txid: delta.txid,
outputIndex: delta.index,
script: script.toHex(),
satoshis: delta.satoshis,
timestamp: delta.timestamp
};
}
function updateWithMempool(confirmedUtxos, mempoolDeltas) {
if (!mempoolDeltas || !mempoolDeltas.length) {
return confirmedUtxos;
}
var isSpentOutputs = false;
var mempoolUnspentOutputs = [];
var spentOutputs = [];
for (var i = 0; i < mempoolDeltas.length; i++) {
var delta = mempoolDeltas[i];
if (delta.prevtxid && delta.satoshis <= 0) {
if (!spentOutputs[delta.prevtxid]) {
spentOutputs[delta.prevtxid] = [delta.prevout];
} else {
spentOutputs[delta.prevtxid].push(delta.prevout);
}
isSpentOutputs = true;
} else {
mempoolUnspentOutputs.push(transformUnspentOutput(delta));
}
}
var utxos = mempoolUnspentOutputs.reverse().concat(confirmedUtxos);
if (isSpentOutputs) {
return utxos.filter(function(utxo) {
if (!spentOutputs[utxo.txid]) {
return true;
} else {
return (spentOutputs[utxo.txid].indexOf(utxo.outputIndex) === -1);
}
});
}
return utxos;
}
function finish(mempoolDeltas) {
if (utxos) {
return setImmediate(function() {
callback(null, updateWithMempool(utxos, mempoolDeltas));
});
} else {
self.client.getAddressUtxos({addresses: addresses}, function(err, response) {
if (err) {
return callback(self._wrapRPCError(err));
}
var utxos = response.result.reverse();
self.utxosCache.set(cacheKey, utxos);
callback(null, updateWithMempool(utxos, mempoolDeltas));
});
}
}
if (queryMempool) {
self.client.getAddressMempool({addresses: addresses}, function(err, response) {
if (err) {
return callback(self._wrapRPCError(err));
}
finish(response.result);
});
} else {
finish();
}
};
AddressService.prototype.syncPercentage = function(callback) {
return callback(null, ((this._tip.height / this._block.getBestBlockHeight()) * 100).toFixed(2) + '%');
};
AddressService.prototype.getAddressTxidsWithHeights = function(address, options, callback) {
var self = this; var self = this;
if (_.isUndefined(options.queryMempool)) {
options.queryMempool = true;
}
var opts = options || {}; var results = [];
var txids = {};
var start = self._encoding.encodeAddressIndexKey(address, opts.start || 0); var start = self._encoding.encodeUtxoIndexKey(address);
var end = Buffer.concat([ start.slice(0, -36), new Buffer((opts.end || 'ffffffff'), 'hex') ]); var criteria = {
var stream = self.db.createKeyStream({
gte: start, gte: start,
lt: end lt: utils.getTerminalKey(start)
};
var utxoStream = self._db.createReadStream(criteria);
var streamErr;
utxoStream.on('end', function() {
if (streamErr) {
return callback(streamErr);
}
callback(null, results);
}); });
var streamErr = null; utxoStream.on('error', function(err) {
stream.on('data', function(buffer) {
var key = self._encoding.decodeAddressIndexKey(buffer);
txids[key.txid] = key.height;
});
stream.on('end', function() {
callback(streamErr, txids);
});
stream.on('error', function(err) {
streamErr = err; streamErr = err;
}); });
utxoStream.on('data', function(data) {
var key = self._decodeUtxoIndexKey(data.key);
var value = self._encoding.decodeUtxoIndexValue(data.value);
results.push({
address: address,
txid: key.txid,
vout: key.oudputIndex,
ts: null,
scriptPubKey: value.scriptBuffer.toString('hex'),
amount: Unit.fromSatoshis(value.satoshis).toBTC(),
confirmations: self._p2p.getBestHeight() - value.height,
satoshis: value.satoshis,
confirmationsFromCache: true
});
});
}; };
// ---- private function prototypes // ---- private function prototypes
AddressService.prototype._setListeners = function() { AddressService.prototype._setListeners = function() {

View File

@ -14,6 +14,28 @@ var BN = require('bn.js');
var consensus = require('bcoin').consensus; var consensus = require('bcoin').consensus;
var constants = require('../../constants'); var constants = require('../../constants');
/*
1. getAddressSummary
2. getAddressUnspentOutputs
3. bitcoind.height
4. getBlockHeader
5. getDetailedTransaction
6. getTransaction
7. sendTransaction
8. getInfo
9. bitcoind.tiphash
10. getBestBlockHash
11. isSynced
12. getAddressHistory
13. getBlock
14. getRawBlock
15. getBlockHashesByTimestamp
16. estimateFee
17. getBlockOverview
18. syncPercentage
*/
var BlockService = function(options) { var BlockService = function(options) {
BaseService.call(this, options); BaseService.call(this, options);
@ -67,27 +89,23 @@ BlockService.prototype.getAPIMethods = function() {
return methods; return methods;
}; };
BlockService.prototype.getBestBlockHash = function() { BlockService.prototype.getBestBlockHash = function(callback) {
return this._meta[this._meta.length - 1].hash; return callback(null, this._meta[this._meta.length - 1].hash);
}; };
BlockService.prototype.getBlock = function(hash, callback) { BlockService.prototype.getBlock = function(hash, callback) {
var self = this;
this._db.get(this._encoding.encodeBlockKey(hash), function(err, data) {
if(err) {
return callback(err);
}
callback(null, self._encoding.decodeBlockValue(data));
});
};
BlockService.prototype._getHash = function(blockArg) { blockArg = this._getHash(blockArg);
return (_.isNumber(blockArg) || (blockArg.length < 40 && /^[0-9]+$/.test(blockArg))) && if (!blockArg) {
this._meta[blockArg] ? this._meta[blockArg] : null; return callback();
}
this._getBlock(blockArg, callback);
}; };
BlockService.prototype.getBlockHeader = function(blockArg, callback) { BlockService.prototype.getBlockHeader = function(blockArg, callback) {
blockArg = this._getHash(blockArg); blockArg = this._getHash(blockArg);
@ -112,14 +130,6 @@ BlockService.prototype.getBlockHeader = function(blockArg, callback) {
}; };
BlockService.prototype._getBlock = function(hash, callback) {
var block = this._blockQueue(hash);
if (block) {
return callback(null, block);
}
this._db.get(this._encoding.encodeBlockKey(hash), callback);
};
BlockService.prototype.getBlockOverview = function(hash, callback) { BlockService.prototype.getBlockOverview = function(hash, callback) {
this._getBlock(hash, function(err, block) { this._getBlock(hash, function(err, block) {
@ -169,14 +179,18 @@ BlockService.prototype.getPublishEvents = function() {
}; };
BlockService.prototype.getRawBlock = function(hash, callback) { BlockService.prototype.getRawBlock = function(hash, callback) {
this.getBlock(hash, function(err, data) { this.getBlock(hash, function(err, block) {
if(err) { if(err) {
return callback(err); return callback(err);
} }
data.toString(); callback(null, block.toString());
}); });
}; };
BlockService.prototype.isSynced = function(callback) {
callback(null, this._p2p.getBestHeight <= this._tip.height);
};
BlockService.prototype.start = function(callback) { BlockService.prototype.start = function(callback) {
var self = this; var self = this;
@ -217,6 +231,12 @@ BlockService.prototype.subscribe = function(name, emitter) {
}; };
BlockService.prototype.syncPercentage = function(callback) {
var p2pHeight = this._p2p.getBestHeight();
var percentage = ((p2pHeight / (this._tip.height || p2pHeight)) * 100).toFixed(2);
callback(null, percentage);
};
BlockService.prototype.unsubscribe = function(name, emitter) { BlockService.prototype.unsubscribe = function(name, emitter) {
var index = this._subscriptions[name].indexOf(emitter); var index = this._subscriptions[name].indexOf(emitter);
@ -327,6 +347,14 @@ BlockService.prototype._findCommonAncestor = function(block) {
} }
}; };
BlockService.prototype._getBlock = function(hash, callback) {
var block = this._blockQueue(hash);
if (block) {
return callback(null, block);
}
this._db.get(this._encoding.encodeBlockKey(hash), callback);
};
BlockService.prototype._getBlockOperations = function(block) { BlockService.prototype._getBlockOperations = function(block) {
var self = this; var self = this;
@ -379,6 +407,13 @@ BlockService.prototype._getDelta = function(tip) {
}; };
BlockService.prototype._getHash = function(blockArg) {
return (_.isNumber(blockArg) || (blockArg.length < 40 && /^[0-9]+$/.test(blockArg))) &&
this._meta[blockArg] ? this._meta[blockArg] : null;
};
BlockService.prototype._getIncompleteChainIndexes = function(block) { BlockService.prototype._getIncompleteChainIndexes = function(block) {
var ret = []; var ret = [];
for(var i = 0; i < this._incompleteChains.length; i++) { for(var i = 0; i < this._incompleteChains.length; i++) {
@ -392,7 +427,9 @@ BlockService.prototype._getIncompleteChainIndexes = function(block) {
}; };
BlockService.prototype._handleReorg = function(block) { BlockService.prototype._handleReorg = function(block) {
this._reorging = true; this._reorging = true;
log.warn('Chain reorganization detected! Our current block tip is: ' + log.warn('Chain reorganization detected! Our current block tip is: ' +
this._tip.hash + ' the current block: ' + block.hash + '.'); this._tip.hash + ' the current block: ' + block.hash + '.');
@ -407,8 +444,11 @@ BlockService.prototype._handleReorg = function(block) {
} }
log.warn('A common ancestor block was found to at hash: ' + commonAncestor + '.'); log.warn('A common ancestor block was found to at hash: ' + commonAncestor + '.');
this._broadcast(this.subscriptions.reorg, 'block/reorg', [commonAncestor, [block]]); this._broadcast(this.subscriptions.reorg, 'block/reorg', [commonAncestor, [block]]);
this._onReorg(commonAncestor, [block]); this._onReorg(commonAncestor, [block]);
this._reorging = false; this._reorging = false;
}; };
@ -446,8 +486,11 @@ BlockService.prototype._loadMeta = function(callback) {
gte: self._encoding.encodeMetaKey(0), gte: self._encoding.encodeMetaKey(0),
lte: self._encoding.encodeMetaKey(0xffffffff) lte: self._encoding.encodeMetaKey(0xffffffff)
}; };
var stream = this._db.createReadStream(criteria); var stream = this._db.createReadStream(criteria);
stream.on('error', self._onDbError.bind(self)); stream.on('error', self._onDbError.bind(self));
stream.on('end', function() { stream.on('end', function() {
if (self._meta.length < 1) { if (self._meta.length < 1) {
self._meta.push({ self._meta.push({
@ -475,11 +518,15 @@ BlockService.prototype._onBlock = function(block) {
} }
// 2. log the reception // 2. log the reception
log.debug('New block received: ' + block.hash); log.debug2('New block received: ' + block.hash);
// 3. store the block for safe keeping // 3. store the block for safe keeping
this._cacheBlock(block); this._cacheBlock(block);
// don't process any more blocks if we are currently in a reorg
if (this._reorging) {
return;
}
// 4. determine block state, reorg, outoforder, normal // 4. determine block state, reorg, outoforder, normal
var blockState = this._determineBlockState(block); var blockState = this._determineBlockState(block);
@ -492,6 +539,7 @@ BlockService.prototype._onBlock = function(block) {
// nothing to do, but wait until ancestor blocks come in // nothing to do, but wait until ancestor blocks come in
break; break;
case 'reorg': case 'reorg':
this._handleReorg();
this.emit('reorg', block); this.emit('reorg', block);
break; break;
default: default:
@ -575,7 +623,6 @@ BlockService.prototype._setListeners = function() {
self._p2p.once('bestHeight', self._onBestHeight.bind(self)); self._p2p.once('bestHeight', self._onBestHeight.bind(self));
self._db.on('error', self._onDbError.bind(self)); self._db.on('error', self._onDbError.bind(self));
self.on('reorg', self._handleReorg.bind(self));
}; };

View File

@ -9,6 +9,28 @@ var BaseService = require('../../service');
var assert = require('assert'); var assert = require('assert');
var Bcoin = require('./bcoin'); var Bcoin = require('./bcoin');
/*
1. getAddressSummary
2. getAddressUnspentOutputs
3. bitcoind.height
4. getBlockHeader
5. getDetailedTransaction
6. getTransaction
7. sendTransaction
8. getInfo
9. bitcoind.tiphash
10. getBestBlockHash
11. isSynced
12. getAddressHistory
13. getBlock
14. getRawBlock
15. getBlockHashesByTimestamp
16. estimateFee
17. getBlockOverview
18. syncPercentage
*/
var P2P = function(options) { var P2P = function(options) {
if (!(this instanceof P2P)) { if (!(this instanceof P2P)) {
@ -61,6 +83,19 @@ P2P.prototype.getHeaders = function(filter) {
}; };
P2P.prototype.getInfo = function(callback) {
callback(null, {
version: '4.0',
protocolversion: 'latest',
blocks: this._getBestHeight(),
timeoffset: 0,
connections: this._pool.numberConnected,
difficulty: 0,
testnet: false,
relayfee: 0
});
};
P2P.prototype.getMempool = function(filter) { P2P.prototype.getMempool = function(filter) {
var peer = this._getPeer(); var peer = this._getPeer();

View File

@ -6,6 +6,28 @@ var inherits = require('util').inherits;
var LRU = require('lru-cache'); var LRU = require('lru-cache');
var utils = require('../../../lib/utils'); var utils = require('../../../lib/utils');
/*
1. getAddressSummary
2. getAddressUnspentOutputs
3. bitcoind.height
4. getBlockHeader
5. getDetailedTransaction
6. getTransaction
7. sendTransaction
8. getInfo
9. bitcoind.tiphash
10. getBestBlockHash
11. isSynced
12. getAddressHistory
13. getBlock
14. getRawBlock
15. getBlockHashesByTimestamp
16. estimateFee
17. getBlockOverview
18. syncPercentage
*/
function TimestampService(options) { function TimestampService(options) {
BaseService.call(this, options); BaseService.call(this, options);
this._db = this.node.services.db; this._db = this.node.services.db;
@ -24,6 +46,7 @@ TimestampService.prototype.getAPIMethods = function() {
}; };
TimestampService.prototype.syncPercentage = function(callback) { TimestampService.prototype.syncPercentage = function(callback) {
return callback(null, ((this._tip.height / this._block.getBestBlockHeight()) * 100).toFixed(2) + '%');
}; };
TimestampService.prototype.getBlockHashesByTimestamp = function(callback) { TimestampService.prototype.getBlockHashesByTimestamp = function(callback) {

View File

@ -10,77 +10,97 @@ var levelup = require('levelup');
function TransactionService(options) { function TransactionService(options) {
BaseService.call(this, options); BaseService.call(this, options);
this._db = this.node.services.db; this._db = this.node.services.db;
this.currentTransactions = {}; this._mempool = this.node.services._mempool;
this._block = this.node.services.block;
this._p2p = this.node.services.p2p;
} }
inherits(TransactionService, BaseService); inherits(TransactionService, BaseService);
TransactionService.dependencies = [ TransactionService.dependencies = [
'p2p',
'db', 'db',
'block', 'block',
'timestamp', 'timestamp',
'mempool' 'mempool'
]; ];
/*
1. getAddressSummary
2. getAddressUnspentOutputs
3. bitcoind.height
4. getBlockHeader
5. getDetailedTransaction
6. getTransaction
7. sendTransaction
8. getInfo
9. bitcoind.tiphash
10. getBestBlockHash
11. isSynced
12. getAddressHistory
13. getBlock
14. getRawBlock
15. getBlockHashesByTimestamp
16. estimateFee
17. getBlockOverview
18. syncPercentage
*/
TransactionService.prototype.getAPIMethods = function() { TransactionService.prototype.getAPIMethods = function() {
return [ return [
['getRawTransaction', this, this.getRawTransaction, 1], ['getRawTransaction', this, this.getRawTransaction, 1],
['getTransaction', this, this.getTransaction, 1], ['getTransaction', this, this.getTransaction, 1],
['getDetailedTransaction', this, this.getDetailedTransaction, 1], ['getDetailedTransaction', this, this.getDetailedTransaction, 1],
['sendTransaction', this, this.sendTransaction, 1], ['sendTransaction', this, this.sendTransaction, 1],
['getSpentInfo', this, this.getSpentInfo, 1],
['syncPercentage', this, this.syncPercentage, 0] ['syncPercentage', this, this.syncPercentage, 0]
]; ];
}; };
TransactionService.prototype.getSpentInfo = function(txid, callback) {
};
TransactionService.prototype.getRawTransaction = function(txid, callback) { TransactionService.prototype.getTransaction = function(txid, options, callback) {
this.getTransaction(txid, function(err, tx) {
if (err) {
return callback(err);
}
return tx.serialize();
});
};
TransactionService.prototype.getDetailedTransaction = TransactionService.prototype.getTransaction = function(txid, options, callback) {
var self = this; var self = this;
var key = self.encoding.encodeTransactionKey(txid); var queryMempool = _.isUndefined(options.queryMempool) ? true : options.queryMempool;
async.waterfall([ var key = self.encoding.encodeTransactionKey(txid);
function(next) { this._db.get(key, function(err, tx) {
self.node.services.db.get(key, function(err, buffer) {
if (err instanceof levelup.errors.NotFoundError) { if(err) {
return next(null, false); return callback(err);
} else if (err) { }
if (queryMempool && !tx) {
this._mempool.getTransaction(tx, function(err, memTx) {
if(err) {
return callback(err); return callback(err);
} }
var tx = self.encoding.decodeTransactionValue(buffer);
next(null, tx); if (memTx) {
return callback(null, { tx: memTx, confirmations: 0});
}
return callback();
}); });
}, function(tx, next) {
} else {
if (tx) { if (tx) {
return next(null, tx); return callback(null, { tx: tx, confirmations: this._p2p.getBestHeight - tx.__height });
} }
if (!options || !options.queryMempool) { return callback();
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.onBlock = function(block, connectBlock, callback) { TransactionService.prototype._onBlock = function(block, connectBlock, callback) {
var self = this; var self = this;
var action = 'put'; var action = 'put';
var reverseAction = 'del'; var reverseAction = 'del';
@ -156,6 +176,7 @@ TransactionService.prototype._onReorg = function(commonAncestor, newBlockList) {
}; };
TransactionService.prototype.start = function(callback) { TransactionService.prototype.start = function(callback) {
var self = this; var self = this;
self._setListeners(); self._setListeners();
@ -173,7 +194,7 @@ TransactionService.prototype.start = function(callback) {
self._tip = tip; self._tip = tip;
self.prefix = prefix; self.prefix = prefix;
self.encoding = new Encoding(self.prefix); self._encoding = new Encoding(self.prefix);
self._startSubscriptions(); self._startSubscriptions();
callback(); callback();