commit
e5a58f986c
|
@ -3,7 +3,7 @@ language: node_js
|
||||||
env:
|
env:
|
||||||
- BITCORENODE_ENV=test BITCORENODE_ASSUME_YES=true
|
- BITCORENODE_ENV=test BITCORENODE_ASSUME_YES=true
|
||||||
node_js:
|
node_js:
|
||||||
- "0.12"
|
- "v0.12.7"
|
||||||
before_install:
|
before_install:
|
||||||
- git config --global user.email "dev@bitpay.com"
|
- git config --global user.email "dev@bitpay.com"
|
||||||
- git config --global user.name "BitPay, Inc."
|
- git config --global user.name "BitPay, Inc."
|
||||||
|
@ -13,4 +13,4 @@ script:
|
||||||
- _mocha -R spec --recursive
|
- _mocha -R spec --recursive
|
||||||
cache:
|
cache:
|
||||||
directories:
|
directories:
|
||||||
- cache
|
- cache
|
||||||
|
|
|
@ -13,7 +13,7 @@ The bitcoin service adds a native interface to Bitcoin Core for querying informa
|
||||||
- `bitcoind.sendTransaction(transaction, allowAbsurdFees)` - Will attempt to add a transaction to the mempool and broadcast to peers.
|
- `bitcoind.sendTransaction(transaction, allowAbsurdFees)` - Will attempt to add a transaction to the mempool and broadcast to peers.
|
||||||
- `bitcoind.getTransaction(txid, queryMempool, callback)` - Get any tx asynchronously by reading it from disk, with an argument to optionally not include the mempool.
|
- `bitcoind.getTransaction(txid, queryMempool, callback)` - Get any tx asynchronously by reading it from disk, with an argument to optionally not include the mempool.
|
||||||
- `bitcoind.getTransactionWithBlockInfo(txid, queryMempool, callback)` - Similar to getTransaction but will also include the block timestamp and height.
|
- `bitcoind.getTransactionWithBlockInfo(txid, queryMempool, callback)` - Similar to getTransaction but will also include the block timestamp and height.
|
||||||
- `bitcoind.getMempoolOutputs(address)` - Will return an array of outputs that match an address from the mempool.
|
- `bitcoind.getMempoolTransactions()` - Will return an array of transaction buffers.
|
||||||
- `bitcoind.getInfo()` - Basic information about the chain including total number of blocks.
|
- `bitcoind.getInfo()` - Basic information about the chain including total number of blocks.
|
||||||
- `bitcoind.isSynced()` - Returns a boolean if the daemon is fully synced (not the initial block download)
|
- `bitcoind.isSynced()` - Returns a boolean if the daemon is fully synced (not the initial block download)
|
||||||
- `bitcoind.syncPercentage()` - Returns the current estimate of blockchain download as a percentage.
|
- `bitcoind.syncPercentage()` - Returns the current estimate of blockchain download as a percentage.
|
||||||
|
|
|
@ -661,5 +661,39 @@ describe('Node Functionality', function() {
|
||||||
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Mempool Index', function() {
|
||||||
|
var unspentOutput;
|
||||||
|
before(function(done) {
|
||||||
|
node.services.address.getUnspentOutputs(address, false, function(err, results) {
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
results.length.should.equal(1);
|
||||||
|
unspentOutput = results[0];
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('will update the mempool index after new tx', function(done) {
|
||||||
|
|
||||||
|
var tx = new Transaction();
|
||||||
|
tx.from(unspentOutput);
|
||||||
|
tx.to(address, unspentOutput.satoshis - 1000);
|
||||||
|
tx.fee(1000);
|
||||||
|
tx.sign(testKey);
|
||||||
|
|
||||||
|
node.services.bitcoind.sendTransaction(tx.serialize());
|
||||||
|
|
||||||
|
setImmediate(function() {
|
||||||
|
var length = node.services.address.mempoolOutputIndex[address].length;
|
||||||
|
length.should.equal(1);
|
||||||
|
should.exist(node.services.address.mempoolOutputIndex[address]);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -300,7 +300,7 @@ describe('Daemon Binding Functionality', function() {
|
||||||
|
|
||||||
var serialized = tx.serialize();
|
var serialized = tx.serialize();
|
||||||
|
|
||||||
bitcoind.on('tx', function(result) {
|
bitcoind.once('tx', function(result) {
|
||||||
result.buffer.toString('hex').should.equal(serialized);
|
result.buffer.toString('hex').should.equal(serialized);
|
||||||
result.hash.should.equal(tx.hash);
|
result.hash.should.equal(tx.hash);
|
||||||
result.mempool.should.equal(true);
|
result.mempool.should.equal(true);
|
||||||
|
@ -357,6 +357,18 @@ describe('Daemon Binding Functionality', function() {
|
||||||
tx.change(changeAddress);
|
tx.change(changeAddress);
|
||||||
tx.sign(privateKey1);
|
tx.sign(privateKey1);
|
||||||
|
|
||||||
|
var tx2;
|
||||||
|
var tx2Key;
|
||||||
|
|
||||||
|
before(function() {
|
||||||
|
tx2 = bitcore.Transaction();
|
||||||
|
tx2.from(utxos[3]);
|
||||||
|
tx2.change(privateKey.toAddress());
|
||||||
|
tx2.to(destKey.toAddress(), utxos[3].amount * 1e8 - 1000);
|
||||||
|
tx2Key = bitcore.PrivateKey.fromWIF(utxos[3].privateKeyWIF);
|
||||||
|
tx2.sign(tx2Key);
|
||||||
|
});
|
||||||
|
|
||||||
it('will add an unchecked transaction', function() {
|
it('will add an unchecked transaction', function() {
|
||||||
var added = bitcoind.addMempoolUncheckedTransaction(tx.serialize());
|
var added = bitcoind.addMempoolUncheckedTransaction(tx.serialize());
|
||||||
added.should.equal(true);
|
added.should.equal(true);
|
||||||
|
@ -370,18 +382,17 @@ describe('Daemon Binding Functionality', function() {
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('get outputs by address', function() {
|
it('get one transaction', function() {
|
||||||
var outputs = bitcoind.getMempoolOutputs(changeAddress);
|
var transactions = bitcoind.getMempoolTransactions();
|
||||||
var expected = [
|
transactions[0].toString('hex').should.equal(tx.serialize());
|
||||||
{
|
});
|
||||||
address: 'mgBCJAsvzgT2qNNeXsoECg2uPKrUsZ76up',
|
|
||||||
script: '76a914073b7eae2823efa349e3b9155b8a735526463a0f88ac',
|
it('get multiple transactions', function() {
|
||||||
satoshis: 40000,
|
bitcoind.sendTransaction(tx2.serialize());
|
||||||
txid: tx.hash,
|
var transactions = bitcoind.getMempoolTransactions();
|
||||||
outputIndex: 1
|
var expected = [tx.serialize(), tx2.serialize()];
|
||||||
}
|
expected.should.contain(transactions[0].toString('hex'));
|
||||||
];
|
expected.should.contain(transactions[1].toString('hex'));
|
||||||
outputs.should.deep.equal(expected);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -6,6 +6,7 @@ var async = require('async');
|
||||||
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');
|
var bitcore = require('bitcore');
|
||||||
var $ = bitcore.util.preconditions;
|
var $ = bitcore.util.preconditions;
|
||||||
var _ = bitcore.deps._;
|
var _ = bitcore.deps._;
|
||||||
|
@ -25,6 +26,9 @@ var AddressService = function(options) {
|
||||||
|
|
||||||
this.node.services.bitcoind.on('tx', this.transactionHandler.bind(this));
|
this.node.services.bitcoind.on('tx', this.transactionHandler.bind(this));
|
||||||
|
|
||||||
|
this.mempoolOutputIndex = {};
|
||||||
|
this.mempoolInputIndex = {};
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
inherits(AddressService, BaseService);
|
inherits(AddressService, BaseService);
|
||||||
|
@ -128,11 +132,83 @@ AddressService.prototype.transactionHandler = function(txInfo) {
|
||||||
this.transactionOutputHandler(messages, tx, i, !txInfo.mempool);
|
this.transactionOutputHandler(messages, tx, i, !txInfo.mempool);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update mempool index
|
||||||
|
if (txInfo.mempool) {
|
||||||
|
this.updateMempoolIndex(tx);
|
||||||
|
}
|
||||||
|
|
||||||
for (var key in messages) {
|
for (var key in messages) {
|
||||||
this.transactionEventHandler(messages[key]);
|
this.transactionEventHandler(messages[key]);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
AddressService.prototype.updateMempoolIndex = function(tx) {
|
||||||
|
var outputLength = tx.outputs.length;
|
||||||
|
for (var outputIndex = 0; outputIndex < outputLength; outputIndex++) {
|
||||||
|
var output = tx.outputs[outputIndex];
|
||||||
|
if (!output.script) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
var addressInfo = this._extractAddressInfoFromScript(output.script);
|
||||||
|
if (!addressInfo) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var addressStr = bitcore.Address({
|
||||||
|
hashBuffer: addressInfo.hashBuffer,
|
||||||
|
type: addressInfo.addressType,
|
||||||
|
network: this.node.network
|
||||||
|
}).toString();
|
||||||
|
|
||||||
|
if (!this.mempoolOutputIndex[addressStr]) {
|
||||||
|
this.mempoolOutputIndex[addressStr] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
this.mempoolOutputIndex[addressStr].push({
|
||||||
|
txid: tx.hash, // TODO use buffer
|
||||||
|
outputIndex: outputIndex,
|
||||||
|
satoshis: output.satoshis,
|
||||||
|
script: output._scriptBuffer.toString('hex') //TODO use a buffer
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
var inputLength = tx.inputs.length;
|
||||||
|
for (var inputIndex = 0; inputIndex < inputLength; inputIndex++) {
|
||||||
|
|
||||||
|
var input = tx.inputs[inputIndex];
|
||||||
|
var address = input.script.toAddress(this.node.network);
|
||||||
|
if (!address) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
var addressStr = address.toString();
|
||||||
|
if (!this.mempoolInputIndex[addressStr]) {
|
||||||
|
this.mempoolInputIndex[addressStr] = [];
|
||||||
|
}
|
||||||
|
this.mempoolInputIndex[addressStr].push({
|
||||||
|
txid: tx.hash, // TODO use buffer
|
||||||
|
inputIndex: inputIndex
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
AddressService.prototype.resetMempoolIndex = function(callback) {
|
||||||
|
var self = this;
|
||||||
|
var transactionBuffers = self.node.services.bitcoind.getMempoolTransactions();
|
||||||
|
this.mempoolInputIndex = {};
|
||||||
|
this.mempoolOutputIndex = {};
|
||||||
|
async.each(transactionBuffers, function(txBuffer, next) {
|
||||||
|
var tx = Transaction().fromBuffer(txBuffer);
|
||||||
|
self.updateMempoolIndex(tx);
|
||||||
|
setImmediate(next);
|
||||||
|
}, function(err) {
|
||||||
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
AddressService.prototype._extractAddressInfoFromScript = function(script) {
|
AddressService.prototype._extractAddressInfoFromScript = function(script) {
|
||||||
var hashBuffer;
|
var hashBuffer;
|
||||||
var addressType;
|
var addressType;
|
||||||
|
@ -567,7 +643,19 @@ AddressService.prototype.getInputs = function(addressStr, options, callback) {
|
||||||
return callback(error);
|
return callback(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO include results from mempool
|
if(options.queryMempool) {
|
||||||
|
var mempoolInputs = self.mempoolInputIndex[addressStr];
|
||||||
|
if (mempoolInputs) {
|
||||||
|
for(var i = 0; i < mempoolInputs.length; i++) {
|
||||||
|
// TODO copy
|
||||||
|
var newInput = mempoolInputs[i];
|
||||||
|
newInput.address = addressStr;
|
||||||
|
newInput.height = -1;
|
||||||
|
newInput.confirmations = 0;
|
||||||
|
inputs.push(newInput);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
callback(null, inputs);
|
callback(null, inputs);
|
||||||
|
|
||||||
|
@ -635,11 +723,11 @@ AddressService.prototype.getOutputs = function(addressStr, options, callback) {
|
||||||
|
|
||||||
var output = {
|
var output = {
|
||||||
address: addressStr,
|
address: addressStr,
|
||||||
txid: key.txid.toString('hex'),
|
txid: key.txid.toString('hex'), //TODO use a buffer
|
||||||
outputIndex: key.outputIndex,
|
outputIndex: key.outputIndex,
|
||||||
height: key.height,
|
height: key.height,
|
||||||
satoshis: value.satoshis,
|
satoshis: value.satoshis,
|
||||||
script: value.scriptBuffer.toString('hex'),
|
script: value.scriptBuffer.toString('hex'), //TODO use a buffer
|
||||||
confirmations: self.node.services.db.tip.__height - key.height + 1
|
confirmations: self.node.services.db.tip.__height - key.height + 1
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -661,7 +749,17 @@ AddressService.prototype.getOutputs = function(addressStr, options, callback) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if(options.queryMempool) {
|
if(options.queryMempool) {
|
||||||
outputs = outputs.concat(self.node.services.bitcoind.getMempoolOutputs(addressStr));
|
var mempoolOutputs = self.mempoolOutputIndex[addressStr];
|
||||||
|
if (mempoolOutputs) {
|
||||||
|
for(var i = 0; i < mempoolOutputs.length; i++) {
|
||||||
|
// TODO copy
|
||||||
|
var newOutput = mempoolOutputs[i];
|
||||||
|
newOutput.address = addressStr;
|
||||||
|
newOutput.height = -1;
|
||||||
|
newOutput.confirmations = 0;
|
||||||
|
outputs.push(newOutput);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
callback(null, outputs);
|
callback(null, outputs);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
var fs = require('fs');
|
||||||
var util = require('util');
|
var util = require('util');
|
||||||
var bindings = require('bindings')('bitcoind.node');
|
var bindings = require('bindings')('bitcoind.node');
|
||||||
var mkdirp = require('mkdirp');
|
var mkdirp = require('mkdirp');
|
||||||
var fs = require('fs');
|
var async = require('async');
|
||||||
var bitcore = require('bitcore');
|
var bitcore = require('bitcore');
|
||||||
|
var Transaction = require('../transaction');
|
||||||
var $ = bitcore.util.preconditions;
|
var $ = bitcore.util.preconditions;
|
||||||
var _ = bitcore.deps._;
|
|
||||||
var index = require('../');
|
var index = require('../');
|
||||||
var log = index.log;
|
var log = index.log;
|
||||||
var Service = require('../service');
|
var Service = require('../service');
|
||||||
|
@ -215,8 +216,8 @@ Bitcoin.prototype.getTransactionWithBlockInfo = function(txid, queryMempool, cal
|
||||||
return bindings.getTransactionWithBlockInfo(txid, queryMempool, callback);
|
return bindings.getTransactionWithBlockInfo(txid, queryMempool, callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
Bitcoin.prototype.getMempoolOutputs = function(address) {
|
Bitcoin.prototype.getMempoolTransactions = function() {
|
||||||
return bindings.getMempoolOutputs(address);
|
return bindings.getMempoolTransactions();
|
||||||
};
|
};
|
||||||
|
|
||||||
Bitcoin.prototype.addMempoolUncheckedTransaction = function(txBuffer) {
|
Bitcoin.prototype.addMempoolUncheckedTransaction = function(txBuffer) {
|
||||||
|
|
|
@ -412,6 +412,20 @@ DB.prototype.disconnectBlock = function(block, callback) {
|
||||||
this.runAllBlockHandlers(block, false, callback);
|
this.runAllBlockHandlers(block, false, callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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
|
* Will collect all database operations for a block from other services
|
||||||
* and save to the database.
|
* and save to the database.
|
||||||
|
@ -674,15 +688,22 @@ DB.prototype.sync = function() {
|
||||||
return self.node.emit('error', err);
|
return self.node.emit('error', err);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.bitcoindSyncing = false;
|
|
||||||
|
|
||||||
if(self.node.stopping) {
|
if(self.node.stopping) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If bitcoind is completely synced
|
|
||||||
if (self.node.services.bitcoind.isSynced()) {
|
if (self.node.services.bitcoind.isSynced()) {
|
||||||
self.node.emit('synced');
|
self.runAllMempoolIndexes(function(err) {
|
||||||
|
if (err) {
|
||||||
|
Error.captureStackTrace(err);
|
||||||
|
return self.node.emit('error', err);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.bitcoindSyncing = false;
|
||||||
|
self.node.emit('synced');
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
self.bitcoindSyncing = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -1531,84 +1531,38 @@ NAN_METHOD(SendTransaction) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GetMempoolOutputs
|
* GetMempoolTransactions
|
||||||
* bitcoindjs.getMempoolOutputs()
|
* bitcoind.getMempoolTransactions()
|
||||||
* Will return outputs by address from the mempool.
|
* Will return an array of transaction buffers.
|
||||||
*/
|
*/
|
||||||
NAN_METHOD(GetMempoolOutputs) {
|
NAN_METHOD(GetMempoolTransactions) {
|
||||||
Isolate* isolate = Isolate::GetCurrent();
|
Isolate* isolate = args.GetIsolate();
|
||||||
HandleScope scope(isolate);
|
HandleScope scope(isolate);
|
||||||
|
|
||||||
// Instatiate an empty array that we will fill later
|
Local<Array> transactions = Array::New(isolate);
|
||||||
// with matching outputs.
|
|
||||||
Local<Array> outputs = Array::New(isolate);
|
|
||||||
int arrayIndex = 0;
|
int arrayIndex = 0;
|
||||||
|
|
||||||
// Decode the input address into the hash bytes
|
{
|
||||||
// that we can then match to the scriptPubKeys data
|
LOCK(mempool.cs);
|
||||||
v8::String::Utf8Value param1(args[0]->ToString());
|
|
||||||
std::string *input = new std::string(*param1);
|
|
||||||
const char* psz = input->c_str();
|
|
||||||
std::vector<unsigned char> vAddress;
|
|
||||||
DecodeBase58(psz, vAddress);
|
|
||||||
vector<unsigned char> hashBytes(vAddress.begin()+1, vAddress.begin()+21);
|
|
||||||
|
|
||||||
// Iterate through the entire mempool
|
// Iterate through the entire mempool
|
||||||
std::map<uint256, CTxMemPoolEntry> mapTx = mempool.mapTx;
|
std::map<uint256, CTxMemPoolEntry> mapTx = mempool.mapTx;
|
||||||
|
|
||||||
for(std::map<uint256, CTxMemPoolEntry>::iterator it = mapTx.begin(); it != mapTx.end(); it++) {
|
for(std::map<uint256, CTxMemPoolEntry>::iterator it = mapTx.begin();
|
||||||
|
it != mapTx.end();
|
||||||
uint256 txid = it->first;
|
it++) {
|
||||||
CTxMemPoolEntry entry = it->second;
|
CTxMemPoolEntry entry = it->second;
|
||||||
const CTransaction tx = entry.GetTx();
|
const CTransaction tx = entry.GetTx();
|
||||||
|
CDataStream dataStreamTx(SER_NETWORK, PROTOCOL_VERSION);
|
||||||
int outputIndex = 0;
|
dataStreamTx << tx;
|
||||||
|
std::string txString = dataStreamTx.str();
|
||||||
// Iterate through each output
|
Local<Value> txBuffer = node::Buffer::New(isolate, txString.c_str(), txString.size());
|
||||||
BOOST_FOREACH(const CTxOut& txout, tx.vout) {
|
transactions->Set(arrayIndex, txBuffer);
|
||||||
|
arrayIndex++;
|
||||||
CScript script = txout.scriptPubKey;
|
|
||||||
|
|
||||||
txnouttype type;
|
|
||||||
std::vector<std::vector<unsigned char> > hashResults;
|
|
||||||
|
|
||||||
if (Solver(script, type, hashResults)) {
|
|
||||||
|
|
||||||
// See if the script is any of the standard address types
|
|
||||||
if (type == TX_PUBKEYHASH || type == TX_SCRIPTHASH) {
|
|
||||||
|
|
||||||
vector<unsigned char> scripthashBytes = hashResults.front();
|
|
||||||
|
|
||||||
// Compare the hash bytes with the input hash bytes
|
|
||||||
if(equal(hashBytes.begin(), hashBytes.end(), scripthashBytes.begin())) {
|
|
||||||
|
|
||||||
Local<Object> output = NanNew<Object>();
|
|
||||||
|
|
||||||
output->Set(NanNew<String>("address"), NanNew<String>(psz));
|
|
||||||
|
|
||||||
std::string scriptHex = HexStr(script.begin(), script.end());
|
|
||||||
output->Set(NanNew<String>("script"), NanNew<String>(scriptHex));
|
|
||||||
|
|
||||||
uint64_t satoshis = txout.nValue;
|
|
||||||
output->Set(NanNew<String>("satoshis"), NanNew<Number>(satoshis)); // can't go above 2 ^ 53 -1
|
|
||||||
output->Set(NanNew<String>("txid"), NanNew<String>(txid.GetHex()));
|
|
||||||
|
|
||||||
output->Set(NanNew<String>("outputIndex"), NanNew<Number>(outputIndex));
|
|
||||||
|
|
||||||
// We have a match and push the results to the array
|
|
||||||
// that is returned as the result
|
|
||||||
outputs->Set(arrayIndex, output);
|
|
||||||
arrayIndex++;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
outputIndex++;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
NanReturnValue(outputs);
|
NanReturnValue(transactions);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1668,7 +1622,7 @@ init(Handle<Object> target) {
|
||||||
NODE_SET_METHOD(target, "isSpent", IsSpent);
|
NODE_SET_METHOD(target, "isSpent", IsSpent);
|
||||||
NODE_SET_METHOD(target, "getBlockIndex", GetBlockIndex);
|
NODE_SET_METHOD(target, "getBlockIndex", GetBlockIndex);
|
||||||
NODE_SET_METHOD(target, "isMainChain", IsMainChain);
|
NODE_SET_METHOD(target, "isMainChain", IsMainChain);
|
||||||
NODE_SET_METHOD(target, "getMempoolOutputs", GetMempoolOutputs);
|
NODE_SET_METHOD(target, "getMempoolTransactions", GetMempoolTransactions);
|
||||||
NODE_SET_METHOD(target, "addMempoolUncheckedTransaction", AddMempoolUncheckedTransaction);
|
NODE_SET_METHOD(target, "addMempoolUncheckedTransaction", AddMempoolUncheckedTransaction);
|
||||||
NODE_SET_METHOD(target, "sendTransaction", SendTransaction);
|
NODE_SET_METHOD(target, "sendTransaction", SendTransaction);
|
||||||
NODE_SET_METHOD(target, "estimateFee", EstimateFee);
|
NODE_SET_METHOD(target, "estimateFee", EstimateFee);
|
||||||
|
|
|
@ -29,7 +29,7 @@ NAN_METHOD(GetTransaction);
|
||||||
NAN_METHOD(GetInfo);
|
NAN_METHOD(GetInfo);
|
||||||
NAN_METHOD(IsSpent);
|
NAN_METHOD(IsSpent);
|
||||||
NAN_METHOD(GetBlockIndex);
|
NAN_METHOD(GetBlockIndex);
|
||||||
NAN_METHOD(GetMempoolOutputs);
|
NAN_METHOD(GetMempoolTransactions);
|
||||||
NAN_METHOD(AddMempoolUncheckedTransaction);
|
NAN_METHOD(AddMempoolUncheckedTransaction);
|
||||||
NAN_METHOD(SendTransaction);
|
NAN_METHOD(SendTransaction);
|
||||||
NAN_METHOD(EstimateFee);
|
NAN_METHOD(EstimateFee);
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
[
|
||||||
|
"010000000ec9e1d96d51f7d0a5b726184cda744207e51a596f13b564109de6ffc0653055cf000000006a4730440220263d48a2f4c3a2aa6032f96253c853531131171d8ae3d30586a45de7ba7c4006022077db4b39926877939baf59e8d357effe7674e7b12ad986c0a4cd99f2b7acafca012103e9100732bb534bea2f6d3b971914ec8403557306600c01c5ce63ec185737c732ffffffff0c16800642bdf90cbbd340be2e801bee9b907db6d59dc4c7cb269358c1e2593a000000006a4730440220085e39cb3a948559c1b1c88ba635eeef37a767322c1e4d96556e998302acb5dc0220421c03de78121692c538a9bc85a58fbe796384541fe87b6e91c39404318c390d012103e9100732bb534bea2f6d3b971914ec8403557306600c01c5ce63ec185737c732ffffffff559fde3733e950db9e3faea1a27872047a5c8bc14e8e6ac4a7b4f7b5f90c42c2000000006b483045022100a46a109a5acfc34b13c591ab69392e2dc2c3ea12d0900c5f7a539ea888e57ae0022015372bad56d63c6d08a5e66e0c9a63b2fc8dce245aa765e37dac06acb84c18d501210207e2a01d4a334c2d7c9ebacc5ea8a0d4b86fd54599b1aebe125d9e664be012c2ffffffff92760c236f6f18751c4ecab1dfcfebac7358a431b31bcd72b0a09d336233bdce000000006a47304402200857fa82e5b287c6ed5624cbc4fcd068a756a7a9ef785a73ce4e51b15a8aa34b022042cbc6f478b711539c6345d0b05d4bc9a9b5c34b2e4d25f234cf6784ff2eed19012103cab1f64f3d5f20a3f4a070694e6f93f5248b502b3842e369d81f05d07dec01e3ffffffff158669557e8a0b71288df37626601e636c5a8b3797f7f3357590313e8efe790a000000006b48304502210085e62cb95066540730b74aeae133277e511b5bf79de8c0ad60e784638e681ddf022043b456285569e0da133f527412218893f05a781d6f77f8cddd12eb90bfdc5937012103e9100732bb534bea2f6d3b971914ec8403557306600c01c5ce63ec185737c732ffffffffc1f9f57f1eda20f3b45669b3f0d1eae73b410ddf2b4fc1cfe10051a6051eff68000000006a47304402200fbe15c73446309040f4264567d0e8cc46691cf5d0626c443fc2dde716211e5402207f84e68e273755d140f346e029213dbc42b65cfb1e2ac41f72402c7ff45feffc012103e9100732bb534bea2f6d3b971914ec8403557306600c01c5ce63ec185737c732ffffffff39fa27e16c540a9bb796e65d4ac624fc33878e522461d6955764bf7b83c03ce8000000006a4730440220131e6aed76da389f21dfd7cd271fad73c5c533b1c18fbfabd6799a0d0e7dc80602205c38855bea0f1dfbbb608bc1b56c75b91a39980de121ffd9f880b522d412d821012103e9100732bb534bea2f6d3b971914ec8403557306600c01c5ce63ec185737c732ffffffff375fb7ae26eb637ccc007a669b9f50ed10fa53127504b80e0fd2be34c50becd7000000006b483045022100f0a9e585aa3113eae4bfb204f38f96e60dc613c04313aae931b677e4d8d7081d022014664874859f3d47447c6c0f257c28c74e8fdaedd5f781d752f3a4b651d3d179012103e9100732bb534bea2f6d3b971914ec8403557306600c01c5ce63ec185737c732ffffffff6f9d6a3c847e6112bb920424ca0d8d6e0956b128acb206e8fb58b2d2f2d7d46b000000006a4730440220736b198484cf5226616a539146e037a93cc75963885eefe58fc29a7be8123c750220619a456c0fe7437ec67c642d88e890344fc1c51a7b3cfc0ae283d61d0f176c5e012103e9100732bb534bea2f6d3b971914ec8403557306600c01c5ce63ec185737c732ffffffff3cccbd8090d60fcf28064333cf2f51ef0f838ba5e26a5e0f38807ee16d38a649000000006b483045022100e1ed25e9365e596d4fc3cbf278490d8ea765c4266c55f19311cf5da33f8d00750220361888a1738ebba827c0c947690b5f2a5f20e9f1be8956c3a34a4ba03f9e60f5012103e9100732bb534bea2f6d3b971914ec8403557306600c01c5ce63ec185737c732ffffffff7f4d60a2e961490aa465a7df461bf09b151bdc0c162f3bef0c1cbed6160d02c7000000006a47304402204e79b15a1db9a356f00dc9f2d559e31561cad1343ba5809a65b52bd868e3963e022055b9326ed5de9aa9970ec67a2ebf1a9dbf9ee513b64bd13837c87320bb4d6947012103e9100732bb534bea2f6d3b971914ec8403557306600c01c5ce63ec185737c732ffffffffe63b9370ba49a129e071750cbb300128015fdd90d7399f9c4e44934eabbaa2f7000000006b483045022100b9ceb2e376c0334d45bf08bfeb06dc250e7cb01d3a08e2fb3506388683552417022024c7c5bda385b904cca691fb6e1ad8c5eba5858a88a2112cb824dca72793b7a7012103e9100732bb534bea2f6d3b971914ec8403557306600c01c5ce63ec185737c732ffffffffc78b96fddededb6cbc1dff9de51f2743fd42e91de2506794b121928af4729528000000006a47304402201f05caddee5a0ff590b27c4ce25be1cbbeb45dc39679a1b8b0e10b1a378d84bc02203e31b01e14d891e9809c43a4df54494c626c5e47eaeeeb99ab4e02bd73c3d6cd012103e9100732bb534bea2f6d3b971914ec8403557306600c01c5ce63ec185737c732ffffffff30093916240e981a77cb869924fa0c38a894c24b1a6e7d26b117bb9caa7d5bbe000000006a4730440220483f297379daacee14babbf929708a861a991373bca3ed4eef240e2c156a162602205f1e93e375a897c6a9ddc3dc616ccf14137096ebd7888040e1053a769d21b945012103e9100732bb534bea2f6d3b971914ec8403557306600c01c5ce63ec185737c732ffffffff022897d411000000001976a91415354ee1828ed12f243f80dcb92c3a8205285f0188ac3be68c02000000001976a9143ecf8ff79932c3de33829a001236985872d10be188ac00000000"
|
||||||
|
]
|
|
@ -10,6 +10,8 @@ var bitcore = require('bitcore');
|
||||||
var Networks = bitcore.Networks;
|
var Networks = bitcore.Networks;
|
||||||
var EventEmitter = require('events').EventEmitter;
|
var EventEmitter = require('events').EventEmitter;
|
||||||
var errors = bitcorenode.errors;
|
var errors = bitcorenode.errors;
|
||||||
|
var Transaction = require('../../../lib/transaction');
|
||||||
|
var txData = require('../../data/transaction.json');
|
||||||
|
|
||||||
var mockdb = {
|
var mockdb = {
|
||||||
};
|
};
|
||||||
|
@ -24,7 +26,7 @@ var mocknode = {
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('Address Service', function() {
|
describe('Address Service', function() {
|
||||||
|
var txBuf = new Buffer(txData[0], 'hex');
|
||||||
describe('#getAPIMethods', function() {
|
describe('#getAPIMethods', function() {
|
||||||
it('should return the correct methods', function() {
|
it('should return the correct methods', function() {
|
||||||
var am = new AddressService({node: mocknode});
|
var am = new AddressService({node: mocknode});
|
||||||
|
@ -410,6 +412,43 @@ describe('Address Service', function() {
|
||||||
am = new AddressService({node: testnode});
|
am = new AddressService({node: testnode});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('will add mempool inputs on close', function(done) {
|
||||||
|
var testStream = new EventEmitter();
|
||||||
|
var db = {
|
||||||
|
store: {
|
||||||
|
createReadStream: sinon.stub().returns(testStream)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var testnode = {
|
||||||
|
services: {
|
||||||
|
db: db,
|
||||||
|
bitcoind: {
|
||||||
|
on: sinon.stub()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var am = new AddressService({node: testnode});
|
||||||
|
var args = {
|
||||||
|
start: 15,
|
||||||
|
end: 12,
|
||||||
|
queryMempool: true
|
||||||
|
};
|
||||||
|
am.mempoolInputIndex[address] = [
|
||||||
|
{
|
||||||
|
address: address,
|
||||||
|
height: -1,
|
||||||
|
confirmations: 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
am.getInputs(address, args, function(err, inputs) {
|
||||||
|
should.not.exist(err);
|
||||||
|
inputs.length.should.equal(1);
|
||||||
|
inputs[0].address.should.equal(address);
|
||||||
|
inputs[0].height.should.equal(-1);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
testStream.emit('close');
|
||||||
|
});
|
||||||
it('will get inputs for an address and timestamp', function(done) {
|
it('will get inputs for an address and timestamp', function(done) {
|
||||||
var testStream = new EventEmitter();
|
var testStream = new EventEmitter();
|
||||||
var args = {
|
var args = {
|
||||||
|
@ -547,9 +586,6 @@ describe('Address Service', function() {
|
||||||
return testStream;
|
return testStream;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
am.node.services.bitcoind = {
|
|
||||||
getMempoolOutputs: sinon.stub().returns([])
|
|
||||||
};
|
|
||||||
am.getOutputs(address, args, function(err, outputs) {
|
am.getOutputs(address, args, function(err, outputs) {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
outputs.length.should.equal(1);
|
outputs.length.should.equal(1);
|
||||||
|
@ -575,18 +611,17 @@ describe('Address Service', function() {
|
||||||
am.node.services.db.store = {
|
am.node.services.db.store = {
|
||||||
createReadStream: sinon.stub().returns(readStream1)
|
createReadStream: sinon.stub().returns(readStream1)
|
||||||
};
|
};
|
||||||
var mempoolOutputs = [
|
|
||||||
|
am.mempoolOutputIndex = {};
|
||||||
|
|
||||||
|
am.mempoolOutputIndex[address] = [
|
||||||
{
|
{
|
||||||
address: '1KiW1A4dx1oRgLHtDtBjcunUGkYtFgZ1W',
|
|
||||||
txid: 'aa2db23f670596e96ed94c405fd11848c8f236d266ee96da37ecd919e53b4371',
|
txid: 'aa2db23f670596e96ed94c405fd11848c8f236d266ee96da37ecd919e53b4371',
|
||||||
satoshis: 307627737,
|
satoshis: 307627737,
|
||||||
script: '76a914f6db95c81dea3d10f0ff8d890927751bf7b203c188ac',
|
script: '76a914f6db95c81dea3d10f0ff8d890927751bf7b203c188ac',
|
||||||
blockHeight: 352532
|
outputIndex: 0
|
||||||
}
|
}
|
||||||
];
|
]
|
||||||
am.node.services.bitcoind = {
|
|
||||||
getMempoolOutputs: sinon.stub().returns(mempoolOutputs)
|
|
||||||
};
|
|
||||||
|
|
||||||
am.getOutputs(address, options, function(err, outputs) {
|
am.getOutputs(address, options, function(err, outputs) {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
|
@ -606,7 +641,8 @@ describe('Address Service', function() {
|
||||||
outputs[2].address.should.equal(address);
|
outputs[2].address.should.equal(address);
|
||||||
outputs[2].txid.should.equal('aa2db23f670596e96ed94c405fd11848c8f236d266ee96da37ecd919e53b4371');
|
outputs[2].txid.should.equal('aa2db23f670596e96ed94c405fd11848c8f236d266ee96da37ecd919e53b4371');
|
||||||
outputs[2].script.should.equal('76a914f6db95c81dea3d10f0ff8d890927751bf7b203c188ac');
|
outputs[2].script.should.equal('76a914f6db95c81dea3d10f0ff8d890927751bf7b203c188ac');
|
||||||
outputs[2].blockHeight.should.equal(352532);
|
outputs[2].height.should.equal(-1);
|
||||||
|
outputs[2].confirmations.should.equal(0);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -875,5 +911,47 @@ describe('Address Service', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
describe('#updateMempoolIndex', function() {
|
||||||
|
var am;
|
||||||
|
var db = {};
|
||||||
|
var tx = Transaction().fromBuffer(txBuf);
|
||||||
|
|
||||||
|
before(function() {
|
||||||
|
am = new AddressService({node: mocknode});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('will update the input and output indexes', function() {
|
||||||
|
am.updateMempoolIndex(tx);
|
||||||
|
am.mempoolInputIndex['18Z29uNgWyUDtNyTKE1PaurbSR131EfANc'][0].txid.should.equal('45202ffdeb8344af4dec07cddf0478485dc65cc7d08303e45959630c89b51ea2');
|
||||||
|
am.mempoolOutputIndex['12w93weN8oti3P1e5VYEuygqyujhADF7J5'][0].txid.should.equal('45202ffdeb8344af4dec07cddf0478485dc65cc7d08303e45959630c89b51ea2');
|
||||||
|
am.mempoolInputIndex['1JT7KDYwT9JY9o2vyqcKNSJgTWeKfV3ui8'].length.should.equal(12);
|
||||||
|
am.mempoolOutputIndex['12w93weN8oti3P1e5VYEuygqyujhADF7J5'].length.should.equal(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
describe('#resetMempoolIndex', function() {
|
||||||
|
var am;
|
||||||
|
var db = {};
|
||||||
|
|
||||||
|
before(function() {
|
||||||
|
var testnode = {
|
||||||
|
db: db,
|
||||||
|
services: {
|
||||||
|
bitcoind: {
|
||||||
|
getMempoolTransactions: sinon.stub().returns([txBuf]),
|
||||||
|
on: sinon.stub()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
am = new AddressService({node: testnode});
|
||||||
|
am.updateMempoolIndex = sinon.stub();
|
||||||
|
|
||||||
|
});
|
||||||
|
it('will reset the input and output indexes', function(done) {
|
||||||
|
am.resetMempoolIndex(function() {
|
||||||
|
am.updateMempoolIndex.callCount.should.equal(1);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -406,7 +406,7 @@ describe('Bitcoin Service', function() {
|
||||||
['sendTransaction', 2],
|
['sendTransaction', 2],
|
||||||
['getTransaction', 3],
|
['getTransaction', 3],
|
||||||
['getTransactionWithBlockInfo', 3],
|
['getTransactionWithBlockInfo', 3],
|
||||||
['getMempoolOutputs', 1],
|
['getMempoolTransactions', 0],
|
||||||
['addMempoolUncheckedTransaction', 1],
|
['addMempoolUncheckedTransaction', 1],
|
||||||
['getInfo', 0]
|
['getInfo', 0]
|
||||||
];
|
];
|
||||||
|
|
|
@ -935,6 +935,7 @@ 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),
|
||||||
|
@ -954,6 +955,7 @@ 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