Service Documentation

This commit is contained in:
Braydon Fuller 2015-09-22 11:38:14 -04:00
parent a624b6fa39
commit 1183e0cae7
16 changed files with 933 additions and 146 deletions

View File

@ -6,7 +6,7 @@ A Bitcoin full node for building applications and services with Node.js. A node
## Install
```bash
npm install -g bitcore-node@0.2.0-beta.X
npm install -g bitcore-node@latest
bitcore-node start
```
@ -31,6 +31,13 @@ To start bitcore-node as a daemon:
bitcore-node start --daemon
```
## Add-on Services
There are several add-on services available to extend the functionality of Bitcore Node:
- [Insight API](https://github.com/bitpay/insight-api/tree/v0.3.0)
- [Insight UI](https://github.com/bitpay/insight/tree/v0.3.0)
## Documentation
- [Services](docs/services.md)

View File

@ -16,14 +16,23 @@ A node represents a collection of services that are loaded together. For more in
```js
var BitcoinNode = require('bitcore-node').Node;
var index = require('bitcore-node');
var Bitcoin = index.services.Bitcoin;
var Node = index.Node;
var configuration = {
datadir: '~/.bitcoin',
network: 'testnet'
network: 'testnet',
services: [
{
name: 'bitcoind',
module: Bitcoin,
config: {}
}
]
};
var node = new BitcoinNode(configuration);
var node = new Node(configuration);
node.on('ready', function() {
console.log('Bitcoin Node Ready');

View File

@ -1,35 +1,142 @@
# Address Service
The address service builds on the [Bitcoin Service](bitcoind.md) and the [Database Service](db.md) to add additional functionality for querying and subscribing to information based on bitcoin addresses.
The address service builds on the [Bitcoin Service](bitcoind.md) and the [Database Service](db.md) to add additional functionality for querying and subscribing to information based on bitcoin addresses. This will typically represent the core functionality for wallet applications.
## API Documentation
Get Unspent Outputs
These methods are exposed over the JSON-RPC interface and can be called directly from a node via:
```js
var address = '15vkcKf7gB23wLAnZLmbVuMiiVDc1Nm4a2';
node.services.address.<methodName>
```
**Get Unspent Outputs**
One of the most common uses will be to retrieve unspent outputs necessary to create a transaction, here is how to get the unspent outputs for an address:
```js
var address = 'mgY65WSfEmsyYaYPQaXhmXMeBhwp4EcsQW';
var includeMempool = true;
node.getUnspentOutputs(address, includeMempool, function(err, unspentOutputs) {
//...
node.services.address.getUnspentOutputs(address, includeMempool, function(err, unspentOutputs) {
// see below
});
```
View Balances
The `unspentOutputs` will have the format:
```js
var address = '15vkcKf7gB23wLAnZLmbVuMiiVDc1Nm4a2';
[
{
address: 'mgY65WSfEmsyYaYPQaXhmXMeBhwp4EcsQW',
txid: '9d956c5d324a1c2b12133f3242deff264a9b9f61be701311373998681b8c1769',
outputIndex: 1,
height: 150,
satoshis: 1000000000,
script: '76a9140b2f0a0c31bfe0406b0ccc1381fdbe311946dadc88ac',
confirmations: 3
}
]
```
**View Balances**
```js
var address = 'mgY65WSfEmsyYaYPQaXhmXMeBhwp4EcsQW';
var includeMempool = true;
node.getBalance(address, includeMempool, function(err, balance) {
//...
node.services.address.getBalance(address, includeMempool, function(err, balance) {
// balance will be in satoshis
});
```
Get Outputs
**View Address History**
This method will give history of an address limited by a range of block heights by using
the "start" and "end" arguments. The "start" value is the more recent, and greater, block height.
The "end" value is the older, and lesser, block height. This feature is most useful for synchronization
as previous history can be omitted. Furthermore for large ranges of block heights, results can be
paginated by using the "from" and "to" arguments.
```js
var address = '15vkcKf7gB23wLAnZLmbVuMiiVDc1Nm4a2';
var includeMempool = true;
node.getOutputs(address, includeMempool, function(err, outputs) {
//...
var addresses = ['mgY65WSfEmsyYaYPQaXhmXMeBhwp4EcsQW'];
var options = {
start: 345000,
end: 344000,
queryMempool: true
};
node.services.address.getAddressHistory(addresses, options, function(err, history) {
// see below
});
```
The history format will be:
```js
{
totalCount: 1, // The total number of items within "start" and "end"
items: [
{
addresses: {
'mgY65WSfEmsyYaYPQaXhmXMeBhwp4EcsQW': {
inputIndexes: [],
outputIndexes: [0]
}
},
satoshis: 1000000000,
height: 150, // the block height of the transaction
confirmations: 3,
timestamp: 1442948127, // in seconds
fees: 191,
tx: <Transaction> // the populated transaction
}
]
}
```
**View Address Summary**
```js
var address = 'mgY65WSfEmsyYaYPQaXhmXMeBhwp4EcsQW';
var options = {
noTxList: false
};
node.services.address.getAddressSummary(address, options, function(err, summary) {
// see below
});
```
The `summary` will have the format (values are in satoshis):
```js
{
totalReceived: 1000000000,
totalSpent: 0,
balance: 1000000000,
unconfirmedBalance: 1000000000,
appearances: 1, // number of transactions
unconfirmedAppearances: 0,
txids: [
'3f7d13efe12e82f873f4d41f7e63bb64708fc4c942eb8c6822fa5bd7606adb00'
]
}
```
## Events
For details on instantiating a bus for a node, see the [Bus Documentation](../bus.md).
- Name: `address/transaction`, Arguments: `[address, address...]`
- Name: `address/balance`, Arguments: `[address, address...]`
**Examples:**
```js
bus.subscribe('address/transaction', ['13FMwCYz3hUhwPcaWuD2M1U2KzfTtvLM89']);
bus.subscribe('address/balance', ['13FMwCYz3hUhwPcaWuD2M1U2KzfTtvLM89']);
bus.on('address/transaction', function(transaction) {
});
bus.on('address/balance', function(balance) {
});
```

View File

@ -1,21 +1,121 @@
# Bitcoin Service
The bitcoin service adds a native interface to Bitcoin Core for querying information about the Bitcoin blockchain. Bindings are linked to Bitcoin Core compiled as a static library.
The Bitcoin Service adds a native Node.js interface to Bitcoin Core for querying information about the Bitcoin blockchain. Bindings are linked to Bitcoin Core compiled as a static library.
## API Documentation
- `bitcoind.start([options], [callback])` - Start the JavaScript Bitcoin node.
- `bitcoind.getBlock(blockHash|blockHeight, callback)` - Get any block asynchronously by block hash or height as a node buffer.
- `bitcoind.isSpent(txid, outputIndex)` - Returns a boolean if a txid and outputIndex is already spent.
- `bitcoind.getBlockIndex(blockHash)` - Will return the block chain work and previous hash.
- `bitcoind.isMainChain(blockHash)` - Returns true if block is on the main chain. Returns false if it is an orphan.
- `bitcoind.estimateFee(blocks)` - Estimates the fees required to have a transaction included in the number of blocks specified as the first argument.
- `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.getTransactionWithBlockInfo(txid, queryMempool, callback)` - Similar to getTransaction but will also include the block timestamp and height.
- `bitcoind.getMempoolTransactions()` - Will return an array of transaction buffers.
These methods are currently only available via directly interfacing with a node:
```js
node.services.bitcoind.<methodName>
```
**Getting Block Information**
It's possible to query blocks by both block hash and by height. Blocks are given as Node.js buffers and can be parsed via Bitcore:
```js
var blockHeight = 0;
node.services.bitcoind.getBlock(blockHeight, function(err, blockBuffer) {
if (err) {
throw err;
}
var block = bitcore.Block.fromBuffer(blockBuffer);
console.log(block);
};
// check if the block is part of the main chain
var mainChain = node.services.bitcoind.isMainChain(block.hash);
console.log(mainChain);
// get only the block index (including chain work and previous hash)
var blockIndex = node.services.bitcoind.getBlockIndex(blockHeight);
console.log(blockIndex);
```
**Retrieving and Sending Transactions**
Get a transaction asynchronously by reading it from disk, with an argument to optionally not include the mempool:
```js
var txid = '7426c707d0e9705bdd8158e60983e37d0f5d63529086d6672b07d9238d5aa623';
var queryMempool = true;
node.services.bitcoind.getTransaction(txid, queryMempool, function(err, transactionBuffer) {
if (err) {
throw err;
}
var transaction = bitcore.Transaction().fromBuffer(transactionBuffer);
});
// also retrieve the block timestamp and height
node.services.bitcoind.getTransactionWithBlockInfo(txid, queryMempool, function(err, info) {
console.log(info.blockHash);
console.log(info.height);
console.log(info.timestamp); // in seconds
var transaction = bitcore.Transaction().fromBuffer(transactionBuffer);
});
```
Send a transaction to the network:
```js
var numberOfBlocks = 3;
var feesPerKilobyte = node.services.bitcoind.estimateFee(numberOfBlocks); // in satoshis
try {
node.services.bitcoind.sendTransaction(transaction.serialize());
} catch(err) {
// handle error
}
```
Get all of the transactions in the mempool:
```js
var mempool = node.services.bitcoind.getMempoolTransactions();
var transactions = [];
for (var i = 0; i < mempool.length; i++) {
transactions.push(bitcore.Transaction().fromBuffer(transactions[i]));
}
```
Determine if an output is spent (excluding the mempool):
```js
var spent = node.services.bitcoind.isSpent(txid, outputIndex);
console.log(spent);
```
**Miscellaneous**
- `bitcoind.start(callback)` - Start the JavaScript Bitcoin node, the callback is called when the daemon is ready.
- `bitcoind.getInfo()` - Basic information about the chain including total number of blocks.
- `bitcoind.getTxOutSetInfo()` - 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.syncPercentage()` - Returns the current estimate of blockchain download as a percentage.
- `bitcoind.stop([callback])` - Stop the JavaScript bitcoin node safely, the callback will be called when bitcoind is closed. This will also be done automatically on `process.exit`. It also takes the bitcoind node off the libuv event loop. If the daemon object is the only thing on the event loop. Node will simply close.
- `bitcoind.stop(callback)` - Stop the JavaScript bitcoin node safely, the callback will be called when bitcoind is closed. This will also be done automatically on `process.exit`. It also takes the bitcoind node off the libuv event loop. If the daemon object is the only thing on the event loop. Node will simply close.
## Events
The Bitcoin Service doesn't expose any events via the Bus, however there are a few events that can be directly registered:
```js
node.services.bitcoind.on('tip', function(blockHash) {
// a new block tip has been added
});
node.services.bitcoind.on('tx', function(txInfo) {
// a new transaction has been broadcast in the network
});
```
The `txInfo` object will have the format:
```js
{
buffer: <Buffer...>,
mempool: true,
hash: '7426c707d0e9705bdd8158e60983e37d0f5d63529086d6672b07d9238d5aa623'
}
```

View File

@ -1,45 +1,119 @@
# Database Service
An extensible interface to the bitcoin block chain. The service builds on the [Bitcoin Service](bitcoind.md), and includes additional methods for working with the block chain.
This service synchronizes a leveldb database with the [Bitcoin Service](bitcoind.md) block chain by connecting and disconnecting blocks to build new indexes that can be queried. Other services can extend the data that is indexed by implementing a `blockHandler` method, similar to the built-in [Address Service](address.md).
## Adding Indexes
For a service to include additional block data, it can implement a `blockHandler` method that will be run to when there are new blocks added or removed.
```js
CustomService.prototype.blockHandler = function(block, add, callback) {
var transactions = block.transactions;
var operations = [];
operations.push({
type: add ? 'put' : 'del',
key: 'key',
value: 'value'
});
callback(null, operations);
};
```
Take a look at the Address Service implementation for more details about how to encode the key, value for the best effeciency 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
Get Transaction
These methods are exposed over the JSON-RPC interface and can be called directly from a node via:
```js
var txid = 'c349b124b820fe6e32136c30e99f6c4f115fce4d750838edf0c46d3cb4d7281e';
var includeMempool = true;
node.getTransaction(txid, includeMempool, function(err, transaction) {
//...
});
node.services.db.<methodName>
```
Get Transaction with Block Info
```js
var txid = 'c349b124b820fe6e32136c30e99f6c4f115fce4d750838edf0c46d3cb4d7281e';
var includeMempool = true;
node.getTransactionWithBlockInfo(txid, includeMempool, function(err, transaction) {
//...
});
```
Get Block
```js
var blockHash = '00000000d17332a156a807b25bc5a2e041d2c730628ceb77e75841056082a2c2';
node.getBlock(blockHash, function(err, block) {
//...
});
```
Get Block Hashes by Timestamp Range
**Query Blocks by Date**
One of the additional indexes created by the Database Service is querying for blocks by ranges of dates:
```js
var newest = 1441914000; // Notice time is in seconds not milliseconds
var oldest = 1441911000;
node.getBlockHashesByTimestamp(newest, oldest, function(err, hashes) {
//...
node.services.db.getBlockHashesByTimestamp(newest, oldest, function(err, hashes) {
// hashes will be an array of block hashes
});
```
**Working with Blocks and Transactions as Bitcore Instances**
```js
var txid = 'c349b124b820fe6e32136c30e99f6c4f115fce4d750838edf0c46d3cb4d7281e';
var includeMempool = true;
node.services.db.getTransaction(txid, includeMempool, function(err, transaction) {
console.log(transaction.toObject());
});
var txid = 'c349b124b820fe6e32136c30e99f6c4f115fce4d750838edf0c46d3cb4d7281e';
var includeMempool = true;
node.services.db.getTransactionWithBlockInfo(txid, includeMempool, function(err, transaction) {
console.log(transaction.toObject());
console.log(transaction.__blockHash);
console.log(transaction.__height);
console.log(transaction.__timestamp);
});
var blockHash = '00000000d17332a156a807b25bc5a2e041d2c730628ceb77e75841056082a2c2';
node.services.db.getBlock(blockHash, function(err, block) {
console.log(block.toObject());
});
// contruct a transaction
var transaction = bitcore.Transaction(<serializedString>);
node.services.db.sendTransaction(transaction, function(err) {
if (err) {
throw err;
}
// otherwise the transaction has been sent
});
```
## Events
For details on instantiating a bus for a node, see the [Bus Documentation](../bus.md).
- Name: `db/transaction`
- Name: `db/block`
**Examples:**
```js
bus.subscribe('db/transaction');
bus.subscribe('db/block');
bus.on('db/block', function(blockHash) {
// blockHash will be a hex string of the block hash
});
bus.on('db/transaction', function(txInfo) {
// see below
});
```
The `txInfo` object will have the format:
```js
{
rejected: true, // If the transaction was rejected into the mempool
tx: <Transaction> // a Bitcore Transaction instance
}
```

View File

@ -231,7 +231,7 @@ describe('Node Functionality', function() {
var bus = node.openBus();
var block;
bus.subscribe('db/block');
bus.on('block', function(data) {
bus.on('db/block', function(data) {
bus.unsubscribe('db/block');
data.should.be.equal(block);
done();
@ -303,7 +303,6 @@ describe('Node Functionality', function() {
if (err) {
throw err;
}
results.totalReceived.should.equal(1000000000);
results.totalSpent.should.equal(0);
results.balance.should.equal(1000000000);

View File

@ -446,4 +446,19 @@ describe('Daemon Binding Functionality', function() {
});
});
describe('#getInfo', function() {
it('will get information', function() {
var info = bitcoind.getInfo();
should.exist(info);
should.exist(info.version);
should.exist(info.blocks);
should.exist(info.timeoffset);
should.exist(info.connections);
should.exist(info.difficulty);
should.exist(info.testnet);
should.exist(info.relayfee);
should.exist(info.errors);
});
});
});

View File

@ -3,6 +3,13 @@
var events = require('events');
var util = require('util');
/**
* The bus represents a connection to node, decoupled from the transport layer, that can
* listen and subscribe to any events that are exposed by available services. Services
* can expose events that can be subscribed to by implementing a `getPublishEvents` method.
* @param {Object} params
* @param {Node} params.node - A reference to the node
*/
function Bus(params) {
events.EventEmitter.call(this);
this.node = params.node;
@ -10,6 +17,12 @@ function Bus(params) {
util.inherits(Bus, events.EventEmitter);
/**
* This function will find the service that exposes the event by name and
* call the associated subscribe method with the arguments excluding the
* first argument of this function.
* @param {String} name - The name of the event
*/
Bus.prototype.subscribe = function(name) {
var events = [];
@ -28,6 +41,10 @@ Bus.prototype.subscribe = function(name) {
}
};
/**
* The inverse of the subscribe method.
* @param {String} name - The name of the event
*/
Bus.prototype.unsubscribe = function(name) {
var events = [];
@ -46,6 +63,9 @@ Bus.prototype.unsubscribe = function(name) {
}
};
/**
* This function will unsubscribe all events.
*/
Bus.prototype.close = function() {
var events = [];

View File

@ -12,12 +12,34 @@ var log = index.log;
var Bus = require('./bus');
var errors = require('./errors');
/**
* A node is a hub of services, and will manage starting and stopping the services in
* the correct order based the the dependency chain. The node also holds common configuration
* properties that can be shared across services, such as network settings.
*
* The array of services should have the format:
* {
* name: 'bitcoind',
* config: {}, // options to pass into constructor
* module: <serviceConstructor>
* }
*
* @param {Object} config - The configuration of the node
* @param {Array} config.services - The array of services
* @param {String} config.datadir - The directory for data (e.g. bitcoind datadir)
* @param {Number} config.port - The HTTP port for services
* @param {Boolean} config.https - Enable https
* @param {Object} config.httpsOptions - Options for https
* @param {String} config.httpsOptions.key - Path to key file
* @param {String} config.httpsOptions.cert - Path to cert file
* @param {}
*/
function Node(config) {
/* jshint maxstatements: 20 */
if(!(this instanceof Node)) {
return new Node(config);
}
this.errors = errors; // So services can use errors without having to have bitcore-node as a dependency
this.errors = errors;
this.log = log;
this.network = null;
this.services = {};
@ -28,19 +50,21 @@ function Node(config) {
$.checkArgument(Array.isArray(config.services));
this._unloadedServices = config.services;
}
$.checkState(config.datadir, 'Node config expects "datadir"');
this.datadir = config.datadir;
this.port = config.port;
this.https = config.https;
this.httpsOptions = config.httpsOptions;
this._setNetwork(config);
}
util.inherits(Node, EventEmitter);
/**
* Will set the this.network based on a network string.
* @param {Object} config
* @param {String} config.network - Possible options "testnet", "regtest" or "livenet"
*/
Node.prototype._setNetwork = function(config) {
if (config.network === 'testnet') {
this.network = Networks.get('testnet');
@ -65,10 +89,18 @@ Node.prototype._setNetwork = function(config) {
$.checkState(this.network, 'Unrecognized network');
};
/**
* Will instantiate a new Bus for this node.
* @returns {Bus}
*/
Node.prototype.openBus = function() {
return new Bus({node: this});
};
/**
* Will get an array of API method descriptions from all of the available services.
* @returns {Array}
*/
Node.prototype.getAllAPIMethods = function() {
var methods = [];
for(var i in this.services) {
@ -78,6 +110,10 @@ Node.prototype.getAllAPIMethods = function() {
return methods;
};
/**
* Will get an array of events from all of the available services.
* @returns {Array}
*/
Node.prototype.getAllPublishEvents = function() {
var events = [];
for (var i in this.services) {
@ -87,6 +123,11 @@ Node.prototype.getAllPublishEvents = function() {
return events;
};
/**
* Will organize services into the order that they should be started
* based on the service's dependencies.
* @returns {Array}
*/
Node.prototype.getServiceOrder = function() {
var services = this._unloadedServices;
@ -127,6 +168,17 @@ Node.prototype.getServiceOrder = function() {
return stack;
};
/**
* Will instantiate an instance of the service module, add it to the node
* services, start the service and add available API methods to the node and
* checking for any conflicts.
* @param {Object} serviceInfo
* @param {String} serviceInfo.name - The name of the service
* @param {Object} serviceInfo.module - The service module constructor
* @param {Object} serviceInfo.config - Options to pass into the constructor
* @param {Function} callback - Called when the service is started
* @private
*/
Node.prototype._startService = function(serviceInfo, callback) {
var self = this;
@ -176,6 +228,10 @@ Node.prototype._startService = function(serviceInfo, callback) {
};
/**
* Will start all running services in the order based on the dependency chain.
* @param {Function} callback - Called when all services are started
*/
Node.prototype.start = function(callback) {
var self = this;
var servicesOrder = this.getServiceOrder();
@ -195,6 +251,11 @@ Node.prototype.start = function(callback) {
);
};
/**
* Will stop all running services in the reverse order that they
* were initially started.
* @param {Function} callback - Called when all services are stopped
*/
Node.prototype.stop = function(callback) {
log.info('Beginning shutdown');
var self = this;

View File

@ -8,8 +8,7 @@ var _ = bitcore.deps._;
* This represents an instance that keeps track of data over a series of
* asynchronous I/O calls to get the transaction history for a group of
* addresses. History can be queried by start and end block heights to limit large sets
* of results (uses leveldb key streaming). See AddressService.prototype.getAddressHistory
* for complete documentation about options.
* of results (uses leveldb key streaming).
*/
function AddressHistory(args) {
this.node = args.node;
@ -27,13 +26,15 @@ function AddressHistory(args) {
AddressHistory.MAX_ADDRESS_QUERIES = 20;
/**
* This function will give detailed history for the configured
* addresses. See AddressService.prototype.getAddressHistory
* for complete documentation about options and response format.
*/
AddressHistory.prototype.get = function(callback) {
var self = this;
var totalCount;
// TODO check for mempool inputs and outputs by a group of addresses, currently
// each address individually loops through the mempool and does not check input scripts.
async.eachLimit(
self.addresses,
AddressHistory.MAX_ADDRESS_QUERIES,
@ -68,6 +69,12 @@ AddressHistory.prototype.get = function(callback) {
);
};
/**
* This function will retrieve input and output information for an address
* and set the property `this.transactionInfo`.
* @param {String} address - A base58check encoded address
* @param {Function} next
*/
AddressHistory.prototype.getTransactionInfo = function(address, next) {
var self = this;
@ -109,9 +116,8 @@ AddressHistory.prototype.getTransactionInfo = function(address, next) {
};
/**
* This function combines results from getInputs and getOutputs by
* combining inputIndexes and outputIndexes for address transaction
* matching combinations.
* This function combines results from getInputs and getOutputs at
* `this.transactionInfo` to be "txid" unique at `this.combinedArray`.
*/
AddressHistory.prototype.combineTransactionInfo = function() {
var combinedArrayMap = {};
@ -154,6 +160,9 @@ AddressHistory.prototype.combineTransactionInfo = function() {
}
};
/**
* A helper function to sort and slice/paginate the `combinedArray`
*/
AddressHistory.prototype.sortAndPaginateCombinedArray = function() {
this.combinedArray.sort(AddressHistory.sortByHeight);
if (!_.isUndefined(this.options.from) && !_.isUndefined(this.options.to)) {
@ -161,6 +170,12 @@ AddressHistory.prototype.sortAndPaginateCombinedArray = function() {
}
};
/**
* A helper sort function to order by height and then by date
* for transactions that are in the mempool.
* @param {Object} a - An item from the `combinedArray`
* @param {Object} b
*/
AddressHistory.sortByHeight = function(a, b) {
if (a.height < 0 && b.height < 0) {
// Both are from the mempool, compare timestamps
@ -184,6 +199,12 @@ AddressHistory.sortByHeight = function(a, b) {
}
};
/**
* This function will transform items from the combinedArray into
* the detailedArray with the full transaction, satoshis and confirmation.
* @param {Object} txInfo - An item from the `combinedArray`
* @param {Function} next
*/
AddressHistory.prototype.getDetailedInfo = function(txInfo, next) {
var self = this;
var queryMempool = _.isUndefined(self.options.queryMempool) ? true : self.options.queryMempool;
@ -218,6 +239,10 @@ AddressHistory.prototype.getDetailedInfo = function(txInfo, next) {
);
};
/**
* A helper function for `getDetailedInfo` for getting the confirmations.
* @param {Transaction} transaction - A transaction with a populated __height value.
*/
AddressHistory.prototype.getConfirmationsDetail = function(transaction) {
var confirmations = 0;
if (transaction.__height >= 0) {
@ -226,6 +251,11 @@ AddressHistory.prototype.getConfirmationsDetail = function(transaction) {
return confirmations;
};
/**
* A helper function for `getDetailedInfo` for getting the satoshis.
* @param {Transaction} transaction - A transaction populated with previous outputs
* @param {Object} txInfo - An item from `combinedArray`
*/
AddressHistory.prototype.getSatoshisDetail = function(transaction, txInfo) {
var satoshis = txInfo.satoshis || 0;

View File

@ -17,6 +17,14 @@ var PublicKey = bitcore.PublicKey;
var Address = bitcore.Address;
var AddressHistory = require('./history');
/**
* The Address Service builds upon the Database Service and the Bitcoin Service to add additional
* functionality for getting information by base58check encoded addresses. This includes getting the
* balance for an address, the history for a collection of addresses, and unspent outputs for
* contstructing transactions. This is typically the core functionality for building a wallet.
* @param {Node} node - An instance of the node
* @param {String} [name] - An optional name of the service
*/
var AddressService = function(options) {
BaseService.call(this, options);
@ -47,6 +55,10 @@ AddressService.PREFIXES = {
AddressService.SPACER_MIN = new Buffer('00', 'hex');
AddressService.SPACER_MAX = new Buffer('ff', 'hex');
/**
* Called by the Node to get the available API methods for this service,
* that can be exposed over the JSON-RPC interface.
*/
AddressService.prototype.getAPIMethods = function() {
return [
['getBalance', this, this.getBalance, 2],
@ -58,6 +70,9 @@ AddressService.prototype.getAPIMethods = function() {
];
};
/**
* Called by the Bus to get the available events for this service.
*/
AddressService.prototype.getPublishEvents = function() {
return [
{
@ -114,8 +129,8 @@ AddressService.prototype.transactionOutputHandler = function(messages, tx, outpu
/**
* This will handle data from the daemon "tx" event, go through each of the outputs
* and send messages to any subscribers for a particular address.
*
* and send messages by calling `transactionEventHandler` to any subscribers for a
* particular address.
* @param {Object} txInfo - The data from the daemon.on('tx') event
* @param {Buffer} txInfo.buffer - The transaction buffer
* @param {Boolean} txInfo.mempool - If the transaction was accepted in the mempool
@ -144,7 +159,27 @@ AddressService.prototype.transactionHandler = function(txInfo) {
}
};
/**
* This function will update the mempool address index with the necessary
* information for further lookups. There are three indexes:
*
* 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 addreses with values:
* txid - A hex string of the transaction hash
* inputIndex - A number of the corresponding input
*
* mempoolSpentIndex, an object keyed by <prevTxId>-<outputIndex>
*
* @param {Transaction} - An instance of a Bitcore Transaction
*/
AddressService.prototype.updateMempoolIndex = function(tx) {
/* jshint maxstatements: 30 */
var outputLength = tx.outputs.length;
for (var outputIndex = 0; outputIndex < outputLength; outputIndex++) {
var output = tx.outputs[outputIndex];
@ -199,6 +234,12 @@ AddressService.prototype.updateMempoolIndex = function(tx) {
};
/**
* 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();
@ -217,6 +258,12 @@ AddressService.prototype.resetMempoolIndex = function(callback) {
});
};
/**
* This function is optimized to return address information about an output script
* without constructing a Bitcore Address instance.
* @param {Script} - An instance of a Bitcore Script
* @private
*/
AddressService.prototype._extractAddressInfoFromScript = function(script) {
var hashBuffer;
var addressType;
@ -242,6 +289,13 @@ AddressService.prototype._extractAddressInfoFromScript = function(script) {
};
};
/**
* The Database Service will run this function when blocks are connected and
* disconnected to the chain during syncing and reorganizations.
* @param {Block} block - An instance of a Bitcore Block
* @param {Boolean} addOutput - If the block is being removed or added to the chain
* @param {Function} callback
*/
AddressService.prototype.blockHandler = function(block, addOutput, callback) {
var txs = block.transactions;
var height = block.__height;
@ -450,6 +504,8 @@ AddressService.prototype._decodeInputValue = function(buffer) {
};
/**
* This function is responsible for emitting events to any subscribers to the
* `address/transaction` event.
* @param {Object} obj
* @param {Transaction} obj.tx - The transaction
* @param {Object} obj.addressInfo
@ -485,6 +541,8 @@ AddressService.prototype.transactionEventHandler = function(obj) {
};
/**
* The function is responsible for emitting events to any subscribers for the
* `address/balance` event.
* @param {Block} block
* @param {Object} obj
* @param {String} obj.hashHex
@ -510,6 +568,14 @@ AddressService.prototype.balanceEventHandler = function(block, obj) {
}
};
/**
* The Bus will use this function to subscribe to the available
* events for this service. For information about the available events
* please see `getPublishEvents`.
* @param {String} name - The name of the event
* @param {EventEmitter} emitter - An event emitter instance
* @param {Array} addresses - An array of addresses to subscribe
*/
AddressService.prototype.subscribe = function(name, emitter, addresses) {
$.checkArgument(emitter instanceof EventEmitter, 'First argument is expected to be an EventEmitter');
$.checkArgument(Array.isArray(addresses), 'Second argument is expected to be an Array of addresses');
@ -523,6 +589,13 @@ AddressService.prototype.subscribe = function(name, emitter, addresses) {
}
};
/**
* The Bus will use this function to unsubscribe to the available
* events for this service.
* @param {String} name - The name of the event
* @param {EventEmitter} emitter - An event emitter instance
* @param {Array} addresses - An array of addresses to subscribe
*/
AddressService.prototype.unsubscribe = function(name, emitter, addresses) {
$.checkArgument(emitter instanceof EventEmitter, 'First argument is expected to be an EventEmitter');
$.checkArgument(Array.isArray(addresses) || _.isUndefined(addresses), 'Second argument is expected to be an Array of addresses or undefined');
@ -543,6 +616,11 @@ AddressService.prototype.unsubscribe = function(name, emitter, addresses) {
}
};
/**
* A helper function for the `unsubscribe` method to unsubscribe from all addresses.
* @param {String} name - The name of the event
* @param {EventEmitter} emiter - An instance of an event emitter
*/
AddressService.prototype.unsubscribeAll = function(name, emitter) {
$.checkArgument(emitter instanceof EventEmitter, 'First argument is expected to be an EventEmitter');
@ -555,6 +633,13 @@ AddressService.prototype.unsubscribeAll = function(name, emitter) {
}
};
/**
* Will sum the total of all unspent outputs to calculate the balance
* for an address.
* @param {String} address - The base58check encoded address
* @param {Boolean} queryMempool - Include mempool in the results
* @param {Function} callback
*/
AddressService.prototype.getBalance = function(address, queryMempool, callback) {
this.getUnspentOutputs(address, queryMempool, function(err, outputs) {
if(err) {
@ -574,6 +659,13 @@ AddressService.prototype.getBalance = function(address, queryMempool, callback)
};
/**
* Will give inputs that spend previous outputs for an address as an object with:
* address - The base58check encoded address
* txid - A string of the transaction hash
* outputIndex - A number of corresponding transaction input
* height - The height of the block the transaction was included, will be -1 for mempool transactions
* confirmations - The number of confirmations, will equal 0 for mempool transactions
*
* @param {String} addressStr - The relevant address
* @param {Object} options - Additional options for query the outputs
* @param {Number} [options.start] - The relevant start block height
@ -677,6 +769,15 @@ AddressService.prototype.getInputs = function(addressStr, options, callback) {
};
/**
* Will give outputs for an address as an object with:
* address - The base58check encoded address
* txid - A string of the transaction hash
* outputIndex - A number of corresponding transaction output
* height - The height of the block the transaction was included, will be -1 for mempool transactions
* satoshis - The satoshis value of the output
* script - The script of the output as a hex string
* confirmations - The number of confirmations, will equal 0 for mempool transactions
*
* @param {String} addressStr - The relevant address
* @param {Object} options - Additional options for query the outputs
* @param {Number} [options.start] - The relevant start block height
@ -779,6 +880,12 @@ AddressService.prototype.getOutputs = function(addressStr, options, callback) {
};
/**
* Will give unspent outputs for an address or an array of addresses.
* @param {Array|String} addresses - An array of addresses
* @param {Boolean} queryMempool - Include or exclude the mempool
* @param {Function} callback
*/
AddressService.prototype.getUnspentOutputs = function(addresses, queryMempool, callback) {
var self = this;
@ -804,6 +911,12 @@ AddressService.prototype.getUnspentOutputs = function(addresses, queryMempool, c
});
};
/**
* Will give unspent outputs for an address.
* @param {String} address - An address in base58check encoding
* @param {Boolean} queryMempool - Include or exclude the mempool
* @param {Function} callback
*/
AddressService.prototype.getUnspentOutputsForAddress = function(address, queryMempool, callback) {
var self = this;
@ -816,7 +929,7 @@ AddressService.prototype.getUnspentOutputsForAddress = function(address, queryMe
}
var isUnspent = function(output, callback) {
self.isUnspent(output, queryMempool, callback);
self.isUnspent(output, callback);
};
async.filter(outputs, isUnspent, function(results) {
@ -825,13 +938,23 @@ AddressService.prototype.getUnspentOutputsForAddress = function(address, queryMe
});
};
AddressService.prototype.isUnspent = function(output, queryMempool, callback) {
this.isSpent(output, queryMempool, function(spent) {
/**
* Will give the inverse of isSpent
* @param {Object} output
* @param {Function} callback
*/
AddressService.prototype.isUnspent = function(output, callback) {
this.isSpent(output, function(spent) {
callback(!spent);
});
};
AddressService.prototype.isSpent = function(output, queryMempool, callback) {
/**
* Will determine if an output is spent, results do not include the mempool.
* @param {Object} output - An output as returned from getOutputs
* @param {Function} callback
*/
AddressService.prototype.isSpent = function(output, callback) {
var self = this;
var txid = output.prevTxId ? output.prevTxId.toString('hex') : output.txid;
@ -883,18 +1006,18 @@ AddressService.prototype.getAddressHistory = function(addresses, options, callba
};
/**
* This will return an object with:
* This will give an object with:
* balance - confirmed balance
* unconfirmedBalance - unconfirmed balance
* totalReceived - satoshis received
* totalSpent - satoshis spent
* appearances - number of times used in confirmed transactions
* unconfirmedAppearances - number of times used in unconfirmed transactions
* appearances - number of transactions
* unconfirmedAppearances - number of unconfirmed transactions
* txids - list of txids (unless noTxList is set)
*
* @param {String} address
* @param {Object} options
* @param {Boolean} options.noTxList - if set, txid array will not be included
* @param {Boolean} [options.noTxList] - if set, txid array will not be included
* @param {Function} callback
*/
AddressService.prototype.getAddressSummary = function(address, options, callback) {

View File

@ -4,18 +4,16 @@ var fs = require('fs');
var util = require('util');
var bindings = require('bindings')('bitcoind.node');
var mkdirp = require('mkdirp');
var async = require('async');
var bitcore = require('bitcore');
var Transaction = require('../transaction');
var $ = bitcore.util.preconditions;
var index = require('../');
var log = index.log;
var Service = require('../service');
/**
* Provides an interface to native bindings to Bitcoin Core
* Provides an interface to native bindings to Bitcoin Core compiled as a static library.
* The C++ bindings can be found at src/libbitcoind.cc
* @param {Object} options
* @param {String} options.datadir - The bitcoin data directory
* @param {Node} options.node - A reference to the node
*/
function Bitcoin(options) {
@ -140,6 +138,10 @@ Bitcoin.prototype._onReady = function(result, callback) {
};
/**
* Called by Node to start the service
* @param {Function} callback
*/
Bitcoin.prototype.start = function(callback) {
var self = this;
@ -176,70 +178,183 @@ Bitcoin.prototype.start = function(callback) {
});
};
/**
* Helper to determine the state of the database.
* @returns {Boolean} If the database is fully synced
*/
Bitcoin.prototype.isSynced = function() {
return bindings.isSynced();
};
/**
* Helper to determine the progress of the database.
* @returns {Number} An estimated percentage of the syncronization status
*/
Bitcoin.prototype.syncPercentage = function() {
return bindings.syncPercentage();
};
Bitcoin.prototype.getBlock = function(blockhash, callback) {
return bindings.getBlock(blockhash, callback);
/**
* Will retreive a block as a Node.js Buffer from disk.
* @param {String|Number} block - A block hash or block height number
*/
Bitcoin.prototype.getBlock = function(block, callback) {
return bindings.getBlock(block, callback);
};
/**
* Will return the spent status of an output (not including the mempool)
* @param {String} txid - The transaction hash
* @param {Number} outputIndex - The output index in the transaction
* @returns {Boolean} If the output has been spent
*/
Bitcoin.prototype.isSpent = function(txid, outputIndex) {
return bindings.isSpent(txid, outputIndex);
};
Bitcoin.prototype.getBlockIndex = function(blockHash) {
return bindings.getBlockIndex(blockHash);
/**
* Will return the block index information, the output will have the format:
* {
* prevHash: '7194fcf33f58c96720f88f21ab28c34ebc5638c5f88d7838517deb27313b59de',
* hash: '7c5caf0af1bf16e3467b275a3b408bc1d251bff3c25be20cb727c47b66a7b216',
* chainWork: '0000000000000000000000000000000000000000000000000000000000000016',
* height: 10
* }
* @param {String|Number} block - A block hash or block height
* @returns {Object}
*/
Bitcoin.prototype.getBlockIndex = function(block) {
return bindings.getBlockIndex(block);
};
/**
* Will return if the block is a part of the main chain.
* @param {String} blockHash
* @returns {Boolean}
*/
Bitcoin.prototype.isMainChain = function(blockHash) {
return bindings.isMainChain(blockHash);
};
/**
* Will estimate the fee per kilobyte.
* @param {Number} blocks - The number of blocks for the transaction to be confirmed.
* @returns {Number}
*/
Bitcoin.prototype.estimateFee = function(blocks) {
return bindings.estimateFee(blocks);
};
/**
* Will add a transaction to the mempool and relay to connected peers, the function
* will throw an error if there were validation problems.
* @param {String} transaction - The hex string of the transaction
* @param {Boolean} allowAbsurdFees - Enable large fees
*/
Bitcoin.prototype.sendTransaction = function(transaction, allowAbsurdFees) {
return bindings.sendTransaction(transaction, allowAbsurdFees);
};
/**
* Will get a transaction as a Node.js Buffer from disk and the mempool.
* @param {String} txid - The transaction hash
* @param {Boolean} queryMempool - Include the mempool
* @param {Function} callback
*/
Bitcoin.prototype.getTransaction = function(txid, queryMempool, callback) {
return bindings.getTransaction(txid, queryMempool, callback);
};
/**
* Will get a transation with additional information about the block, in the format:
* {
* blockHash: '2725743288feae6bdaa976590af7cb12d7b535b5a242787de6d2789c73682ed1',
* height: 48,
* timestamp: 1442951110, // in seconds
* buffer: <Buffer...> // transaction buffer
* }
* @param {String} txid - The transaction hash
* @param {Boolean} queryMempool - Include the mempool
* @param {Function} callback
*/
Bitcoin.prototype.getTransactionWithBlockInfo = function(txid, queryMempool, callback) {
return bindings.getTransactionWithBlockInfo(txid, queryMempool, callback);
};
/**
* Will return the entire mempool as an Array of transaction Buffers.
* @returns {Array}
*/
Bitcoin.prototype.getMempoolTransactions = function() {
return bindings.getMempoolTransactions();
};
Bitcoin.prototype.addMempoolUncheckedTransaction = function(txBuffer) {
return bindings.addMempoolUncheckedTransaction(txBuffer);
/**
* Will add a transaction to the mempool without any validation. This is used
* exclusively for testing purposes.
* @param {String} transaction - The hex string for the transaction
*/
Bitcoin.prototype.addMempoolUncheckedTransaction = function(transaction) {
return bindings.addMempoolUncheckedTransaction(transaction);
};
/**
* Will get the best block hash for the chain.
* @returns {String}
*/
Bitcoin.prototype.getBestBlockHash = function() {
return bindings.getBestBlockHash();
};
/**
* Will get the next block hash for a block hash.
* @param {String} hash - The starting block hash
* @returns {String}
*/
Bitcoin.prototype.getNextBlockHash = function(hash) {
return bindings.getNextBlockHash(hash);
};
/**
* This query is expensive and can take quite a while to return. It will give
* statistics about the database, in the format:
* {
* height: 151,
* bestblock: '085241174bf5c9d606e591b3cbf52f9306f3d6aca50ea77604ca898472dc4669',
* transactions: 151,
* txouts: 151,
* bytes_serialized: 10431,
* hash_serialized: 'b1e42afb98d0d9f1978c048648685590a7ef0be46f83f509fc0d0668d6a39d8c',
* total_amount: 750000000000
* }
* @returns {Object}
*/
Bitcoin.prototype.getTxOutSetInfo = function() {
return bindings.getTxOutSetInfo();
};
/**
* This will return information about the database in the format:
* {
* version: 110000,
* protocolversion: 70002,
* blocks: 151,
* timeoffset: 0,
* connections: 0,
* difficulty: 4.6565423739069247e-10,
* testnet: false,
* relayfee: 1000,
* errors: ''
* }
*/
Bitcoin.prototype.getInfo = function() {
return bindings.getInfo();
};
/**
* Called by Node to stop the service.
* @param {Function} callback
*/
Bitcoin.prototype.stop = function(callback) {
return bindings.stop(function(err, status) {
if (err) {

View File

@ -18,12 +18,13 @@ var Transaction = require('../transaction');
var Service = require('../service');
/**
* Represents the current state of the bitcoin blockchain. Other services
* can extend the data that is indexed by implementing a `blockHandler` method.
* This service synchronizes a leveldb database with bitcoin block chain by connecting and
* disconnecting blocks to build new indexes that can be queried. Other services can extend
* the data that is indexed by implementing a `blockHandler` method.
*
* @param {Object} options
* @param {String} options.datadir - The bitcoin data directory
* @param {Node} options.node - A reference to the node
* @param {Node} options.store - A levelup backend store
*/
function DB(options) {
/* jshint maxstatements: 20 */
@ -64,6 +65,10 @@ DB.PREFIXES = {
BLOCKS: new Buffer('01', 'hex')
};
/**
* This function will set `this.dataPath` based on `this.node.network`.
* @private
*/
DB.prototype._setDataPath = function() {
$.checkState(this.node.datadir, 'Node is expected to have a "datadir" property');
var regtest = Networks.get('regtest');
@ -78,6 +83,10 @@ DB.prototype._setDataPath = function() {
}
};
/**
* Called by Node to start the service.
* @param {Function} callback
*/
DB.prototype.start = function(callback) {
var self = this;
@ -140,9 +149,12 @@ DB.prototype.start = function(callback) {
});
}
});
};
/**
* Called by Node to stop the service
* @param {Function} callback
*/
DB.prototype.stop = function(callback) {
var self = this;
@ -156,6 +168,10 @@ DB.prototype.stop = function(callback) {
});
};
/**
* Will give information about the database from bitcoin.
* @param {Function} callback
*/
DB.prototype.getInfo = function(callback) {
var self = this;
setImmediate(function() {
@ -166,22 +182,32 @@ DB.prototype.getInfo = function(callback) {
/**
* Closes the underlying store database
* @param {Function} callback - A function that accepts: Error
* @param {Function} callback
*/
DB.prototype.close = function(callback) {
this.store.close(callback);
};
/**
* This function is responsible for emitting `db/transaction` events.
* @param {Object} txInfo - The data from the bitcoind.on('tx') event
* @param {Buffer} txInfo.buffer - The transaction buffer
* @param {Boolean} txInfo.mempool - If the transaction was accepted in the mempool
* @param {String} txInfo.hash - The hash of the transaction
*/
DB.prototype.transactionHandler = function(txInfo) {
var tx = Transaction().fromBuffer(txInfo.buffer);
for (var i = 0; i < this.subscriptions.transaction.length; i++) {
this.subscriptions.transaction[i].emit('transaction', {
this.subscriptions.transaction[i].emit('db/transaction', {
rejected: !txInfo.mempool,
tx: tx
});
}
};
/**
* Called by Node to determine the available API methods.
*/
DB.prototype.getAPIMethods = function() {
var methods = [
['getBlock', this, this.getBlock, 1],
@ -194,17 +220,21 @@ DB.prototype.getAPIMethods = function() {
return methods;
};
/**
* Will get a block from bitcoind and give a Bitcore Block
* @param {String|Number} hash - A block hash or block height
*/
DB.prototype.getBlock = function(hash, callback) {
this.node.services.bitcoind.getBlock(hash, function(err, blockData) {
this.node.services.bitcoind.getBlock(hash, function(err, blockBuffer) {
if (err) {
return callback(err);
}
callback(null, Block.fromBuffer(blockData));
callback(null, Block.fromBuffer(blockBuffer));
});
};
/**
* get block hashes between two timestamps
* Get block hashes between two timestamps
* @param {Number} high - high timestamp, in seconds, inclusive
* @param {Number} low - low timestamp, in seconds, inclusive
* @param {Function} callback
@ -212,10 +242,12 @@ DB.prototype.getBlock = function(hash, callback) {
DB.prototype.getBlockHashesByTimestamp = function(high, low, callback) {
var self = this;
var hashes = [];
var lowKey;
var highKey;
try {
var lowKey = this._encodeBlockIndexKey(low);
var highKey = this._encodeBlockIndexKey(high);
lowKey = this._encodeBlockIndexKey(low);
highKey = this._encodeBlockIndexKey(high);
} catch(e) {
return callback(e);
}
@ -244,13 +276,18 @@ DB.prototype.getBlockHashesByTimestamp = function(high, low, callback) {
if (error) {
return callback(error);
}
callback(null, hashes);
});
return stream;
};
/**
* Will give a Bitcore Transaction from bitcoind by txid
* @param {String} txid - A transaction hash
* @param {Boolean} queryMempool - Include the mempool
* @param {Function} callback
*/
DB.prototype.getTransaction = function(txid, queryMempool, callback) {
this.node.services.bitcoind.getTransaction(txid, queryMempool, function(err, txBuffer) {
if (err) {
@ -264,6 +301,12 @@ DB.prototype.getTransaction = function(txid, queryMempool, callback) {
});
};
/**
* Will give a Bitcore Transaction and populated information about the block included.
* @param {String} txid - A transaction hash
* @param {Boolean} queryMempool - Include the mempool
* @param {Function} callback
*/
DB.prototype.getTransactionWithBlockInfo = function(txid, queryMempool, callback) {
this.node.services.bitcoind.getTransactionWithBlockInfo(txid, queryMempool, function(err, obj) {
if (err) {
@ -279,20 +322,34 @@ DB.prototype.getTransactionWithBlockInfo = function(txid, queryMempool, callback
});
};
/**
* Will send a transaction to the Bitcoin network.
* @param {Transaction} tx - An instance of a Bitcore Transaction
* @param {Function} callback
*/
DB.prototype.sendTransaction = function(tx, callback) {
var txString;
if (tx instanceof Transaction) {
tx = tx.toString();
txString = tx.serialize();
} else {
txString = tx;
}
$.checkArgument(typeof tx === 'string', 'Argument must be a hex string or Transaction');
$.checkArgument(typeof txString === 'string', 'Argument must be a hex string or Transaction');
try {
var txid = this.node.services.bitcoind.sendTransaction(tx);
var txid = this.node.services.bitcoind.sendTransaction(txString);
return callback(null, txid);
} catch(err) {
return callback(err);
}
};
/**
* Will estimate fees for a transaction and give a result in
* satoshis per kilobyte. Similar to the bitcoind estimateFee method.
* @param {Number} blocks - The number of blocks for the transaction to be included.
* @param {Function} callback
*/
DB.prototype.estimateFee = function(blocks, callback) {
var self = this;
setImmediate(function() {
@ -300,6 +357,9 @@ DB.prototype.estimateFee = function(blocks, callback) {
});
};
/**
* Called by the Bus to determine the available events.
*/
DB.prototype.getPublishEvents = function() {
return [
{
@ -412,6 +472,10 @@ DB.prototype.disconnectBlock = function(block, 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,
@ -427,8 +491,8 @@ DB.prototype.runAllMempoolIndexes = function(callback) {
};
/**
* Will collect all database operations for a block from other services
* and save to the database.
* Will collect all database operations for a block from other services that implement
* `blockHandler` methods and then save operations to the database.
* @param {Block} block - The bitcore block
* @param {Boolean} add - If the block is being added/connected or removed/disconnected
* @param {Function} callback
@ -439,7 +503,7 @@ DB.prototype.runAllBlockHandlers = function(block, add, callback) {
// Notify block subscribers
for (var i = 0; i < this.subscriptions.block.length; i++) {
this.subscriptions.block[i].emit('block', block.hash);
this.subscriptions.block[i].emit('db/block', block.hash);
}
// Update block index
@ -497,7 +561,7 @@ DB.prototype._decodeBlockIndexValue = function(value) {
/**
* This function will find the common ancestor between the current chain and a forked block,
* by moving backwards from the forked block until it meets the current chain.
* by moving backwards on both chains until there is a meeting point.
* @param {Block} block - The new tip that forks the current chain.
* @param {Function} done - A callback function that is called when complete.
*/

View File

@ -11,6 +11,20 @@ var index = require('../');
var log = index.log;
var fs = require('fs');
/**
* This service respresents a hub for combining several services over a single HTTP port. Services
* can extend routes by implementing the methods `getRoutePrefix` and `setupRoutes`. Additionally
* events that are exposed via the `getPublishEvents` and API methods exposed via `getAPIMethods`
* will be available over a socket.io connection.
*
* @param {Object} options
* @param {Node} options.node - A reference to the node
* @param {Boolean} options.https - Enable https, will default to node.https settings.
* @param {Object} options.httpsOptions - Options passed into https.createServer, defaults to node settings.
* @param {String} options.httpsOptions.key - Path to key file
* @param {String} options.httpsOptions.cert - Path to cert file
* @param {Number} options.port - The port for the service, defaults to node settings.
*/
var WebService = function(options) {
var self = this;
this.node = options.node;
@ -30,8 +44,11 @@ inherits(WebService, BaseService);
WebService.dependencies = [];
/**
* Called by Node to start the service
* @param {Function} callback
*/
WebService.prototype.start = function(callback) {
var self = this;
this.app = express();
this.app.use(bodyParser.json());
@ -48,6 +65,10 @@ WebService.prototype.start = function(callback) {
setImmediate(callback);
};
/**
* Called by Node. stop the service
* @param {Function} callback
*/
WebService.prototype.stop = function(callback) {
var self = this;
@ -55,11 +76,14 @@ WebService.prototype.stop = function(callback) {
if(self.server) {
self.server.close();
}
callback();
});
};
/**
* This function will iterate over all of the available services gathering
* all of the exposed HTTP routes.
*/
WebService.prototype.setupAllRoutes = function() {
for(var key in this.node.services) {
var subApp = new express.Router();
@ -67,13 +91,17 @@ WebService.prototype.setupAllRoutes = function() {
if(service.getRoutePrefix && service.setupRoutes) {
this.app.use('/' + this.node.services[key].getRoutePrefix(), subApp);
this.node.services[key].setupRoutes(subApp, express);
this.node.services[key].setupRoutes(subApp, express);
} else {
log.debug('No routes defined for: ' + key);
}
}
};
/**
* This function will contruct an API methods map of all of the
* available methods that can be called from enable services.
*/
WebService.prototype.createMethodsMap = function() {
var self = this;
var methods = this.node.getAllAPIMethods();
@ -93,6 +121,10 @@ WebService.prototype.createMethodsMap = function() {
});
};
/**
* This function will gather all of the available events exposed from
* the enabled services.
*/
WebService.prototype.getEventNames = function() {
var events = this.node.getAllPublishEvents();
var eventNames = [];
@ -101,9 +133,8 @@ WebService.prototype.getEventNames = function() {
if(eventNames.indexOf(name) > -1) {
throw new Error('Duplicate event ' + name);
}
eventNames.push(name);
};
}
events.forEach(function(event) {
addEventName(event.name);
@ -118,9 +149,12 @@ WebService.prototype.getEventNames = function() {
return eventNames;
};
/**
* This function is responsible for managing a socket.io connection, including
* instantiating a new Bus, subscribing/unsubscribing and handling RPC commands.
* @param {Socket} socket - A socket.io socket instance
*/
WebService.prototype.socketHandler = function(socket) {
var self = this;
var bus = this.node.openBus();
socket.on('message', this.socketMessageHandler.bind(this));
@ -153,6 +187,12 @@ WebService.prototype.socketHandler = function(socket) {
});
};
/**
* This method will handle incoming RPC messages to a socket.io connection,
* call the appropriate method, and respond with the result.
* @param {Object} message - The socket.io "message" object
* @param {Function} socketCallback
*/
WebService.prototype.socketMessageHandler = function(message, socketCallback) {
if (this.methodsMap[message.method]) {
var params = message.params;
@ -195,6 +235,10 @@ WebService.prototype.socketMessageHandler = function(message, socketCallback) {
}
};
/**
* This method will read `key` and `cert` from disk based on `httpsOptions` and
* replace the options with the files.
*/
WebService.prototype.transformHttpsOptions = function() {
if(!this.httpsOptions || !this.httpsOptions.key || !this.httpsOptions.cert) {
throw new Error('Missing https options');

View File

@ -454,7 +454,7 @@ describe('Address Service', function() {
store: {
createReadStream: sinon.stub().returns(testStream)
}
}
};
var testnode = {
services: {
db: db,
@ -475,7 +475,7 @@ describe('Address Service', function() {
height: -1,
confirmations: 0
}
]
];
am.getInputs(address, args, function(err, inputs) {
should.not.exist(err);
inputs.length.should.equal(1);
@ -657,7 +657,7 @@ describe('Address Service', function() {
script: '76a914f6db95c81dea3d10f0ff8d890927751bf7b203c188ac',
outputIndex: 0
}
]
];
am.getOutputs(address, options, function(err, outputs) {
should.not.exist(err);
@ -836,7 +836,7 @@ describe('Address Service', function() {
var am = new AddressService({node: mocknode});
am.getOutputs = sinon.stub().callsArgWith(2, null, outputs);
am.isUnspent = function(output, queryMempool, callback) {
am.isUnspent = function(output, callback) {
callback(!outputs[i].spent);
i++;
};
@ -878,24 +878,24 @@ describe('Address Service', function() {
});
it('should give true when isSpent() gives false', function(done) {
am.isSpent = sinon.stub().callsArgWith(2, false);
am.isUnspent('1KiW1A4dx1oRgLHtDtBjcunUGkYtFgZ1W', false, function(unspent) {
am.isSpent = sinon.stub().callsArgWith(1, false);
am.isUnspent('1KiW1A4dx1oRgLHtDtBjcunUGkYtFgZ1W', function(unspent) {
unspent.should.equal(true);
done();
});
});
it('should give false when isSpent() gives true', function(done) {
am.isSpent = sinon.stub().callsArgWith(2, true);
am.isUnspent('1KiW1A4dx1oRgLHtDtBjcunUGkYtFgZ1W', false, function(unspent) {
am.isSpent = sinon.stub().callsArgWith(1, true);
am.isUnspent('1KiW1A4dx1oRgLHtDtBjcunUGkYtFgZ1W', function(unspent) {
unspent.should.equal(false);
done();
});
});
it('should give false when isSpent() returns an error', function(done) {
am.isSpent = sinon.stub().callsArgWith(2, new Error('error'));
am.isUnspent('1KiW1A4dx1oRgLHtDtBjcunUGkYtFgZ1W', false, function(unspent) {
am.isSpent = sinon.stub().callsArgWith(1, new Error('error'));
am.isUnspent('1KiW1A4dx1oRgLHtDtBjcunUGkYtFgZ1W', function(unspent) {
unspent.should.equal(false);
done();
});
@ -922,7 +922,7 @@ describe('Address Service', function() {
});
it('should give true if bitcoind.isSpent gives true', function(done) {
am.isSpent('output', true, function(spent) {
am.isSpent('output', function(spent) {
spent.should.equal(true);
done();
});
@ -1001,30 +1001,30 @@ describe('Address Service', function() {
};
var inputs = [
{
"txid": "9f183412de12a6c1943fc86c390174c1cde38d709217fdb59dcf540230fa58a6",
"height": -1,
"confirmations": 0,
"addresses": {
"mpkDdnLq26djg17s6cYknjnysAm3QwRzu2": {
"outputIndexes": [],
"inputIndexes": [
'txid': '9f183412de12a6c1943fc86c390174c1cde38d709217fdb59dcf540230fa58a6',
'height': -1,
'confirmations': 0,
'addresses': {
'mpkDdnLq26djg17s6cYknjnysAm3QwRzu2': {
'outputIndexes': [],
'inputIndexes': [
3
]
}
},
"address": "mpkDdnLq26djg17s6cYknjnysAm3QwRzu2"
'address': 'mpkDdnLq26djg17s6cYknjnysAm3QwRzu2'
}
];
var outputs = [
{
"address": "mpkDdnLq26djg17s6cYknjnysAm3QwRzu2",
"txid": "689e9f543fa4aa5b2daa3b5bb65f9a00ad5aa1a2e9e1fc4e11061d85f2aa9bc5",
"outputIndex": 0,
"height": 556351,
"satoshis": 3487110,
"script": "76a914653b58493c2208481e0902a8ffb97b8112b13fe188ac",
"confirmations": 13190
'address': 'mpkDdnLq26djg17s6cYknjnysAm3QwRzu2',
'txid': '689e9f543fa4aa5b2daa3b5bb65f9a00ad5aa1a2e9e1fc4e11061d85f2aa9bc5',
'outputIndex': 0,
'height': 556351,
'satoshis': 3487110,
'script': '76a914653b58493c2208481e0902a8ffb97b8112b13fe188ac',
'confirmations': 13190
}
];

View File

@ -513,6 +513,21 @@ describe('DB Service', function() {
});
describe('#sendTransaction', function() {
it('should handle a basic serialized transaction hex string', function(done) {
var db = new DB(baseConfig);
db.node = {};
db.node.services = {};
db.node.services.bitcoind = {
sendTransaction: sinon.stub().returns('txid')
};
var tx = 'hexstring';
db.sendTransaction(tx, function(err, txid) {
should.not.exist(err);
txid.should.equal('txid');
done();
});
});
it('should give the txid on success', function(done) {
var db = new DB(baseConfig);
db.node = {};
@ -522,8 +537,10 @@ describe('DB Service', function() {
};
var tx = new Transaction();
tx.serialize = sinon.stub().returns('txstring');
db.sendTransaction(tx, function(err, txid) {
should.not.exist(err);
tx.serialize.callCount.should.equal(1);
txid.should.equal('txid');
done();
});
@ -537,7 +554,9 @@ describe('DB Service', function() {
};
var tx = new Transaction();
tx.serialize = sinon.stub().returns('txstring');
db.sendTransaction(tx, function(err, txid) {
tx.serialize.callCount.should.equal(1);
should.exist(err);
done();
});