This commit is contained in:
Chris Kleeschulte 2017-07-17 18:20:48 -04:00
parent eb39e5bf19
commit 17bbfcc6ce
8 changed files with 156 additions and 328 deletions

View File

@ -570,14 +570,14 @@ BlockService.prototype._onBlock = function(block) {
// 3. store the block for safe keeping
this._cacheBlock(block);
// don't process any more blocks if we are currently in a reorg
// 4. don't process any more blocks if we are currently in a reorg
if (this._reorging) {
return;
}
// 4. determine block state, reorg, outoforder, normal
// 5. determine block state, reorg, outoforder, normal
var blockState = this._determineBlockState(block);
// 5. update internal data structures depending on blockstate
// 6. update internal data structures depending on blockstate
this._updateChainInfo(block, blockState);
// 7. react to state of block

View File

@ -1,74 +1,28 @@
'use strict';
var bitcore = require('bitcore-lib');
var BufferReader = bitcore.encoding.BufferReader;
var tx = require('bcoin').tx;
function Encoding(servicePrefix) {
this.servicePrefix = servicePrefix;
}
Encoding.prototype.encodeMempoolAddressIndexKey = function(address, txid) {
var prefix = new Buffer('00', 'hex');
var buffers = [this.servicePrefix, prefix];
var addressSizeBuffer = new Buffer(1);
addressSizeBuffer.writeUInt8(address.length);
buffers.push(addressSizeBuffer);
var addressBuffer = new Buffer(address, 'utf8');
buffers.push(addressBuffer);
var txidBuffer = new Buffer(txid, 'hex');
buffers.push(txidBuffer);
return Buffer.concat(buffers);
};
Encoding.prototype.decodeMempoolAddressIndexKey = function(buffer) {
var reader = new BufferReader(buffer);
reader.read(3);
var addressSize = reader.readUInt8();
var address = reader.read(addressSize).toString('utf8');
var txid = reader.read(32).toString('hex');
return {
address: address,
txid: txid
};
};
Encoding.prototype.encodeMempoolTransactionKey = function(txid) {
var prefix = new Buffer('01', 'hex');
var buffers = [this.servicePrefix, prefix];
var buffers = [this.servicePrefix];
var txidBuffer = new Buffer(txid, 'hex');
buffers.push(txidBuffer);
return Buffer.concat(buffers);
};
Encoding.prototype.decodeMempoolTransactionKey = function(buffer) {
return buffer.slice(4).toString('hex');
return buffer.slice(2).toString('hex');
};
Encoding.prototype.encodeMempoolTransactionValue = function(transaction) {
var inputValues = transaction.__inputValues || [];
var inputValuesBuffer = new Buffer(8 * inputValues.length);
for(var i = 0; i < inputValues.length; i++) {
inputValuesBuffer.writeDoubleBE(inputValues[i], i * 8);
}
var inputValuesLengthBuffer = new Buffer(2);
inputValuesLengthBuffer.writeUInt16BE(inputValues.length * 8);
return new Buffer.concat([inputValuesLengthBuffer, inputValuesBuffer, transaction.toBuffer()]);
return transaction.toRaw();
};
Encoding.prototype.decodeMempoolTransactionValue = function(buffer) {
var inputValues = [];
var inputValuesLength = buffer.readUInt16BE();
for(var i = 0; i < inputValuesLength / 8; i++) {
inputValues.push(buffer.readDoubleBE(i * 8 + 2));
}
var transaction = new bitcore.Transaction(buffer.slice(inputValues.length * 8 + 2));
transaction.__inputValues = inputValues;
return transaction;
return tx.fromRaw(buffer);
};
module.exports = Encoding;

View File

@ -4,279 +4,89 @@ var util = require('util');
var Encoding = require('./encoding');
var index = require('../../index');
var log = index.log;
var async = require('async');
var MempoolService = function(options) {
BaseService.call(this, options);
this.node = options.node;
this.name = options.name;
this._txIndex = {};
this._addressIndex = {};
this.db = this.node.services.db;
this._handleBlocks = false;
this._db = this.node.services.db;
this._tx = this.node.services.transaction;
};
util.inherits(MempoolService, BaseService);
MempoolService.dependencies = ['db'];
MempoolService.prototype.getPublishEvents = function() {
return [];
};
MempoolService.prototype.getAPIMethods = function() {
return [];
var methods = [
['getTransaction', this, this.getTransaction, 1]
];
return methods;
};
MempoolService.prototype.start = function(callback) {
var self = this;
self.node.services.db.getPrefix(self.name, function(err, servicePrefix) {
self._db.getPrefix(self.name, function(err, prefix) {
if(err) {
return callback(err);
}
self.servicePrefix = servicePrefix;
self._encoding = new Encoding(self.servicePrefix);
self._setListeners();
self._encoding = new Encoding(prefix);
self._startSubscriptions();
callback();
});
};
MempoolService.prototype._setListeners = function() {
this._startSubscriptions();
MempoolService.prototype._startSubscriptions = function() {
if (this._subscribed) {
return;
}
this._subscribed = true;
if (!this._bus) {
this._bus = this.node.openBus({remoteAddress: 'localhost'});
}
this._bus.on('p2p/block', this._onBlock.bind(this));
this._bus.on('p2p/transaction', this._onTransaction.bind(this));
this._bus.subscribe('p2p/block');
this._bus.subscribe('p2p/transaction');
};
MempoolService.prototype._startSubscriptions = function() {
var bus = this.node.openBus({ remoteAddress: 'localhost' });
bus.on('p2p/transaction', this._onTransaction.bind(this));
bus.subscribe('p2p/transaction');
MempoolService.prototype._onBlock = function(block) {
// remove this block's txs from mempool
var ops = block.transactions.map(function(tx) {
return {
type: 'del',
key: tx.id
};
});
this._db.batch(ops);
};
MempoolService.prototype._onTransaction = function(tx) {
var txOps = this._getTxOperation(tx);
this._db.batch(txOps);
};
MempoolService.prototype._getTxOperations = function(tx) {
var inputValues = this._tx.getInputValues(tx);
tx.__inputValues = inputValues;
return {
type: 'put',
key: this._encoding.encodeMempoolTransactionKey(tx.id),
value: this._encoding.encodeMempoolTransactionValue(tx)
};
};
MempoolService.prototype.getTransaction = function(txid, callback) {
this._db.get(this._encoding.encodeMempoolTransactionKey(txid), callback);
};
MempoolService.prototype.stop = function(callback) {
callback();
};
MempoolService.prototype.setupRoutes = function() {
};
MempoolService.prototype.getRoutePrefix = function() {
return this.name;
};
MempoolService.prototype._getTransactionAddressDetailOperations = function(tx, action) {
var self = this;
if(tx.isCoinbase()) {
return [];
}
var operations = [];
var txid = tx.id;
var inputs = tx.inputs;
var outputs = tx.outputs;
var outputLength = outputs.length;
for (var outputIndex = 0; outputIndex < outputLength; outputIndex++) {
var output = outputs[outputIndex];
var script = output.script;
if(!script) {
log.debug('Invalid script');
continue;
}
var address = self.getAddressString(script);
if(!address) {
continue;
}
var key = self.encoding.encodeMempoolAddressIndexKey(address, txid);
operations.push({
type: action,
key: key
});
}
//TODO deal with P2PK
for(var i = 0; i < inputs.length; i++) {
var input = inputs[i];
if(!input.script) {
log.debug('Invalid script');
continue;
}
var inputAddress = self.getAddressString(input.script);
if(!inputAddress) {
continue;
}
var inputKey = self.encodeMempoolAddressIndexKey(inputAddress, txid);
operations.push({
type: action,
key: inputKey
});
}
return operations;
};
MempoolService.prototype.getAddressString = function(script, output) {
var address = script.toAddress();
if(address) {
return address.toString();
}
try {
var pubkey = script.getPublicKey();
if(pubkey) {
return pubkey.toString('hex');
}
} catch(e) {
}
if(output && output.script && output.script.isPublicKeyOut()) {
return output.script.getPublicKey().toString('hex');
}
return null;
};
MempoolService.prototype._getInputValues = function(tx, callback) {
var self = this;
if (tx.isCoinbase()) {
return callback(null, []);
}
async.mapLimit(tx.inputs, this.concurrency, function(input, next) {
self.getTransaction(input.prevTxId.toString('hex'), {}, function(err, prevTx) {
if(err) {
return next(err);
}
if (!prevTx) {
return next(null, 0);
}
if (!prevTx.outputs[input.outputIndex]) {
return next(new Error('Input did not have utxo.'));
}
var satoshis = prevTx.outputs[input.outputIndex].satoshis;
next(null, satoshis);
});
}, callback);
};
MempoolService.prototype._updateCache = function(operation) {
if (operation.type === 'del') {
return this._cache.del(operation.key);
}
this._cache.set(operation.key, operation.value);
};
MempoolService.prototype.getTransaction = function(txid, callback) {
var self = this;
var key = self._encoding.encodeMempoolTransactionKey(txid);
var txBuffer = self._cache.get(key);
if (txBuffer) {
return setImmediate(function() {
callback(null, self._encoding.decodeMempoolTransactionValue(txBuffer));
});
}
self.db.get(key, function(err, value) {
if(err) {
return callback(err);
}
callback(null, self._encoding.decodeMempoolTransactionValue(value));
});
};
MempoolService.prototype._getTransactionOperation = function(tx, action, callback) {
var self = this;
self._getInputValues(tx, function(err, inputValues) {
if(err) {
return callback(err);
}
tx.__inputValues = inputValues;
var operation = {
type: action,
key: self._encoding.encodeMempoolTransactionKey(tx.id),
value: self._encoding.encodeMempoolTransactionValue(tx)
};
callback(null, operation);
});
};
MempoolService.prototype._updateMempool = function(tx, action, callback) {
var self = this;
self._getTransactionOperation(tx, action, function(err, operation) {
if(err) {
return callback(err);
}
var operations = [operation].concat(self._getTransactionAddressDetailOperations(tx, action));
self.db.batch(operations, function(err) {
if(err) {
log.error('batch operation for updating Mempool failed.');
return;
}
operations.forEach(self._updateCache);
});
});
};
MempoolService.prototype.getTransactionsByAddress = function(address, callback) {
var self = this;
var txids = [];
var maxTxid = new Buffer(new Array(65).join('f'), 'hex');
var start = self._encoding.encodeMempoolAddressKey(address);
var end = Buffer.concat([self._encoding.encodeMempoolAddressKey(address), maxTxid]);
var stream = self.db.createKeyStream({
gte: start,
lte: end
});
var streamErr;
stream.on('error', function(err) {
streamErr = err;
});
stream.on('data', function(data) {
var key = self._encoding.decodeMempoolAddressKey(data);
txids.push(key.txid);
});
stream.on('end', function() {
async.mapLimit(txids, 10, function(txid, next) {
self.getTransaction(txid, next);
}, callback);
});
};
MempoolService.prototype.getTransactionsByAddresses = function(addresses, callback) {
var self = this;
var transactions = {};
async.eachLimit(addresses, 10, function(address, next) {
self.getTransactionsByAddress(address, function(err, txs) {
if(err) {
return next(err);
}
txs.forEach(function(tx) {
transactions[tx.id] = tx;
});
next();
});
}, function(err) {
if(err) {
return callback(err);
}
callback(null, Object.values(transactions));
});
};
module.exports = MempoolService;

View File

@ -9,28 +9,6 @@ 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)) {
@ -85,14 +63,8 @@ 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
connections: this._pool.numberConnected
});
};
@ -245,7 +217,8 @@ P2P.prototype._initPool = function() {
if (this._configPeers) {
opts.addrs = this._configPeers;
}
opts.dnsSeed = false;
// TODO: bcoin stuff goes here
opts.dnsSeed = false; // do not set to true unless you want to be pwned
opts.maxPeers = this._maxPeers;
opts.network = this.node.getNetworkName();
this._pool = new p2p.Pool(opts);
@ -258,7 +231,6 @@ P2P.prototype._initPubSub = function() {
this.subscriptions.transaction = [];
};
P2P.prototype._onPeerBlock = function(peer, message) {
this._broadcast(this.subscriptions.block, 'p2p/block', message.block);
};

View File

@ -206,7 +206,7 @@ TimestampService.prototype._onReorg = function(oldBlockList, newBlockList, commo
}
// set the tip to the common ancestor in case something goes wrong with the reorg
var tipOps = utils.encodeTip({ height: commonAncestor.header.height });
var tipOps = utils.encodeTip({ hash: commonAncestor.hash, height: commonAncestor.header.height }, this.name);
var removalOps = [{
type: 'put',

View File

@ -35,7 +35,8 @@ TransactionService.prototype.getAPIMethods = function() {
['getTransaction', this, this.getTransaction, 1],
['getDetailedTransaction', this, this.getDetailedTransaction, 1],
['sendTransaction', this, this.sendTransaction, 1],
['syncPercentage', this, this.syncPercentage, 0]
['syncPercentage', this, this.syncPercentage, 0],
['getInputValues', this, this._getInputValues, 1]
];
};
@ -140,9 +141,9 @@ TransactionService.prototype._getBlockTimestamp = function(hash) {
return this._timestamp.getTimestamp(hash);
};
TransactionService.prototype._getInputValues = function(tx, opts) {
TransactionService.prototype._getInputValues = function(tx) {
return tx.inputs.forEach(function(input) {
return tx.inputs.map(function(input) {
var value = this._inputValuesCache.get(input.prevTxId);
if (value) {
return value[input.outputIndex];

View File

@ -0,0 +1,41 @@
'use strict';
var should = require('chai').should();
var tx = require('bcoin').tx;
var Encoding = require('../../../lib/services/mempool/encoding');
describe('Block service encoding', function() {
var servicePrefix = new Buffer('0000', 'hex');
var encoding = new Encoding(servicePrefix);
var hash = '25e28f9fb0ada5353b7d98d85af5524b2f8df5b0b0e2d188f05968bceca603eb';
var txString = '0100000004de9b4bb17f627096a9ee0b4528e4eae17df5b5c69edc29704c2e84a7371db29f010000006b483045022100f5b1a0d33b7be291c3953c25f8ae39d98601aa7099a8674daf638a08b86c7173022006ce372da5ad088a1cc6e5c49c2760a1b6f085eb1b51b502211b6bc9508661f9012102ec5e3731e54475dd2902326f43602a03ae3d62753324139163f81f20e787514cffffffff7a1d4e5fc2b8177ec738cd723a16cf2bf493791e55573445fc0df630fe5e2d64010000006b483045022100cf97f6cb8f126703e9768545dfb20ffb10ba78ae3d101aa46775f5a239b075fc02203150c4a89a11eaf5e404f4f96b62efa4455e9525765a025525c7105a7e47b6db012102c01e11b1d331f999bbdb83e8831de503cd52a01e3834a95ccafd615c67703d77ffffffff9e52447116415ca0d0567418a1a4ef8f27be3ff5a96bf87c922f3723d7db5d7c000000006b483045022100f6c117e536701be41a6b0b544d7c3b1091301e4e64a6265b6eb167b15d16959d022076916de4b115e700964194ce36a24cb9105f86482f4abbc63110c3f537cd5770012102ddf84cc7bee2d6a82ac09628a8ad4a26cd449fc528b81e7e6cc615707b8169dfffffffff5815d9750eb3572e30d6fd9df7afb4dbd76e042f3aa4988ac763b3fdf8397f80010000006a473044022028f4402b736066d93d2a32b28ccd3b7a21d84bb58fcd07fe392a611db94cdec5022018902ee0bf2c3c840c1b81ead4e6c87c88c48b2005bf5eea796464e561a620a8012102b6cdd1a6cd129ef796faeedb0b840fcd0ca00c57e16e38e46ee7028d59812ae7ffffffff0220a10700000000001976a914c342bcd1a7784d9842f7386b8b3b8a3d4171a06e88ac59611100000000001976a91449f8c749a9960dc29b5cbe7d2397cea7d26611bb88ac00000000'
describe('Mempool', function() {
it('should encode mempool transaction key', function() {
encoding.encodeMempoolTransactionKey(hash).should.deep.equal(Buffer.concat([ servicePrefix, new Buffer(hash, 'hex') ]));
});
it('should decode mempool transaction key', function() {
encoding.decodeMempoolTransactionKey(Buffer.concat([ servicePrefix, new Buffer(hash, 'hex') ])).should.deep.equal(hash);
});
it('should encode mempool transaction value', function() {
var mytx = tx.fromRaw(txString, 'hex');
mytx.__inputValues = [1012955, 447698, 446664, 391348];
encoding.encodeMempoolTransactionValue(mytx).should.deep.equal(new Buffer(txString, 'hex'));
});
it('should decode mempool transaction value', function() {
var mytx = encoding.decodeMempoolTransactionValue(new Buffer(txString, 'hex'));
mytx.should.deep.equal(tx.fromRaw(txString, 'hex'));
});
});
});

View File

@ -0,0 +1,50 @@
'use strict';
var expect = require('chai').expect;
var MempoolService = require('../../../lib/services/mempool');
var sinon = require('sinon');
var Encoding = require('../../../lib/services/block/encoding');
var Tx = require('bcoin').tx;
describe('Mempool Service', function() {
var mempoolService;
var sandbox;
var tx = Tx.fromRaw( '0100000004de9b4bb17f627096a9ee0b4528e4eae17df5b5c69edc29704c2e84a7371db29f010000006b483045022100f5b1a0d33b7be291c3953c25f8ae39d98601aa7099a8674daf638a08b86c7173022006ce372da5ad088a1cc6e5c49c2760a1b6f085eb1b51b502211b6bc9508661f9012102ec5e3731e54475dd2902326f43602a03ae3d62753324139163f81f20e787514cffffffff7a1d4e5fc2b8177ec738cd723a16cf2bf493791e55573445fc0df630fe5e2d64010000006b483045022100cf97f6cb8f126703e9768545dfb20ffb10ba78ae3d101aa46775f5a239b075fc02203150c4a89a11eaf5e404f4f96b62efa4455e9525765a025525c7105a7e47b6db012102c01e11b1d331f999bbdb83e8831de503cd52a01e3834a95ccafd615c67703d77ffffffff9e52447116415ca0d0567418a1a4ef8f27be3ff5a96bf87c922f3723d7db5d7c000000006b483045022100f6c117e536701be41a6b0b544d7c3b1091301e4e64a6265b6eb167b15d16959d022076916de4b115e700964194ce36a24cb9105f86482f4abbc63110c3f537cd5770012102ddf84cc7bee2d6a82ac09628a8ad4a26cd449fc528b81e7e6cc615707b8169dfffffffff5815d9750eb3572e30d6fd9df7afb4dbd76e042f3aa4988ac763b3fdf8397f80010000006a473044022028f4402b736066d93d2a32b28ccd3b7a21d84bb58fcd07fe392a611db94cdec5022018902ee0bf2c3c840c1b81ead4e6c87c88c48b2005bf5eea796464e561a620a8012102b6cdd1a6cd129ef796faeedb0b840fcd0ca00c57e16e38e46ee7028d59812ae7ffffffff0220a10700000000001976a914c342bcd1a7784d9842f7386b8b3b8a3d4171a06e88ac59611100000000001976a91449f8c749a9960dc29b5cbe7d2397cea7d26611bb88ac00000000', 'hex');
beforeEach(function() {
sandbox = sinon.sandbox.create();
mempoolService = new MempoolService({
node: {
getNetworkName: function() { return 'regtest'; },
services: []
}
});
mempoolService._encoding = new Encoding(new Buffer('0000', 'hex'));
});
afterEach(function() {
sandbox.restore();
});
describe('#start', function() {
it('should get the db prefix', function(done) {
var getPrefix = sandbox.stub().callsArgWith(1, null, new Buffer('0001', 'hex'));
var startSubs = sandbox.stub(mempoolService, '_startSubscriptions');
mempoolService._db = { getPrefix: getPrefix };
mempoolService.start(function() {
expect(getPrefix.calledOnce).to.be.true;
expect(startSubs.calledOnce).to.be.true;
done();
});
});
describe('#getTransaction', function(done) {
var get = sandbox.stub().callsArgWith(1, null, tx.toJSON());
});
});
});