refactor: improve generator perf.

This commit is contained in:
Christopher Jeffrey 2016-09-21 22:58:27 -07:00
parent 2899219033
commit ec0d50d506
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
24 changed files with 7003 additions and 7575 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -177,45 +177,43 @@ ChainEntry.prototype.getRetargetAncestors = function getRetargetAncestors() {
* @param {Function} callback - Returns [Error, ChainEntry[]]. * @param {Function} callback - Returns [Error, ChainEntry[]].
*/ */
ChainEntry.prototype.getAncestors = function getAncestors(max) { ChainEntry.prototype.getAncestors = spawn.co(function* getAncestors(max) {
return spawn(function *() { var entry = this;
var entry = this; var ancestors = [];
var ancestors = []; var cached;
var cached;
if (max === 0) if (max === 0)
return ancestors;
assert(utils.isNumber(max));
// Try to do this iteratively and synchronously
// so we don't have to wait on nextTicks.
for (;;) {
ancestors.push(entry);
if (ancestors.length >= max)
return ancestors; return ancestors;
assert(utils.isNumber(max)); cached = this.chain.db.getCache(entry.prevBlock);
// Try to do this iteratively and synchronously if (!cached) {
// so we don't have to wait on nextTicks. ancestors.pop();
for (;;) { break;
ancestors.push(entry);
if (ancestors.length >= max)
return ancestors;
cached = this.chain.db.getCache(entry.prevBlock);
if (!cached) {
ancestors.pop();
break;
}
entry = cached;
} }
while (entry) { entry = cached;
ancestors.push(entry); }
if (ancestors.length >= max)
break;
entry = yield entry.getPrevious();
}
return ancestors; while (entry) {
}, this); ancestors.push(entry);
}; if (ancestors.length >= max)
break;
entry = yield entry.getPrevious();
}
return ancestors;
});
/** /**
* Test whether the entry is in the main chain. * Test whether the entry is in the main chain.
@ -232,31 +230,29 @@ ChainEntry.prototype.isMainChain = function isMainChain() {
* @param {Function} callback - Returns [Error, ChainEntry[]]. * @param {Function} callback - Returns [Error, ChainEntry[]].
*/ */
ChainEntry.prototype.getAncestorByHeight = function getAncestorByHeight(height) { ChainEntry.prototype.getAncestorByHeight = spawn.co(function* getAncestorByHeight(height) {
return spawn(function *() { var main, entry;
var main, entry;
if (height < 0) if (height < 0)
return yield utils.wait(); return yield utils.wait();
assert(height >= 0); assert(height >= 0);
assert(height <= this.height); assert(height <= this.height);
main = yield this.isMainChain(); main = yield this.isMainChain();
if (main) if (main)
return yield this.chain.db.get(height); return yield this.chain.db.get(height);
entry = yield this.getAncestor(this.height - height); entry = yield this.getAncestor(this.height - height);
if (!entry) if (!entry)
return; return;
assert(entry.height === height); assert(entry.height === height);
return entry; return entry;
}, this); });
};
/** /**
* Get a single ancestor by index. Note that index-0 is * Get a single ancestor by index. Note that index-0 is
@ -266,20 +262,18 @@ ChainEntry.prototype.getAncestorByHeight = function getAncestorByHeight(height)
* @returns {Function} callback - Returns [Error, ChainEntry]. * @returns {Function} callback - Returns [Error, ChainEntry].
*/ */
ChainEntry.prototype.getAncestor = function getAncestor(index) { ChainEntry.prototype.getAncestor = spawn.co(function* getAncestor(index) {
return spawn(function *() { var ancestors;
var ancestors;
assert(index >= 0); assert(index >= 0);
ancestors = yield this.getAncestors(index + 1); ancestors = yield this.getAncestors(index + 1);
if (ancestors.length < index + 1) if (ancestors.length < index + 1)
return; return;
return ancestors[index]; return ancestors[index];
}, this); });
};
/** /**
* Get previous entry. * Get previous entry.
@ -295,14 +289,12 @@ ChainEntry.prototype.getPrevious = function getPrevious() {
* @param {Function} callback - Returns [Error, ChainEntry]. * @param {Function} callback - Returns [Error, ChainEntry].
*/ */
ChainEntry.prototype.getNext = function getNext() { ChainEntry.prototype.getNext = spawn.co(function* getNext() {
return spawn(function *() { var hash = yield this.chain.db.getNextHash(this.hash);
var hash = yield this.chain.db.getNextHash(this.hash); if (!hash)
if (!hash) return;
return; return yield this.chain.db.get(hash);
return yield this.chain.db.get(hash); });
}, this);
};
/** /**
* Get median time past. * Get median time past.
@ -330,13 +322,11 @@ ChainEntry.prototype.getMedianTime = function getMedianTime(ancestors) {
* @param {Function} callback - Returns [Error, Number]. * @param {Function} callback - Returns [Error, Number].
*/ */
ChainEntry.prototype.getMedianTimeAsync = function getMedianTimeAsync() { ChainEntry.prototype.getMedianTimeAsync = spawn.co(function* getMedianTimeAsync() {
return spawn(function *() { var MEDIAN_TIMESPAN = constants.block.MEDIAN_TIMESPAN;
var MEDIAN_TIMESPAN = constants.block.MEDIAN_TIMESPAN; var ancestors = yield this.getAncestors(MEDIAN_TIMESPAN);
var ancestors = yield this.getAncestors(MEDIAN_TIMESPAN); return this.getMedianTime(ancestors);
return this.getMedianTime(ancestors); });
}, this);
};
/** /**
* Check isSuperMajority against majorityRejectOutdated. * Check isSuperMajority against majorityRejectOutdated.
@ -419,13 +409,11 @@ ChainEntry.prototype.isSuperMajority = function isSuperMajority(version, require
* @returns {Boolean} * @returns {Boolean}
*/ */
ChainEntry.prototype.isSuperMajorityAsync = function isSuperMajorityAsync(version, required) { ChainEntry.prototype.isSuperMajorityAsync = spawn.co(function* isSuperMajorityAsync(version, required) {
return spawn(function *() { var majorityWindow = this.network.block.majorityWindow;
var majorityWindow = this.network.block.majorityWindow; var ancestors = yield this.getAncestors(majorityWindow);
var ancestors = yield this.getAncestors(majorityWindow); return this.isSuperMajority(version, required, ancestors);
return this.isSuperMajority(version, required, ancestors); });
}, this);
};
/** /**
* Test whether the entry is potentially an ancestor of a checkpoint. * Test whether the entry is potentially an ancestor of a checkpoint.

View File

@ -290,12 +290,10 @@ LowlevelUp.prototype.approximateSize = function approximateSize(start, end) {
* @param {Function} callback - Returns [Error, Boolean]. * @param {Function} callback - Returns [Error, Boolean].
*/ */
LowlevelUp.prototype.has = function has(key) { LowlevelUp.prototype.has = spawn.co(function* has(key) {
return spawn(function *() { var value = yield this.get(key);
var value = yield this.get(key); return value != null;
return value != null; });
}, this);
};
/** /**
* Get and deserialize a record with a callback. * Get and deserialize a record with a callback.
@ -305,15 +303,13 @@ LowlevelUp.prototype.has = function has(key) {
* @param {Function} callback - Returns [Error, Object]. * @param {Function} callback - Returns [Error, Object].
*/ */
LowlevelUp.prototype.fetch = function fetch(key, parse) { LowlevelUp.prototype.fetch = spawn.co(function* fetch(key, parse) {
return spawn(function *() { var value = yield this.get(key);
var value = yield this.get(key); if (!value)
if (!value) return;
return;
return parse(value, key); return parse(value, key);
}, this); });
};
/** /**
* Collect all keys from iterator options. * Collect all keys from iterator options.
@ -321,29 +317,27 @@ LowlevelUp.prototype.fetch = function fetch(key, parse) {
* @param {Function} callback - Returns [Error, Array]. * @param {Function} callback - Returns [Error, Array].
*/ */
LowlevelUp.prototype.iterate = function iterate(options) { LowlevelUp.prototype.iterate = spawn.co(function* iterate(options) {
return spawn(function *() { var items = [];
var items = []; var iter, kv, result;
var iter, kv, result;
assert(typeof options.parse === 'function', 'Parse must be a function.'); assert(typeof options.parse === 'function', 'Parse must be a function.');
iter = this.iterator(options); iter = this.iterator(options);
for (;;) { for (;;) {
kv = yield iter.next(); kv = yield iter.next();
if (!kv) if (!kv)
return items; return items;
result = options.parse(kv[0], kv[1]); result = options.parse(kv[0], kv[1]);
if (result) if (result)
items.push(result); items.push(result);
} }
return items; return items;
}, this); });
};
/** /**
* Write and assert a version number for the database. * Write and assert a version number for the database.
@ -351,23 +345,21 @@ LowlevelUp.prototype.iterate = function iterate(options) {
* @param {Function} callback * @param {Function} callback
*/ */
LowlevelUp.prototype.checkVersion = function checkVersion(key, version) { LowlevelUp.prototype.checkVersion = spawn.co(function* checkVersion(key, version) {
return spawn(function *() { var data = yield this.get(key);
var data = yield this.get(key);
if (!data) { if (!data) {
data = new Buffer(4); data = new Buffer(4);
data.writeUInt32LE(version, 0, true); data.writeUInt32LE(version, 0, true);
yield this.put(key, data); yield this.put(key, data);
return; return;
} }
data = data.readUInt32LE(0, true); data = data.readUInt32LE(0, true);
if (data !== version) if (data !== version)
throw new Error(VERSION_ERROR); throw new Error(VERSION_ERROR);
}, this); });
};
/** /**
* Clone the database. * Clone the database.
@ -375,60 +367,58 @@ LowlevelUp.prototype.checkVersion = function checkVersion(key, version) {
* @param {Function} callback * @param {Function} callback
*/ */
LowlevelUp.prototype.clone = function clone(path) { LowlevelUp.prototype.clone = spawn.co(function* clone(path) {
return spawn(function *() { var opt = { keys: true, values: true };
var opt = { keys: true, values: true }; var options = utils.merge({}, this.options);
var options = utils.merge({}, this.options); var hwm = 256 << 20;
var hwm = 256 << 20; var total = 0;
var total = 0; var tmp, batch, iter, items, key, value;
var tmp, batch, iter, items, key, value;
assert(!this.loading); assert(!this.loading);
assert(!this.closing); assert(!this.closing);
assert(this.loaded); assert(this.loaded);
options.createIfMissing = true; options.createIfMissing = true;
options.errorIfExists = true; options.errorIfExists = true;
tmp = new LowlevelUp(path, options); tmp = new LowlevelUp(path, options);
yield tmp.open(); yield tmp.open();
batch = tmp.batch(); batch = tmp.batch();
iter = this.iterator(opt); iter = this.iterator(opt);
for (;;) { for (;;) {
items = yield iter.next(); items = yield iter.next();
if (!items) { if (!items) {
try { try {
yield batch.write(); yield batch.write();
} catch (e) { } catch (e) {
yield tmp.close(); yield tmp.close();
throw e; throw e;
}
return;
}
key = items[0];
value = items[0];
batch.put(key, value);
total += value.length;
if (total >= hwm) {
total = 0;
try {
yield batch.write();
} catch (e) {
yield tmp.close();
throw e;
}
batch = tmp.batch();
} }
return;
} }
}, this);
}; key = items[0];
value = items[0];
batch.put(key, value);
total += value.length;
if (total >= hwm) {
total = 0;
try {
yield batch.write();
} catch (e) {
yield tmp.close();
throw e;
}
batch = tmp.batch();
}
}
});
function Batch(db) { function Batch(db) {
this.db = db; this.db = db;

View File

@ -56,71 +56,69 @@ utils.inherits(HTTPClient, AsyncObject);
* @param {Function} callback * @param {Function} callback
*/ */
HTTPClient.prototype._open = function _open() { HTTPClient.prototype._open = spawn.co(function* _open() {
return spawn(function *() { var self = this;
var self = this; var IOClient;
var IOClient;
try { try {
IOClient = require('socket.io-client'); IOClient = require('socket.io-client');
} catch (e) { } catch (e) {
; ;
} }
if (!IOClient) if (!IOClient)
return; return;
this.socket = new IOClient(this.uri, { this.socket = new IOClient(this.uri, {
transports: ['websocket'], transports: ['websocket'],
forceNew: true forceNew: true
});
this.socket.on('error', function(err) {
self.emit('error', err);
});
this.socket.on('version', function(info) {
if (info.network !== self.network.type)
self.emit('error', new Error('Wrong network.'));
});
this.socket.on('wallet tx', function(details) {
self.emit('tx', details);
});
this.socket.on('wallet confirmed', function(details) {
self.emit('confirmed', details);
});
this.socket.on('wallet unconfirmed', function(details) {
self.emit('unconfirmed', details);
});
this.socket.on('wallet conflict', function(details) {
self.emit('conflict', details);
});
this.socket.on('wallet updated', function(details) {
self.emit('updated', details);
});
this.socket.on('wallet address', function(receive) {
self.emit('address', receive);
});
this.socket.on('wallet balance', function(balance) {
self.emit('balance', {
id: balance.id,
confirmed: utils.satoshi(balance.confirmed),
unconfirmed: utils.satoshi(balance.unconfirmed),
total: utils.satoshi(balance.total)
}); });
});
this.socket.on('error', function(err) { yield this._onConnect();
self.emit('error', err); yield this._sendAuth();
}); });
this.socket.on('version', function(info) {
if (info.network !== self.network.type)
self.emit('error', new Error('Wrong network.'));
});
this.socket.on('wallet tx', function(details) {
self.emit('tx', details);
});
this.socket.on('wallet confirmed', function(details) {
self.emit('confirmed', details);
});
this.socket.on('wallet unconfirmed', function(details) {
self.emit('unconfirmed', details);
});
this.socket.on('wallet conflict', function(details) {
self.emit('conflict', details);
});
this.socket.on('wallet updated', function(details) {
self.emit('updated', details);
});
this.socket.on('wallet address', function(receive) {
self.emit('address', receive);
});
this.socket.on('wallet balance', function(balance) {
self.emit('balance', {
id: balance.id,
confirmed: utils.satoshi(balance.confirmed),
unconfirmed: utils.satoshi(balance.unconfirmed),
total: utils.satoshi(balance.total)
});
});
yield this._onConnect();
yield this._sendAuth();
}, this);
};
HTTPClient.prototype._onConnect = function _onConnect() { HTTPClient.prototype._onConnect = function _onConnect() {
var self = this; var self = this;
@ -165,58 +163,56 @@ HTTPClient.prototype._close = function close() {
* @param {Function} callback - Returns [Error, Object?]. * @param {Function} callback - Returns [Error, Object?].
*/ */
HTTPClient.prototype._request = function _request(method, endpoint, json) { HTTPClient.prototype._request = spawn.co(function* _request(method, endpoint, json) {
return spawn(function *() { var query, network, height, res;
var query, network, height, res;
if (this.token) { if (this.token) {
if (!json) if (!json)
json = {}; json = {};
json.token = this.token; json.token = this.token;
} }
if (json && method === 'get') { if (json && method === 'get') {
query = json; query = json;
json = null; json = null;
} }
res = yield request({ res = yield request({
method: method, method: method,
uri: this.uri + endpoint, uri: this.uri + endpoint,
query: query, query: query,
json: json, json: json,
auth: { auth: {
username: 'bitcoinrpc', username: 'bitcoinrpc',
password: this.apiKey || '' password: this.apiKey || ''
}, },
expect: 'json' expect: 'json'
}); });
network = res.headers['x-bcoin-network']; network = res.headers['x-bcoin-network'];
if (network !== this.network.type) if (network !== this.network.type)
throw new Error('Wrong network.'); throw new Error('Wrong network.');
height = +res.headers['x-bcoin-height']; height = +res.headers['x-bcoin-height'];
if (utils.isNumber(height)) if (utils.isNumber(height))
this.network.updateHeight(height); this.network.updateHeight(height);
if (res.statusCode === 404) if (res.statusCode === 404)
return; return;
if (!res.body) if (!res.body)
throw new Error('No body.'); throw new Error('No body.');
if (res.statusCode !== 200) { if (res.statusCode !== 200) {
if (res.body.error) if (res.body.error)
throw new Error(res.body.error); throw new Error(res.body.error);
throw new Error('Status code: ' + res.statusCode); throw new Error('Status code: ' + res.statusCode);
} }
return res.body; return res.body;
}, this); });
};
/** /**
* Make a GET http request to endpoint. * Make a GET http request to endpoint.
@ -569,13 +565,11 @@ HTTPClient.prototype.send = function send(id, options) {
* @param {Function} callback * @param {Function} callback
*/ */
HTTPClient.prototype.retoken = function retoken(id, passphrase) { HTTPClient.prototype.retoken = spawn.co(function* retoken(id, passphrase) {
return spawn(function *() { var options = { passphrase: passphrase };
var options = { passphrase: passphrase }; var body = yield this._post('/wallet/' + id + '/retoken', options);
var body = yield this._post('/wallet/' + id + '/retoken', options); return body.token;
return body.token; });
}, this);
};
/** /**
* Change or set master key's passphrase. * Change or set master key's passphrase.

File diff suppressed because it is too large Load Diff

View File

@ -44,38 +44,36 @@ function RPCClient(options) {
* @param {Function} callback - Returns [Error, Object?]. * @param {Function} callback - Returns [Error, Object?].
*/ */
RPCClient.prototype.call = function call(method, params) { RPCClient.prototype.call = spawn.co(function* call(method, params) {
return spawn(function *() { var res = yield request.promise({
var res = yield request.promise({ method: 'POST',
method: 'POST', uri: this.uri,
uri: this.uri, json: {
json: { method: method,
method: method, params: params,
params: params, id: this.id++
id: this.id++ },
}, auth: {
auth: { username: 'bitcoinrpc',
username: 'bitcoinrpc', password: this.apiKey || ''
password: this.apiKey || '' },
}, expect: 'json'
expect: 'json' });
});
if (!res.body) if (!res.body)
return; return;
if (res.statusCode === 400)
return res.body.result;
if (res.statusCode !== 200) {
if (res.body.error)
throw new Error(res.body.error.message);
throw new Error('Status code: ' + res.statusCode);
}
if (res.statusCode === 400)
return res.body.result; return res.body.result;
}, this);
}; if (res.statusCode !== 200) {
if (res.body.error)
throw new Error(res.body.error.message);
throw new Error('Status code: ' + res.statusCode);
}
return res.body.result;
});
/* /*
* Expose * Expose

View File

@ -1062,20 +1062,18 @@ HTTPServer.prototype._initIO = function _initIO() {
* @param {Function} callback * @param {Function} callback
*/ */
HTTPServer.prototype.open = function open() { HTTPServer.prototype.open = spawn.co(function* open() {
return spawn(function *() { yield this.server.open();
yield this.server.open();
this.logger.info('HTTP server loaded.'); this.logger.info('HTTP server loaded.');
if (this.apiKey) { if (this.apiKey) {
this.logger.info('HTTP API key: %s', this.apiKey); this.logger.info('HTTP API key: %s', this.apiKey);
this.apiKey = null; this.apiKey = null;
} else if (!this.apiHash) { } else if (!this.apiHash) {
this.logger.warning('WARNING: Your http server is open to the world.'); this.logger.warning('WARNING: Your http server is open to the world.');
} }
}, this); });
};
/** /**
* Close the server, wait for server socket to close. * Close the server, wait for server socket to close.

View File

@ -89,28 +89,26 @@ HTTPWallet.prototype._init = function _init() {
* @param {Function} callback * @param {Function} callback
*/ */
HTTPWallet.prototype.open = function open(options) { HTTPWallet.prototype.open = spawn.co(function* open(options) {
return spawn(function *() { var wallet;
var wallet;
this.id = options.id; this.id = options.id;
if (options.token) { if (options.token) {
this.token = options.token; this.token = options.token;
if (Buffer.isBuffer(this.token)) if (Buffer.isBuffer(this.token))
this.token = this.token.toString('hex'); this.token = this.token.toString('hex');
this.client.token = this.token; this.client.token = this.token;
} }
yield this.client.open(); yield this.client.open();
wallet = yield this.client.getWallet(this.id); wallet = yield this.client.getWallet(this.id);
yield this.client.join(this.id, wallet.token); yield this.client.join(this.id, wallet.token);
return wallet; return wallet;
}, this); });
};
/** /**
* Open the client and create a wallet. * Open the client and create a wallet.
@ -118,17 +116,15 @@ HTTPWallet.prototype.open = function open(options) {
* @param {Function} callback * @param {Function} callback
*/ */
HTTPWallet.prototype.create = function create(options) { HTTPWallet.prototype.create = spawn.co(function* create(options) {
return spawn(function *() { var wallet;
var wallet; yield this.client.open();
yield this.client.open(); wallet = yield this.client.createWallet(options);
wallet = yield this.client.createWallet(options); return yield this.open({
return yield this.open({ id: wallet.id,
id: wallet.id, token: wallet.token
token: wallet.token });
}); });
}, this);
};
/** /**
* Close the client, wait for the socket to close. * Close the client, wait for the socket to close.
@ -296,16 +292,14 @@ HTTPWallet.prototype.setPassphrase = function setPassphrase(old, new_) {
* @see Wallet#retoken * @see Wallet#retoken
*/ */
HTTPWallet.prototype.retoken = function retoken(passphrase) { HTTPWallet.prototype.retoken = spawn.co(function* retoken(passphrase) {
return spawn(function *() { var token = yield this.client.retoken(this.id, passphrase);
var token = yield this.client.retoken(this.id, passphrase);
this.token = token; this.token = token;
this.client.token = token; this.client.token = token;
return token; return token;
}, this); });
};
/* /*
* Expose * Expose

View File

@ -119,13 +119,11 @@ utils.inherits(Mempool, AsyncObject);
* @param {Function} callback * @param {Function} callback
*/ */
Mempool.prototype._open = function open() { Mempool.prototype._open = spawn.co(function* open() {
return spawn(function *() { var size = (this.maxSize / 1024).toFixed(2);
var size = (this.maxSize / 1024).toFixed(2); yield this.chain.open();
yield this.chain.open(); this.logger.info('Mempool loaded (maxsize=%dkb).', size);
this.logger.info('Mempool loaded (maxsize=%dkb).', size); });
}, this);
};
/** /**
* Close the chain, wait for the database to close. * Close the chain, wait for the database to close.
@ -155,46 +153,44 @@ Mempool.prototype._lock = function _lock(tx, force) {
* @param {Function} callback * @param {Function} callback
*/ */
Mempool.prototype.addBlock = function addBlock(block) { Mempool.prototype.addBlock = spawn.co(function* addBlock(block) {
return spawn(function *() { var unlock = yield this._lock();
var unlock = yield this._lock(); var entries = [];
var entries = []; var i, entry, tx, hash;
var i, entry, tx, hash;
for (i = block.txs.length - 1; i >= 0; i--) { for (i = block.txs.length - 1; i >= 0; i--) {
tx = block.txs[i]; tx = block.txs[i];
hash = tx.hash('hex'); hash = tx.hash('hex');
if (tx.isCoinbase()) if (tx.isCoinbase())
continue; continue;
entry = this.getEntry(hash); entry = this.getEntry(hash);
if (!entry) { if (!entry) {
this.removeOrphan(hash); this.removeOrphan(hash);
continue; continue;
}
this.removeUnchecked(entry);
this.emit('confirmed', tx, block);
entries.push(entry);
} }
this.blockSinceBump = true; this.removeUnchecked(entry);
this.lastFeeUpdate = utils.now(); this.emit('confirmed', tx, block);
if (this.fees) entries.push(entry);
this.fees.processBlock(block.height, entries, this.chain.isFull()); }
// We need to reset the rejects filter periodically. this.blockSinceBump = true;
// There may be a locktime in a TX that is now valid. this.lastFeeUpdate = utils.now();
this.rejects.reset();
yield utils.wait(); if (this.fees)
unlock(); this.fees.processBlock(block.height, entries, this.chain.isFull());
}, this);
}; // We need to reset the rejects filter periodically.
// There may be a locktime in a TX that is now valid.
this.rejects.reset();
yield utils.wait();
unlock();
});
/** /**
* Notify the mempool that a block has been disconnected * Notify the mempool that a block has been disconnected
@ -203,38 +199,36 @@ Mempool.prototype.addBlock = function addBlock(block) {
* @param {Function} callback * @param {Function} callback
*/ */
Mempool.prototype.removeBlock = function removeBlock(block) { Mempool.prototype.removeBlock = spawn.co(function* removeBlock(block) {
return spawn(function *() { var unlock = yield this.lock();
var unlock = yield this.lock(); var i, entry, tx, hash;
var i, entry, tx, hash;
for (i = 0; i < block.txs.length; i++) { for (i = 0; i < block.txs.length; i++) {
tx = block.txs[i]; tx = block.txs[i];
hash = tx.hash('hex'); hash = tx.hash('hex');
if (tx.isCoinbase()) if (tx.isCoinbase())
continue; continue;
if (this.hasTX(hash)) if (this.hasTX(hash))
continue; continue;
entry = MempoolEntry.fromTX(tx, block.height); entry = MempoolEntry.fromTX(tx, block.height);
try { try {
yield this.addUnchecked(entry, true); yield this.addUnchecked(entry, true);
} catch (e) { } catch (e) {
unlock(); unlock();
throw e; throw e;
}
this.emit('unconfirmed', tx, block);
} }
this.rejects.reset(); this.emit('unconfirmed', tx, block);
}
unlock(); this.rejects.reset();
}, this);
}; unlock();
});
/** /**
* Ensure the size of the mempool stays below 300mb. * Ensure the size of the mempool stays below 300mb.
@ -530,26 +524,24 @@ Mempool.prototype.hasReject = function hasReject(hash) {
* @param {Function} callback - Returns [{@link VerifyError}]. * @param {Function} callback - Returns [{@link VerifyError}].
*/ */
Mempool.prototype.addTX = function addTX(tx) { Mempool.prototype.addTX = spawn.co(function* addTX(tx) {
return spawn(function *() { var unlock = yield this._lock(tx);
var unlock = yield this._lock(tx); var missing;
var missing;
try { try {
missing = yield this._addTX(tx); missing = yield this._addTX(tx);
} catch (err) { } catch (err) {
if (err.type === 'VerifyError') { if (err.type === 'VerifyError') {
if (!tx.hasWitness() && !err.malleated) if (!tx.hasWitness() && !err.malleated)
this.rejects.add(tx.hash()); this.rejects.add(tx.hash());
}
unlock();
throw err;
} }
unlock(); unlock();
return missing; throw err;
}, this); }
};
unlock();
return missing;
});
/** /**
* Add a transaction to the mempool. * Add a transaction to the mempool.
@ -558,117 +550,115 @@ Mempool.prototype.addTX = function addTX(tx) {
* @param {Function} callback - Returns [{@link VerifyError}]. * @param {Function} callback - Returns [{@link VerifyError}].
*/ */
Mempool.prototype._addTX = function _addTX(tx) { Mempool.prototype._addTX = spawn.co(function* _addTX(tx) {
return spawn(function *() { var lockFlags = constants.flags.STANDARD_LOCKTIME_FLAGS;
var lockFlags = constants.flags.STANDARD_LOCKTIME_FLAGS; var hash = tx.hash('hex');
var hash = tx.hash('hex'); var ret, entry, missing;
var ret, entry, missing; var result, exists;
var result, exists;
assert(!tx.mutable, 'Cannot add mutable TX to mempool.'); assert(!tx.mutable, 'Cannot add mutable TX to mempool.');
ret = new VerifyResult(); ret = new VerifyResult();
if (tx.ts !== 0) { if (tx.ts !== 0) {
throw new VerifyError(tx,
'alreadyknown',
'txn-already-known',
0);
}
if (!tx.isSane(ret)) {
throw new VerifyError(tx,
'invalid',
ret.reason,
ret.score);
}
if (tx.isCoinbase()) {
throw new VerifyError(tx,
'invalid',
'coinbase',
100);
}
if (this.requireStandard) {
if (!this.chain.state.hasCSV() && tx.version >= 2) {
throw new VerifyError(tx, throw new VerifyError(tx,
'alreadyknown', 'nonstandard',
'txn-already-known', 'premature-version2-tx',
0); 0);
} }
}
if (!tx.isSane(ret)) { if (!this.chain.state.hasWitness() && !this.prematureWitness) {
if (tx.hasWitness()) {
throw new VerifyError(tx, throw new VerifyError(tx,
'invalid', 'nonstandard',
'no-witness-yet',
0);
}
}
if (this.requireStandard) {
if (!tx.isStandard(ret)) {
throw new VerifyError(tx,
'nonstandard',
ret.reason, ret.reason,
ret.score); ret.score);
} }
}
if (tx.isCoinbase()) { result = yield this.chain.checkFinal(this.chain.tip, tx, lockFlags);
throw new VerifyError(tx,
'invalid',
'coinbase',
100);
}
if (this.requireStandard) { if (!result) {
if (!this.chain.state.hasCSV() && tx.version >= 2) { throw new VerifyError(tx,
throw new VerifyError(tx, 'nonstandard',
'nonstandard', 'non-final',
'premature-version2-tx', 0);
0); }
}
}
if (!this.chain.state.hasWitness() && !this.prematureWitness) { if (this.has(hash)) {
if (tx.hasWitness()) { throw new VerifyError(tx,
throw new VerifyError(tx, 'alreadyknown',
'nonstandard', 'txn-already-in-mempool',
'no-witness-yet', 0);
0); }
}
}
if (this.requireStandard) { exists = yield this.chain.db.hasCoins(hash);
if (!tx.isStandard(ret)) {
throw new VerifyError(tx,
'nonstandard',
ret.reason,
ret.score);
}
}
result = yield this.chain.checkFinal(this.chain.tip, tx, lockFlags); if (exists) {
throw new VerifyError(tx,
'alreadyknown',
'txn-already-known',
0);
}
if (!result) { if (this.isDoubleSpend(tx)) {
throw new VerifyError(tx, throw new VerifyError(tx,
'nonstandard', 'duplicate',
'non-final', 'bad-txns-inputs-spent',
0); 0);
} }
if (this.has(hash)) { yield this.fillAllCoins(tx);
throw new VerifyError(tx,
'alreadyknown',
'txn-already-in-mempool',
0);
}
exists = yield this.chain.db.hasCoins(hash); if (!tx.hasCoins()) {
missing = this.storeOrphan(tx);
return missing;
}
if (exists) { entry = MempoolEntry.fromTX(tx, this.chain.height);
throw new VerifyError(tx,
'alreadyknown',
'txn-already-known',
0);
}
if (this.isDoubleSpend(tx)) { yield this.verify(entry);
throw new VerifyError(tx, yield this.addUnchecked(entry, true);
'duplicate',
'bad-txns-inputs-spent',
0);
}
yield this.fillAllCoins(tx); if (this.limitMempoolSize(hash)) {
throw new VerifyError(tx,
if (!tx.hasCoins()) { 'insufficientfee',
missing = this.storeOrphan(tx); 'mempool full',
return missing; 0);
} }
});
entry = MempoolEntry.fromTX(tx, this.chain.height);
yield this.verify(entry);
yield this.addUnchecked(entry, true);
if (this.limitMempoolSize(hash)) {
throw new VerifyError(tx,
'insufficientfee',
'mempool full',
0);
}
}, this);
};
/** /**
* Add a transaction to the mempool without performing any * Add a transaction to the mempool without performing any
@ -680,57 +670,55 @@ Mempool.prototype._addTX = function _addTX(tx) {
* @param {Function} callback - Returns [{@link VerifyError}]. * @param {Function} callback - Returns [{@link VerifyError}].
*/ */
Mempool.prototype.addUnchecked = function addUnchecked(entry, force) { Mempool.prototype.addUnchecked = spawn.co(function* addUnchecked(entry, force) {
return spawn(function *() { var unlock = yield this._lock(null, force);
var unlock = yield this._lock(null, force); var i, resolved, tx, orphan;
var i, resolved, tx, orphan;
this.trackEntry(entry); this.trackEntry(entry);
this.emit('tx', entry.tx); this.emit('tx', entry.tx);
this.emit('add tx', entry.tx); this.emit('add tx', entry.tx);
if (this.fees) if (this.fees)
this.fees.processTX(entry, this.chain.isFull()); this.fees.processTX(entry, this.chain.isFull());
this.logger.debug('Added tx %s to mempool.', entry.tx.rhash); this.logger.debug('Added tx %s to mempool.', entry.tx.rhash);
resolved = this.resolveOrphans(entry.tx); resolved = this.resolveOrphans(entry.tx);
for (i = 0; i < resolved.length; i++) { for (i = 0; i < resolved.length; i++) {
tx = resolved[i]; tx = resolved[i];
orphan = MempoolEntry.fromTX(tx, this.chain.height); orphan = MempoolEntry.fromTX(tx, this.chain.height);
try { try {
yield this.verify(orphan); yield this.verify(orphan);
} catch (err) { } catch (err) {
if (err.type === 'VerifyError') { if (err.type === 'VerifyError') {
this.logger.debug('Could not resolve orphan %s: %s.', this.logger.debug('Could not resolve orphan %s: %s.',
tx.rhash, tx.rhash,
err.message); err.message);
if (!tx.hasWitness() && !err.malleated) if (!tx.hasWitness() && !err.malleated)
this.rejects.add(tx.hash()); this.rejects.add(tx.hash());
continue;
}
this.emit('error', err);
continue; continue;
} }
this.emit('error', err);
try { continue;
yield this.addUnchecked(orphan, true);
} catch (err) {
this.emit('error', err);
continue;
}
this.logger.spam('Resolved orphan %s in mempool.', orphan.tx.rhash);
} }
unlock(); try {
}, this); yield this.addUnchecked(orphan, true);
}; } catch (err) {
this.emit('error', err);
continue;
}
this.logger.spam('Resolved orphan %s in mempool.', orphan.tx.rhash);
}
unlock();
});
/** /**
* Remove a transaction from the mempool. Generally * Remove a transaction from the mempool. Generally
@ -816,155 +804,153 @@ Mempool.prototype.getMinRate = function getMinRate() {
* @param {Function} callback - Returns [{@link VerifyError}]. * @param {Function} callback - Returns [{@link VerifyError}].
*/ */
Mempool.prototype.verify = function verify(entry) { Mempool.prototype.verify = spawn.co(function* verify(entry) {
return spawn(function *() { var height = this.chain.height + 1;
var height = this.chain.height + 1; var lockFlags = flags.STANDARD_LOCKTIME_FLAGS;
var lockFlags = flags.STANDARD_LOCKTIME_FLAGS; var flags1 = flags.STANDARD_VERIFY_FLAGS;
var flags1 = flags.STANDARD_VERIFY_FLAGS; var flags2 = flags1 & ~(flags.VERIFY_WITNESS | flags.VERIFY_CLEANSTACK);
var flags2 = flags1 & ~(flags.VERIFY_WITNESS | flags.VERIFY_CLEANSTACK); var flags3 = flags1 & ~flags.VERIFY_CLEANSTACK;
var flags3 = flags1 & ~flags.VERIFY_CLEANSTACK; var mandatory = flags.MANDATORY_VERIFY_FLAGS;
var mandatory = flags.MANDATORY_VERIFY_FLAGS; var tx = entry.tx;
var tx = entry.tx; var ret = new VerifyResult();
var ret = new VerifyResult(); var fee, modFee, now, size, minRate;
var fee, modFee, now, size, minRate; var rejectFee, minRelayFee, count, result;
var rejectFee, minRelayFee, count, result;
result = yield this.checkLocks(tx, lockFlags); result = yield this.checkLocks(tx, lockFlags);
if (!result) { if (!result) {
throw new VerifyError(tx,
'nonstandard',
'non-BIP68-final',
0);
}
if (this.requireStandard) {
if (!tx.hasStandardInputs()) {
throw new VerifyError(tx, throw new VerifyError(tx,
'nonstandard', 'nonstandard',
'non-BIP68-final', 'bad-txns-nonstandard-inputs',
0); 0);
} }
if (this.chain.state.hasWitness()) {
if (this.requireStandard) { if (!tx.hasStandardWitness(ret)) {
if (!tx.hasStandardInputs()) { ret = new VerifyError(tx,
throw new VerifyError(tx,
'nonstandard', 'nonstandard',
'bad-txns-nonstandard-inputs', ret.reason,
0); ret.score);
} ret.malleated = ret.score > 0;
if (this.chain.state.hasWitness()) { throw ret;
if (!tx.hasStandardWitness(ret)) {
ret = new VerifyError(tx,
'nonstandard',
ret.reason,
ret.score);
ret.malleated = ret.score > 0;
throw ret;
}
} }
} }
}
if (tx.getSigopsWeight(flags) > constants.tx.MAX_SIGOPS_WEIGHT) { if (tx.getSigopsWeight(flags) > constants.tx.MAX_SIGOPS_WEIGHT) {
throw new VerifyError(tx, throw new VerifyError(tx,
'nonstandard', 'nonstandard',
'bad-txns-too-many-sigops', 'bad-txns-too-many-sigops',
0); 0);
} }
fee = tx.getFee(); fee = tx.getFee();
modFee = entry.fees; modFee = entry.fees;
size = entry.size; size = entry.size;
minRate = this.getMinRate(); minRate = this.getMinRate();
if (minRate > this.minRelayFee) if (minRate > this.minRelayFee)
this.network.updateMinRelay(minRate); this.network.updateMinRelay(minRate);
rejectFee = tx.getMinFee(size, minRate); rejectFee = tx.getMinFee(size, minRate);
minRelayFee = tx.getMinFee(size, this.minRelayFee); minRelayFee = tx.getMinFee(size, this.minRelayFee);
if (rejectFee > 0 && modFee < rejectFee) { if (rejectFee > 0 && modFee < rejectFee) {
throw new VerifyError(tx,
'insufficientfee',
'mempool min fee not met',
0);
}
if (this.relayPriority && modFee < minRelayFee) {
if (!entry.isFree(height)) {
throw new VerifyError(tx, throw new VerifyError(tx,
'insufficientfee', 'insufficientfee',
'mempool min fee not met', 'insufficient priority',
0); 0);
} }
}
if (this.relayPriority && modFee < minRelayFee) { // Continuously rate-limit free (really, very-low-fee)
if (!entry.isFree(height)) { // transactions. This mitigates 'penny-flooding'. i.e.
throw new VerifyError(tx, // sending thousands of free transactions just to be
'insufficientfee', // annoying or make others' transactions take longer
'insufficient priority', // to confirm.
0); if (this.limitFree && modFee < minRelayFee) {
} now = utils.now();
}
// Continuously rate-limit free (really, very-low-fee) // Use an exponentially decaying ~10-minute window:
// transactions. This mitigates 'penny-flooding'. i.e. this.freeCount *= Math.pow(1 - 1 / 600, now - this.lastTime);
// sending thousands of free transactions just to be this.lastTime = now;
// annoying or make others' transactions take longer
// to confirm.
if (this.limitFree && modFee < minRelayFee) {
now = utils.now();
// Use an exponentially decaying ~10-minute window: // The limitFreeRelay unit is thousand-bytes-per-minute
this.freeCount *= Math.pow(1 - 1 / 600, now - this.lastTime); // At default rate it would take over a month to fill 1GB
this.lastTime = now; if (this.freeCount > this.limitFreeRelay * 10 * 1000) {
// The limitFreeRelay unit is thousand-bytes-per-minute
// At default rate it would take over a month to fill 1GB
if (this.freeCount > this.limitFreeRelay * 10 * 1000) {
throw new VerifyError(tx,
'insufficientfee',
'rate limited free transaction',
0);
}
this.freeCount += size;
}
if (this.rejectAbsurdFees && fee > minRelayFee * 10000)
throw new VerifyError(tx, 'highfee', 'absurdly-high-fee', 0);
count = this.countAncestors(tx);
if (count > constants.mempool.ANCESTOR_LIMIT) {
throw new VerifyError(tx, throw new VerifyError(tx,
'nonstandard', 'insufficientfee',
'too-long-mempool-chain', 'rate limited free transaction',
0); 0);
} }
if (!tx.checkInputs(height, ret)) this.freeCount += size;
throw new VerifyError(tx, 'invalid', ret.reason, ret.score); }
// Standard verification if (this.rejectAbsurdFees && fee > minRelayFee * 10000)
try { throw new VerifyError(tx, 'highfee', 'absurdly-high-fee', 0);
yield this.checkInputs(tx, flags1);
} catch (error) {
if (tx.hasWitness())
throw error;
// Try without segwit and cleanstack. count = this.countAncestors(tx);
result = yield this.checkResult(tx, flags2);
// If it failed, the first verification if (count > constants.mempool.ANCESTOR_LIMIT) {
// was the only result we needed. throw new VerifyError(tx,
if (!result) 'nonstandard',
throw error; 'too-long-mempool-chain',
0);
}
// If it succeeded, segwit may be causing the if (!tx.checkInputs(height, ret))
// failure. Try with segwit but without cleanstack. throw new VerifyError(tx, 'invalid', ret.reason, ret.score);
result = yield this.checkResult(tx, flags3);
// Cleanstack was causing the failure. // Standard verification
if (result) try {
throw error; yield this.checkInputs(tx, flags1);
} catch (error) {
// Do not insert into reject cache. if (tx.hasWitness())
error.malleated = true;
throw error; throw error;
}
// Paranoid checks. // Try without segwit and cleanstack.
if (this.paranoid) { result = yield this.checkResult(tx, flags2);
result = yield this.checkResult(tx, mandatory);
assert(result, 'BUG: Verify failed for mandatory but not standard.'); // If it failed, the first verification
} // was the only result we needed.
}, this); if (!result)
}; throw error;
// If it succeeded, segwit may be causing the
// failure. Try with segwit but without cleanstack.
result = yield this.checkResult(tx, flags3);
// Cleanstack was causing the failure.
if (result)
throw error;
// Do not insert into reject cache.
error.malleated = true;
throw error;
}
// Paranoid checks.
if (this.paranoid) {
result = yield this.checkResult(tx, mandatory);
assert(result, 'BUG: Verify failed for mandatory but not standard.');
}
});
/** /**
* Verify inputs, return a boolean * Verify inputs, return a boolean
@ -974,18 +960,16 @@ Mempool.prototype.verify = function verify(entry) {
* @param {Function} callback * @param {Function} callback
*/ */
Mempool.prototype.checkResult = function checkResult(tx, flags) { Mempool.prototype.checkResult = spawn.co(function* checkResult(tx, flags) {
return spawn(function *() { try {
try { yield this.checkInputs(tx, flags);
yield this.checkInputs(tx, flags); } catch (err) {
} catch (err) { if (err.type === 'VerifyError')
if (err.type === 'VerifyError') return false;
return false; throw err;
throw err; }
} return true;
return true; });
}, this);
};
/** /**
* Verify inputs for standard * Verify inputs for standard
@ -995,36 +979,34 @@ Mempool.prototype.checkResult = function checkResult(tx, flags) {
* @param {Function} callback * @param {Function} callback
*/ */
Mempool.prototype.checkInputs = function checkInputs(tx, flags) { Mempool.prototype.checkInputs = spawn.co(function* checkInputs(tx, flags) {
return spawn(function *() { var result = yield tx.verifyAsync(flags);
var result = yield tx.verifyAsync(flags); if (result)
if (result) return;
return;
if (!(flags & constants.flags.UNSTANDARD_VERIFY_FLAGS)) {
throw new VerifyError(tx,
'nonstandard',
'non-mandatory-script-verify-flag',
0);
}
flags &= ~constants.flags.UNSTANDARD_VERIFY_FLAGS;
result = yield tx.verifyAsync(flags);
if (result) {
throw new VerifyError(tx,
'nonstandard',
'non-mandatory-script-verify-flag',
0);
}
if (!(flags & constants.flags.UNSTANDARD_VERIFY_FLAGS)) {
throw new VerifyError(tx, throw new VerifyError(tx,
'nonstandard', 'nonstandard',
'mandatory-script-verify-flag', 'non-mandatory-script-verify-flag',
100); 0);
}, this); }
};
flags &= ~constants.flags.UNSTANDARD_VERIFY_FLAGS;
result = yield tx.verifyAsync(flags);
if (result) {
throw new VerifyError(tx,
'nonstandard',
'non-mandatory-script-verify-flag',
0);
}
throw new VerifyError(tx,
'nonstandard',
'mandatory-script-verify-flag',
100);
});
/** /**
* Count the highest number of * Count the highest number of
@ -1405,32 +1387,30 @@ Mempool.prototype.fillAllHistory = function fillAllHistory(tx) {
* @param {Function} callback - Returns [Error, {@link TX}]. * @param {Function} callback - Returns [Error, {@link TX}].
*/ */
Mempool.prototype.fillAllCoins = function fillAllCoins(tx) { Mempool.prototype.fillAllCoins = spawn.co(function* fillAllCoins(tx) {
return spawn(function *() { var i, input, hash, index, coin;
var i, input, hash, index, coin;
this.fillCoins(tx); this.fillCoins(tx);
if (tx.hasCoins())
return tx;
for (i = 0; i < tx.inputs.length; i++) {
input = tx.inputs[i];
hash = input.prevout.hash;
index = input.prevout.index;
if (this.isSpent(hash, index))
continue;
coin = yield this.chain.db.getCoin(hash, index);
if (coin)
input.coin = coin;
}
if (tx.hasCoins())
return tx; return tx;
}, this);
}; for (i = 0; i < tx.inputs.length; i++) {
input = tx.inputs[i];
hash = input.prevout.hash;
index = input.prevout.index;
if (this.isSpent(hash, index))
continue;
coin = yield this.chain.db.getCoin(hash, index);
if (coin)
input.coin = coin;
}
return tx;
});
/** /**
* Get a snapshot of all transaction hashes in the mempool. Used * Get a snapshot of all transaction hashes in the mempool. Used
@ -1483,38 +1463,36 @@ Mempool.prototype.isDoubleSpend = function isDoubleSpend(tx) {
* @param {Function} callback - Returns [Error, Number]. * @param {Function} callback - Returns [Error, Number].
*/ */
Mempool.prototype.getConfidence = function getConfidence(hash) { Mempool.prototype.getConfidence = spawn.co(function* getConfidence(hash) {
return spawn(function *() { var tx, result;
var tx, result;
if (hash instanceof bcoin.tx) { if (hash instanceof bcoin.tx) {
tx = hash; tx = hash;
hash = hash.hash('hex'); hash = hash.hash('hex');
} else { } else {
tx = this.getTX(hash); tx = this.getTX(hash);
} }
if (this.hasTX(hash)) if (this.hasTX(hash))
return constants.confidence.PENDING; return constants.confidence.PENDING;
if (tx && this.isDoubleSpend(tx)) if (tx && this.isDoubleSpend(tx))
return constants.confidence.INCONFLICT; return constants.confidence.INCONFLICT;
if (tx && tx.block) {
result = yield this.chain.db.isMainChain(tx.block);
if (result)
return constants.confidence.BUILDING;
return constants.confidence.DEAD;
}
result = yield this.chain.db.hasCoins(hash);
if (tx && tx.block) {
result = yield this.chain.db.isMainChain(tx.block);
if (result) if (result)
return constants.confidence.BUILDING; return constants.confidence.BUILDING;
return constants.confidence.DEAD;
}
return constants.confidence.UNKNOWN; result = yield this.chain.db.hasCoins(hash);
}, this);
}; if (result)
return constants.confidence.BUILDING;
return constants.confidence.UNKNOWN;
});
/** /**
* Map a transaction to the mempool. * Map a transaction to the mempool.

View File

@ -134,17 +134,15 @@ Miner.prototype._init = function _init() {
* @param {Function} callback * @param {Function} callback
*/ */
Miner.prototype._open = function open() { Miner.prototype._open = spawn.co(function* open() {
return spawn(function *() { if (this.mempool)
if (this.mempool) yield this.mempool.open();
yield this.mempool.open(); else
else yield this.chain.open();
yield this.chain.open();
this.logger.info('Miner loaded (flags=%s).', this.logger.info('Miner loaded (flags=%s).',
this.coinbaseFlags.toString('utf8')); this.coinbaseFlags.toString('utf8'));
}, this); });
};
/** /**
* Close the miner. * Close the miner.
@ -242,29 +240,28 @@ Miner.prototype.stop = function stop() {
* @param {Function} callback - Returns [Error, {@link MinerBlock}]. * @param {Function} callback - Returns [Error, {@link MinerBlock}].
*/ */
Miner.prototype.createBlock = function createBlock(tip) { Miner.prototype.createBlock = spawn.co(function* createBlock(tip) {
return spawn(function *() { var i, ts, attempt, txs, tx, target, version;
var i, ts, attempt, txs, tx, target, version;
if (!this.loaded) if (!this.loaded)
yield this.open(); yield this.open();
if (!tip) if (!tip)
tip = this.chain.tip; tip = this.chain.tip;
assert(tip); assert(tip);
ts = Math.max(bcoin.now(), tip.ts + 1); ts = Math.max(bcoin.now(), tip.ts + 1);
// Find target // Find target
target = yield this.chain.getTargetAsync(ts, tip); target = yield this.chain.getTargetAsync(ts, tip);
if (this.version != null) { if (this.version != null) {
version = this.version; version = this.version;
} else { } else {
// Calculate version with versionbits // Calculate version with versionbits
version = yield this.chain.computeBlockVersion(tip); version = yield this.chain.computeBlockVersion(tip);
} }
attempt = new MinerBlock({ attempt = new MinerBlock({
workerPool: this.workerPool, workerPool: this.workerPool,
@ -289,8 +286,7 @@ Miner.prototype.createBlock = function createBlock(tip) {
} }
return attempt; return attempt;
}, this); });
};
/** /**
* Mine a single block. * Mine a single block.
@ -298,13 +294,11 @@ Miner.prototype.createBlock = function createBlock(tip) {
* @param {Function} callback - Returns [Error, [{@link Block}]]. * @param {Function} callback - Returns [Error, [{@link Block}]].
*/ */
Miner.prototype.mineBlock = function mineBlock(tip) { Miner.prototype.mineBlock = spawn.co(function* mineBlock(tip) {
return spawn(function *() {
// Create a new block and start hashing // Create a new block and start hashing
var attempt = yield this.createBlock(tip); var attempt = yield this.createBlock(tip);
return yield attempt.mineAsync(); return yield attempt.mineAsync();
}, this); });
};
/* /*
* Expose * Expose

View File

@ -349,19 +349,17 @@ MinerBlock.prototype.sendStatus = function sendStatus() {
* @param {Function} callback - Returns [Error, {@link Block}]. * @param {Function} callback - Returns [Error, {@link Block}].
*/ */
MinerBlock.prototype.mine = function mine() { MinerBlock.prototype.mine = spawn.co(function* mine() {
return spawn(function *() { yield this.wait(100);
yield this.wait(100);
// Try to find a block: do one iteration of extraNonce // Try to find a block: do one iteration of extraNonce
if (!this.findNonce()) { if (!this.findNonce()) {
yield this.mine(); yield this.mine();
return; return;
} }
return this.block; return this.block;
}, this); });
};
/** /**
* Wait for a timeout. * Wait for a timeout.
@ -393,20 +391,18 @@ MinerBlock.prototype.mineSync = function mineSync() {
* @param {Function} callback - Returns [Error, {@link Block}]. * @param {Function} callback - Returns [Error, {@link Block}].
*/ */
MinerBlock.prototype.mineAsync = function mineAsync() { MinerBlock.prototype.mineAsync = spawn.co(function* mineAsync() {
return spawn(function *() { var block;
var block;
if (!this.workerPool) if (!this.workerPool)
return yield this.mine(); return yield this.mine();
block = yield this.workerPool.mine(this); block = yield this.workerPool.mine(this);
this.workerPool.destroy(); this.workerPool.destroy();
return block; return block;
}, this); });
};
/** /**
* Destroy the minerblock. Stop mining. Clear timeout. * Destroy the minerblock. Stop mining. Clear timeout.

View File

@ -1430,50 +1430,48 @@ Peer.prototype._handleMempool = function _handleMempool(packet) {
* [Error, {@link Block}|{@link MempoolEntry}]. * [Error, {@link Block}|{@link MempoolEntry}].
*/ */
Peer.prototype._getItem = function _getItem(item) { Peer.prototype._getItem = spawn.co(function* _getItem(item) {
return spawn(function *() { var entry = this.pool.invMap[item.hash];
var entry = this.pool.invMap[item.hash];
if (entry) { if (entry) {
this.logger.debug( this.logger.debug(
'Peer requested %s %s as a %s packet (%s).', 'Peer requested %s %s as a %s packet (%s).',
entry.type === constants.inv.TX ? 'tx' : 'block', entry.type === constants.inv.TX ? 'tx' : 'block',
utils.revHex(entry.hash), utils.revHex(entry.hash),
item.hasWitness() ? 'witness' : 'normal', item.hasWitness() ? 'witness' : 'normal',
this.hostname); this.hostname);
entry.ack(this); entry.ack(this);
if (entry.msg) { if (entry.msg) {
if (item.isTX()) { if (item.isTX()) {
if (entry.type === constants.inv.TX) if (entry.type === constants.inv.TX)
return entry.msg; return entry.msg;
} else { } else {
if (entry.type === constants.inv.BLOCK) if (entry.type === constants.inv.BLOCK)
return entry.msg; return entry.msg;
}
return;
} }
return;
} }
}
if (this.options.selfish) if (this.options.selfish)
return;
if (item.isTX()) {
if (!this.mempool)
return; return;
return this.mempool.getTX(item.hash);
}
if (item.isTX()) { if (this.chain.db.options.spv)
if (!this.mempool) return;
return;
return this.mempool.getTX(item.hash);
}
if (this.chain.db.options.spv) if (this.chain.db.options.prune)
return; return;
if (this.chain.db.options.prune) return yield this.chain.db.getBlock(item.hash);
return; });
return yield this.chain.db.getBlock(item.hash);
}, this);
};
/** /**
* Handle `getdata` packet. * Handle `getdata` packet.
@ -2351,24 +2349,22 @@ Peer.prototype.reject = function reject(obj, code, reason, score) {
* @param {Function} callback * @param {Function} callback
*/ */
Peer.prototype.resolveOrphan = function resolveOrphan(tip, orphan) { Peer.prototype.resolveOrphan = spawn.co(function* resolveOrphan(tip, orphan) {
return spawn(function *() { var root, locator;
var root, locator;
assert(orphan); assert(orphan);
locator = yield this.chain.getLocator(tip); locator = yield this.chain.getLocator(tip);
root = this.chain.getOrphanRoot(orphan); root = this.chain.getOrphanRoot(orphan);
// Was probably resolved. // Was probably resolved.
if (!root) { if (!root) {
this.logger.debug('Orphan root was already resolved.'); this.logger.debug('Orphan root was already resolved.');
return; return;
} }
this.sendGetBlocks(locator, root); this.sendGetBlocks(locator, root);
}, this); });
};
/** /**
* Send `getheaders` to peer after building locator. * Send `getheaders` to peer after building locator.
@ -2377,12 +2373,10 @@ Peer.prototype.resolveOrphan = function resolveOrphan(tip, orphan) {
* @param {Function} callback * @param {Function} callback
*/ */
Peer.prototype.getHeaders = function getHeaders(tip, stop) { Peer.prototype.getHeaders = spawn.co(function* getHeaders(tip, stop) {
return spawn(function *() { var locator = yield this.chain.getLocator(tip);
var locator = yield this.chain.getLocator(tip); this.sendGetHeaders(locator, stop);
this.sendGetHeaders(locator, stop); });
}, this);
};
/** /**
* Send `getblocks` to peer after building locator. * Send `getblocks` to peer after building locator.
@ -2391,12 +2385,10 @@ Peer.prototype.getHeaders = function getHeaders(tip, stop) {
* @param {Function} callback * @param {Function} callback
*/ */
Peer.prototype.getBlocks = function getBlocks(tip, stop) { Peer.prototype.getBlocks = spawn.co(function* getBlocks(tip, stop) {
return spawn(function *() { var locator = yield this.chain.getLocator(tip);
var locator = yield this.chain.getLocator(tip); this.sendGetBlocks(locator, stop);
this.sendGetBlocks(locator, stop); });
}, this);
};
/** /**
* Start syncing from peer. * Start syncing from peer.

View File

@ -290,40 +290,38 @@ Pool.prototype._lock = function _lock(force) {
* @param {Function} callback * @param {Function} callback
*/ */
Pool.prototype._open = function _open() { Pool.prototype._open = spawn.co(function* _open() {
return spawn(function *() { var ip, key;
var ip, key;
try { try {
ip = yield this.getIP(); ip = yield this.getIP();
} catch (e) { } catch (e) {
this.logger.error(e); this.logger.error(e);
} }
if (ip) { if (ip) {
this.address.setHost(ip); this.address.setHost(ip);
this.logger.info('External IP found: %s.', ip); this.logger.info('External IP found: %s.', ip);
} }
if (this.mempool) if (this.mempool)
yield this.mempool.open(); yield this.mempool.open();
else else
yield this.chain.open(); yield this.chain.open();
this.logger.info('Pool loaded (maxpeers=%d).', this.maxPeers); this.logger.info('Pool loaded (maxpeers=%d).', this.maxPeers);
if (this.identityKey) { if (this.identityKey) {
key = bcoin.ec.publicKeyCreate(this.identityKey, true); key = bcoin.ec.publicKeyCreate(this.identityKey, true);
this.logger.info('Identity public key: %s.', key.toString('hex')); this.logger.info('Identity public key: %s.', key.toString('hex'));
this.logger.info('Identity address: %s.', bcoin.bip150.address(key)); this.logger.info('Identity address: %s.', bcoin.bip150.address(key));
} }
if (!this.options.listen) if (!this.options.listen)
return; return;
yield this.listen(); yield this.listen();
}, this); });
};
/** /**
* Close and destroy the pool. * Close and destroy the pool.
@ -331,37 +329,35 @@ Pool.prototype._open = function _open() {
* @param {Function} callback * @param {Function} callback
*/ */
Pool.prototype._close = function close() { Pool.prototype._close = spawn.co(function* close() {
return spawn(function *() { var i, items, hashes, hash;
var i, items, hashes, hash;
this.stopSync(); this.stopSync();
items = this.invItems.slice(); items = this.invItems.slice();
for (i = 0; i < items.length; i++) for (i = 0; i < items.length; i++)
items[i].finish(); items[i].finish();
hashes = Object.keys(this.requestMap); hashes = Object.keys(this.requestMap);
for (i = 0; i < hashes.length; i++) { for (i = 0; i < hashes.length; i++) {
hash = hashes[i]; hash = hashes[i];
this.requestMap[hash].finish(new Error('Pool closed.')); this.requestMap[hash].finish(new Error('Pool closed.'));
} }
this.peers.destroy(); this.peers.destroy();
this.stopInterval(); this.stopInterval();
this.stopTimeout(); this.stopTimeout();
if (this.pendingWatch != null) { if (this.pendingWatch != null) {
clearTimeout(this.pendingWatch); clearTimeout(this.pendingWatch);
this.pendingWatch = null; this.pendingWatch = null;
} }
yield this.unlisten(); yield this.unlisten();
}, this); });
};
/** /**
* Connect to the network. * Connect to the network.
@ -724,68 +720,66 @@ Pool.prototype.stopSync = function stopSync() {
* @param {Function} callback * @param {Function} callback
*/ */
Pool.prototype._handleHeaders = function _handleHeaders(headers, peer) { Pool.prototype._handleHeaders = spawn.co(function* _handleHeaders(headers, peer) {
return spawn(function *() { var i, unlock, ret, header, hash, last;
var i, unlock, ret, header, hash, last;
if (!this.options.headers) if (!this.options.headers)
return; return;
unlock = yield this._lock(); unlock = yield this._lock();
ret = new VerifyResult(); ret = new VerifyResult();
this.logger.debug( this.logger.debug(
'Received %s headers from peer (%s).', 'Received %s headers from peer (%s).',
headers.length, headers.length,
peer.hostname); peer.hostname);
this.emit('headers', headers); this.emit('headers', headers);
if (peer.isLoader()) { if (peer.isLoader()) {
// Reset interval to avoid stall behavior. // Reset interval to avoid stall behavior.
this.startInterval(); this.startInterval();
// Reset timeout to avoid killing the loader. // Reset timeout to avoid killing the loader.
this.startTimeout(); this.startTimeout();
}
for (i = 0; i < headers.length; i++) {
header = headers[i];
hash = header.hash('hex');
if (last && header.prevBlock !== last) {
peer.setMisbehavior(100);
unlock();
throw new Error('Bad header chain.');
} }
for (i = 0; i < headers.length; i++) { if (!header.verify(ret)) {
header = headers[i]; peer.reject(header, 'invalid', ret.reason, 100);
hash = header.hash('hex'); unlock();
throw new Error('Invalid header.');
if (last && header.prevBlock !== last) {
peer.setMisbehavior(100);
unlock();
throw new Error('Bad header chain.');
}
if (!header.verify(ret)) {
peer.reject(header, 'invalid', ret.reason, 100);
unlock();
throw new Error('Invalid header.');
}
last = hash;
yield this.getData(peer, this.blockType, hash);
} }
// Schedule the getdata's we just added. last = hash;
this.scheduleRequests(peer);
// Restart the getheaders process yield this.getData(peer, this.blockType, hash);
// Technically `last` is not indexed yet so }
// the locator hashes will not be entirely
// accurate. However, it shouldn't matter
// that much since FindForkInGlobalIndex
// simply tries to find the latest block in
// the peer's chain.
if (last && headers.length === 2000)
yield peer.getHeaders(last, null);
unlock(); // Schedule the getdata's we just added.
}, this); this.scheduleRequests(peer);
};
// Restart the getheaders process
// Technically `last` is not indexed yet so
// the locator hashes will not be entirely
// accurate. However, it shouldn't matter
// that much since FindForkInGlobalIndex
// simply tries to find the latest block in
// the peer's chain.
if (last && headers.length === 2000)
yield peer.getHeaders(last, null);
unlock();
});
/** /**
* Handle `inv` packet from peer (containing only BLOCK types). * Handle `inv` packet from peer (containing only BLOCK types).
@ -795,66 +789,64 @@ Pool.prototype._handleHeaders = function _handleHeaders(headers, peer) {
* @param {Function} callback * @param {Function} callback
*/ */
Pool.prototype._handleBlocks = function _handleBlocks(hashes, peer) { Pool.prototype._handleBlocks = spawn.co(function* _handleBlocks(hashes, peer) {
return spawn(function *() { var i, hash, exists;
var i, hash, exists;
assert(!this.options.headers); assert(!this.options.headers);
this.logger.debug( this.logger.debug(
'Received %s block hashes from peer (%s).', 'Received %s block hashes from peer (%s).',
hashes.length, hashes.length,
peer.hostname); peer.hostname);
this.emit('blocks', hashes); this.emit('blocks', hashes);
if (peer.isLoader()) { if (peer.isLoader()) {
// Reset interval to avoid stall behavior. // Reset interval to avoid stall behavior.
this.startInterval(); this.startInterval();
// Reset timeout to avoid killing the loader. // Reset timeout to avoid killing the loader.
this.startTimeout(); this.startTimeout();
}
for (i = 0; i < hashes.length; i++) {
hash = hashes[i];
// Resolve orphan chain.
if (this.chain.hasOrphan(hash)) {
// There is a possible race condition here.
// The orphan may get resolved by the time
// we create the locator. In that case, we
// should probably actually move to the
// `exists` clause below if it is the last
// hash.
this.logger.debug('Received known orphan hash (%s).', peer.hostname);
yield peer.resolveOrphan(null, hash);
continue;
} }
for (i = 0; i < hashes.length; i++) { exists = yield this.getData(peer, this.blockType, hash);
hash = hashes[i];
// Resolve orphan chain. // Normally we request the hashContinue.
if (this.chain.hasOrphan(hash)) { // In the odd case where we already have
// There is a possible race condition here. // it, we can do one of two things: either
// The orphan may get resolved by the time // force re-downloading of the block to
// we create the locator. In that case, we // continue the sync, or do a getblocks
// should probably actually move to the // from the last hash (this will reset
// `exists` clause below if it is the last // the hashContinue on the remote node).
// hash. if (exists && i === hashes.length - 1) {
this.logger.debug('Received known orphan hash (%s).', peer.hostname); // Make sure we _actually_ have this block.
yield peer.resolveOrphan(null, hash); if (!this.requestMap[hash]) {
this.logger.debug('Received existing hash (%s).', peer.hostname);
yield peer.getBlocks(hash, null);
continue; continue;
} }
// Otherwise, we're still requesting it. Ignore.
exists = yield this.getData(peer, this.blockType, hash); this.logger.debug('Received requested hash (%s).', peer.hostname);
// Normally we request the hashContinue.
// In the odd case where we already have
// it, we can do one of two things: either
// force re-downloading of the block to
// continue the sync, or do a getblocks
// from the last hash (this will reset
// the hashContinue on the remote node).
if (exists && i === hashes.length - 1) {
// Make sure we _actually_ have this block.
if (!this.requestMap[hash]) {
this.logger.debug('Received existing hash (%s).', peer.hostname);
yield peer.getBlocks(hash, null);
continue;
}
// Otherwise, we're still requesting it. Ignore.
this.logger.debug('Received requested hash (%s).', peer.hostname);
}
} }
}
this.scheduleRequests(peer); this.scheduleRequests(peer);
}, this); });
};
/** /**
* Handle `inv` packet from peer (containing only BLOCK types). * Handle `inv` packet from peer (containing only BLOCK types).
@ -865,30 +857,28 @@ Pool.prototype._handleBlocks = function _handleBlocks(hashes, peer) {
* @param {Function} callback * @param {Function} callback
*/ */
Pool.prototype._handleInv = function _handleInv(hashes, peer) { Pool.prototype._handleInv = spawn.co(function* _handleInv(hashes, peer) {
return spawn(function *() { var unlock = yield this._lock();
var unlock = yield this._lock(); var i, hash;
var i, hash;
// Ignore for now if we're still syncing // Ignore for now if we're still syncing
if (!this.chain.synced && !peer.isLoader()) if (!this.chain.synced && !peer.isLoader())
return; return;
if (!this.options.headers) { if (!this.options.headers) {
yield this._handleBlocks(hashes, peer); yield this._handleBlocks(hashes, peer);
unlock();
return;
}
for (i = 0; i < hashes.length; i++) {
hash = hashes[i];
yield peer.getHeaders(null, hash);
}
this.scheduleRequests(peer);
unlock(); unlock();
}, this); return;
}; }
for (i = 0; i < hashes.length; i++) {
hash = hashes[i];
yield peer.getHeaders(null, hash);
}
this.scheduleRequests(peer);
unlock();
});
/** /**
* Handle `block` packet. Attempt to add to chain. * Handle `block` packet. Attempt to add to chain.
@ -898,81 +888,79 @@ Pool.prototype._handleInv = function _handleInv(hashes, peer) {
* @param {Function} callback * @param {Function} callback
*/ */
Pool.prototype._handleBlock = function _handleBlock(block, peer) { Pool.prototype._handleBlock = spawn.co(function* _handleBlock(block, peer) {
return spawn(function *() { var requested;
var requested;
// Fulfill the load request. // Fulfill the load request.
requested = this.fulfill(block); requested = this.fulfill(block);
// Someone is sending us blocks without // Someone is sending us blocks without
// us requesting them. // us requesting them.
if (!requested) { if (!requested) {
peer.invFilter.add(block.hash()); peer.invFilter.add(block.hash());
this.logger.warning( this.logger.warning(
'Received unrequested block: %s (%s).', 'Received unrequested block: %s (%s).',
block.rhash, peer.hostname); block.rhash, peer.hostname);
return yield utils.wait(); return yield utils.wait();
}
try {
yield this.chain.add(block);
} catch (err) {
if (err.type !== 'VerifyError') {
this.scheduleRequests(peer);
throw err;
} }
try { if (err.score !== -1)
yield this.chain.add(block); peer.reject(block, err.code, err.reason, err.score);
} catch (err) {
if (err.type !== 'VerifyError') { if (err.reason === 'bad-prevblk') {
this.scheduleRequests(peer); if (this.options.headers) {
peer.setMisbehavior(10);
throw err; throw err;
} }
this.logger.debug('Peer sent an orphan block. Resolving.');
if (err.score !== -1) yield peer.resolveOrphan(null, block.hash('hex'));
peer.reject(block, err.code, err.reason, err.score);
if (err.reason === 'bad-prevblk') {
if (this.options.headers) {
peer.setMisbehavior(10);
throw err;
}
this.logger.debug('Peer sent an orphan block. Resolving.');
yield peer.resolveOrphan(null, block.hash('hex'));
this.scheduleRequests(peer);
throw err;
}
this.scheduleRequests(peer); this.scheduleRequests(peer);
throw err; throw err;
} }
this.scheduleRequests(peer); this.scheduleRequests(peer);
throw err;
}
this.emit('chain-progress', this.chain.getProgress(), peer); this.scheduleRequests(peer);
if (this.logger.level >= 4 && this.chain.total % 20 === 0) { this.emit('chain-progress', this.chain.getProgress(), peer);
this.logger.debug('Status:'
+ ' ts=%s height=%d highest=%d progress=%s'
+ ' blocks=%d orphans=%d active=%d'
+ ' queue=%d target=%s peers=%d'
+ ' pending=%d jobs=%d',
utils.date(block.ts),
this.chain.height,
this.chain.bestHeight,
(this.chain.getProgress() * 100).toFixed(2) + '%',
this.chain.total,
this.chain.orphan.count,
this.activeBlocks,
peer.queueBlock.length,
block.bits,
this.peers.all.length,
this.chain.locker.pending.length,
this.chain.locker.jobs.length);
}
if (this.chain.total % 2000 === 0) { if (this.logger.level >= 4 && this.chain.total % 20 === 0) {
this.logger.info( this.logger.debug('Status:'
'Received 2000 more blocks (height=%d, hash=%s).', + ' ts=%s height=%d highest=%d progress=%s'
this.chain.height, + ' blocks=%d orphans=%d active=%d'
block.rhash); + ' queue=%d target=%s peers=%d'
} + ' pending=%d jobs=%d',
}, this); utils.date(block.ts),
}; this.chain.height,
this.chain.bestHeight,
(this.chain.getProgress() * 100).toFixed(2) + '%',
this.chain.total,
this.chain.orphan.count,
this.activeBlocks,
peer.queueBlock.length,
block.bits,
this.peers.all.length,
this.chain.locker.pending.length,
this.chain.locker.jobs.length);
}
if (this.chain.total % 2000 === 0) {
this.logger.info(
'Received 2000 more blocks (height=%d, hash=%s).',
this.chain.height,
block.rhash);
}
});
/** /**
* Send `mempool` to all peers. * Send `mempool` to all peers.
@ -1304,54 +1292,52 @@ Pool.prototype.hasReject = function hasReject(hash) {
* @param {Function} callback * @param {Function} callback
*/ */
Pool.prototype._handleTX = function _handleTX(tx, peer) { Pool.prototype._handleTX = spawn.co(function* _handleTX(tx, peer) {
return spawn(function *() { var i, requested, missing;
var i, requested, missing;
// Fulfill the load request. // Fulfill the load request.
requested = this.fulfill(tx); requested = this.fulfill(tx);
if (!requested) { if (!requested) {
peer.invFilter.add(tx.hash()); peer.invFilter.add(tx.hash());
if (!this.mempool) if (!this.mempool)
this.txFilter.add(tx.hash()); this.txFilter.add(tx.hash());
this.logger.warning('Peer sent unrequested tx: %s (%s).', this.logger.warning('Peer sent unrequested tx: %s (%s).',
tx.rhash, peer.hostname); tx.rhash, peer.hostname);
if (this.hasReject(tx.hash())) { if (this.hasReject(tx.hash())) {
throw new VerifyError(tx, throw new VerifyError(tx,
'alreadyknown', 'alreadyknown',
'txn-already-in-mempool', 'txn-already-in-mempool',
0); 0);
}
} }
}
if (!this.mempool) { if (!this.mempool) {
this.emit('tx', tx, peer); this.emit('tx', tx, peer);
return; return;
} }
try { try {
missing = yield this.mempool.addTX(tx); missing = yield this.mempool.addTX(tx);
} catch (err) { } catch (err) {
if (err.type === 'VerifyError') { if (err.type === 'VerifyError') {
if (err.score !== -1) if (err.score !== -1)
peer.reject(tx, err.code, err.reason, err.score); peer.reject(tx, err.code, err.reason, err.score);
throw err;
}
throw err; throw err;
} }
throw err;
}
if (missing) { if (missing) {
for (i = 0; i < missing.length; i++) for (i = 0; i < missing.length; i++)
yield this.getData(peer, this.txType, missing[i]); yield this.getData(peer, this.txType, missing[i]);
} }
this.emit('tx', tx, peer); this.emit('tx', tx, peer);
}, this); });
};
/** /**
* Create a leech peer from an existing socket. * Create a leech peer from an existing socket.
@ -1517,45 +1503,43 @@ Pool.prototype.watchAddress = function watchAddress(address) {
* @param {Function} callback * @param {Function} callback
*/ */
Pool.prototype.getData = function getData(peer, type, hash) { Pool.prototype.getData = spawn.co(function* getData(peer, type, hash) {
return spawn(function *() { var self = this;
var self = this; var item, exists;
var item, exists;
if (!this.loaded) if (!this.loaded)
return; return;
exists = yield this.has(peer, type, hash); exists = yield this.has(peer, type, hash);
if (exists) if (exists)
return true; return true;
item = new LoadRequest(this, peer, type, hash); item = new LoadRequest(this, peer, type, hash);
if (type === this.txType) { if (type === this.txType) {
if (peer.queueTX.length === 0) { if (peer.queueTX.length === 0) {
utils.nextTick(function() { utils.nextTick(function() {
self.logger.debug( self.logger.debug(
'Requesting %d/%d txs from peer with getdata (%s).', 'Requesting %d/%d txs from peer with getdata (%s).',
peer.queueTX.length, peer.queueTX.length,
self.activeTX, self.activeTX,
peer.hostname); peer.hostname);
peer.getData(peer.queueTX); peer.getData(peer.queueTX);
peer.queueTX.length = 0; peer.queueTX.length = 0;
}); });
}
peer.queueTX.push(item.start());
return false;
} }
peer.queueBlock.push(item); peer.queueTX.push(item.start());
return false; return false;
}, this); }
};
peer.queueBlock.push(item);
return false;
});
/** /**
* Queue a `getdata` request to be sent. Promise * Queue a `getdata` request to be sent. Promise
@ -1581,31 +1565,29 @@ Pool.prototype.getDataSync = function getDataSync(peer, type, hash) {
* @param {Function} callback - Returns [Error, Boolean]. * @param {Function} callback - Returns [Error, Boolean].
*/ */
Pool.prototype.has = function has(peer, type, hash) { Pool.prototype.has = spawn.co(function* has(peer, type, hash) {
return spawn(function *() { var exists = yield this.exists(type, hash);
var exists = yield this.exists(type, hash);
if (exists) if (exists)
return true; return true;
// Check the pending requests. // Check the pending requests.
if (this.requestMap[hash]) if (this.requestMap[hash])
return true; return true;
if (type !== this.txType)
return false;
// If we recently rejected this item. Ignore.
if (this.hasReject(hash)) {
this.logger.spam(
'Peer sent a known reject of %s (%s).',
utils.revHex(hash), peer.hostname);
return true;
}
if (type !== this.txType)
return false; return false;
}, this);
}; // If we recently rejected this item. Ignore.
if (this.hasReject(hash)) {
this.logger.spam(
'Peer sent a known reject of %s (%s).',
utils.revHex(hash), peer.hostname);
return true;
}
return false;
});
/** /**
* Test whether the chain or mempool has seen an item. * Test whether the chain or mempool has seen an item.
@ -1873,64 +1855,60 @@ Pool.prototype.isIgnored = function isIgnored(addr) {
* @param {Function} callback * @param {Function} callback
*/ */
Pool.prototype.getIP = function getIP() { Pool.prototype.getIP = spawn.co(function* getIP() {
return spawn(function *() { var request, res, ip;
var request, res, ip;
if (utils.isBrowser) if (utils.isBrowser)
throw new Error('Could not find IP.'); throw new Error('Could not find IP.');
request = require('../http/request'); request = require('../http/request');
try { try {
res = yield request.promise({ res = yield request.promise({
method: 'GET', method: 'GET',
uri: 'http://icanhazip.com', uri: 'http://icanhazip.com',
expect: 'text', expect: 'text',
timeout: 3000 timeout: 3000
}); });
} catch (e) { } catch (e) {
return yield this.getIP2(); return yield this.getIP2();
} }
ip = res.body.trim(); ip = res.body.trim();
if (IP.version(ip) === -1) if (IP.version(ip) === -1)
return yield this.getIP2(); return yield this.getIP2();
return IP.normalize(ip); return IP.normalize(ip);
}, this); });
};
/** /**
* Attempt to retrieve external IP from dyndns.org. * Attempt to retrieve external IP from dyndns.org.
* @param {Function} callback * @param {Function} callback
*/ */
Pool.prototype.getIP2 = function getIP2() { Pool.prototype.getIP2 = spawn.co(function* getIP2() {
return spawn(function *() { var request, res, ip;
var request, res, ip;
if (utils.isBrowser) if (utils.isBrowser)
throw new Error('Could not find IP.'); throw new Error('Could not find IP.');
request = require('../http/request'); request = require('../http/request');
res = yield request.promise({ res = yield request.promise({
method: 'GET', method: 'GET',
uri: 'http://checkip.dyndns.org', uri: 'http://checkip.dyndns.org',
expect: 'html', expect: 'html',
timeout: 3000 timeout: 3000
}); });
ip = /IP Address:\s*([0-9a-f.:]+)/i.exec(res.body); ip = /IP Address:\s*([0-9a-f.:]+)/i.exec(res.body);
if (!ip || IP.version(ip[1]) === -1) if (!ip || IP.version(ip[1]) === -1)
throw new Error('Could not find IP.'); throw new Error('Could not find IP.');
return IP.normalize(ip[1]); return IP.normalize(ip[1]);
}, this); });
};
/** /**
* Peer List * Peer List

View File

@ -223,29 +223,27 @@ Fullnode.prototype._init = function _init() {
* @param {Function} callback * @param {Function} callback
*/ */
Fullnode.prototype._open = function open() { Fullnode.prototype._open = spawn.co(function* open() {
return spawn(function *() { yield this.chain.open();
yield this.chain.open(); yield this.mempool.open();
yield this.mempool.open(); yield this.miner.open();
yield this.miner.open(); yield this.pool.open();
yield this.pool.open(); yield this.walletdb.open();
yield this.walletdb.open();
// Ensure primary wallet. // Ensure primary wallet.
yield this.openWallet(); yield this.openWallet();
// Rescan for any missed transactions. // Rescan for any missed transactions.
yield this.rescan(); yield this.rescan();
// Rebroadcast pending transactions. // Rebroadcast pending transactions.
yield this.resend(); yield this.resend();
if (this.http) if (this.http)
yield this.http.open(); yield this.http.open();
this.logger.info('Node is loaded.'); this.logger.info('Node is loaded.');
}, this); });
};
/** /**
* Close the node, wait for the database to close. * Close the node, wait for the database to close.
@ -253,22 +251,20 @@ Fullnode.prototype._open = function open() {
* @param {Function} callback * @param {Function} callback
*/ */
Fullnode.prototype._close = function close() { Fullnode.prototype._close = spawn.co(function* close() {
return spawn(function *() { this.wallet = null;
this.wallet = null;
if (this.http) if (this.http)
yield this.http.close(); yield this.http.close();
this.walletdb.close(); this.walletdb.close();
this.pool.close(); this.pool.close();
this.miner.close(); this.miner.close();
this.mempool.close(); this.mempool.close();
this.chain.close(); this.chain.close();
this.logger.info('Node is closed.'); this.logger.info('Node is closed.');
}, this); });
};
/** /**
* Rescan for any missed transactions. * Rescan for any missed transactions.
@ -309,26 +305,24 @@ Fullnode.prototype.broadcast = function broadcast(item, callback) {
* @param {TX} tx * @param {TX} tx
*/ */
Fullnode.prototype.sendTX = function sendTX(tx) { Fullnode.prototype.sendTX = spawn.co(function* sendTX(tx) {
return spawn(function *() { try {
try { yield this.mempool.addTX(tx);
yield this.mempool.addTX(tx); } catch (err) {
} catch (err) { if (err.type === 'VerifyError') {
if (err.type === 'VerifyError') { this._error(err);
this._error(err); this.logger.warning('Verification failed for tx: %s.', tx.rhash);
this.logger.warning('Verification failed for tx: %s.', tx.rhash); this.logger.warning('Attempting to broadcast anyway...');
this.logger.warning('Attempting to broadcast anyway...'); return this.pool.broadcast(tx);
return this.pool.broadcast(tx);
}
throw err;
} }
throw err;
}
if (!this.options.selfish) if (!this.options.selfish)
tx = tx.toInv(); tx = tx.toInv();
return this.pool.broadcast(tx); return this.pool.broadcast(tx);
}, this); });
};
/** /**
* Listen on a server socket on * Listen on a server socket on
@ -410,24 +404,22 @@ Fullnode.prototype.getCoin = function getCoin(hash, index) {
* @param {Function} callback - Returns [Error, {@link Coin}[]]. * @param {Function} callback - Returns [Error, {@link Coin}[]].
*/ */
Fullnode.prototype.getCoinsByAddress = function getCoinsByAddress(addresses) { Fullnode.prototype.getCoinsByAddress = spawn.co(function* getCoinsByAddress(addresses) {
return spawn(function *() { var coins = this.mempool.getCoinsByAddress(addresses);
var coins = this.mempool.getCoinsByAddress(addresses); var i, blockCoins, coin, spent;
var i, blockCoins, coin, spent;
blockCoins = yield this.chain.db.getCoinsByAddress(addresses); blockCoins = yield this.chain.db.getCoinsByAddress(addresses);
for (i = 0; i < blockCoins.length; i++) { for (i = 0; i < blockCoins.length; i++) {
coin = blockCoins[i]; coin = blockCoins[i];
spent = this.mempool.isSpent(coin.hash, coin.index); spent = this.mempool.isSpent(coin.hash, coin.index);
if (!spent) if (!spent)
coins.push(coin); coins.push(coin);
} }
return coins; return coins;
}, this); });
};
/** /**
* Retrieve transactions pertaining to an * Retrieve transactions pertaining to an
@ -436,13 +428,11 @@ Fullnode.prototype.getCoinsByAddress = function getCoinsByAddress(addresses) {
* @param {Function} callback - Returns [Error, {@link TX}[]]. * @param {Function} callback - Returns [Error, {@link TX}[]].
*/ */
Fullnode.prototype.getTXByAddress = function getTXByAddress(addresses) { Fullnode.prototype.getTXByAddress = spawn.co(function* getTXByAddress(addresses) {
return spawn(function *() { var mempool = this.mempool.getTXByAddress(addresses);
var mempool = this.mempool.getTXByAddress(addresses); var txs = yield this.chain.db.getTXByAddress(addresses);
var txs = yield this.chain.db.getTXByAddress(addresses); return mempool.concat(txs);
return mempool.concat(txs); });
}, this);
};
/** /**
* Retrieve a transaction from the mempool or chain database. * Retrieve a transaction from the mempool or chain database.

View File

@ -233,33 +233,31 @@ Node.prototype.location = function location(name) {
* @param {Function} callback * @param {Function} callback
*/ */
Node.prototype.openWallet = function openWallet() { Node.prototype.openWallet = spawn.co(function* openWallet() {
return spawn(function *() { var options, wallet;
var options, wallet;
assert(!this.wallet); assert(!this.wallet);
options = { options = {
id: 'primary', id: 'primary',
passphrase: this.options.passphrase passphrase: this.options.passphrase
}; };
wallet = yield this.walletdb.ensure(options); wallet = yield this.walletdb.ensure(options);
this.logger.info( this.logger.info(
'Loaded wallet with id=%s wid=%d address=%s', 'Loaded wallet with id=%s wid=%d address=%s',
wallet.id, wallet.wid, wallet.getAddress()); wallet.id, wallet.wid, wallet.getAddress());
// Set the miner payout address if the // Set the miner payout address if the
// programmer didn't pass one in. // programmer didn't pass one in.
if (this.miner) { if (this.miner) {
if (!this.options.payoutAddress) if (!this.options.payoutAddress)
this.miner.address = wallet.getAddress(); this.miner.address = wallet.getAddress();
} }
this.wallet = wallet; this.wallet = wallet;
}, this); });
};
/** /**
* Resend all pending transactions. * Resend all pending transactions.

View File

@ -147,30 +147,28 @@ SPVNode.prototype._init = function _init() {
* @param {Function} callback * @param {Function} callback
*/ */
SPVNode.prototype._open = function open(callback) { SPVNode.prototype._open = spawn.co(function* open(callback) {
return spawn(function *() { yield this.chain.open();
yield this.chain.open(); yield this.pool.open();
yield this.pool.open(); yield this.walletdb.open();
yield this.walletdb.open();
// Ensure primary wallet. // Ensure primary wallet.
yield this.openWallet(); yield this.openWallet();
// Load bloom filter. // Load bloom filter.
yield this.openFilter(); yield this.openFilter();
// Rescan for any missed transactions. // Rescan for any missed transactions.
yield this.rescan(); yield this.rescan();
// Rebroadcast pending transactions. // Rebroadcast pending transactions.
yield this.resend(); yield this.resend();
if (this.http) if (this.http)
yield this.http.open(); yield this.http.open();
this.logger.info('Node is loaded.'); this.logger.info('Node is loaded.');
}, this); });
};
/** /**
* Close the node, wait for the database to close. * Close the node, wait for the database to close.
@ -178,34 +176,30 @@ SPVNode.prototype._open = function open(callback) {
* @param {Function} callback * @param {Function} callback
*/ */
SPVNode.prototype._close = function close() { SPVNode.prototype._close = spawn.co(function* close() {
return spawn(function *() { this.wallet = null;
this.wallet = null; if (this.http)
if (this.http) yield this.http.close();
yield this.http.close(); yield this.walletdb.close();
yield this.walletdb.close(); yield this.pool.close();
yield this.pool.close(); yield this.chain.close();
yield this.chain.close(); });
}, this);
};
/** /**
* Initialize p2p bloom filter for address watching. * Initialize p2p bloom filter for address watching.
* @param {Function} callback * @param {Function} callback
*/ */
SPVNode.prototype.openFilter = function openFilter() { SPVNode.prototype.openFilter = spawn.co(function* openFilter() {
return spawn(function *() { var hashes = yield this.walletdb.getAddressHashes();
var hashes = yield this.walletdb.getAddressHashes(); var i;
var i;
if (hashes.length > 0) if (hashes.length > 0)
this.logger.info('Adding %d addresses to filter.', hashes.length); this.logger.info('Adding %d addresses to filter.', hashes.length);
for (i = 0; i < hashes.length; i++) for (i = 0; i < hashes.length; i++)
this.pool.watch(hashes[i], 'hex'); this.pool.watch(hashes[i], 'hex');
}, this); });
};
/** /**
* Rescan for any missed transactions. * Rescan for any missed transactions.

View File

@ -53,98 +53,94 @@ AsyncObject.prototype._onClose = function _onClose() {
}); });
}; };
AsyncObject.prototype.open = function open() { AsyncObject.prototype.open = spawn.co(function* open() {
return spawn(function *() { var err, unlock;
var err, unlock;
assert(!this.closing, 'Cannot open while closing.'); assert(!this.closing, 'Cannot open while closing.');
if (this.loaded) if (this.loaded)
return yield wait(); return yield wait();
if (this.loading) if (this.loading)
return yield this._onOpen(); return yield this._onOpen();
if (this.locker) if (this.locker)
unlock = yield this.locker.lock(); unlock = yield this.locker.lock();
this.emit('preopen'); this.emit('preopen');
this.loading = true; this.loading = true;
try { try {
yield this._open(); yield this._open();
} catch (e) { } catch (e) {
err = e; err = e;
} }
yield wait(); yield wait();
if (err) {
this.loading = false;
this._error('open', err);
if (unlock)
unlock();
throw err;
}
if (err) {
this.loading = false; this.loading = false;
this.loaded = true; this._error('open', err);
this.emit('open');
if (unlock) if (unlock)
unlock(); unlock();
}, this); throw err;
}; }
this.loading = false;
this.loaded = true;
this.emit('open');
if (unlock)
unlock();
});
/** /**
* Close the object (recallable). * Close the object (recallable).
* @param {Function} callback * @param {Function} callback
*/ */
AsyncObject.prototype.close = function close() { AsyncObject.prototype.close = spawn.co(function* close() {
return spawn(function *() { var unlock, err;
var unlock, err;
assert(!this.loading, 'Cannot close while loading.'); assert(!this.loading, 'Cannot close while loading.');
if (!this.loaded) if (!this.loaded)
return yield wait(); return yield wait();
if (this.closing) if (this.closing)
return yield this._onClose(); return yield this._onClose();
if (this.locker) if (this.locker)
unlock = yield this.locker.lock(); unlock = yield this.locker.lock();
this.emit('preclose'); this.emit('preclose');
this.closing = true; this.closing = true;
this.loaded = false; this.loaded = false;
try { try {
yield this._close(); yield this._close();
} catch (e) { } catch (e) {
err = e; err = e;
} }
yield wait(); yield wait();
if (err) {
this.closing = false;
this._error('close', err);
if (unlock)
unlock();
throw err;
}
if (err) {
this.closing = false; this.closing = false;
this.emit('close'); this._error('close', err);
if (unlock) if (unlock)
unlock(); unlock();
}, this); throw err;
}; }
this.closing = false;
this.emit('close');
if (unlock)
unlock();
});
/** /**
* Close the object (recallable). * Close the object (recallable).

View File

@ -1,11 +1,7 @@
'use strict'; 'use strict';
// See: https://github.com/yoursnetwork/asink function exec(gen) {
function spawn(generator, self) {
return new Promise(function(resolve, reject) { return new Promise(function(resolve, reject) {
var gen = generator.call(self);
function step(value, rejection) { function step(value, rejection) {
var next; var next;
@ -38,4 +34,18 @@ function spawn(generator, self) {
}); });
} }
function spawn(generator, self) {
var gen = generator.call(self);
return exec(gen);
}
function co(generator) {
return function() {
var gen = generator.apply(this, arguments);
return exec(gen);
};
}
spawn.co = co;
module.exports = spawn; module.exports = spawn;

View File

@ -204,22 +204,20 @@ Account.MAX_LOOKAHEAD = 5;
* @param {Function} callback * @param {Function} callback
*/ */
Account.prototype.init = function init() { Account.prototype.init = spawn.co(function* init() {
return spawn(function *() { // Waiting for more keys.
// Waiting for more keys. if (this.keys.length !== this.n - 1) {
if (this.keys.length !== this.n - 1) { assert(!this.initialized);
assert(!this.initialized); this.save();
this.save(); return;
return; }
}
assert(this.receiveDepth === 0); assert(this.receiveDepth === 0);
assert(this.changeDepth === 0); assert(this.changeDepth === 0);
this.initialized = true; this.initialized = true;
yield this.setDepth(1, 1); yield this.setDepth(1, 1);
}, this); });
};
/** /**
* Open the account (done after retrieval). * Open the account (done after retrieval).
@ -306,30 +304,28 @@ Account.prototype.spliceKey = function spliceKey(key) {
* @param {Function} callback * @param {Function} callback
*/ */
Account.prototype.addKey = function addKey(key) { Account.prototype.addKey = spawn.co(function* addKey(key) {
return spawn(function *() { var result = false;
var result = false; var exists;
var exists;
try { try {
result = this.pushKey(key); result = this.pushKey(key);
} catch (e) { } catch (e) {
throw e; throw e;
} }
exists = yield this._checkKeys(); exists = yield this._checkKeys();
if (exists) { if (exists) {
this.spliceKey(key); this.spliceKey(key);
throw new Error('Cannot add a key from another account.'); throw new Error('Cannot add a key from another account.');
} }
// Try to initialize again. // Try to initialize again.
yield this.init(); yield this.init();
return result; return result;
}, this); });
};
/** /**
* Ensure accounts are not sharing keys. * Ensure accounts are not sharing keys.
@ -337,27 +333,25 @@ Account.prototype.addKey = function addKey(key) {
* @param {Function} callback * @param {Function} callback
*/ */
Account.prototype._checkKeys = function _checkKeys() { Account.prototype._checkKeys = spawn.co(function* _checkKeys() {
return spawn(function *() { var ring, hash, paths;
var ring, hash, paths;
if (this.initialized || this.type !== Account.types.MULTISIG) if (this.initialized || this.type !== Account.types.MULTISIG)
return false; return false;
if (this.keys.length !== this.n - 1) if (this.keys.length !== this.n - 1)
return false; return false;
ring = this.deriveReceive(0); ring = this.deriveReceive(0);
hash = ring.getScriptHash('hex'); hash = ring.getScriptHash('hex');
paths = yield this.db.getAddressPaths(hash); paths = yield this.db.getAddressPaths(hash);
if (!paths) if (!paths)
return false; return false;
return paths[this.wid] != null; return paths[this.wid] != null;
}, this); });
};
/** /**
* Remove a public account key from the account (multisig). * Remove a public account key from the account (multisig).
@ -404,29 +398,27 @@ Account.prototype.createChange = function createChange() {
* @param {Function} callback - Returns [Error, {@link KeyRing}]. * @param {Function} callback - Returns [Error, {@link KeyRing}].
*/ */
Account.prototype.createAddress = function createAddress(change) { Account.prototype.createAddress = spawn.co(function* createAddress(change) {
return spawn(function *() { var ring, lookahead;
var ring, lookahead;
if (change) { if (change) {
ring = this.deriveChange(this.changeDepth); ring = this.deriveChange(this.changeDepth);
lookahead = this.deriveChange(this.changeDepth + this.lookahead); lookahead = this.deriveChange(this.changeDepth + this.lookahead);
this.changeDepth++; this.changeDepth++;
this.changeAddress = ring; this.changeAddress = ring;
} else { } else {
ring = this.deriveReceive(this.receiveDepth); ring = this.deriveReceive(this.receiveDepth);
lookahead = this.deriveReceive(this.receiveDepth + this.lookahead); lookahead = this.deriveReceive(this.receiveDepth + this.lookahead);
this.receiveDepth++; this.receiveDepth++;
this.receiveAddress = ring; this.receiveAddress = ring;
} }
yield this.saveAddress([ring, lookahead]); yield this.saveAddress([ring, lookahead]);
this.save(); this.save();
return ring; return ring;
}, this); });
};
/** /**
* Derive a receiving address at `index`. Do not increment depth. * Derive a receiving address at `index`. Do not increment depth.
@ -568,47 +560,45 @@ Account.prototype.saveAddress = function saveAddress(rings) {
* @param {Function} callback - Returns [Error, {@link KeyRing}, {@link KeyRing}]. * @param {Function} callback - Returns [Error, {@link KeyRing}, {@link KeyRing}].
*/ */
Account.prototype.setDepth = function setDepth(receiveDepth, changeDepth) { Account.prototype.setDepth = spawn.co(function* setDepth(receiveDepth, changeDepth) {
return spawn(function *() { var rings = [];
var rings = []; var i, receive, change;
var i, receive, change;
if (receiveDepth > this.receiveDepth) { if (receiveDepth > this.receiveDepth) {
for (i = this.receiveDepth; i < receiveDepth; i++) { for (i = this.receiveDepth; i < receiveDepth; i++) {
receive = this.deriveReceive(i); receive = this.deriveReceive(i);
rings.push(receive); rings.push(receive);
}
for (i = receiveDepth; i < receiveDepth + this.lookahead; i++)
rings.push(this.deriveReceive(i));
this.receiveAddress = receive;
this.receiveDepth = receiveDepth;
} }
if (changeDepth > this.changeDepth) { for (i = receiveDepth; i < receiveDepth + this.lookahead; i++)
for (i = this.changeDepth; i < changeDepth; i++) { rings.push(this.deriveReceive(i));
change = this.deriveChange(i);
rings.push(change);
}
for (i = changeDepth; i < changeDepth + this.lookahead; i++) this.receiveAddress = receive;
rings.push(this.deriveChange(i)); this.receiveDepth = receiveDepth;
}
this.changeAddress = change; if (changeDepth > this.changeDepth) {
this.changeDepth = changeDepth; for (i = this.changeDepth; i < changeDepth; i++) {
change = this.deriveChange(i);
rings.push(change);
} }
if (rings.length === 0) for (i = changeDepth; i < changeDepth + this.lookahead; i++)
return []; rings.push(this.deriveChange(i));
yield this.saveAddress(rings); this.changeAddress = change;
this.changeDepth = changeDepth;
}
this.save(); if (rings.length === 0)
return [];
return [receive, change]; yield this.saveAddress(rings);
}, this);
}; this.save();
return [receive, change];
});
/** /**
* Convert the account to a more inspection-friendly object. * Convert the account to a more inspection-friendly object.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -247,25 +247,23 @@ Workers.prototype.verify = function verify(tx, flags) {
* @param {Function} callback * @param {Function} callback
*/ */
Workers.prototype.sign = function sign(tx, ring, type) { Workers.prototype.sign = spawn.co(function* sign(tx, ring, type) {
return spawn(function *() { var i, result, input, sig, sigs, total;
var i, result, input, sig, sigs, total;
result = yield this.execute('sign', [tx, ring, type], -1); result = yield this.execute('sign', [tx, ring, type], -1);
sigs = result[0]; sigs = result[0];
total = result[1]; total = result[1];
for (i = 0; i < sigs.length; i++) { for (i = 0; i < sigs.length; i++) {
sig = sigs[i]; sig = sigs[i];
input = tx.inputs[i]; input = tx.inputs[i];
input.script = sig[0]; input.script = sig[0];
input.witness = sig[1]; input.witness = sig[1];
} }
return total; return total;
}, this); });
};
/** /**
* Execute the mining job (no timeout). * Execute the mining job (no timeout).