node: classify.

This commit is contained in:
Christopher Jeffrey 2017-11-16 19:43:07 -08:00
parent 4ebfb5d9ff
commit 93fe6669bf
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
4 changed files with 1021 additions and 1030 deletions

View File

@ -18,508 +18,498 @@ const HTTP = require('./http');
const RPC = require('./rpc'); const RPC = require('./rpc');
/** /**
* Full Node
* Respresents a fullnode complete with a * Respresents a fullnode complete with a
* chain, mempool, miner, etc. * chain, mempool, miner, etc.
* @alias module:node.FullNode * @alias module:node.FullNode
* @extends Node * @extends Node
* @constructor
* @param {Object?} options
* @property {Chain} chain
* @property {PolicyEstimator} fees
* @property {Mempool} mempool
* @property {Pool} pool
* @property {Miner} miner
* @property {HTTP} http
* @emits FullNode#block
* @emits FullNode#tx
* @emits FullNode#connect
* @emits FullNode#disconnect
* @emits FullNode#reset
* @emits FullNode#error
*/ */
function FullNode(options) { class FullNode extends Node {
if (!(this instanceof FullNode)) /**
return new FullNode(options); * Create a full node.
* @constructor
* @param {Object?} options
*/
Node.call(this, 'bcoin', 'bcoin.conf', 'debug.log', options); constructor(options) {
super('bcoin', 'bcoin.conf', 'debug.log', options);
this.opened = false; this.opened = false;
// SPV flag. // SPV flag.
this.spv = false; this.spv = false;
// Instantiate blockchain. // Instantiate blockchain.
this.chain = new Chain({ this.chain = new Chain({
network: this.network, network: this.network,
logger: this.logger, logger: this.logger,
workers: this.workers, workers: this.workers,
db: this.config.str('db'), db: this.config.str('db'),
prefix: this.config.prefix, prefix: this.config.prefix,
maxFiles: this.config.uint('max-files'), maxFiles: this.config.uint('max-files'),
cacheSize: this.config.mb('cache-size'), cacheSize: this.config.mb('cache-size'),
forceFlags: this.config.bool('force-flags'), forceFlags: this.config.bool('force-flags'),
bip91: this.config.bool('bip91'), bip91: this.config.bool('bip91'),
bip148: this.config.bool('bip148'), bip148: this.config.bool('bip148'),
prune: this.config.bool('prune'), prune: this.config.bool('prune'),
checkpoints: this.config.bool('checkpoints'), checkpoints: this.config.bool('checkpoints'),
coinCache: this.config.mb('coin-cache'), coinCache: this.config.mb('coin-cache'),
entryCache: this.config.uint('entry-cache'), entryCache: this.config.uint('entry-cache'),
indexTX: this.config.bool('index-tx'), indexTX: this.config.bool('index-tx'),
indexAddress: this.config.bool('index-address') indexAddress: this.config.bool('index-address')
}); });
// Fee estimation. // Fee estimation.
this.fees = new Fees(this.logger); this.fees = new Fees(this.logger);
this.fees.init(); this.fees.init();
// Mempool needs access to the chain. // Mempool needs access to the chain.
this.mempool = new Mempool({ this.mempool = new Mempool({
network: this.network, network: this.network,
logger: this.logger, logger: this.logger,
workers: this.workers, workers: this.workers,
chain: this.chain, chain: this.chain,
fees: this.fees, fees: this.fees,
db: this.config.str('db'), db: this.config.str('db'),
prefix: this.config.prefix, prefix: this.config.prefix,
persistent: this.config.bool('persistent-mempool'), persistent: this.config.bool('persistent-mempool'),
maxSize: this.config.mb('mempool-size'), maxSize: this.config.mb('mempool-size'),
limitFree: this.config.bool('limit-free'), limitFree: this.config.bool('limit-free'),
limitFreeRelay: this.config.uint('limit-free-relay'), limitFreeRelay: this.config.uint('limit-free-relay'),
requireStandard: this.config.bool('require-standard'), requireStandard: this.config.bool('require-standard'),
rejectAbsurdFees: this.config.bool('reject-absurd-fees'), rejectAbsurdFees: this.config.bool('reject-absurd-fees'),
replaceByFee: this.config.bool('replace-by-fee'), replaceByFee: this.config.bool('replace-by-fee'),
indexAddress: this.config.bool('index-address') indexAddress: this.config.bool('index-address')
}); });
// Pool needs access to the chain and mempool. // Pool needs access to the chain and mempool.
this.pool = new Pool({ this.pool = new Pool({
network: this.network, network: this.network,
logger: this.logger, logger: this.logger,
chain: this.chain, chain: this.chain,
mempool: this.mempool, mempool: this.mempool,
prefix: this.config.prefix, prefix: this.config.prefix,
selfish: this.config.bool('selfish'), selfish: this.config.bool('selfish'),
compact: this.config.bool('compact'), compact: this.config.bool('compact'),
bip37: this.config.bool('bip37'), bip37: this.config.bool('bip37'),
bip151: this.config.bool('bip151'), bip151: this.config.bool('bip151'),
bip150: this.config.bool('bip150'), bip150: this.config.bool('bip150'),
identityKey: this.config.buf('identity-key'), identityKey: this.config.buf('identity-key'),
maxOutbound: this.config.uint('max-outbound'), maxOutbound: this.config.uint('max-outbound'),
maxInbound: this.config.uint('max-inbound'), maxInbound: this.config.uint('max-inbound'),
createSocket: this.config.func('create-socket'), createSocket: this.config.func('create-socket'),
proxy: this.config.str('proxy'), proxy: this.config.str('proxy'),
onion: this.config.bool('onion'), onion: this.config.bool('onion'),
upnp: this.config.bool('upnp'), upnp: this.config.bool('upnp'),
seeds: this.config.array('seeds'), seeds: this.config.array('seeds'),
nodes: this.config.array('nodes'), nodes: this.config.array('nodes'),
only: this.config.array('only'), only: this.config.array('only'),
publicHost: this.config.str('public-host'), publicHost: this.config.str('public-host'),
publicPort: this.config.uint('public-port'), publicPort: this.config.uint('public-port'),
host: this.config.str('host'), host: this.config.str('host'),
port: this.config.uint('port'), port: this.config.uint('port'),
listen: this.config.bool('listen'), listen: this.config.bool('listen'),
persistent: this.config.bool('persistent') persistent: this.config.bool('persistent')
}); });
// Miner needs access to the chain and mempool. // Miner needs access to the chain and mempool.
this.miner = new Miner({ this.miner = new Miner({
network: this.network, network: this.network,
logger: this.logger, logger: this.logger,
workers: this.workers, workers: this.workers,
chain: this.chain, chain: this.chain,
mempool: this.mempool, mempool: this.mempool,
address: this.config.array('coinbase-address'), address: this.config.array('coinbase-address'),
coinbaseFlags: this.config.str('coinbase-flags'), coinbaseFlags: this.config.str('coinbase-flags'),
preverify: this.config.bool('preverify'), preverify: this.config.bool('preverify'),
maxWeight: this.config.uint('max-weight'), maxWeight: this.config.uint('max-weight'),
reservedWeight: this.config.uint('reserved-weight'), reservedWeight: this.config.uint('reserved-weight'),
reservedSigops: this.config.uint('reserved-sigops') reservedSigops: this.config.uint('reserved-sigops')
}); });
// RPC needs access to the node. // RPC needs access to the node.
this.rpc = new RPC(this); this.rpc = new RPC(this);
// HTTP needs access to the node. // HTTP needs access to the node.
this.http = new HTTP({ this.http = new HTTP({
network: this.network, network: this.network,
logger: this.logger, logger: this.logger,
node: this, node: this,
prefix: this.config.prefix, prefix: this.config.prefix,
ssl: this.config.bool('ssl'), ssl: this.config.bool('ssl'),
keyFile: this.config.path('ssl-key'), keyFile: this.config.path('ssl-key'),
certFile: this.config.path('ssl-cert'), certFile: this.config.path('ssl-cert'),
host: this.config.str('http-host'), host: this.config.str('http-host'),
port: this.config.uint('http-port'), port: this.config.uint('http-port'),
apiKey: this.config.str('api-key'), apiKey: this.config.str('api-key'),
noAuth: this.config.bool('no-auth') noAuth: this.config.bool('no-auth')
}); });
this.init(); this.init();
}
Object.setPrototypeOf(FullNode.prototype, Node.prototype);
/**
* Initialize the node.
* @private
*/
FullNode.prototype.init = function init() {
// Bind to errors
this.chain.on('error', err => this.error(err));
this.mempool.on('error', err => this.error(err));
this.pool.on('error', err => this.error(err));
this.miner.on('error', err => this.error(err));
if (this.http)
this.http.on('error', err => this.error(err));
this.mempool.on('tx', (tx) => {
this.miner.cpu.notifyEntry();
this.emit('tx', tx);
});
this.chain.on('connect', async (entry, block) => {
try {
await this.mempool._addBlock(entry, block.txs);
} catch (e) {
this.error(e);
}
this.emit('block', block);
this.emit('connect', entry, block);
});
this.chain.on('disconnect', async (entry, block) => {
try {
await this.mempool._removeBlock(entry, block.txs);
} catch (e) {
this.error(e);
}
this.emit('disconnect', entry, block);
});
this.chain.on('reorganize', async (tip, competitor) => {
try {
await this.mempool._handleReorg();
} catch (e) {
this.error(e);
}
this.emit('reorganize', tip, competitor);
});
this.chain.on('reset', async (tip) => {
try {
await this.mempool._reset();
} catch (e) {
this.error(e);
}
this.emit('reset', tip);
});
this.loadPlugins();
};
/**
* Open the node and all its child objects,
* wait for the database to load.
* @alias FullNode#open
* @returns {Promise}
*/
FullNode.prototype.open = async function open() {
assert(!this.opened, 'FullNode is already open.');
this.opened = true;
await this.handlePreopen();
await this.chain.open();
await this.mempool.open();
await this.miner.open();
await this.pool.open();
await this.openPlugins();
await this.http.open();
await this.handleOpen();
this.logger.info('Node is loaded.');
};
/**
* Close the node, wait for the database to close.
* @alias FullNode#close
* @returns {Promise}
*/
FullNode.prototype.close = async function close() {
assert(this.opened, 'FullNode is not open.');
this.opened = false;
await this.handlePreclose();
await this.http.close();
await this.closePlugins();
await this.pool.close();
await this.miner.close();
await this.mempool.close();
await this.chain.close();
await this.handleClose();
this.logger.info('Node is closed.');
};
/**
* Rescan for any missed transactions.
* @param {Number|Hash} start - Start block.
* @param {Bloom} filter
* @param {Function} iter - Iterator.
* @returns {Promise}
*/
FullNode.prototype.scan = function scan(start, filter, iter) {
return this.chain.scan(start, filter, iter);
};
/**
* Broadcast a transaction (note that this will _not_ be verified
* by the mempool - use with care, lest you get banned from
* bitcoind nodes).
* @param {TX|Block} item
* @returns {Promise}
*/
FullNode.prototype.broadcast = async function broadcast(item) {
try {
await this.pool.broadcast(item);
} catch (e) {
this.emit('error', e);
} }
};
/** /**
* Add transaction to mempool, broadcast. * Initialize the node.
* @param {TX} tx * @private
*/ */
FullNode.prototype.sendTX = async function sendTX(tx) { init() {
let missing; // Bind to errors
this.chain.on('error', err => this.error(err));
this.mempool.on('error', err => this.error(err));
this.pool.on('error', err => this.error(err));
this.miner.on('error', err => this.error(err));
try { if (this.http)
missing = await this.mempool.addTX(tx); this.http.on('error', err => this.error(err));
} catch (err) {
if (err.type === 'VerifyError' && err.score === 0) { this.mempool.on('tx', (tx) => {
this.error(err); this.miner.cpu.notifyEntry();
this.logger.warning('Verification failed for tx: %s.', tx.txid()); this.emit('tx', tx);
});
this.chain.on('connect', async (entry, block) => {
try {
await this.mempool._addBlock(entry, block.txs);
} catch (e) {
this.error(e);
}
this.emit('block', block);
this.emit('connect', entry, block);
});
this.chain.on('disconnect', async (entry, block) => {
try {
await this.mempool._removeBlock(entry, block.txs);
} catch (e) {
this.error(e);
}
this.emit('disconnect', entry, block);
});
this.chain.on('reorganize', async (tip, competitor) => {
try {
await this.mempool._handleReorg();
} catch (e) {
this.error(e);
}
this.emit('reorganize', tip, competitor);
});
this.chain.on('reset', async (tip) => {
try {
await this.mempool._reset();
} catch (e) {
this.error(e);
}
this.emit('reset', tip);
});
this.loadPlugins();
}
/**
* Open the node and all its child objects,
* wait for the database to load.
* @alias FullNode#open
* @returns {Promise}
*/
async open() {
assert(!this.opened, 'FullNode is already open.');
this.opened = true;
await this.handlePreopen();
await this.chain.open();
await this.mempool.open();
await this.miner.open();
await this.pool.open();
await this.openPlugins();
await this.http.open();
await this.handleOpen();
this.logger.info('Node is loaded.');
}
/**
* Close the node, wait for the database to close.
* @alias FullNode#close
* @returns {Promise}
*/
async close() {
assert(this.opened, 'FullNode is not open.');
this.opened = false;
await this.handlePreclose();
await this.http.close();
await this.closePlugins();
await this.pool.close();
await this.miner.close();
await this.mempool.close();
await this.chain.close();
await this.handleClose();
this.logger.info('Node is closed.');
}
/**
* Rescan for any missed transactions.
* @param {Number|Hash} start - Start block.
* @param {Bloom} filter
* @param {Function} iter - Iterator.
* @returns {Promise}
*/
scan(start, filter, iter) {
return this.chain.scan(start, filter, iter);
}
/**
* Broadcast a transaction (note that this will _not_ be verified
* by the mempool - use with care, lest you get banned from
* bitcoind nodes).
* @param {TX|Block} item
* @returns {Promise}
*/
async broadcast(item) {
try {
await this.pool.broadcast(item);
} catch (e) {
this.emit('error', e);
}
}
/**
* Add transaction to mempool, broadcast.
* @param {TX} tx
*/
async sendTX(tx) {
let missing;
try {
missing = await this.mempool.addTX(tx);
} catch (err) {
if (err.type === 'VerifyError' && err.score === 0) {
this.error(err);
this.logger.warning('Verification failed for tx: %s.', tx.txid());
this.logger.warning('Attempting to broadcast anyway...');
this.broadcast(tx);
return;
}
throw err;
}
if (missing) {
this.logger.warning('TX was orphaned in mempool: %s.', tx.txid());
this.logger.warning('Attempting to broadcast anyway...'); this.logger.warning('Attempting to broadcast anyway...');
this.broadcast(tx); this.broadcast(tx);
return; return;
} }
throw err;
// We need to announce by hand if
// we're running in selfish mode.
if (this.pool.options.selfish)
this.pool.broadcast(tx);
} }
if (missing) { /**
this.logger.warning('TX was orphaned in mempool: %s.', tx.txid()); * Add transaction to mempool, broadcast. Silence errors.
this.logger.warning('Attempting to broadcast anyway...'); * @param {TX} tx
this.broadcast(tx); * @returns {Promise}
return; */
async relay(tx) {
try {
await this.sendTX(tx);
} catch (e) {
this.error(e);
}
} }
// We need to announce by hand if /**
// we're running in selfish mode. * Connect to the network.
if (this.pool.options.selfish) * @returns {Promise}
this.pool.broadcast(tx); */
};
/** connect() {
* Add transaction to mempool, broadcast. Silence errors. return this.pool.connect();
* @param {TX} tx
* @returns {Promise}
*/
FullNode.prototype.relay = async function relay(tx) {
try {
await this.sendTX(tx);
} catch (e) {
this.error(e);
}
};
/**
* Connect to the network.
* @returns {Promise}
*/
FullNode.prototype.connect = function connect() {
return this.pool.connect();
};
/**
* Disconnect from the network.
* @returns {Promise}
*/
FullNode.prototype.disconnect = function disconnect() {
return this.pool.disconnect();
};
/**
* Start the blockchain sync.
*/
FullNode.prototype.startSync = function startSync() {
return this.pool.startSync();
};
/**
* Stop syncing the blockchain.
*/
FullNode.prototype.stopSync = function stopSync() {
return this.pool.stopSync();
};
/**
* Retrieve a block from the chain database.
* @param {Hash} hash
* @returns {Promise} - Returns {@link Block}.
*/
FullNode.prototype.getBlock = function getBlock(hash) {
return this.chain.getBlock(hash);
};
/**
* Retrieve a coin from the mempool or chain database.
* Takes into account spent coins in the mempool.
* @param {Hash} hash
* @param {Number} index
* @returns {Promise} - Returns {@link Coin}.
*/
FullNode.prototype.getCoin = async function getCoin(hash, index) {
const coin = this.mempool.getCoin(hash, index);
if (coin)
return coin;
if (this.mempool.isSpent(hash, index))
return null;
return await this.chain.getCoin(hash, index);
};
/**
* Get coins that pertain to an address from the mempool or chain database.
* Takes into account spent coins in the mempool.
* @param {Address} addrs
* @returns {Promise} - Returns {@link Coin}[].
*/
FullNode.prototype.getCoinsByAddress = async function getCoinsByAddress(addrs) {
const mempool = this.mempool.getCoinsByAddress(addrs);
const chain = await this.chain.getCoinsByAddress(addrs);
const out = [];
for (const coin of chain) {
const spent = this.mempool.isSpent(coin.hash, coin.index);
if (spent)
continue;
out.push(coin);
} }
for (const coin of mempool) /**
out.push(coin); * Disconnect from the network.
* @returns {Promise}
*/
return out; disconnect() {
}; return this.pool.disconnect();
}
/** /**
* Retrieve transactions pertaining to an * Start the blockchain sync.
* address from the mempool or chain database. */
* @param {Address} addrs
* @returns {Promise} - Returns {@link TXMeta}[].
*/
FullNode.prototype.getMetaByAddress = async function getMetaByAddress(addrs) { startSync() {
const mempool = this.mempool.getMetaByAddress(addrs); return this.pool.startSync();
const chain = await this.chain.getMetaByAddress(addrs); }
return chain.concat(mempool);
};
/** /**
* Retrieve a transaction from the mempool or chain database. * Stop syncing the blockchain.
* @param {Hash} hash */
* @returns {Promise} - Returns {@link TXMeta}.
*/
FullNode.prototype.getMeta = async function getMeta(hash) { stopSync() {
const meta = this.mempool.getMeta(hash); return this.pool.stopSync();
}
if (meta) /**
return meta; * Retrieve a block from the chain database.
* @param {Hash} hash
* @returns {Promise} - Returns {@link Block}.
*/
return await this.chain.getMeta(hash); getBlock(hash) {
}; return this.chain.getBlock(hash);
}
/** /**
* Retrieve a spent coin viewpoint from mempool or chain database. * Retrieve a coin from the mempool or chain database.
* @param {TXMeta} meta * Takes into account spent coins in the mempool.
* @returns {Promise} - Returns {@link CoinView}. * @param {Hash} hash
*/ * @param {Number} index
* @returns {Promise} - Returns {@link Coin}.
*/
FullNode.prototype.getMetaView = async function getMetaView(meta) { async getCoin(hash, index) {
if (meta.height === -1) const coin = this.mempool.getCoin(hash, index);
return this.mempool.getSpentView(meta.tx);
return this.chain.getSpentView(meta.tx);
};
/** if (coin)
* Retrieve transactions pertaining to an return coin;
* address from the mempool or chain database.
* @param {Address} addrs
* @returns {Promise} - Returns {@link TX}[].
*/
FullNode.prototype.getTXByAddress = async function getTXByAddress(addrs) { if (this.mempool.isSpent(hash, index))
const mtxs = await this.getMetaByAddress(addrs); return null;
const out = [];
for (const mtx of mtxs) return this.chain.getCoin(hash, index);
out.push(mtx.tx); }
return out; /**
}; * Get coins that pertain to an address from the mempool or chain database.
* Takes into account spent coins in the mempool.
* @param {Address} addrs
* @returns {Promise} - Returns {@link Coin}[].
*/
/** async getCoinsByAddress(addrs) {
* Retrieve a transaction from the mempool or chain database. const mempool = this.mempool.getCoinsByAddress(addrs);
* @param {Hash} hash const chain = await this.chain.getCoinsByAddress(addrs);
* @returns {Promise} - Returns {@link TX}. const out = [];
*/
FullNode.prototype.getTX = async function getTX(hash) { for (const coin of chain) {
const mtx = await this.getMeta(hash); const spent = this.mempool.isSpent(coin.hash, coin.index);
if (!mtx) if (spent)
return null; continue;
return mtx.tx; out.push(coin);
}; }
/** for (const coin of mempool)
* Test whether the mempool or chain contains a transaction. out.push(coin);
* @param {Hash} hash
* @returns {Promise} - Returns Boolean.
*/
FullNode.prototype.hasTX = async function hasTX(hash) { return out;
if (this.mempool.hasEntry(hash)) }
return true;
return await this.chain.hasTX(hash); /**
}; * Retrieve transactions pertaining to an
* address from the mempool or chain database.
* @param {Address} addrs
* @returns {Promise} - Returns {@link TXMeta}[].
*/
async getMetaByAddress(addrs) {
const mempool = this.mempool.getMetaByAddress(addrs);
const chain = await this.chain.getMetaByAddress(addrs);
return chain.concat(mempool);
}
/**
* Retrieve a transaction from the mempool or chain database.
* @param {Hash} hash
* @returns {Promise} - Returns {@link TXMeta}.
*/
async getMeta(hash) {
const meta = this.mempool.getMeta(hash);
if (meta)
return meta;
return this.chain.getMeta(hash);
}
/**
* Retrieve a spent coin viewpoint from mempool or chain database.
* @param {TXMeta} meta
* @returns {Promise} - Returns {@link CoinView}.
*/
async getMetaView(meta) {
if (meta.height === -1)
return this.mempool.getSpentView(meta.tx);
return this.chain.getSpentView(meta.tx);
}
/**
* Retrieve transactions pertaining to an
* address from the mempool or chain database.
* @param {Address} addrs
* @returns {Promise} - Returns {@link TX}[].
*/
async getTXByAddress(addrs) {
const mtxs = await this.getMetaByAddress(addrs);
const out = [];
for (const mtx of mtxs)
out.push(mtx.tx);
return out;
}
/**
* Retrieve a transaction from the mempool or chain database.
* @param {Hash} hash
* @returns {Promise} - Returns {@link TX}.
*/
async getTX(hash) {
const mtx = await this.getMeta(hash);
if (!mtx)
return null;
return mtx.tx;
}
/**
* Test whether the mempool or chain contains a transaction.
* @param {Hash} hash
* @returns {Promise} - Returns Boolean.
*/
async hasTX(hash) {
if (this.mempool.hasEntry(hash))
return true;
return this.chain.hasTX(hash);
}
}
/* /*
* Expose * Expose

View File

@ -23,15 +23,16 @@ const Outpoint = require('../primitives/outpoint');
const Network = require('../protocol/network'); const Network = require('../protocol/network');
const pkg = require('../pkg'); const pkg = require('../pkg');
/**
* HTTP
* @alias module:http.Server
*/
class HTTP extends Server { class HTTP extends Server {
/** /**
* HTTP * Create an http server.
* @alias module:http.Server
* @constructor * @constructor
* @param {Object} options * @param {Object} options
* @param {Fullnode} options.node
* @see HTTPBase
* @emits HTTP#socket
*/ */
constructor(options) { constructor(options) {

View File

@ -16,381 +16,384 @@ const Network = require('../protocol/network');
const WorkerPool = require('../workers/workerpool'); const WorkerPool = require('../workers/workerpool');
/** /**
* Node
* Base class from which every other * Base class from which every other
* Node-like object inherits. * Node-like object inherits.
* @alias module:node.Node * @alias module:node.Node
* @constructor * @extends EventEmitter
* @abstract * @abstract
* @param {Object} options
*/ */
function Node(module, config, file, options) { class Node extends EventEmitter {
if (!(this instanceof Node)) /**
return new Node(module, config, file, options); * Create a node.
* @constructor
* @param {Object} options
*/
EventEmitter.call(this); constructor(module, config, file, options) {
super();
this.config = new Config(module, { this.config = new Config(module, {
suffix: 'network', suffix: 'network',
fallback: 'main', fallback: 'main',
alias: { 'n': 'network' } alias: { 'n': 'network' }
}); });
this.config.inject(options); this.config.inject(options);
this.config.load(options); this.config.load(options);
if (options.config) if (options.config)
this.config.open(config); this.config.open(config);
this.network = Network.get(this.config.getSuffix()); this.network = Network.get(this.config.getSuffix());
this.startTime = -1; this.startTime = -1;
this.bound = []; this.bound = [];
this.plugins = Object.create(null); this.plugins = Object.create(null);
this.stack = []; this.stack = [];
this.logger = null; this.logger = null;
this.workers = null; this.workers = null;
this.spv = false; this.spv = false;
this.chain = null; this.chain = null;
this.fees = null; this.fees = null;
this.mempool = null; this.mempool = null;
this.pool = null; this.pool = null;
this.miner = null; this.miner = null;
this.http = null; this.http = null;
this._init(file); this._init(file);
} }
Object.setPrototypeOf(Node.prototype, EventEmitter.prototype); /**
* Initialize node.
* @private
* @param {Object} options
*/
/** _init(file) {
* Initialize node. const config = this.config;
* @private
* @param {Object} options
*/
Node.prototype._init = function _init(file) { let logger = new Logger();
const config = this.config;
let logger = new Logger(); if (config.has('logger'))
logger = config.obj('logger');
if (config.has('logger')) logger.set({
logger = config.obj('logger'); filename: config.bool('log-file')
? config.location(file)
: null,
level: config.str('log-level'),
console: config.bool('log-console'),
shrink: config.bool('log-shrink')
});
logger.set({ this.logger = logger.context('node');
filename: config.bool('log-file')
? config.location(file)
: null,
level: config.str('log-level'),
console: config.bool('log-console'),
shrink: config.bool('log-shrink')
});
this.logger = logger.context('node'); this.workers = new WorkerPool({
enabled: config.bool('workers'),
size: config.uint('workers-size'),
timeout: config.uint('workers-timeout'),
file: config.str('worker-file')
});
this.workers = new WorkerPool({ this.on('error', () => {});
enabled: config.bool('workers'),
size: config.uint('workers-size'),
timeout: config.uint('workers-timeout'),
file: config.str('worker-file')
});
this.on('error', () => {}); this.workers.on('spawn', (child) => {
this.logger.info('Spawning worker process: %d.', child.id);
});
this.workers.on('spawn', (child) => { this.workers.on('exit', (code, child) => {
this.logger.info('Spawning worker process: %d.', child.id); this.logger.warning('Worker %d exited: %s.', child.id, code);
}); });
this.workers.on('exit', (code, child) => { this.workers.on('log', (text, child) => {
this.logger.warning('Worker %d exited: %s.', child.id, code); this.logger.debug('Worker %d says:', child.id);
}); this.logger.debug(text);
});
this.workers.on('log', (text, child) => { this.workers.on('error', (err, child) => {
this.logger.debug('Worker %d says:', child.id); if (child) {
this.logger.debug(text); this.logger.error('Worker %d error: %s', child.id, err.message);
}); return;
}
this.emit('error', err);
});
}
this.workers.on('error', (err, child) => { /**
if (child) { * Ensure prefix directory.
this.logger.error('Worker %d error: %s', child.id, err.message); * @returns {Promise}
return; */
async ensure() {
if (fs.unsupported)
return undefined;
return fs.mkdirp(this.config.prefix);
}
/**
* Create a file path using `prefix`.
* @param {String} file
* @returns {String}
*/
location(name) {
return this.config.location(name);
}
/**
* Open node. Bind all events.
* @private
*/
async handlePreopen() {
await this.logger.open();
await this.workers.open();
this._bind(this.network.time, 'offset', (offset) => {
this.logger.info('Time offset: %d (%d minutes).', offset, offset / 60 | 0);
});
this._bind(this.network.time, 'sample', (sample, total) => {
this.logger.debug(
'Added time data: samples=%d, offset=%d (%d minutes).',
total, sample, sample / 60 | 0);
});
this._bind(this.network.time, 'mismatch', () => {
this.logger.warning('Adjusted time mismatch!');
this.logger.warning('Please make sure your system clock is correct!');
});
}
/**
* Open node.
* @private
*/
async handleOpen() {
this.startTime = Date.now();
if (!this.workers.enabled) {
this.logger.warning('Warning: worker pool is disabled.');
this.logger.warning('Verification will be slow.');
} }
}
/**
* Open node. Bind all events.
* @private
*/
async handlePreclose() {
;
}
/**
* Close node. Unbind all events.
* @private
*/
async handleClose() {
for (const [obj, event, listener] of this.bound)
obj.removeListener(event, listener);
this.bound.length = 0;
this.startTime = -1;
await this.workers.close();
await this.logger.close();
}
/**
* Bind to an event on `obj`, save listener for removal.
* @private
* @param {EventEmitter} obj
* @param {String} event
* @param {Function} listener
*/
_bind(obj, event, listener) {
this.bound.push([obj, event, listener]);
obj.on(event, listener);
}
/**
* Emit and log an error.
* @private
* @param {Error} err
*/
error(err) {
this.logger.error(err);
this.emit('error', err); this.emit('error', err);
});
};
/**
* Ensure prefix directory.
* @returns {Promise}
*/
Node.prototype.ensure = async function ensure() {
if (fs.unsupported)
return undefined;
return fs.mkdirp(this.config.prefix);
};
/**
* Create a file path using `prefix`.
* @param {String} file
* @returns {String}
*/
Node.prototype.location = function location(name) {
return this.config.location(name);
};
/**
* Open node. Bind all events.
* @private
*/
Node.prototype.handlePreopen = async function handlePreopen() {
await this.logger.open();
await this.workers.open();
this._bind(this.network.time, 'offset', (offset) => {
this.logger.info('Time offset: %d (%d minutes).', offset, offset / 60 | 0);
});
this._bind(this.network.time, 'sample', (sample, total) => {
this.logger.debug(
'Added time data: samples=%d, offset=%d (%d minutes).',
total, sample, sample / 60 | 0);
});
this._bind(this.network.time, 'mismatch', () => {
this.logger.warning('Adjusted time mismatch!');
this.logger.warning('Please make sure your system clock is correct!');
});
};
/**
* Open node.
* @private
*/
Node.prototype.handleOpen = async function handleOpen() {
this.startTime = Date.now();
if (!this.workers.enabled) {
this.logger.warning('Warning: worker pool is disabled.');
this.logger.warning('Verification will be slow.');
} }
};
/** /**
* Open node. Bind all events. * Get node uptime in seconds.
* @private * @returns {Number}
*/ */
Node.prototype.handlePreclose = async function handlePreclose() { uptime() {
; if (this.startTime === -1)
}; return 0;
/** return Math.floor((Date.now() - this.startTime) / 1000);
* Close node. Unbind all events. }
* @private
*/
Node.prototype.handleClose = async function handleClose() { /**
for (const [obj, event, listener] of this.bound) * Attach a plugin.
obj.removeListener(event, listener); * @param {Object} plugin
* @returns {Object} Plugin instance.
*/
this.bound.length = 0; use(plugin) {
this.startTime = -1; assert(plugin, 'Plugin must be an object.');
assert(typeof plugin.init === 'function', '`init` must be a function.');
await this.workers.close(); assert(!this.loaded, 'Cannot add plugin after node is loaded.');
await this.logger.close();
};
/** const instance = plugin.init(this);
* Bind to an event on `obj`, save listener for removal.
* @private
* @param {EventEmitter} obj
* @param {String} event
* @param {Function} listener
*/
Node.prototype._bind = function _bind(obj, event, listener) { assert(!instance.open || typeof instance.open === 'function',
this.bound.push([obj, event, listener]); '`open` must be a function.');
obj.on(event, listener); assert(!instance.close || typeof instance.close === 'function',
}; '`close` must be a function.');
/** if (plugin.id) {
* Emit and log an error. assert(typeof plugin.id === 'string', '`id` must be a string.');
* @private
* @param {Error} err
*/
Node.prototype.error = function error(err) { // Reserved names
this.logger.error(err); switch (plugin.id) {
this.emit('error', err); case 'chain':
}; case 'fees':
case 'mempool':
case 'miner':
case 'pool':
case 'rpc':
case 'http':
assert(false, `${plugin.id} is already added.`);
break;
}
/** assert(!this.plugins[plugin.id], `${plugin.id} is already added.`);
* Get node uptime in seconds.
* @returns {Number}
*/
Node.prototype.uptime = function uptime() { this.plugins[plugin.id] = instance;
if (this.startTime === -1) }
return 0;
return Math.floor((Date.now() - this.startTime) / 1000); this.stack.push(instance);
};
/** if (typeof instance.on === 'function')
* Attach a plugin. instance.on('error', err => this.error(err));
* @param {Object} plugin
* @returns {Object} Plugin instance.
*/
Node.prototype.use = function use(plugin) { return instance;
assert(plugin, 'Plugin must be an object.'); }
assert(typeof plugin.init === 'function', '`init` must be a function.');
assert(!this.loaded, 'Cannot add plugin after node is loaded.'); /**
* Test whether a plugin is available.
* @param {String} name
* @returns {Boolean}
*/
const instance = plugin.init(this); has(name) {
return this.plugins[name] != null;
}
assert(!instance.open || typeof instance.open === 'function', /**
'`open` must be a function.'); * Get a plugin.
assert(!instance.close || typeof instance.close === 'function', * @param {String} name
'`close` must be a function.'); * @returns {Object|null}
*/
if (plugin.id) { get(name) {
assert(typeof plugin.id === 'string', '`id` must be a string.'); assert(typeof name === 'string', 'Plugin name must be a string.');
// Reserved names // Reserved names.
switch (plugin.id) { switch (name) {
case 'chain': case 'chain':
assert(this.chain, 'chain is not loaded.');
return this.chain;
case 'fees': case 'fees':
assert(this.fees, 'fees is not loaded.');
return this.fees;
case 'mempool': case 'mempool':
assert(this.mempool, 'mempool is not loaded.');
return this.mempool;
case 'miner': case 'miner':
assert(this.miner, 'miner is not loaded.');
return this.miner;
case 'pool': case 'pool':
assert(this.pool, 'pool is not loaded.');
return this.pool;
case 'rpc': case 'rpc':
assert(this.rpc, 'rpc is not loaded.');
return this.rpc;
case 'http': case 'http':
assert(false, `${plugin.id} is already added.`); assert(this.http, 'http is not loaded.');
break; return this.http;
} }
assert(!this.plugins[plugin.id], `${plugin.id} is already added.`); return this.plugins[name] || null;
this.plugins[plugin.id] = instance;
} }
this.stack.push(instance); /**
* Require a plugin.
* @param {String} name
* @returns {Object}
* @throws {Error} on onloaded plugin
*/
if (typeof instance.on === 'function') require(name) {
instance.on('error', err => this.error(err)); const plugin = this.get(name);
assert(plugin, `${name} is not loaded.`);
return instance; return plugin;
};
/**
* Test whether a plugin is available.
* @param {String} name
* @returns {Boolean}
*/
Node.prototype.has = function has(name) {
return this.plugins[name] != null;
};
/**
* Get a plugin.
* @param {String} name
* @returns {Object|null}
*/
Node.prototype.get = function get(name) {
assert(typeof name === 'string', 'Plugin name must be a string.');
// Reserved names.
switch (name) {
case 'chain':
assert(this.chain, 'chain is not loaded.');
return this.chain;
case 'fees':
assert(this.fees, 'fees is not loaded.');
return this.fees;
case 'mempool':
assert(this.mempool, 'mempool is not loaded.');
return this.mempool;
case 'miner':
assert(this.miner, 'miner is not loaded.');
return this.miner;
case 'pool':
assert(this.pool, 'pool is not loaded.');
return this.pool;
case 'rpc':
assert(this.rpc, 'rpc is not loaded.');
return this.rpc;
case 'http':
assert(this.http, 'http is not loaded.');
return this.http;
} }
return this.plugins[name] || null; /**
}; * Load plugins.
* @private
*/
/** loadPlugins() {
* Require a plugin. const plugins = this.config.array('plugins', []);
* @param {String} name const loader = this.config.func('loader');
* @returns {Object}
* @throws {Error} on onloaded plugin
*/
Node.prototype.require = function require(name) { for (let plugin of plugins) {
const plugin = this.get(name); if (typeof plugin === 'string') {
assert(plugin, `${name} is not loaded.`); assert(loader, 'Must pass a loader function.');
return plugin; plugin = loader(plugin);
}; }
this.use(plugin);
/**
* Load plugins.
* @private
*/
Node.prototype.loadPlugins = function loadPlugins() {
const plugins = this.config.array('plugins', []);
const loader = this.config.func('loader');
for (let plugin of plugins) {
if (typeof plugin === 'string') {
assert(loader, 'Must pass a loader function.');
plugin = loader(plugin);
} }
this.use(plugin);
} }
};
/** /**
* Open plugins. * Open plugins.
* @private * @private
*/ */
Node.prototype.openPlugins = async function openPlugins() { async openPlugins() {
for (const plugin of this.stack) { for (const plugin of this.stack) {
if (plugin.open) if (plugin.open)
await plugin.open(); await plugin.open();
}
} }
};
/** /**
* Close plugins. * Close plugins.
* @private * @private
*/ */
Node.prototype.closePlugins = async function closePlugins() { async closePlugins() {
for (const plugin of this.stack) { for (const plugin of this.stack) {
if (plugin.close) if (plugin.close)
await plugin.close(); await plugin.close();
}
} }
}; }
/* /*
* Expose * Expose

View File

@ -16,335 +16,332 @@ const HTTP = require('./http');
const RPC = require('../rpc'); const RPC = require('../rpc');
/** /**
* SPV Node
* Create an spv node which only maintains * Create an spv node which only maintains
* a chain, a pool, and an http server. * a chain, a pool, and an http server.
* @alias module:node.SPVNode * @alias module:node.SPVNode
* @extends Node * @extends Node
* @constructor
* @param {Object?} options
* @param {Buffer?} options.sslKey
* @param {Buffer?} options.sslCert
* @param {Number?} options.httpPort
* @param {String?} options.httpHost
* @property {Boolean} loaded
* @property {Chain} chain
* @property {Pool} pool
* @property {HTTP} http
* @emits SPVNode#block
* @emits SPVNode#tx
* @emits SPVNode#error
*/ */
function SPVNode(options) { class SPVNode extends Node {
if (!(this instanceof SPVNode)) /**
return new SPVNode(options); * Create SPV node.
* @constructor
* @param {Object?} options
* @param {Buffer?} options.sslKey
* @param {Buffer?} options.sslCert
* @param {Number?} options.httpPort
* @param {String?} options.httpHost
*/
Node.call(this, 'bcoin', 'bcoin.conf', 'debug.log', options); constructor(options) {
super('bcoin', 'bcoin.conf', 'debug.log', options);
this.opened = false; this.opened = false;
// SPV flag. // SPV flag.
this.spv = true; this.spv = true;
this.chain = new Chain({ this.chain = new Chain({
network: this.network, network: this.network,
logger: this.logger, logger: this.logger,
db: this.config.str('db'), db: this.config.str('db'),
prefix: this.config.prefix, prefix: this.config.prefix,
maxFiles: this.config.uint('max-files'), maxFiles: this.config.uint('max-files'),
cacheSize: this.config.mb('cache-size'), cacheSize: this.config.mb('cache-size'),
entryCache: this.config.uint('entry-cache'), entryCache: this.config.uint('entry-cache'),
forceFlags: this.config.bool('force-flags'), forceFlags: this.config.bool('force-flags'),
checkpoints: this.config.bool('checkpoints'), checkpoints: this.config.bool('checkpoints'),
bip91: this.config.bool('bip91'), bip91: this.config.bool('bip91'),
bip148: this.config.bool('bip148'), bip148: this.config.bool('bip148'),
spv: true spv: true
}); });
this.pool = new Pool({ this.pool = new Pool({
network: this.network, network: this.network,
logger: this.logger, logger: this.logger,
chain: this.chain, chain: this.chain,
prefix: this.config.prefix, prefix: this.config.prefix,
proxy: this.config.str('proxy'), proxy: this.config.str('proxy'),
onion: this.config.bool('onion'), onion: this.config.bool('onion'),
upnp: this.config.bool('upnp'), upnp: this.config.bool('upnp'),
seeds: this.config.array('seeds'), seeds: this.config.array('seeds'),
nodes: this.config.array('nodes'), nodes: this.config.array('nodes'),
only: this.config.array('only'), only: this.config.array('only'),
bip151: this.config.bool('bip151'), bip151: this.config.bool('bip151'),
bip150: this.config.bool('bip150'), bip150: this.config.bool('bip150'),
identityKey: this.config.buf('identity-key'), identityKey: this.config.buf('identity-key'),
maxOutbound: this.config.uint('max-outbound'), maxOutbound: this.config.uint('max-outbound'),
createSocket: this.config.func('create-socket'), createSocket: this.config.func('create-socket'),
persistent: this.config.bool('persistent'), persistent: this.config.bool('persistent'),
selfish: true, selfish: true,
listen: false listen: false
}); });
this.rpc = new RPC(this); this.rpc = new RPC(this);
this.http = new HTTP({ this.http = new HTTP({
network: this.network, network: this.network,
logger: this.logger, logger: this.logger,
node: this, node: this,
prefix: this.config.prefix, prefix: this.config.prefix,
ssl: this.config.bool('ssl'), ssl: this.config.bool('ssl'),
keyFile: this.config.path('ssl-key'), keyFile: this.config.path('ssl-key'),
certFile: this.config.path('ssl-cert'), certFile: this.config.path('ssl-cert'),
host: this.config.str('http-host'), host: this.config.str('http-host'),
port: this.config.uint('http-port'), port: this.config.uint('http-port'),
apiKey: this.config.str('api-key'), apiKey: this.config.str('api-key'),
noAuth: this.config.bool('no-auth') noAuth: this.config.bool('no-auth')
}); });
this.rescanJob = null; this.rescanJob = null;
this.scanLock = new Lock(); this.scanLock = new Lock();
this.watchLock = new Lock(); this.watchLock = new Lock();
this.init(); this.init();
} }
Object.setPrototypeOf(SPVNode.prototype, Node.prototype); /**
* Initialize the node.
* @private
*/
/** init() {
* Initialize the node. // Bind to errors
* @private this.chain.on('error', err => this.error(err));
*/ this.pool.on('error', err => this.error(err));
SPVNode.prototype.init = function init() { if (this.http)
// Bind to errors this.http.on('error', err => this.error(err));
this.chain.on('error', err => this.error(err));
this.pool.on('error', err => this.error(err));
if (this.http) this.pool.on('tx', (tx) => {
this.http.on('error', err => this.error(err)); if (this.rescanJob)
return;
this.pool.on('tx', (tx) => { this.emit('tx', tx);
if (this.rescanJob) });
return;
this.emit('tx', tx); this.chain.on('block', (block) => {
}); this.emit('block', block);
});
this.chain.on('block', (block) => { this.chain.on('connect', async (entry, block) => {
this.emit('block', block); if (this.rescanJob) {
}); try {
await this.watchBlock(entry, block);
this.chain.on('connect', async (entry, block) => { } catch (e) {
if (this.rescanJob) { this.error(e);
try { }
await this.watchBlock(entry, block); return;
} catch (e) {
this.error(e);
} }
return;
}
this.emit('connect', entry, block); this.emit('connect', entry, block);
}); });
this.chain.on('disconnect', (entry, block) => { this.chain.on('disconnect', (entry, block) => {
this.emit('disconnect', entry, block); this.emit('disconnect', entry, block);
}); });
this.chain.on('reorganize', (tip, competitor) => { this.chain.on('reorganize', (tip, competitor) => {
this.emit('reorganize', tip, competitor); this.emit('reorganize', tip, competitor);
}); });
this.chain.on('reset', (tip) => { this.chain.on('reset', (tip) => {
this.emit('reset', tip); this.emit('reset', tip);
}); });
this.loadPlugins(); this.loadPlugins();
};
/**
* Open the node and all its child objects,
* wait for the database to load.
* @returns {Promise}
*/
SPVNode.prototype.open = async function open() {
assert(!this.opened, 'SPVNode is already open.');
this.opened = true;
await this.handlePreopen();
await this.chain.open();
await this.pool.open();
await this.openPlugins();
await this.http.open();
await this.handleOpen();
this.logger.info('Node is loaded.');
};
/**
* Close the node, wait for the database to close.
* @returns {Promise}
*/
SPVNode.prototype.close = async function close() {
assert(this.opened, 'SPVNode is not open.');
this.opened = false;
await this.handlePreclose();
await this.http.close();
await this.closePlugins();
await this.pool.close();
await this.chain.close();
await this.handleClose();
};
/**
* Scan for any missed transactions.
* Note that this will replay the blockchain sync.
* @param {Number|Hash} start - Start block.
* @param {Bloom} filter
* @param {Function} iter - Iterator.
* @returns {Promise}
*/
SPVNode.prototype.scan = async function scan(start, filter, iter) {
const unlock = await this.scanLock.lock();
const height = this.chain.height;
try {
await this.chain.replay(start);
if (this.chain.height < height) {
// We need to somehow defer this.
// await this.connect();
// this.startSync();
// await this.watchUntil(height, iter);
}
} finally {
unlock();
} }
};
/** /**
* Watch the blockchain until a certain height. * Open the node and all its child objects,
* @param {Number} height * wait for the database to load.
* @param {Function} iter * @returns {Promise}
* @returns {Promise} */
*/
SPVNode.prototype.watchUntil = function watchUntil(height, iter) { async open() {
return new Promise((resolve, reject) => { assert(!this.opened, 'SPVNode is already open.');
this.rescanJob = new RescanJob(resolve, reject, height, iter); this.opened = true;
});
};
/** await this.handlePreopen();
* Handled watched block. await this.chain.open();
* @param {ChainEntry} entry await this.pool.open();
* @param {MerkleBlock} block
* @returns {Promise}
*/
SPVNode.prototype.watchBlock = async function watchBlock(entry, block) { await this.openPlugins();
const unlock = await this.watchLock.lock();
try { await this.http.open();
if (entry.height < this.rescanJob.height) { await this.handleOpen();
await this.rescanJob.iter(entry, block.txs);
return; this.logger.info('Node is loaded.');
}
/**
* Close the node, wait for the database to close.
* @returns {Promise}
*/
async close() {
assert(this.opened, 'SPVNode is not open.');
this.opened = false;
await this.handlePreclose();
await this.http.close();
await this.closePlugins();
await this.pool.close();
await this.chain.close();
await this.handleClose();
}
/**
* Scan for any missed transactions.
* Note that this will replay the blockchain sync.
* @param {Number|Hash} start - Start block.
* @param {Bloom} filter
* @param {Function} iter - Iterator.
* @returns {Promise}
*/
async scan(start, filter, iter) {
const unlock = await this.scanLock.lock();
const height = this.chain.height;
try {
await this.chain.replay(start);
if (this.chain.height < height) {
// We need to somehow defer this.
// await this.connect();
// this.startSync();
// await this.watchUntil(height, iter);
}
} finally {
unlock();
} }
this.rescanJob.resolve();
this.rescanJob = null;
} catch (e) {
this.rescanJob.reject(e);
this.rescanJob = null;
} finally {
unlock();
} }
};
/** /**
* Broadcast a transaction (note that this will _not_ be verified * Watch the blockchain until a certain height.
* by the mempool - use with care, lest you get banned from * @param {Number} height
* bitcoind nodes). * @param {Function} iter
* @param {TX|Block} item * @returns {Promise}
* @returns {Promise} */
*/
SPVNode.prototype.broadcast = async function broadcast(item) { watchUntil(height, iter) {
try { return new Promise((resolve, reject) => {
await this.pool.broadcast(item); this.rescanJob = new RescanJob(resolve, reject, height, iter);
} catch (e) { });
this.emit('error', e);
} }
};
/** /**
* Broadcast a transaction (note that this will _not_ be verified * Handled watched block.
* by the mempool - use with care, lest you get banned from * @param {ChainEntry} entry
* bitcoind nodes). * @param {MerkleBlock} block
* @param {TX} tx * @returns {Promise}
* @returns {Promise} */
*/
SPVNode.prototype.sendTX = function sendTX(tx) { async watchBlock(entry, block) {
return this.broadcast(tx); const unlock = await this.watchLock.lock();
}; try {
if (entry.height < this.rescanJob.height) {
await this.rescanJob.iter(entry, block.txs);
return;
}
this.rescanJob.resolve();
this.rescanJob = null;
} catch (e) {
this.rescanJob.reject(e);
this.rescanJob = null;
} finally {
unlock();
}
}
/** /**
* Broadcast a transaction. Silence errors. * Broadcast a transaction (note that this will _not_ be verified
* @param {TX} tx * by the mempool - use with care, lest you get banned from
* @returns {Promise} * bitcoind nodes).
*/ * @param {TX|Block} item
* @returns {Promise}
*/
SPVNode.prototype.relay = function relay(tx) { async broadcast(item) {
return this.broadcast(tx); try {
}; await this.pool.broadcast(item);
} catch (e) {
this.emit('error', e);
}
}
/** /**
* Connect to the network. * Broadcast a transaction (note that this will _not_ be verified
* @returns {Promise} * by the mempool - use with care, lest you get banned from
*/ * bitcoind nodes).
* @param {TX} tx
* @returns {Promise}
*/
SPVNode.prototype.connect = function connect() { sendTX(tx) {
return this.pool.connect(); return this.broadcast(tx);
}; }
/** /**
* Disconnect from the network. * Broadcast a transaction. Silence errors.
* @returns {Promise} * @param {TX} tx
*/ * @returns {Promise}
*/
SPVNode.prototype.disconnect = function disconnect() { relay(tx) {
return this.pool.disconnect(); return this.broadcast(tx);
}; }
/** /**
* Start the blockchain sync. * Connect to the network.
*/ * @returns {Promise}
*/
SPVNode.prototype.startSync = function startSync() { connect() {
return this.pool.startSync(); return this.pool.connect();
}; }
/** /**
* Stop syncing the blockchain. * Disconnect from the network.
*/ * @returns {Promise}
*/
SPVNode.prototype.stopSync = function stopSync() { disconnect() {
return this.pool.stopSync(); return this.pool.disconnect();
}; }
/**
* Start the blockchain sync.
*/
startSync() {
return this.pool.startSync();
}
/**
* Stop syncing the blockchain.
*/
stopSync() {
return this.pool.stopSync();
}
}
/* /*
* Helpers * Helpers
*/ */
function RescanJob(resolve, reject, height, iter) { class RescanJob {
this.resolve = resolve; constructor(resolve, reject, height, iter) {
this.reject = reject; this.resolve = resolve;
this.height = height; this.reject = reject;
this.iter = iter; this.height = height;
this.iter = iter;
}
} }
/* /*