node: classify.
This commit is contained in:
parent
4ebfb5d9ff
commit
93fe6669bf
@ -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
|
||||
|
||||
@ -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) {
|
||||
|
||||
605
lib/node/node.js
605
lib/node/node.js
@ -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
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
Loading…
Reference in New Issue
Block a user