wip
This commit is contained in:
parent
98ea052405
commit
b471857bf0
@ -7,7 +7,7 @@ function Encoding(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 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');
|
||||
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);
|
||||
};
|
||||
|
||||
@ -36,10 +45,14 @@ Encoding.prototype.decodeAddressIndexKey = function(buffer) {
|
||||
var address = reader.read(addressSize).toString('utf8');
|
||||
var height = reader.readUInt32BE();
|
||||
var txid = reader.read(32).toString('hex');
|
||||
var index = reader.readUInt32BE();
|
||||
var input = reader.readUInt8();
|
||||
return {
|
||||
address: address,
|
||||
height: height,
|
||||
txid: txid,
|
||||
index: index,
|
||||
input: input
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@ -7,10 +7,33 @@ var index = require('../../');
|
||||
var log = index.log;
|
||||
var errors = index.errors;
|
||||
var bitcore = require('bitcore-lib');
|
||||
var Unit = bitcore.Unit;
|
||||
var _ = bitcore.deps._;
|
||||
var Address = bitcore.Address;
|
||||
var Encoding = require('./encoding');
|
||||
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) {
|
||||
BaseService.call(this, options);
|
||||
@ -28,25 +51,6 @@ AddressService.dependencies = [
|
||||
];
|
||||
|
||||
// ---- 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) {
|
||||
var self = this;
|
||||
|
||||
@ -127,266 +131,275 @@ AddressService.prototype.stop = function(callback) {
|
||||
|
||||
AddressService.prototype.getAPIMethods = function() {
|
||||
return [
|
||||
['getAddressBalance', this, this.getAddressBalance, 2],
|
||||
['getAddressHistory', this, this.getAddressHistory, 2],
|
||||
['getAddressSummary', this, this.getAddressSummary, 1],
|
||||
['getAddressTxids', this, this.getAddressTxids, 2],
|
||||
['getAddressUnspentOutputs', this, this.getAddressUnspentOutputs, 1],
|
||||
['syncPercentage', this, this.syncPercentage, 0]
|
||||
];
|
||||
};
|
||||
|
||||
AddressService.prototype.getAddressBalance = function(addresses, options, callback) {
|
||||
|
||||
AddressService.prototype.getAddressHistory = function(addresses, options, callback) {
|
||||
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);
|
||||
var criteria = {
|
||||
gte: start,
|
||||
lte: Buffer.concat([ start.slice(-36), new Buffer(new Array(73).join('f'), 'hex') ])
|
||||
async.mapLimit(addresses, 4, function(address, next) {
|
||||
|
||||
self._getAddressHistory(address, next);
|
||||
|
||||
}, 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);
|
||||
stream.on('data', function(data) {
|
||||
callback(null, results);
|
||||
|
||||
});
|
||||
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 txids = [];
|
||||
|
||||
async.eachLimit(addresses, 4, function(address, next) {
|
||||
self.getAddressTxids(address, options, function(err, tmpTxids) {
|
||||
var results = [];
|
||||
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) {
|
||||
return next(err);
|
||||
log.error(err);
|
||||
txStream.emit('error', err);
|
||||
return callback();
|
||||
}
|
||||
|
||||
txids = _.union(txids, tmpTxids);
|
||||
return next();
|
||||
if (!tx) {
|
||||
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);
|
||||
});
|
||||
}, callback);
|
||||
txStream.on('error', function(err) {
|
||||
log.error(err);
|
||||
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 startTime = new Date();
|
||||
var address = new Address(addressArg);
|
||||
|
||||
if (_.isUndefined(options.queryMempool)) {
|
||||
options.queryMempool = true;
|
||||
}
|
||||
|
||||
async.waterfall([
|
||||
function(next) {
|
||||
self._getAddressConfirmedSummary(address, options, next);
|
||||
},
|
||||
function(result, next) {
|
||||
self._getAddressMempoolSummary(address, options, result, next);
|
||||
},
|
||||
function(result, next) {
|
||||
self._setAndSortTxidsFromAppearanceIds(result, next);
|
||||
}
|
||||
], function(err, result) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
var result = {
|
||||
addrStr: address,
|
||||
balance: 0,
|
||||
balanceSat: 0,
|
||||
totalReceived: 0,
|
||||
totalReceivedSat: 0,
|
||||
totalSent: 0,
|
||||
totalSentSat: 0,
|
||||
unconfirmedBalance: 0,
|
||||
unconfirmedBalanceSat: 0,
|
||||
unconfirmedTxApperances: 0,
|
||||
txApperances: 0,
|
||||
transactions: []
|
||||
};
|
||||
|
||||
var summary = self._transformAddressSummaryFromResult(result, options);
|
||||
|
||||
var timeDelta = new Date() - startTime;
|
||||
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({
|
||||
// txid criteria
|
||||
var start = self._encoding.encodeAddressIndexKey(address);
|
||||
var criteria = {
|
||||
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;
|
||||
stream.on('close', function() {
|
||||
// tx stream
|
||||
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) {
|
||||
var key = self._encoding.decodeAddressIndexKey(buffer);
|
||||
txids[key.txid] = true;
|
||||
// pipe txids into tx stream for processing
|
||||
txidStream.pipe(txStream);
|
||||
|
||||
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() {
|
||||
callback(streamErr, Object.keys(txids));
|
||||
});
|
||||
|
||||
stream.on('error', function(err) {
|
||||
streamErr = err;
|
||||
});
|
||||
txStream._flush = function(callback) {
|
||||
txStream.emit('end');
|
||||
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;
|
||||
if (_.isUndefined(options.queryMempool)) {
|
||||
options.queryMempool = true;
|
||||
}
|
||||
|
||||
var opts = options || {};
|
||||
var txids = {};
|
||||
var results = [];
|
||||
|
||||
var start = self._encoding.encodeAddressIndexKey(address, opts.start || 0);
|
||||
var end = Buffer.concat([ start.slice(0, -36), new Buffer((opts.end || 'ffffffff'), 'hex') ]);
|
||||
|
||||
var stream = self.db.createKeyStream({
|
||||
var start = self._encoding.encodeUtxoIndexKey(address);
|
||||
var criteria = {
|
||||
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;
|
||||
|
||||
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) {
|
||||
utxoStream.on('error', function(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
|
||||
AddressService.prototype._setListeners = function() {
|
||||
|
||||
|
||||
@ -14,6 +14,28 @@ var BN = require('bn.js');
|
||||
var consensus = require('bcoin').consensus;
|
||||
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) {
|
||||
|
||||
BaseService.call(this, options);
|
||||
@ -67,27 +89,23 @@ BlockService.prototype.getAPIMethods = function() {
|
||||
return methods;
|
||||
};
|
||||
|
||||
BlockService.prototype.getBestBlockHash = function() {
|
||||
return this._meta[this._meta.length - 1].hash;
|
||||
BlockService.prototype.getBestBlockHash = function(callback) {
|
||||
return callback(null, this._meta[this._meta.length - 1].hash);
|
||||
};
|
||||
|
||||
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))) &&
|
||||
this._meta[blockArg] ? this._meta[blockArg] : null;
|
||||
if (!blockArg) {
|
||||
return callback();
|
||||
}
|
||||
|
||||
this._getBlock(blockArg, callback);
|
||||
|
||||
};
|
||||
|
||||
|
||||
BlockService.prototype.getBlockHeader = function(blockArg, callback) {
|
||||
|
||||
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) {
|
||||
|
||||
this._getBlock(hash, function(err, block) {
|
||||
@ -169,14 +179,18 @@ BlockService.prototype.getPublishEvents = function() {
|
||||
};
|
||||
|
||||
BlockService.prototype.getRawBlock = function(hash, callback) {
|
||||
this.getBlock(hash, function(err, data) {
|
||||
this.getBlock(hash, function(err, block) {
|
||||
if(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) {
|
||||
|
||||
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) {
|
||||
|
||||
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) {
|
||||
|
||||
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) {
|
||||
var ret = [];
|
||||
for(var i = 0; i < this._incompleteChains.length; i++) {
|
||||
@ -392,7 +427,9 @@ BlockService.prototype._getIncompleteChainIndexes = function(block) {
|
||||
};
|
||||
|
||||
BlockService.prototype._handleReorg = function(block) {
|
||||
|
||||
this._reorging = true;
|
||||
|
||||
log.warn('Chain reorganization detected! Our current block tip is: ' +
|
||||
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 + '.');
|
||||
|
||||
this._broadcast(this.subscriptions.reorg, 'block/reorg', [commonAncestor, [block]]);
|
||||
|
||||
this._onReorg(commonAncestor, [block]);
|
||||
|
||||
this._reorging = false;
|
||||
};
|
||||
|
||||
@ -446,8 +486,11 @@ BlockService.prototype._loadMeta = function(callback) {
|
||||
gte: self._encoding.encodeMetaKey(0),
|
||||
lte: self._encoding.encodeMetaKey(0xffffffff)
|
||||
};
|
||||
|
||||
var stream = this._db.createReadStream(criteria);
|
||||
|
||||
stream.on('error', self._onDbError.bind(self));
|
||||
|
||||
stream.on('end', function() {
|
||||
if (self._meta.length < 1) {
|
||||
self._meta.push({
|
||||
@ -475,11 +518,15 @@ BlockService.prototype._onBlock = function(block) {
|
||||
}
|
||||
|
||||
// 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
|
||||
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
|
||||
var blockState = this._determineBlockState(block);
|
||||
|
||||
@ -492,6 +539,7 @@ BlockService.prototype._onBlock = function(block) {
|
||||
// nothing to do, but wait until ancestor blocks come in
|
||||
break;
|
||||
case 'reorg':
|
||||
this._handleReorg();
|
||||
this.emit('reorg', block);
|
||||
break;
|
||||
default:
|
||||
@ -575,7 +623,6 @@ BlockService.prototype._setListeners = function() {
|
||||
|
||||
self._p2p.once('bestHeight', self._onBestHeight.bind(self));
|
||||
self._db.on('error', self._onDbError.bind(self));
|
||||
self.on('reorg', self._handleReorg.bind(self));
|
||||
|
||||
};
|
||||
|
||||
|
||||
@ -9,6 +9,28 @@ var BaseService = require('../../service');
|
||||
var assert = require('assert');
|
||||
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) {
|
||||
|
||||
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) {
|
||||
|
||||
var peer = this._getPeer();
|
||||
|
||||
@ -6,6 +6,28 @@ var inherits = require('util').inherits;
|
||||
var LRU = require('lru-cache');
|
||||
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) {
|
||||
BaseService.call(this, options);
|
||||
this._db = this.node.services.db;
|
||||
@ -24,6 +46,7 @@ TimestampService.prototype.getAPIMethods = function() {
|
||||
};
|
||||
|
||||
TimestampService.prototype.syncPercentage = function(callback) {
|
||||
return callback(null, ((this._tip.height / this._block.getBestBlockHeight()) * 100).toFixed(2) + '%');
|
||||
};
|
||||
|
||||
TimestampService.prototype.getBlockHashesByTimestamp = function(callback) {
|
||||
|
||||
@ -10,77 +10,97 @@ var levelup = require('levelup');
|
||||
function TransactionService(options) {
|
||||
BaseService.call(this, options);
|
||||
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);
|
||||
|
||||
TransactionService.dependencies = [
|
||||
'p2p',
|
||||
'db',
|
||||
'block',
|
||||
'timestamp',
|
||||
'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() {
|
||||
return [
|
||||
['getRawTransaction', this, this.getRawTransaction, 1],
|
||||
['getTransaction', this, this.getTransaction, 1],
|
||||
['getDetailedTransaction', this, this.getDetailedTransaction, 1],
|
||||
['sendTransaction', this, this.sendTransaction, 1],
|
||||
['getSpentInfo', this, this.getSpentInfo, 1],
|
||||
['syncPercentage', this, this.syncPercentage, 0]
|
||||
];
|
||||
};
|
||||
|
||||
TransactionService.prototype.getSpentInfo = function(txid, callback) {
|
||||
};
|
||||
|
||||
TransactionService.prototype.getRawTransaction = function(txid, 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) {
|
||||
TransactionService.prototype.getTransaction = function(txid, options, callback) {
|
||||
|
||||
var self = this;
|
||||
|
||||
var key = self.encoding.encodeTransactionKey(txid);
|
||||
var queryMempool = _.isUndefined(options.queryMempool) ? true : options.queryMempool;
|
||||
|
||||
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) {
|
||||
var key = self.encoding.encodeTransactionKey(txid);
|
||||
this._db.get(key, function(err, tx) {
|
||||
|
||||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
if (queryMempool && !tx) {
|
||||
|
||||
this._mempool.getTransaction(tx, function(err, memTx) {
|
||||
|
||||
if(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) {
|
||||
return next(null, tx);
|
||||
return callback(null, { tx: tx, confirmations: this._p2p.getBestHeight - tx.__height });
|
||||
}
|
||||
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);
|
||||
return callback();
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
};
|
||||
|
||||
TransactionService.prototype.onBlock = function(block, connectBlock, callback) {
|
||||
TransactionService.prototype._onBlock = function(block, connectBlock, callback) {
|
||||
var self = this;
|
||||
var action = 'put';
|
||||
var reverseAction = 'del';
|
||||
@ -156,6 +176,7 @@ TransactionService.prototype._onReorg = function(commonAncestor, newBlockList) {
|
||||
};
|
||||
|
||||
TransactionService.prototype.start = function(callback) {
|
||||
|
||||
var self = this;
|
||||
self._setListeners();
|
||||
|
||||
@ -173,7 +194,7 @@ TransactionService.prototype.start = function(callback) {
|
||||
|
||||
self._tip = tip;
|
||||
self.prefix = prefix;
|
||||
self.encoding = new Encoding(self.prefix);
|
||||
self._encoding = new Encoding(self.prefix);
|
||||
self._startSubscriptions();
|
||||
callback();
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user