Merge pull request #352 from braydonf/mem
Optimized address service mempool index size
This commit is contained in:
commit
02620a5b47
|
@ -101,7 +101,11 @@ node.services.bitcoind.on('tip', function(blockHash) {
|
||||||
});
|
});
|
||||||
|
|
||||||
node.services.bitcoind.on('tx', function(txInfo) {
|
node.services.bitcoind.on('tx', function(txInfo) {
|
||||||
// a new transaction has been broadcast in the network
|
// a new transaction has entered the mempool
|
||||||
|
});
|
||||||
|
|
||||||
|
node.services.bitcoind.on('txleave', function(txLeaveInfo) {
|
||||||
|
// a new transaction has left the mempool
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -110,7 +114,16 @@ The `txInfo` object will have the format:
|
||||||
```js
|
```js
|
||||||
{
|
{
|
||||||
buffer: <Buffer...>,
|
buffer: <Buffer...>,
|
||||||
mempool: true,
|
mempool: true, // will currently always be true
|
||||||
|
hash: '7426c707d0e9705bdd8158e60983e37d0f5d63529086d6672b07d9238d5aa623'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `txLeaveInfo` object will have the format:
|
||||||
|
|
||||||
|
```js
|
||||||
|
{
|
||||||
|
buffer: <Buffer...>,
|
||||||
hash: '7426c707d0e9705bdd8158e60983e37d0f5d63529086d6672b07d9238d5aa623'
|
hash: '7426c707d0e9705bdd8158e60983e37d0f5d63529086d6672b07d9238d5aa623'
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
|
@ -19,16 +19,6 @@ CustomService.prototype.blockHandler = function(block, add, callback) {
|
||||||
|
|
||||||
Take a look at the Address Service implementation for more details about how to encode the key, value for the best efficiency and ways to format the keys for streaming reads.
|
Take a look at the Address Service implementation for more details about how to encode the key, value for the best efficiency and ways to format the keys for streaming reads.
|
||||||
|
|
||||||
Additionally the mempool can have an index, the mempool index will be updated once bitcoind and the db have both fully synced. A service can implement a `resetMempoolIndex` method that will be run during this time, and the "synced" event will wait until this task has been finished:
|
|
||||||
|
|
||||||
```js
|
|
||||||
CustomService.prototype.resetMempoolIndex = function(callback) {
|
|
||||||
var transactionBuffers = this.node.services.bitcoind.getMempoolTransactions();
|
|
||||||
// interact over the transactions asynchronously here
|
|
||||||
callback();
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
## API Documentation
|
## API Documentation
|
||||||
These methods are exposed over the JSON-RPC interface and can be called directly from a node via:
|
These methods are exposed over the JSON-RPC interface and can be called directly from a node via:
|
||||||
|
|
||||||
|
|
|
@ -367,14 +367,27 @@ index f94771a..72ee00e 100644
|
||||||
|
|
||||||
|
|
||||||
diff --git a/src/net.h b/src/net.h
|
diff --git a/src/net.h b/src/net.h
|
||||||
index 17502b9..e181d68 100644
|
index 17502b9..c9ae1b2 100644
|
||||||
--- a/src/net.h
|
--- a/src/net.h
|
||||||
+++ b/src/net.h
|
+++ b/src/net.h
|
||||||
@@ -99,6 +99,7 @@ struct CNodeSignals
|
@@ -99,6 +99,8 @@ struct CNodeSignals
|
||||||
{
|
{
|
||||||
boost::signals2::signal<int ()> GetHeight;
|
boost::signals2::signal<int ()> GetHeight;
|
||||||
boost::signals2::signal<bool (CNode*), CombinerAll> ProcessMessages;
|
boost::signals2::signal<bool (CNode*), CombinerAll> ProcessMessages;
|
||||||
+ boost::signals2::signal<bool (const CTransaction&)> TxToMemPool;
|
+ boost::signals2::signal<bool (const CTransaction&)> TxToMemPool;
|
||||||
|
+ boost::signals2::signal<bool (const CTransaction&)> TxLeaveMemPool;
|
||||||
boost::signals2::signal<bool (CNode*, bool), CombinerAll> SendMessages;
|
boost::signals2::signal<bool (CNode*, bool), CombinerAll> SendMessages;
|
||||||
boost::signals2::signal<void (NodeId, const CNode*)> InitializeNode;
|
boost::signals2::signal<void (NodeId, const CNode*)> InitializeNode;
|
||||||
boost::signals2::signal<void (NodeId)> FinalizeNode;
|
boost::signals2::signal<void (NodeId)> FinalizeNode;
|
||||||
|
diff --git a/src/txmempool.cpp b/src/txmempool.cpp
|
||||||
|
index c3d1b60..03e265d 100644
|
||||||
|
--- a/src/txmempool.cpp
|
||||||
|
+++ b/src/txmempool.cpp
|
||||||
|
@@ -133,6 +133,7 @@ void CTxMemPool::remove(const CTransaction &origTx, std::list<CTransaction>& rem
|
||||||
|
if (!mapTx.count(hash))
|
||||||
|
continue;
|
||||||
|
const CTransaction& tx = mapTx[hash].GetTx();
|
||||||
|
+ GetNodeSignals().TxLeaveMemPool(tx);
|
||||||
|
if (fRecursive) {
|
||||||
|
for (unsigned int i = 0; i < tx.vout.size(); i++) {
|
||||||
|
std::map<COutPoint, CInPoint>::iterator it = mapNextTx.find(COutPoint(hash, i));
|
||||||
|
|
|
@ -726,12 +726,15 @@ describe('Node Functionality', function() {
|
||||||
node.services.bitcoind.sendTransaction(tx.serialize());
|
node.services.bitcoind.sendTransaction(tx.serialize());
|
||||||
|
|
||||||
setImmediate(function() {
|
setImmediate(function() {
|
||||||
var length = node.services.address.mempoolOutputIndex[address].length;
|
var hashBuffer = bitcore.Address(address).hashBuffer;
|
||||||
length.should.equal(1);
|
node.services.address._getOutputsMempool(address, hashBuffer, function(err, outs) {
|
||||||
should.exist(node.services.address.mempoolOutputIndex[address]);
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
outs.length.should.equal(1);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -360,6 +360,30 @@ describe('Daemon Binding Functionality', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('transactions leaving the mempool', function() {
|
||||||
|
it('receive event when transaction leaves', function(done) {
|
||||||
|
|
||||||
|
// add transaction to build a new block
|
||||||
|
var tx = bitcore.Transaction();
|
||||||
|
tx.from(utxos[4]);
|
||||||
|
tx.change(privateKey.toAddress());
|
||||||
|
tx.to(destKey.toAddress(), utxos[4].amount * 1e8 - 1000);
|
||||||
|
tx.sign(bitcore.PrivateKey.fromWIF(utxos[4].privateKeyWIF));
|
||||||
|
bitcoind.sendTransaction(tx.serialize());
|
||||||
|
|
||||||
|
bitcoind.once('txleave', function(txInfo) {
|
||||||
|
txInfo.hash.should.equal(tx.hash);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
client.generate(1, function(err, response) {
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('mempool functionality', function() {
|
describe('mempool functionality', function() {
|
||||||
|
|
||||||
var fromAddress = 'mszYqVnqKoQx4jcTdJXxwKAissE3Jbrrc1';
|
var fromAddress = 'mszYqVnqKoQx4jcTdJXxwKAissE3Jbrrc1';
|
||||||
|
|
|
@ -1,14 +1,18 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
var fs = require('fs');
|
||||||
var BaseService = require('../../service');
|
var BaseService = require('../../service');
|
||||||
var inherits = require('util').inherits;
|
var inherits = require('util').inherits;
|
||||||
var async = require('async');
|
var async = require('async');
|
||||||
|
var mkdirp = require('mkdirp');
|
||||||
var index = require('../../');
|
var index = require('../../');
|
||||||
var log = index.log;
|
var log = index.log;
|
||||||
var errors = index.errors;
|
var errors = index.errors;
|
||||||
var Transaction = require('../../transaction');
|
|
||||||
var bitcore = require('bitcore-lib');
|
var bitcore = require('bitcore-lib');
|
||||||
|
var Networks = bitcore.Networks;
|
||||||
var levelup = require('levelup');
|
var levelup = require('levelup');
|
||||||
|
var leveldown = require('leveldown');
|
||||||
|
var memdown = require('memdown');
|
||||||
var $ = bitcore.util.preconditions;
|
var $ = bitcore.util.preconditions;
|
||||||
var _ = bitcore.deps._;
|
var _ = bitcore.deps._;
|
||||||
var Hash = bitcore.crypto.Hash;
|
var Hash = bitcore.crypto.Hash;
|
||||||
|
@ -35,11 +39,16 @@ var AddressService = function(options) {
|
||||||
this.subscriptions['address/balance'] = {};
|
this.subscriptions['address/balance'] = {};
|
||||||
|
|
||||||
this.node.services.bitcoind.on('tx', this.transactionHandler.bind(this));
|
this.node.services.bitcoind.on('tx', this.transactionHandler.bind(this));
|
||||||
|
this.node.services.bitcoind.on('txleave', this.transactionLeaveHandler.bind(this));
|
||||||
|
|
||||||
this.mempoolOutputIndex = {};
|
this._setMempoolIndexPath();
|
||||||
this.mempoolInputIndex = {};
|
if (options.mempoolMemoryIndex) {
|
||||||
this.mempoolSpentIndex = {};
|
this.levelupStore = memdown;
|
||||||
|
} else {
|
||||||
|
this.levelupStore = leveldown;
|
||||||
|
}
|
||||||
|
this.mempoolIndex = null; // Used for larger mempool indexes
|
||||||
|
this.mempoolSpentIndex = {}; // Used for small quick synchronous lookups
|
||||||
};
|
};
|
||||||
|
|
||||||
inherits(AddressService, BaseService);
|
inherits(AddressService, BaseService);
|
||||||
|
@ -55,9 +64,73 @@ AddressService.PREFIXES = {
|
||||||
SPENTSMAP: new Buffer('05', 'hex') // Get the input that spends an output
|
SPENTSMAP: new Buffer('05', 'hex') // Get the input that spends an output
|
||||||
};
|
};
|
||||||
|
|
||||||
|
AddressService.MEMPREFIXES = {
|
||||||
|
OUTPUTS: new Buffer('01', 'hex'), // Query mempool outputs by address
|
||||||
|
SPENTS: new Buffer('02', 'hex'), // Query mempool inputs by address
|
||||||
|
SPENTSMAP: new Buffer('03', 'hex') // Query mempool for the input that spends an output
|
||||||
|
};
|
||||||
|
|
||||||
AddressService.SPACER_MIN = new Buffer('00', 'hex');
|
AddressService.SPACER_MIN = new Buffer('00', 'hex');
|
||||||
AddressService.SPACER_MAX = new Buffer('ff', 'hex');
|
AddressService.SPACER_MAX = new Buffer('ff', 'hex');
|
||||||
|
|
||||||
|
AddressService.prototype.start = function(callback) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
async.series([
|
||||||
|
function(next) {
|
||||||
|
// Flush any existing mempool index
|
||||||
|
if (fs.existsSync(self.mempoolIndexPath)) {
|
||||||
|
leveldown.destroy(self.mempoolIndexPath, next);
|
||||||
|
} else {
|
||||||
|
setImmediate(next);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
function(next) {
|
||||||
|
if (!fs.existsSync(self.mempoolIndexPath)) {
|
||||||
|
mkdirp(self.mempoolIndexPath, next);
|
||||||
|
} else {
|
||||||
|
setImmediate(next);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
function(next) {
|
||||||
|
self.mempoolIndex = levelup(
|
||||||
|
self.mempoolIndexPath,
|
||||||
|
{
|
||||||
|
db: self.levelupStore,
|
||||||
|
keyEncoding: 'binary',
|
||||||
|
valueEncoding: 'binary',
|
||||||
|
fillCache: false
|
||||||
|
},
|
||||||
|
next
|
||||||
|
);
|
||||||
|
}
|
||||||
|
], callback);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
AddressService.prototype.stop = function(callback) {
|
||||||
|
// TODO Keep track of ongoing db requests before shutting down
|
||||||
|
this.mempoolIndex.close(callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function will set `this.dataPath` based on `this.node.network`.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
AddressService.prototype._setMempoolIndexPath = function() {
|
||||||
|
$.checkState(this.node.datadir, 'Node is expected to have a "datadir" property');
|
||||||
|
var regtest = Networks.get('regtest');
|
||||||
|
if (this.node.network === Networks.livenet) {
|
||||||
|
this.mempoolIndexPath = this.node.datadir + '/bitcore-addressmempool.db';
|
||||||
|
} else if (this.node.network === Networks.testnet) {
|
||||||
|
this.mempoolIndexPath = this.node.datadir + '/testnet3/bitcore-addressmempool.db';
|
||||||
|
} else if (this.node.network === regtest) {
|
||||||
|
this.mempoolIndexPath = this.node.datadir + '/regtest/bitcore-addressmempool.db';
|
||||||
|
} else {
|
||||||
|
throw new Error('Unknown network: ' + this.network);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called by the Node to get the available API methods for this service,
|
* Called by the Node to get the available API methods for this service,
|
||||||
* that can be exposed over the JSON-RPC interface.
|
* that can be exposed over the JSON-RPC interface.
|
||||||
|
@ -131,6 +204,17 @@ AddressService.prototype.transactionOutputHandler = function(messages, tx, outpu
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This will handle data from the daemon "txleave" that a transaction has left the mempool.
|
||||||
|
* @param {Object} txInfo - The data from the daemon.on('txleave') event
|
||||||
|
* @param {Buffer} txInfo.buffer - The transaction buffer
|
||||||
|
* @param {String} txInfo.hash - The hash of the transaction
|
||||||
|
*/
|
||||||
|
AddressService.prototype.transactionLeaveHandler = function(txInfo) {
|
||||||
|
var tx = bitcore.Transaction().fromBuffer(txInfo.buffer);
|
||||||
|
this.updateMempoolIndex(tx, false);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This will handle data from the daemon "tx" event, go through each of the outputs
|
* This will handle data from the daemon "tx" event, go through each of the outputs
|
||||||
* and send messages by calling `transactionEventHandler` to any subscribers for a
|
* and send messages by calling `transactionEventHandler` to any subscribers for a
|
||||||
|
@ -139,8 +223,10 @@ AddressService.prototype.transactionOutputHandler = function(messages, tx, outpu
|
||||||
* @param {Buffer} txInfo.buffer - The transaction buffer
|
* @param {Buffer} txInfo.buffer - The transaction buffer
|
||||||
* @param {Boolean} txInfo.mempool - If the transaction was accepted in the mempool
|
* @param {Boolean} txInfo.mempool - If the transaction was accepted in the mempool
|
||||||
* @param {String} txInfo.hash - The hash of the transaction
|
* @param {String} txInfo.hash - The hash of the transaction
|
||||||
|
* @param {Function} [callback] - Optional callback
|
||||||
*/
|
*/
|
||||||
AddressService.prototype.transactionHandler = function(txInfo) {
|
AddressService.prototype.transactionHandler = function(txInfo, callback) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
// Basic transaction format is handled by the daemon
|
// Basic transaction format is handled by the daemon
|
||||||
// and we can safely assume the buffer is properly formatted.
|
// and we can safely assume the buffer is properly formatted.
|
||||||
|
@ -153,39 +239,48 @@ AddressService.prototype.transactionHandler = function(txInfo) {
|
||||||
this.transactionOutputHandler(messages, tx, i, !txInfo.mempool);
|
this.transactionOutputHandler(messages, tx, i, !txInfo.mempool);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update mempool index
|
if (!callback) {
|
||||||
if (txInfo.mempool) {
|
callback = function(err) {
|
||||||
this.updateMempoolIndex(tx);
|
if (err) {
|
||||||
|
return log.error(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var key in messages) {
|
function finish(err) {
|
||||||
this.transactionEventHandler(messages[key]);
|
if (err) {
|
||||||
this.balanceEventHandler(null, messages[key].addressInfo);
|
return callback(err);
|
||||||
}
|
}
|
||||||
|
for (var key in messages) {
|
||||||
|
self.transactionEventHandler(messages[key]);
|
||||||
|
self.balanceEventHandler(null, messages[key].addressInfo);
|
||||||
|
}
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (txInfo.mempool) {
|
||||||
|
self.updateMempoolIndex(tx, true, finish);
|
||||||
|
} else {
|
||||||
|
setImmediate(finish);
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function will update the mempool address index with the necessary
|
* This function will update the mempool address index with the necessary
|
||||||
* information for further lookups. There are three indexes:
|
* information for further lookups.
|
||||||
*
|
|
||||||
* mempoolOutputIndex, an object keyed by base58check encoded addresses with values:
|
|
||||||
* txid - A hex string of the transaction hash
|
|
||||||
* outputIndex - A number of the corresponding output
|
|
||||||
* satoshis - Total number of satoshis
|
|
||||||
* script - The script as a hex string
|
|
||||||
*
|
|
||||||
* mempoolInputIndex, an object keyed by base58check encoded addresses with values:
|
|
||||||
* txid - A hex string of the transaction hash
|
|
||||||
* inputIndex - A number of the corresponding input
|
|
||||||
*
|
|
||||||
* mempoolSpentIndex, an object keyed by <prevTxId>-<outputIndex> with (buffer) values:
|
|
||||||
* inputTxId - A 32 byte buffer of the input txid
|
|
||||||
* inputIndex - 4 bytes stored as UInt32BE
|
|
||||||
*
|
|
||||||
* @param {Transaction} - An instance of a Bitcore Transaction
|
* @param {Transaction} - An instance of a Bitcore Transaction
|
||||||
|
* @param {Boolean} - Add/remove from the index
|
||||||
*/
|
*/
|
||||||
AddressService.prototype.updateMempoolIndex = function(tx) {
|
AddressService.prototype.updateMempoolIndex = function(tx, add, callback) {
|
||||||
/* jshint maxstatements: 30 */
|
/* jshint maxstatements: 100 */
|
||||||
|
|
||||||
|
var operations = [];
|
||||||
|
|
||||||
|
var action = 'put';
|
||||||
|
if (!add) {
|
||||||
|
action = 'del';
|
||||||
|
}
|
||||||
|
|
||||||
var txid = tx.hash;
|
var txid = tx.hash;
|
||||||
var txidBuffer = new Buffer(txid, 'hex');
|
var txidBuffer = new Buffer(txid, 'hex');
|
||||||
|
@ -201,21 +296,23 @@ AddressService.prototype.updateMempoolIndex = function(tx) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var addressStr = bitcore.Address({
|
// Update output index
|
||||||
hashBuffer: addressInfo.hashBuffer,
|
var outputIndexBuffer = new Buffer(4);
|
||||||
type: addressInfo.addressType,
|
outputIndexBuffer.writeUInt32BE(outputIndex);
|
||||||
network: this.node.network
|
|
||||||
}).toString();
|
|
||||||
|
|
||||||
if (!this.mempoolOutputIndex[addressStr]) {
|
var outKey = Buffer.concat([
|
||||||
this.mempoolOutputIndex[addressStr] = [];
|
AddressService.MEMPREFIXES.OUTPUTS,
|
||||||
}
|
addressInfo.hashBuffer,
|
||||||
|
txidBuffer,
|
||||||
|
outputIndexBuffer
|
||||||
|
]);
|
||||||
|
|
||||||
this.mempoolOutputIndex[addressStr].push({
|
var outValue = this._encodeOutputValue(output.satoshis, output._scriptBuffer);
|
||||||
txid: txid,
|
|
||||||
outputIndex: outputIndex,
|
operations.push({
|
||||||
satoshis: output.satoshis,
|
type: action,
|
||||||
script: output._scriptBuffer.toString('hex') //TODO use a buffer
|
key: outKey,
|
||||||
|
value: outValue
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -224,55 +321,75 @@ AddressService.prototype.updateMempoolIndex = function(tx) {
|
||||||
|
|
||||||
var input = tx.inputs[inputIndex];
|
var input = tx.inputs[inputIndex];
|
||||||
|
|
||||||
// Update spent index
|
var inputOutputIndexBuffer = new Buffer(4);
|
||||||
var spentIndexKey = [input.prevTxId.toString('hex'), input.outputIndex].join('-');
|
inputOutputIndexBuffer.writeUInt32BE(input.outputIndex);
|
||||||
|
|
||||||
|
// Add an additional small spent index for fast synchronous lookups
|
||||||
|
var spentIndexSyncKey = this._encodeSpentIndexSyncKey(
|
||||||
|
input.prevTxId,
|
||||||
|
input.outputIndex
|
||||||
|
);
|
||||||
|
if (add) {
|
||||||
|
this.mempoolSpentIndex[spentIndexSyncKey] = true;
|
||||||
|
} else {
|
||||||
|
delete this.mempoolSpentIndex[spentIndexSyncKey];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a more detailed spent index with values
|
||||||
|
var spentIndexKey = Buffer.concat([
|
||||||
|
AddressService.MEMPREFIXES.SPENTSMAP,
|
||||||
|
input.prevTxId,
|
||||||
|
inputOutputIndexBuffer
|
||||||
|
]);
|
||||||
var inputIndexBuffer = new Buffer(4);
|
var inputIndexBuffer = new Buffer(4);
|
||||||
inputIndexBuffer.writeUInt32BE(inputIndex);
|
inputIndexBuffer.writeUInt32BE(inputIndex);
|
||||||
var inputIndexValue = Buffer.concat([
|
var inputIndexValue = Buffer.concat([
|
||||||
txidBuffer,
|
txidBuffer,
|
||||||
inputIndexBuffer
|
inputIndexBuffer
|
||||||
]);
|
]);
|
||||||
this.mempoolSpentIndex[spentIndexKey] = inputIndexValue;
|
operations.push({
|
||||||
|
type: action,
|
||||||
|
key: spentIndexKey,
|
||||||
|
value: inputIndexValue
|
||||||
|
});
|
||||||
|
|
||||||
var address = input.script.toAddress(this.node.network);
|
// Update input index
|
||||||
if (!address) {
|
var inputHashBuffer;
|
||||||
|
if (input.script.isPublicKeyHashIn()) {
|
||||||
|
inputHashBuffer = Hash.sha256ripemd160(input.script.chunks[1].buf);
|
||||||
|
} else if (input.script.isScriptHashIn()) {
|
||||||
|
inputHashBuffer = Hash.sha256ripemd160(input.script.chunks[input.script.chunks.length - 1].buf);
|
||||||
|
} else {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
var addressStr = address.toString();
|
var inputKey = Buffer.concat([
|
||||||
if (!this.mempoolInputIndex[addressStr]) {
|
AddressService.MEMPREFIXES.SPENTS,
|
||||||
this.mempoolInputIndex[addressStr] = [];
|
inputHashBuffer,
|
||||||
}
|
input.prevTxId,
|
||||||
this.mempoolInputIndex[addressStr].push({
|
inputOutputIndexBuffer
|
||||||
txid: tx.hash, // TODO use buffer
|
]);
|
||||||
inputIndex: inputIndex
|
var inputValue = Buffer.concat([
|
||||||
|
txidBuffer,
|
||||||
|
inputIndexBuffer
|
||||||
|
]);
|
||||||
|
operations.push({
|
||||||
|
type: action,
|
||||||
|
key: inputKey,
|
||||||
|
value: inputValue
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
if (!callback) {
|
||||||
|
callback = function(err) {
|
||||||
/**
|
|
||||||
* This function is called by the Database Service when the database itself
|
|
||||||
* has finished synchronization. It will retrieve a copy of all transactions
|
|
||||||
* from the mempool and create an address index for fast look-ups. The previous
|
|
||||||
* index will be reset.
|
|
||||||
*/
|
|
||||||
AddressService.prototype.resetMempoolIndex = function(callback) {
|
|
||||||
var self = this;
|
|
||||||
var transactionBuffers = self.node.services.bitcoind.getMempoolTransactions();
|
|
||||||
this.mempoolInputIndex = {};
|
|
||||||
this.mempoolOutputIndex = {};
|
|
||||||
this.mempoolSpentIndex = {};
|
|
||||||
async.each(transactionBuffers, function(txBuffer, next) {
|
|
||||||
var tx = Transaction().fromBuffer(txBuffer);
|
|
||||||
self.updateMempoolIndex(tx);
|
|
||||||
setImmediate(next);
|
|
||||||
}, function(err) {
|
|
||||||
if (err) {
|
if (err) {
|
||||||
return callback(err);
|
return log.error(err);
|
||||||
}
|
}
|
||||||
callback();
|
};
|
||||||
});
|
}
|
||||||
|
|
||||||
|
this.mempoolIndex.batch(operations, callback);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -436,6 +553,16 @@ AddressService.prototype.blockHandler = function(block, addOutput, callback) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
AddressService.prototype._encodeSpentIndexSyncKey = function(txidBuffer, outputIndex) {
|
||||||
|
var outputIndexBuffer = new Buffer(4);
|
||||||
|
outputIndexBuffer.writeUInt32BE(outputIndex);
|
||||||
|
var key = Buffer.concat([
|
||||||
|
txidBuffer,
|
||||||
|
outputIndexBuffer
|
||||||
|
]);
|
||||||
|
return key.toString('binary');
|
||||||
|
};
|
||||||
|
|
||||||
AddressService.prototype._encodeOutputKey = function(hashBuffer, height, txidBuffer, outputIndex) {
|
AddressService.prototype._encodeOutputKey = function(hashBuffer, height, txidBuffer, outputIndex) {
|
||||||
var heightBuffer = new Buffer(4);
|
var heightBuffer = new Buffer(4);
|
||||||
heightBuffer.writeUInt32BE(height);
|
heightBuffer.writeUInt32BE(height);
|
||||||
|
@ -748,15 +875,9 @@ AddressService.prototype.getInputForOutput = function(txid, outputIndex, options
|
||||||
txidBuffer = new Buffer(txid, 'hex');
|
txidBuffer = new Buffer(txid, 'hex');
|
||||||
}
|
}
|
||||||
if (options.queryMempool) {
|
if (options.queryMempool) {
|
||||||
var spentIndexKey = [txid.toString('hex'), outputIndex].join('-');
|
var spentIndexSyncKey = this._encodeSpentIndexSyncKey(txidBuffer, outputIndex);
|
||||||
if (this.mempoolSpentIndex[spentIndexKey]) {
|
if (this.mempoolSpentIndex[spentIndexSyncKey]) {
|
||||||
var mempoolValue = this.mempoolSpentIndex[spentIndexKey];
|
return this._getSpentMempool(txidBuffer, outputIndex, callback);
|
||||||
var inputTxId = mempoolValue.slice(0, 32);
|
|
||||||
var inputIndex = mempoolValue.readUInt32BE(32);
|
|
||||||
return callback(null, {
|
|
||||||
inputTxId: inputTxId.toString('hex'),
|
|
||||||
inputIndex: inputIndex
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var key = this._encodeInputKeyMap(txidBuffer, outputIndex);
|
var key = this._encodeInputKeyMap(txidBuffer, outputIndex);
|
||||||
|
@ -867,19 +988,16 @@ AddressService.prototype.getInputs = function(addressStr, options, callback) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if(options.queryMempool) {
|
if(options.queryMempool) {
|
||||||
var mempoolInputs = self.mempoolInputIndex[addressStr];
|
self._getInputsMempool(addressStr, hashBuffer, function(err, mempoolInputs) {
|
||||||
if (mempoolInputs) {
|
if (err) {
|
||||||
for(var i = 0; i < mempoolInputs.length; i++) {
|
return callback(err);
|
||||||
var newInput = _.clone(mempoolInputs[i]);
|
|
||||||
newInput.address = addressStr;
|
|
||||||
newInput.height = -1;
|
|
||||||
newInput.confirmations = 0;
|
|
||||||
inputs.push(newInput);
|
|
||||||
}
|
}
|
||||||
}
|
inputs = inputs.concat(mempoolInputs);
|
||||||
}
|
|
||||||
|
|
||||||
callback(null, inputs);
|
callback(null, inputs);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
callback(null, inputs);
|
||||||
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -887,6 +1005,80 @@ AddressService.prototype.getInputs = function(addressStr, options, callback) {
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
AddressService.prototype._getInputsMempool = function(addressStr, hashBuffer, callback) {
|
||||||
|
var self = this;
|
||||||
|
var mempoolInputs = [];
|
||||||
|
|
||||||
|
var stream = self.mempoolIndex.createReadStream({
|
||||||
|
gte: Buffer.concat([
|
||||||
|
AddressService.MEMPREFIXES.SPENTS,
|
||||||
|
hashBuffer,
|
||||||
|
AddressService.SPACER_MIN
|
||||||
|
]),
|
||||||
|
lte: Buffer.concat([
|
||||||
|
AddressService.MEMPREFIXES.SPENTS,
|
||||||
|
hashBuffer,
|
||||||
|
AddressService.SPACER_MAX
|
||||||
|
]),
|
||||||
|
valueEncoding: 'binary',
|
||||||
|
keyEncoding: 'binary'
|
||||||
|
});
|
||||||
|
|
||||||
|
stream.on('data', function(data) {
|
||||||
|
var txid = data.value.slice(0, 32);
|
||||||
|
var inputIndex = data.value.readUInt32BE(32);
|
||||||
|
var output = {
|
||||||
|
address: addressStr,
|
||||||
|
txid: txid.toString('hex'), //TODO use a buffer
|
||||||
|
inputIndex: inputIndex,
|
||||||
|
height: -1,
|
||||||
|
confirmations: 0
|
||||||
|
};
|
||||||
|
mempoolInputs.push(output);
|
||||||
|
});
|
||||||
|
|
||||||
|
var error;
|
||||||
|
|
||||||
|
stream.on('error', function(streamError) {
|
||||||
|
if (streamError) {
|
||||||
|
error = streamError;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
stream.on('close', function() {
|
||||||
|
if (error) {
|
||||||
|
return callback(error);
|
||||||
|
}
|
||||||
|
callback(null, mempoolInputs);
|
||||||
|
});
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
AddressService.prototype._getSpentMempool = function(txidBuffer, outputIndex, callback) {
|
||||||
|
var outputIndexBuffer = new Buffer(4);
|
||||||
|
outputIndexBuffer.writeUInt32BE(outputIndex);
|
||||||
|
var spentIndexKey = Buffer.concat([
|
||||||
|
AddressService.MEMPREFIXES.SPENTSMAP,
|
||||||
|
txidBuffer,
|
||||||
|
outputIndexBuffer
|
||||||
|
]);
|
||||||
|
|
||||||
|
this.mempoolIndex.get(
|
||||||
|
spentIndexKey,
|
||||||
|
function(err, mempoolValue) {
|
||||||
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
var inputTxId = mempoolValue.slice(0, 32);
|
||||||
|
var inputIndex = mempoolValue.readUInt32BE(32);
|
||||||
|
callback(null, {
|
||||||
|
inputTxId: inputTxId.toString('hex'),
|
||||||
|
inputIndex: inputIndex
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Will give outputs for an address as an object with:
|
* Will give outputs for an address as an object with:
|
||||||
* address - The base58check encoded address
|
* address - The base58check encoded address
|
||||||
|
@ -980,24 +1172,75 @@ AddressService.prototype.getOutputs = function(addressStr, options, callback) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if(options.queryMempool) {
|
if(options.queryMempool) {
|
||||||
var mempoolOutputs = self.mempoolOutputIndex[addressStr];
|
self._getOutputsMempool(addressStr, hashBuffer, function(err, mempoolOutputs) {
|
||||||
if (mempoolOutputs) {
|
if (err) {
|
||||||
for(var i = 0; i < mempoolOutputs.length; i++) {
|
return callback(err);
|
||||||
var newOutput = _.clone(mempoolOutputs[i]);
|
|
||||||
newOutput.address = addressStr;
|
|
||||||
newOutput.height = -1;
|
|
||||||
newOutput.confirmations = 0;
|
|
||||||
outputs.push(newOutput);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
outputs = outputs.concat(mempoolOutputs);
|
||||||
callback(null, outputs);
|
callback(null, outputs);
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
callback(null, outputs);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return stream;
|
return stream;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
AddressService.prototype._getOutputsMempool = function(addressStr, hashBuffer, callback) {
|
||||||
|
var self = this;
|
||||||
|
var mempoolOutputs = [];
|
||||||
|
|
||||||
|
var stream = self.mempoolIndex.createReadStream({
|
||||||
|
gte: Buffer.concat([
|
||||||
|
AddressService.MEMPREFIXES.OUTPUTS,
|
||||||
|
hashBuffer,
|
||||||
|
AddressService.SPACER_MIN
|
||||||
|
]),
|
||||||
|
lte: Buffer.concat([
|
||||||
|
AddressService.MEMPREFIXES.OUTPUTS,
|
||||||
|
hashBuffer,
|
||||||
|
AddressService.SPACER_MAX
|
||||||
|
]),
|
||||||
|
valueEncoding: 'binary',
|
||||||
|
keyEncoding: 'binary'
|
||||||
|
});
|
||||||
|
|
||||||
|
stream.on('data', function(data) {
|
||||||
|
// Format of data: prefix: 1, hashBuffer: 20, txid: 32, outputIndex: 4
|
||||||
|
var txid = data.key.slice(21, 53);
|
||||||
|
var outputIndex = data.key.readUInt32BE(53);
|
||||||
|
var value = self._decodeOutputValue(data.value);
|
||||||
|
var output = {
|
||||||
|
address: addressStr,
|
||||||
|
txid: txid.toString('hex'), //TODO use a buffer
|
||||||
|
outputIndex: outputIndex,
|
||||||
|
height: -1,
|
||||||
|
satoshis: value.satoshis,
|
||||||
|
script: value.scriptBuffer.toString('hex'), //TODO use a buffer
|
||||||
|
confirmations: 0
|
||||||
|
};
|
||||||
|
mempoolOutputs.push(output);
|
||||||
|
});
|
||||||
|
|
||||||
|
var error;
|
||||||
|
|
||||||
|
stream.on('error', function(streamError) {
|
||||||
|
if (streamError) {
|
||||||
|
error = streamError;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
stream.on('close', function() {
|
||||||
|
if (error) {
|
||||||
|
return callback(error);
|
||||||
|
}
|
||||||
|
callback(null, mempoolOutputs);
|
||||||
|
});
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Will give unspent outputs for an address or an array of addresses.
|
* Will give unspent outputs for an address or an array of addresses.
|
||||||
* @param {Array|String} addresses - An array of addresses
|
* @param {Array|String} addresses - An array of addresses
|
||||||
|
@ -1088,8 +1331,9 @@ AddressService.prototype.isSpent = function(output, options, callback) {
|
||||||
var txid = output.prevTxId ? output.prevTxId.toString('hex') : output.txid;
|
var txid = output.prevTxId ? output.prevTxId.toString('hex') : output.txid;
|
||||||
var spent = self.node.services.bitcoind.isSpent(txid, output.outputIndex);
|
var spent = self.node.services.bitcoind.isSpent(txid, output.outputIndex);
|
||||||
if (!spent && queryMempool) {
|
if (!spent && queryMempool) {
|
||||||
var spentIndexKey = [txid, output.outputIndex].join('-');
|
var txidBuffer = new Buffer(txid, 'hex');
|
||||||
spent = self.mempoolSpentIndex[spentIndexKey] ? true : false;
|
var spentIndexSyncKey = this._encodeSpentIndexSyncKey(txidBuffer, output.outputIndex);
|
||||||
|
spent = self.mempoolSpentIndex[spentIndexSyncKey] ? true : false;
|
||||||
}
|
}
|
||||||
setImmediate(function() {
|
setImmediate(function() {
|
||||||
// TODO error should be the first argument?
|
// TODO error should be the first argument?
|
||||||
|
@ -1097,6 +1341,7 @@ AddressService.prototype.isSpent = function(output, options, callback) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This will give the history for many addresses limited by a range of block heights (to limit
|
* This will give the history for many addresses limited by a range of block heights (to limit
|
||||||
* the database lookup times) and/or paginated to limit the results length.
|
* the database lookup times) and/or paginated to limit the results length.
|
||||||
|
@ -1195,8 +1440,11 @@ AddressService.prototype.getAddressSummary = function(address, options, callback
|
||||||
for(var i = 0; i < outputs.length; i++) {
|
for(var i = 0; i < outputs.length; i++) {
|
||||||
// Bitcoind's isSpent only works for confirmed transactions
|
// Bitcoind's isSpent only works for confirmed transactions
|
||||||
var spentDB = self.node.services.bitcoind.isSpent(outputs[i].txid, outputs[i].outputIndex);
|
var spentDB = self.node.services.bitcoind.isSpent(outputs[i].txid, outputs[i].outputIndex);
|
||||||
var spentIndexKey = [outputs[i].txid, outputs[i].outputIndex].join('-');
|
var spentIndexSyncKey = self._encodeSpentIndexSyncKey(
|
||||||
var spentMempool = self.mempoolSpentIndex[spentIndexKey];
|
new Buffer(outputs[i].txid, 'hex'), // TODO: get buffer directly
|
||||||
|
outputs[i].outputIndex
|
||||||
|
);
|
||||||
|
var spentMempool = self.mempoolSpentIndex[spentIndexSyncKey];
|
||||||
|
|
||||||
txids.push(outputs[i]);
|
txids.push(outputs[i]);
|
||||||
|
|
||||||
|
|
|
@ -110,12 +110,19 @@ Bitcoin.prototype._registerEventHandlers = function() {
|
||||||
// Set the height and emit a new tip
|
// Set the height and emit a new tip
|
||||||
bindings.onTipUpdate(self._onTipUpdate.bind(this));
|
bindings.onTipUpdate(self._onTipUpdate.bind(this));
|
||||||
|
|
||||||
// Register callback function to handle incoming transactions
|
// Register callback function to handle transactions entering the mempool
|
||||||
bindings.startTxMon(function(txs) {
|
bindings.startTxMon(function(txs) {
|
||||||
for(var i = 0; i < txs.length; i++) {
|
for(var i = 0; i < txs.length; i++) {
|
||||||
self.emit('tx', txs[i]);
|
self.emit('tx', txs[i]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Register callback function to handle transactions leaving the mempool
|
||||||
|
bindings.startTxMonLeave(function(txs) {
|
||||||
|
for(var i = 0; i < txs.length; i++) {
|
||||||
|
self.emit('txleave', txs[i]);
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
Bitcoin.prototype._onReady = function(result, callback) {
|
Bitcoin.prototype._onReady = function(result, callback) {
|
||||||
|
|
|
@ -454,24 +454,6 @@ DB.prototype.disconnectBlock = function(block, callback) {
|
||||||
this.runAllBlockHandlers(block, false, callback);
|
this.runAllBlockHandlers(block, false, callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Will run all `resetMempoolIndex` methods implemented on sibling
|
|
||||||
* services to update the mempool indexes.
|
|
||||||
*/
|
|
||||||
DB.prototype.runAllMempoolIndexes = function(callback) {
|
|
||||||
async.eachSeries(
|
|
||||||
this.node.services,
|
|
||||||
function(service, next) {
|
|
||||||
if (service.resetMempoolIndex) {
|
|
||||||
service.resetMempoolIndex(next);
|
|
||||||
} else {
|
|
||||||
setImmediate(next);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
callback
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Will collect all database operations for a block from other services that implement
|
* Will collect all database operations for a block from other services that implement
|
||||||
* `blockHandler` methods and then save operations to the database.
|
* `blockHandler` methods and then save operations to the database.
|
||||||
|
@ -745,15 +727,8 @@ DB.prototype.sync = function() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (self.node.services.bitcoind.isSynced()) {
|
if (self.node.services.bitcoind.isSynced()) {
|
||||||
self.runAllMempoolIndexes(function(err) {
|
|
||||||
if (err) {
|
|
||||||
Error.captureStackTrace(err);
|
|
||||||
return self.node.emit('error', err);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.bitcoindSyncing = false;
|
self.bitcoindSyncing = false;
|
||||||
self.node.emit('synced');
|
self.node.emit('synced');
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
self.bitcoindSyncing = false;
|
self.bitcoindSyncing = false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,6 +39,9 @@ extern int64_t nTimeBestReceived;
|
||||||
static void
|
static void
|
||||||
tx_notifier(uv_async_t *handle);
|
tx_notifier(uv_async_t *handle);
|
||||||
|
|
||||||
|
static void
|
||||||
|
txleave_notifier(uv_async_t *handle);
|
||||||
|
|
||||||
static void
|
static void
|
||||||
async_tip_update(uv_work_t *req);
|
async_tip_update(uv_work_t *req);
|
||||||
|
|
||||||
|
@ -90,6 +93,9 @@ async_get_tx_and_info_after(uv_work_t *req);
|
||||||
static bool
|
static bool
|
||||||
queueTx(const CTransaction&);
|
queueTx(const CTransaction&);
|
||||||
|
|
||||||
|
static bool
|
||||||
|
queueTxLeave(const CTransaction&);
|
||||||
|
|
||||||
extern "C" void
|
extern "C" void
|
||||||
init(Handle<Object>);
|
init(Handle<Object>);
|
||||||
|
|
||||||
|
@ -98,9 +104,13 @@ init(Handle<Object>);
|
||||||
* Used only by bitcoind functions.
|
* Used only by bitcoind functions.
|
||||||
*/
|
*/
|
||||||
static std::vector<CTransaction> txQueue;
|
static std::vector<CTransaction> txQueue;
|
||||||
|
static std::vector<CTransaction> txQueueLeave;
|
||||||
static uv_async_t txmon_async;
|
static uv_async_t txmon_async;
|
||||||
|
static uv_async_t txmonleave_async;
|
||||||
static Eternal<Function> txmon_callback;
|
static Eternal<Function> txmon_callback;
|
||||||
|
static Eternal<Function> txmonleave_callback;
|
||||||
static bool txmon_callback_available;
|
static bool txmon_callback_available;
|
||||||
|
static bool txmonleave_callback_available;
|
||||||
|
|
||||||
static volatile bool shutdown_complete = false;
|
static volatile bool shutdown_complete = false;
|
||||||
static char *g_data_dir = NULL;
|
static char *g_data_dir = NULL;
|
||||||
|
@ -259,6 +269,21 @@ NAN_METHOD(StartTxMon) {
|
||||||
info.GetReturnValue().Set(Null());
|
info.GetReturnValue().Set(Null());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
NAN_METHOD(StartTxMonLeave) {
|
||||||
|
Isolate* isolate = info.GetIsolate();
|
||||||
|
Local<Function> callback = Local<Function>::Cast(info[0]);
|
||||||
|
Eternal<Function> cb(isolate, callback);
|
||||||
|
txmonleave_callback = cb;
|
||||||
|
txmonleave_callback_available = true;
|
||||||
|
|
||||||
|
CNodeSignals& nodeSignals = GetNodeSignals();
|
||||||
|
nodeSignals.TxLeaveMemPool.connect(&queueTxLeave);
|
||||||
|
|
||||||
|
uv_async_init(uv_default_loop(), &txmonleave_async, txleave_notifier);
|
||||||
|
|
||||||
|
info.GetReturnValue().Set(Null());
|
||||||
|
};
|
||||||
|
|
||||||
static void
|
static void
|
||||||
tx_notifier(uv_async_t *handle) {
|
tx_notifier(uv_async_t *handle) {
|
||||||
Isolate* isolate = Isolate::GetCurrent();
|
Isolate* isolate = Isolate::GetCurrent();
|
||||||
|
@ -307,6 +332,53 @@ queueTx(const CTransaction& tx) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
txleave_notifier(uv_async_t *handle) {
|
||||||
|
Isolate* isolate = Isolate::GetCurrent();
|
||||||
|
HandleScope scope(isolate);
|
||||||
|
|
||||||
|
Local<Array> results = Array::New(isolate);
|
||||||
|
int arrayIndex = 0;
|
||||||
|
|
||||||
|
LOCK(cs_main);
|
||||||
|
BOOST_FOREACH(const CTransaction& tx, txQueueLeave) {
|
||||||
|
|
||||||
|
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
|
||||||
|
ssTx << tx;
|
||||||
|
std::string stx = ssTx.str();
|
||||||
|
Nan::MaybeLocal<v8::Object> txBuffer = Nan::CopyBuffer((char *)stx.c_str(), stx.size());
|
||||||
|
|
||||||
|
uint256 hash = tx.GetHash();
|
||||||
|
|
||||||
|
Local<Object> obj = New<Object>();
|
||||||
|
|
||||||
|
Nan::Set(obj, New("buffer").ToLocalChecked(), txBuffer.ToLocalChecked());
|
||||||
|
Nan::Set(obj, New("hash").ToLocalChecked(), New(hash.GetHex()).ToLocalChecked());
|
||||||
|
|
||||||
|
results->Set(arrayIndex, obj);
|
||||||
|
arrayIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
const unsigned argc = 1;
|
||||||
|
Local<Value> argv[argc] = {
|
||||||
|
Local<Value>::New(isolate, results)
|
||||||
|
};
|
||||||
|
|
||||||
|
Local<Function> cb = txmonleave_callback.Get(isolate);
|
||||||
|
|
||||||
|
cb->Call(isolate->GetCurrentContext()->Global(), argc, argv);
|
||||||
|
|
||||||
|
txQueueLeave.clear();
|
||||||
|
|
||||||
|
}
|
||||||
|
static bool
|
||||||
|
queueTxLeave(const CTransaction& tx) {
|
||||||
|
LOCK(cs_main);
|
||||||
|
txQueueLeave.push_back(tx);
|
||||||
|
uv_async_send(&txmonleave_async);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Functions
|
* Functions
|
||||||
*/
|
*/
|
||||||
|
@ -1527,6 +1599,7 @@ NAN_MODULE_INIT(init) {
|
||||||
Nan::Set(target, New("sendTransaction").ToLocalChecked(), GetFunction(New<FunctionTemplate>(SendTransaction)).ToLocalChecked());
|
Nan::Set(target, New("sendTransaction").ToLocalChecked(), GetFunction(New<FunctionTemplate>(SendTransaction)).ToLocalChecked());
|
||||||
Nan::Set(target, New("estimateFee").ToLocalChecked(), GetFunction(New<FunctionTemplate>(EstimateFee)).ToLocalChecked());
|
Nan::Set(target, New("estimateFee").ToLocalChecked(), GetFunction(New<FunctionTemplate>(EstimateFee)).ToLocalChecked());
|
||||||
Nan::Set(target, New("startTxMon").ToLocalChecked(), GetFunction(New<FunctionTemplate>(StartTxMon)).ToLocalChecked());
|
Nan::Set(target, New("startTxMon").ToLocalChecked(), GetFunction(New<FunctionTemplate>(StartTxMon)).ToLocalChecked());
|
||||||
|
Nan::Set(target, New("startTxMonLeave").ToLocalChecked(), GetFunction(New<FunctionTemplate>(StartTxMonLeave)).ToLocalChecked());
|
||||||
Nan::Set(target, New("syncPercentage").ToLocalChecked(), GetFunction(New<FunctionTemplate>(SyncPercentage)).ToLocalChecked());
|
Nan::Set(target, New("syncPercentage").ToLocalChecked(), GetFunction(New<FunctionTemplate>(SyncPercentage)).ToLocalChecked());
|
||||||
Nan::Set(target, New("isSynced").ToLocalChecked(), GetFunction(New<FunctionTemplate>(IsSynced)).ToLocalChecked());
|
Nan::Set(target, New("isSynced").ToLocalChecked(), GetFunction(New<FunctionTemplate>(IsSynced)).ToLocalChecked());
|
||||||
Nan::Set(target, New("getBestBlockHash").ToLocalChecked(), GetFunction(New<FunctionTemplate>(GetBestBlockHash)).ToLocalChecked());
|
Nan::Set(target, New("getBestBlockHash").ToLocalChecked(), GetFunction(New<FunctionTemplate>(GetBestBlockHash)).ToLocalChecked());
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -144,7 +144,8 @@ describe('Bitcoin Service', function() {
|
||||||
name.should.equal('bitcoind.node');
|
name.should.equal('bitcoind.node');
|
||||||
return {
|
return {
|
||||||
onTipUpdate: sinon.stub(),
|
onTipUpdate: sinon.stub(),
|
||||||
startTxMon: sinon.stub().callsArgWith(0, [transaction])
|
startTxMon: sinon.stub().callsArgWith(0, [transaction]),
|
||||||
|
startTxMonLeave: sinon.stub().callsArgWith(0, [transaction])
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -175,7 +176,8 @@ describe('Bitcoin Service', function() {
|
||||||
callback(height++);
|
callback(height++);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
startTxMon: sinon.stub()
|
startTxMon: sinon.stub(),
|
||||||
|
startTxMonLeave: sinon.stub()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -839,7 +839,6 @@ describe('DB Service', function() {
|
||||||
var blockBuffer = new Buffer(blockData, 'hex');
|
var blockBuffer = new Buffer(blockData, 'hex');
|
||||||
var block = Block.fromBuffer(blockBuffer);
|
var block = Block.fromBuffer(blockBuffer);
|
||||||
db.node.services = {};
|
db.node.services = {};
|
||||||
db.runAllMempoolIndexes = sinon.stub().callsArg(0);
|
|
||||||
db.node.services.bitcoind = {
|
db.node.services.bitcoind = {
|
||||||
getBlock: sinon.stub().callsArgWith(1, null, blockBuffer),
|
getBlock: sinon.stub().callsArgWith(1, null, blockBuffer),
|
||||||
isSynced: sinon.stub().returns(true),
|
isSynced: sinon.stub().returns(true),
|
||||||
|
@ -858,7 +857,6 @@ describe('DB Service', function() {
|
||||||
callback();
|
callback();
|
||||||
};
|
};
|
||||||
db.node.once('synced', function() {
|
db.node.once('synced', function() {
|
||||||
db.runAllMempoolIndexes.callCount.should.equal(1);
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
db.sync();
|
db.sync();
|
||||||
|
|
Loading…
Reference in New Issue