364 lines
10 KiB
JavaScript
364 lines
10 KiB
JavaScript
'use strict';
|
|
|
|
var fs = require('fs');
|
|
var util = require('util');
|
|
var bindings = require('bindings')('bitcoind.node');
|
|
var mkdirp = require('mkdirp');
|
|
var bitcore = require('bitcore-lib');
|
|
var $ = bitcore.util.preconditions;
|
|
var index = require('../');
|
|
var log = index.log;
|
|
var Service = require('../service');
|
|
|
|
/**
|
|
* Provides an interface to native bindings to [Bitcoin Core](https://github.com/bitcoin/bitcoin)
|
|
* compiled as a static library. The C++ bindings can be found at `src/libbitcoind.cc`
|
|
* @param {Object} options
|
|
* @param {Node} options.node - A reference to the node
|
|
*/
|
|
function Bitcoin(options) {
|
|
if (!(this instanceof Bitcoin)) {
|
|
return new Bitcoin(options);
|
|
}
|
|
|
|
this._reindex = false;
|
|
this._reindexWait = 1000;
|
|
Service.call(this, options);
|
|
$.checkState(this.node.datadir, 'Node is missing datadir property');
|
|
}
|
|
|
|
util.inherits(Bitcoin, Service);
|
|
|
|
Bitcoin.dependencies = [];
|
|
|
|
Bitcoin.DEFAULT_CONFIG = 'whitelist=127.0.0.1\n' + 'txindex=1\n';
|
|
|
|
Bitcoin.prototype._loadConfiguration = function() {
|
|
/* jshint maxstatements: 25 */
|
|
|
|
$.checkArgument(this.node.datadir, 'Please specify "datadir" in configuration options');
|
|
var configPath = this.node.datadir + '/bitcoin.conf';
|
|
this.configuration = {};
|
|
|
|
if (!fs.existsSync(this.node.datadir)) {
|
|
mkdirp.sync(this.node.datadir);
|
|
}
|
|
|
|
if (!fs.existsSync(configPath)) {
|
|
var defaultConfig = Bitcoin.DEFAULT_CONFIG;
|
|
if(this.node.https && this.node.httpsOptions) {
|
|
defaultConfig += 'rpcssl=1\n';
|
|
defaultConfig += 'rpcsslprivatekeyfile=' + this.node.httpsOptions.key + '\n';
|
|
defaultConfig += 'rpcsslcertificatechainfile=' + this.node.httpsOptions.cert + '\n';
|
|
}
|
|
fs.writeFileSync(configPath, defaultConfig);
|
|
}
|
|
|
|
var file = fs.readFileSync(configPath);
|
|
var unparsed = file.toString().split('\n');
|
|
for(var i = 0; i < unparsed.length; i++) {
|
|
var line = unparsed[i];
|
|
if (!line.match(/^\#/) && line.match(/\=/)) {
|
|
var option = line.split('=');
|
|
var value;
|
|
if (!Number.isNaN(Number(option[1]))) {
|
|
value = Number(option[1]);
|
|
} else {
|
|
value = option[1];
|
|
}
|
|
this.configuration[option[0]] = value;
|
|
}
|
|
}
|
|
|
|
$.checkState(
|
|
this.configuration.txindex && this.configuration.txindex === 1,
|
|
'Txindex option is required in order to use most of the features of bitcore-node. ' +
|
|
'Please add "txindex=1" to your configuration and reindex an existing database if ' +
|
|
'necessary with reindex=1'
|
|
);
|
|
|
|
if (this.configuration.reindex && this.configuration.reindex === 1) {
|
|
log.warn('Reindex option is currently enabled. This means that bitcoind is undergoing a reindex. ' +
|
|
'The reindex flag will start the index from beginning every time the node is started, so it ' +
|
|
'should be removed after the reindex has been initiated. Once the reindex is complete, the rest ' +
|
|
'of bitcore-node services will start.');
|
|
this._reindex = true;
|
|
}
|
|
|
|
};
|
|
|
|
Bitcoin.prototype._onTipUpdate = function(result) {
|
|
if (result) {
|
|
// Emit and event that the tip was updated
|
|
this.height = result;
|
|
this.emit('tip', result);
|
|
|
|
// TODO stopping status
|
|
if(!this.node.stopping) {
|
|
var percentage = this.syncPercentage();
|
|
log.info('Bitcoin Height:', this.height, 'Percentage:', percentage);
|
|
}
|
|
|
|
// Recursively wait until the next update
|
|
bindings.onTipUpdate(this._onTipUpdate.bind(this));
|
|
}
|
|
};
|
|
|
|
Bitcoin.prototype._registerEventHandlers = function() {
|
|
var self = this;
|
|
|
|
// Set the height and emit a new tip
|
|
bindings.onTipUpdate(self._onTipUpdate.bind(this));
|
|
|
|
// Register callback function to handle transactions entering the mempool
|
|
bindings.startTxMon(function(txs) {
|
|
for(var i = 0; i < txs.length; i++) {
|
|
self.emit('tx', txs[i]);
|
|
}
|
|
});
|
|
|
|
// Register callback function to handle transactions leaving the mempool
|
|
bindings.startTxMonLeave(function(txs) {
|
|
for(var i = 0; i < txs.length; i++) {
|
|
self.emit('txleave', txs[i]);
|
|
}
|
|
});
|
|
};
|
|
|
|
Bitcoin.prototype._onReady = function(result, callback) {
|
|
var self = this;
|
|
|
|
self._registerEventHandlers();
|
|
|
|
var info = self.getInfo();
|
|
self.height = info.blocks;
|
|
|
|
self.getBlock(0, function(err, block) {
|
|
if (err) {
|
|
return callback(err);
|
|
}
|
|
self.genesisBuffer = block;
|
|
self.emit('ready', result);
|
|
log.info('Bitcoin Daemon Ready');
|
|
callback();
|
|
});
|
|
|
|
};
|
|
|
|
/**
|
|
* Called by Node to start the service
|
|
* @param {Function} callback
|
|
*/
|
|
Bitcoin.prototype.start = function(callback) {
|
|
var self = this;
|
|
|
|
this._loadConfiguration();
|
|
|
|
var networkName = this.node.network.name;
|
|
if (this.node.network.regtestEnabled) {
|
|
networkName = 'regtest';
|
|
}
|
|
|
|
bindings.start({
|
|
datadir: this.node.datadir,
|
|
network: networkName
|
|
}, function(err) {
|
|
if(err) {
|
|
return callback(err);
|
|
}
|
|
// Wait until the block chain is ready
|
|
bindings.onBlocksReady(function(err, result) {
|
|
if (err) {
|
|
return callback(err);
|
|
}
|
|
if (self._reindex) {
|
|
var interval = setInterval(function() {
|
|
var percentSynced = bindings.syncPercentage();
|
|
log.info("Bitcoin Core Daemon Reindex Percentage: " + percentSynced);
|
|
if (percentSynced >= 100) {
|
|
self._reindex = false;
|
|
self._onReady(result, callback);
|
|
clearInterval(interval);
|
|
}
|
|
}, self._reindexWait);
|
|
|
|
}
|
|
else {
|
|
self._onReady(result, 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();
|
|
};
|
|
|
|
/**
|
|
* Will retrieve 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);
|
|
};
|
|
|
|
/**
|
|
* 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 transaction 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();
|
|
};
|
|
|
|
/**
|
|
* 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 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) {
|
|
return callback(err);
|
|
} else {
|
|
log.info(status);
|
|
return callback();
|
|
}
|
|
});
|
|
};
|
|
|
|
module.exports = Bitcoin;
|