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');
/**
* Full Node
* Respresents a fullnode complete with a
* chain, mempool, miner, etc.
* @alias module:node.FullNode
* @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) {
if (!(this instanceof FullNode))
return new FullNode(options);
class FullNode extends Node {
/**
* 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.
this.spv = false;
// SPV flag.
this.spv = false;
// Instantiate blockchain.
this.chain = new Chain({
network: this.network,
logger: this.logger,
workers: this.workers,
db: this.config.str('db'),
prefix: this.config.prefix,
maxFiles: this.config.uint('max-files'),
cacheSize: this.config.mb('cache-size'),
forceFlags: this.config.bool('force-flags'),
bip91: this.config.bool('bip91'),
bip148: this.config.bool('bip148'),
prune: this.config.bool('prune'),
checkpoints: this.config.bool('checkpoints'),
coinCache: this.config.mb('coin-cache'),
entryCache: this.config.uint('entry-cache'),
indexTX: this.config.bool('index-tx'),
indexAddress: this.config.bool('index-address')
});
// Instantiate blockchain.
this.chain = new Chain({
network: this.network,
logger: this.logger,
workers: this.workers,
db: this.config.str('db'),
prefix: this.config.prefix,
maxFiles: this.config.uint('max-files'),
cacheSize: this.config.mb('cache-size'),
forceFlags: this.config.bool('force-flags'),
bip91: this.config.bool('bip91'),
bip148: this.config.bool('bip148'),
prune: this.config.bool('prune'),
checkpoints: this.config.bool('checkpoints'),
coinCache: this.config.mb('coin-cache'),
entryCache: this.config.uint('entry-cache'),
indexTX: this.config.bool('index-tx'),
indexAddress: this.config.bool('index-address')
});
// Fee estimation.
this.fees = new Fees(this.logger);
this.fees.init();
// Fee estimation.
this.fees = new Fees(this.logger);
this.fees.init();
// Mempool needs access to the chain.
this.mempool = new Mempool({
network: this.network,
logger: this.logger,
workers: this.workers,
chain: this.chain,
fees: this.fees,
db: this.config.str('db'),
prefix: this.config.prefix,
persistent: this.config.bool('persistent-mempool'),
maxSize: this.config.mb('mempool-size'),
limitFree: this.config.bool('limit-free'),
limitFreeRelay: this.config.uint('limit-free-relay'),
requireStandard: this.config.bool('require-standard'),
rejectAbsurdFees: this.config.bool('reject-absurd-fees'),
replaceByFee: this.config.bool('replace-by-fee'),
indexAddress: this.config.bool('index-address')
});
// Mempool needs access to the chain.
this.mempool = new Mempool({
network: this.network,
logger: this.logger,
workers: this.workers,
chain: this.chain,
fees: this.fees,
db: this.config.str('db'),
prefix: this.config.prefix,
persistent: this.config.bool('persistent-mempool'),
maxSize: this.config.mb('mempool-size'),
limitFree: this.config.bool('limit-free'),
limitFreeRelay: this.config.uint('limit-free-relay'),
requireStandard: this.config.bool('require-standard'),
rejectAbsurdFees: this.config.bool('reject-absurd-fees'),
replaceByFee: this.config.bool('replace-by-fee'),
indexAddress: this.config.bool('index-address')
});
// Pool needs access to the chain and mempool.
this.pool = new Pool({
network: this.network,
logger: this.logger,
chain: this.chain,
mempool: this.mempool,
prefix: this.config.prefix,
selfish: this.config.bool('selfish'),
compact: this.config.bool('compact'),
bip37: this.config.bool('bip37'),
bip151: this.config.bool('bip151'),
bip150: this.config.bool('bip150'),
identityKey: this.config.buf('identity-key'),
maxOutbound: this.config.uint('max-outbound'),
maxInbound: this.config.uint('max-inbound'),
createSocket: this.config.func('create-socket'),
proxy: this.config.str('proxy'),
onion: this.config.bool('onion'),
upnp: this.config.bool('upnp'),
seeds: this.config.array('seeds'),
nodes: this.config.array('nodes'),
only: this.config.array('only'),
publicHost: this.config.str('public-host'),
publicPort: this.config.uint('public-port'),
host: this.config.str('host'),
port: this.config.uint('port'),
listen: this.config.bool('listen'),
persistent: this.config.bool('persistent')
});
// Pool needs access to the chain and mempool.
this.pool = new Pool({
network: this.network,
logger: this.logger,
chain: this.chain,
mempool: this.mempool,
prefix: this.config.prefix,
selfish: this.config.bool('selfish'),
compact: this.config.bool('compact'),
bip37: this.config.bool('bip37'),
bip151: this.config.bool('bip151'),
bip150: this.config.bool('bip150'),
identityKey: this.config.buf('identity-key'),
maxOutbound: this.config.uint('max-outbound'),
maxInbound: this.config.uint('max-inbound'),
createSocket: this.config.func('create-socket'),
proxy: this.config.str('proxy'),
onion: this.config.bool('onion'),
upnp: this.config.bool('upnp'),
seeds: this.config.array('seeds'),
nodes: this.config.array('nodes'),
only: this.config.array('only'),
publicHost: this.config.str('public-host'),
publicPort: this.config.uint('public-port'),
host: this.config.str('host'),
port: this.config.uint('port'),
listen: this.config.bool('listen'),
persistent: this.config.bool('persistent')
});
// Miner needs access to the chain and mempool.
this.miner = new Miner({
network: this.network,
logger: this.logger,
workers: this.workers,
chain: this.chain,
mempool: this.mempool,
address: this.config.array('coinbase-address'),
coinbaseFlags: this.config.str('coinbase-flags'),
preverify: this.config.bool('preverify'),
maxWeight: this.config.uint('max-weight'),
reservedWeight: this.config.uint('reserved-weight'),
reservedSigops: this.config.uint('reserved-sigops')
});
// Miner needs access to the chain and mempool.
this.miner = new Miner({
network: this.network,
logger: this.logger,
workers: this.workers,
chain: this.chain,
mempool: this.mempool,
address: this.config.array('coinbase-address'),
coinbaseFlags: this.config.str('coinbase-flags'),
preverify: this.config.bool('preverify'),
maxWeight: this.config.uint('max-weight'),
reservedWeight: this.config.uint('reserved-weight'),
reservedSigops: this.config.uint('reserved-sigops')
});
// RPC needs access to the node.
this.rpc = new RPC(this);
// RPC needs access to the node.
this.rpc = new RPC(this);
// HTTP needs access to the node.
this.http = new HTTP({
network: this.network,
logger: this.logger,
node: this,
prefix: this.config.prefix,
ssl: this.config.bool('ssl'),
keyFile: this.config.path('ssl-key'),
certFile: this.config.path('ssl-cert'),
host: this.config.str('http-host'),
port: this.config.uint('http-port'),
apiKey: this.config.str('api-key'),
noAuth: this.config.bool('no-auth')
});
// HTTP needs access to the node.
this.http = new HTTP({
network: this.network,
logger: this.logger,
node: this,
prefix: this.config.prefix,
ssl: this.config.bool('ssl'),
keyFile: this.config.path('ssl-key'),
certFile: this.config.path('ssl-cert'),
host: this.config.str('http-host'),
port: this.config.uint('http-port'),
apiKey: this.config.str('api-key'),
noAuth: this.config.bool('no-auth')
});
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);
this.init();
}
};
/**
* Add transaction to mempool, broadcast.
* @param {TX} tx
*/
/**
* Initialize the node.
* @private
*/
FullNode.prototype.sendTX = async function sendTX(tx) {
let missing;
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));
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());
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}
*/
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.broadcast(tx);
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());
this.logger.warning('Attempting to broadcast anyway...');
this.broadcast(tx);
return;
/**
* Add transaction to mempool, broadcast. Silence errors.
* @param {TX} tx
* @returns {Promise}
*/
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.
if (this.pool.options.selfish)
this.pool.broadcast(tx);
};
/**
* Connect to the network.
* @returns {Promise}
*/
/**
* Add transaction to mempool, broadcast. Silence errors.
* @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);
connect() {
return this.pool.connect();
}
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
* address from the mempool or chain database.
* @param {Address} addrs
* @returns {Promise} - Returns {@link TXMeta}[].
*/
/**
* Start the blockchain sync.
*/
FullNode.prototype.getMetaByAddress = async function getMetaByAddress(addrs) {
const mempool = this.mempool.getMetaByAddress(addrs);
const chain = await this.chain.getMetaByAddress(addrs);
return chain.concat(mempool);
};
startSync() {
return this.pool.startSync();
}
/**
* Retrieve a transaction from the mempool or chain database.
* @param {Hash} hash
* @returns {Promise} - Returns {@link TXMeta}.
*/
/**
* Stop syncing the blockchain.
*/
FullNode.prototype.getMeta = async function getMeta(hash) {
const meta = this.mempool.getMeta(hash);
stopSync() {
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.
* @param {TXMeta} meta
* @returns {Promise} - Returns {@link CoinView}.
*/
/**
* 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.getMetaView = async function getMetaView(meta) {
if (meta.height === -1)
return this.mempool.getSpentView(meta.tx);
return this.chain.getSpentView(meta.tx);
};
async getCoin(hash, index) {
const coin = this.mempool.getCoin(hash, index);
/**
* Retrieve transactions pertaining to an
* address from the mempool or chain database.
* @param {Address} addrs
* @returns {Promise} - Returns {@link TX}[].
*/
if (coin)
return coin;
FullNode.prototype.getTXByAddress = async function getTXByAddress(addrs) {
const mtxs = await this.getMetaByAddress(addrs);
const out = [];
if (this.mempool.isSpent(hash, index))
return null;
for (const mtx of mtxs)
out.push(mtx.tx);
return this.chain.getCoin(hash, index);
}
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}[].
*/
/**
* Retrieve a transaction from the mempool or chain database.
* @param {Hash} hash
* @returns {Promise} - Returns {@link TX}.
*/
async getCoinsByAddress(addrs) {
const mempool = this.mempool.getCoinsByAddress(addrs);
const chain = await this.chain.getCoinsByAddress(addrs);
const out = [];
FullNode.prototype.getTX = async function getTX(hash) {
const mtx = await this.getMeta(hash);
for (const coin of chain) {
const spent = this.mempool.isSpent(coin.hash, coin.index);
if (!mtx)
return null;
if (spent)
continue;
return mtx.tx;
};
out.push(coin);
}
/**
* Test whether the mempool or chain contains a transaction.
* @param {Hash} hash
* @returns {Promise} - Returns Boolean.
*/
for (const coin of mempool)
out.push(coin);
FullNode.prototype.hasTX = async function hasTX(hash) {
if (this.mempool.hasEntry(hash))
return true;
return out;
}
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

View File

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

View File

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

View File

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