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[]].
*/
ChainEntry.prototype.getAncestors = function getAncestors(max) {
return spawn(function *() {
var entry = this;
var ancestors = [];
var cached;
ChainEntry.prototype.getAncestors = spawn.co(function* getAncestors(max) {
var entry = this;
var ancestors = [];
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;
assert(utils.isNumber(max));
cached = this.chain.db.getCache(entry.prevBlock);
// 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;
cached = this.chain.db.getCache(entry.prevBlock);
if (!cached) {
ancestors.pop();
break;
}
entry = cached;
if (!cached) {
ancestors.pop();
break;
}
while (entry) {
ancestors.push(entry);
if (ancestors.length >= max)
break;
entry = yield entry.getPrevious();
}
entry = cached;
}
return ancestors;
}, this);
};
while (entry) {
ancestors.push(entry);
if (ancestors.length >= max)
break;
entry = yield entry.getPrevious();
}
return ancestors;
});
/**
* Test whether the entry is in the main chain.
@ -232,31 +230,29 @@ ChainEntry.prototype.isMainChain = function isMainChain() {
* @param {Function} callback - Returns [Error, ChainEntry[]].
*/
ChainEntry.prototype.getAncestorByHeight = function getAncestorByHeight(height) {
return spawn(function *() {
var main, entry;
ChainEntry.prototype.getAncestorByHeight = spawn.co(function* getAncestorByHeight(height) {
var main, entry;
if (height < 0)
return yield utils.wait();
if (height < 0)
return yield utils.wait();
assert(height >= 0);
assert(height <= this.height);
assert(height >= 0);
assert(height <= this.height);
main = yield this.isMainChain();
main = yield this.isMainChain();
if (main)
return yield this.chain.db.get(height);
if (main)
return yield this.chain.db.get(height);
entry = yield this.getAncestor(this.height - height);
entry = yield this.getAncestor(this.height - height);
if (!entry)
return;
if (!entry)
return;
assert(entry.height === height);
assert(entry.height === height);
return entry;
}, this);
};
return entry;
});
/**
* 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].
*/
ChainEntry.prototype.getAncestor = function getAncestor(index) {
return spawn(function *() {
var ancestors;
ChainEntry.prototype.getAncestor = spawn.co(function* getAncestor(index) {
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)
return;
if (ancestors.length < index + 1)
return;
return ancestors[index];
}, this);
};
return ancestors[index];
});
/**
* Get previous entry.
@ -295,14 +289,12 @@ ChainEntry.prototype.getPrevious = function getPrevious() {
* @param {Function} callback - Returns [Error, ChainEntry].
*/
ChainEntry.prototype.getNext = function getNext() {
return spawn(function *() {
var hash = yield this.chain.db.getNextHash(this.hash);
if (!hash)
return;
return yield this.chain.db.get(hash);
}, this);
};
ChainEntry.prototype.getNext = spawn.co(function* getNext() {
var hash = yield this.chain.db.getNextHash(this.hash);
if (!hash)
return;
return yield this.chain.db.get(hash);
});
/**
* Get median time past.
@ -330,13 +322,11 @@ ChainEntry.prototype.getMedianTime = function getMedianTime(ancestors) {
* @param {Function} callback - Returns [Error, Number].
*/
ChainEntry.prototype.getMedianTimeAsync = function getMedianTimeAsync() {
return spawn(function *() {
var MEDIAN_TIMESPAN = constants.block.MEDIAN_TIMESPAN;
var ancestors = yield this.getAncestors(MEDIAN_TIMESPAN);
return this.getMedianTime(ancestors);
}, this);
};
ChainEntry.prototype.getMedianTimeAsync = spawn.co(function* getMedianTimeAsync() {
var MEDIAN_TIMESPAN = constants.block.MEDIAN_TIMESPAN;
var ancestors = yield this.getAncestors(MEDIAN_TIMESPAN);
return this.getMedianTime(ancestors);
});
/**
* Check isSuperMajority against majorityRejectOutdated.
@ -419,13 +409,11 @@ ChainEntry.prototype.isSuperMajority = function isSuperMajority(version, require
* @returns {Boolean}
*/
ChainEntry.prototype.isSuperMajorityAsync = function isSuperMajorityAsync(version, required) {
return spawn(function *() {
var majorityWindow = this.network.block.majorityWindow;
var ancestors = yield this.getAncestors(majorityWindow);
return this.isSuperMajority(version, required, ancestors);
}, this);
};
ChainEntry.prototype.isSuperMajorityAsync = spawn.co(function* isSuperMajorityAsync(version, required) {
var majorityWindow = this.network.block.majorityWindow;
var ancestors = yield this.getAncestors(majorityWindow);
return this.isSuperMajority(version, required, ancestors);
});
/**
* 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].
*/
LowlevelUp.prototype.has = function has(key) {
return spawn(function *() {
var value = yield this.get(key);
return value != null;
}, this);
};
LowlevelUp.prototype.has = spawn.co(function* has(key) {
var value = yield this.get(key);
return value != null;
});
/**
* Get and deserialize a record with a callback.
@ -305,15 +303,13 @@ LowlevelUp.prototype.has = function has(key) {
* @param {Function} callback - Returns [Error, Object].
*/
LowlevelUp.prototype.fetch = function fetch(key, parse) {
return spawn(function *() {
var value = yield this.get(key);
if (!value)
return;
LowlevelUp.prototype.fetch = spawn.co(function* fetch(key, parse) {
var value = yield this.get(key);
if (!value)
return;
return parse(value, key);
}, this);
};
return parse(value, key);
});
/**
* Collect all keys from iterator options.
@ -321,29 +317,27 @@ LowlevelUp.prototype.fetch = function fetch(key, parse) {
* @param {Function} callback - Returns [Error, Array].
*/
LowlevelUp.prototype.iterate = function iterate(options) {
return spawn(function *() {
var items = [];
var iter, kv, result;
LowlevelUp.prototype.iterate = spawn.co(function* iterate(options) {
var items = [];
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 (;;) {
kv = yield iter.next();
if (!kv)
return items;
for (;;) {
kv = yield iter.next();
if (!kv)
return items;
result = options.parse(kv[0], kv[1]);
result = options.parse(kv[0], kv[1]);
if (result)
items.push(result);
}
if (result)
items.push(result);
}
return items;
}, this);
};
return items;
});
/**
* Write and assert a version number for the database.
@ -351,23 +345,21 @@ LowlevelUp.prototype.iterate = function iterate(options) {
* @param {Function} callback
*/
LowlevelUp.prototype.checkVersion = function checkVersion(key, version) {
return spawn(function *() {
var data = yield this.get(key);
LowlevelUp.prototype.checkVersion = spawn.co(function* checkVersion(key, version) {
var data = yield this.get(key);
if (!data) {
data = new Buffer(4);
data.writeUInt32LE(version, 0, true);
yield this.put(key, data);
return;
}
if (!data) {
data = new Buffer(4);
data.writeUInt32LE(version, 0, true);
yield this.put(key, data);
return;
}
data = data.readUInt32LE(0, true);
data = data.readUInt32LE(0, true);
if (data !== version)
throw new Error(VERSION_ERROR);
}, this);
};
if (data !== version)
throw new Error(VERSION_ERROR);
});
/**
* Clone the database.
@ -375,60 +367,58 @@ LowlevelUp.prototype.checkVersion = function checkVersion(key, version) {
* @param {Function} callback
*/
LowlevelUp.prototype.clone = function clone(path) {
return spawn(function *() {
var opt = { keys: true, values: true };
var options = utils.merge({}, this.options);
var hwm = 256 << 20;
var total = 0;
var tmp, batch, iter, items, key, value;
LowlevelUp.prototype.clone = spawn.co(function* clone(path) {
var opt = { keys: true, values: true };
var options = utils.merge({}, this.options);
var hwm = 256 << 20;
var total = 0;
var tmp, batch, iter, items, key, value;
assert(!this.loading);
assert(!this.closing);
assert(this.loaded);
assert(!this.loading);
assert(!this.closing);
assert(this.loaded);
options.createIfMissing = true;
options.errorIfExists = true;
options.createIfMissing = true;
options.errorIfExists = true;
tmp = new LowlevelUp(path, options);
tmp = new LowlevelUp(path, options);
yield tmp.open();
yield tmp.open();
batch = tmp.batch();
iter = this.iterator(opt);
batch = tmp.batch();
iter = this.iterator(opt);
for (;;) {
items = yield iter.next();
for (;;) {
items = yield iter.next();
if (!items) {
try {
yield batch.write();
} catch (e) {
yield tmp.close();
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();
if (!items) {
try {
yield batch.write();
} catch (e) {
yield tmp.close();
throw e;
}
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) {
this.db = db;

View File

@ -56,71 +56,69 @@ utils.inherits(HTTPClient, AsyncObject);
* @param {Function} callback
*/
HTTPClient.prototype._open = function _open() {
return spawn(function *() {
var self = this;
var IOClient;
HTTPClient.prototype._open = spawn.co(function* _open() {
var self = this;
var IOClient;
try {
IOClient = require('socket.io-client');
} catch (e) {
;
}
try {
IOClient = require('socket.io-client');
} catch (e) {
;
}
if (!IOClient)
return;
if (!IOClient)
return;
this.socket = new IOClient(this.uri, {
transports: ['websocket'],
forceNew: true
this.socket = new IOClient(this.uri, {
transports: ['websocket'],
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) {
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)
});
});
yield this._onConnect();
yield this._sendAuth();
}, this);
};
yield this._onConnect();
yield this._sendAuth();
});
HTTPClient.prototype._onConnect = function _onConnect() {
var self = this;
@ -165,58 +163,56 @@ HTTPClient.prototype._close = function close() {
* @param {Function} callback - Returns [Error, Object?].
*/
HTTPClient.prototype._request = function _request(method, endpoint, json) {
return spawn(function *() {
var query, network, height, res;
HTTPClient.prototype._request = spawn.co(function* _request(method, endpoint, json) {
var query, network, height, res;
if (this.token) {
if (!json)
json = {};
json.token = this.token;
}
if (this.token) {
if (!json)
json = {};
json.token = this.token;
}
if (json && method === 'get') {
query = json;
json = null;
}
if (json && method === 'get') {
query = json;
json = null;
}
res = yield request({
method: method,
uri: this.uri + endpoint,
query: query,
json: json,
auth: {
username: 'bitcoinrpc',
password: this.apiKey || ''
},
expect: 'json'
});
res = yield request({
method: method,
uri: this.uri + endpoint,
query: query,
json: json,
auth: {
username: 'bitcoinrpc',
password: this.apiKey || ''
},
expect: 'json'
});
network = res.headers['x-bcoin-network'];
network = res.headers['x-bcoin-network'];
if (network !== this.network.type)
throw new Error('Wrong network.');
if (network !== this.network.type)
throw new Error('Wrong network.');
height = +res.headers['x-bcoin-height'];
height = +res.headers['x-bcoin-height'];
if (utils.isNumber(height))
this.network.updateHeight(height);
if (utils.isNumber(height))
this.network.updateHeight(height);
if (res.statusCode === 404)
return;
if (res.statusCode === 404)
return;
if (!res.body)
throw new Error('No body.');
if (!res.body)
throw new Error('No body.');
if (res.statusCode !== 200) {
if (res.body.error)
throw new Error(res.body.error);
throw new Error('Status code: ' + res.statusCode);
}
if (res.statusCode !== 200) {
if (res.body.error)
throw new Error(res.body.error);
throw new Error('Status code: ' + res.statusCode);
}
return res.body;
}, this);
};
return res.body;
});
/**
* Make a GET http request to endpoint.
@ -569,13 +565,11 @@ HTTPClient.prototype.send = function send(id, options) {
* @param {Function} callback
*/
HTTPClient.prototype.retoken = function retoken(id, passphrase) {
return spawn(function *() {
var options = { passphrase: passphrase };
var body = yield this._post('/wallet/' + id + '/retoken', options);
return body.token;
}, this);
};
HTTPClient.prototype.retoken = spawn.co(function* retoken(id, passphrase) {
var options = { passphrase: passphrase };
var body = yield this._post('/wallet/' + id + '/retoken', options);
return body.token;
});
/**
* 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?].
*/
RPCClient.prototype.call = function call(method, params) {
return spawn(function *() {
var res = yield request.promise({
method: 'POST',
uri: this.uri,
json: {
method: method,
params: params,
id: this.id++
},
auth: {
username: 'bitcoinrpc',
password: this.apiKey || ''
},
expect: 'json'
});
RPCClient.prototype.call = spawn.co(function* call(method, params) {
var res = yield request.promise({
method: 'POST',
uri: this.uri,
json: {
method: method,
params: params,
id: this.id++
},
auth: {
username: 'bitcoinrpc',
password: this.apiKey || ''
},
expect: 'json'
});
if (!res.body)
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.body)
return;
if (res.statusCode === 400)
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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -290,40 +290,38 @@ Pool.prototype._lock = function _lock(force) {
* @param {Function} callback
*/
Pool.prototype._open = function _open() {
return spawn(function *() {
var ip, key;
Pool.prototype._open = spawn.co(function* _open() {
var ip, key;
try {
ip = yield this.getIP();
} catch (e) {
this.logger.error(e);
}
try {
ip = yield this.getIP();
} catch (e) {
this.logger.error(e);
}
if (ip) {
this.address.setHost(ip);
this.logger.info('External IP found: %s.', ip);
}
if (ip) {
this.address.setHost(ip);
this.logger.info('External IP found: %s.', ip);
}
if (this.mempool)
yield this.mempool.open();
else
yield this.chain.open();
if (this.mempool)
yield this.mempool.open();
else
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) {
key = bcoin.ec.publicKeyCreate(this.identityKey, true);
this.logger.info('Identity public key: %s.', key.toString('hex'));
this.logger.info('Identity address: %s.', bcoin.bip150.address(key));
}
if (this.identityKey) {
key = bcoin.ec.publicKeyCreate(this.identityKey, true);
this.logger.info('Identity public key: %s.', key.toString('hex'));
this.logger.info('Identity address: %s.', bcoin.bip150.address(key));
}
if (!this.options.listen)
return;
if (!this.options.listen)
return;
yield this.listen();
}, this);
};
yield this.listen();
});
/**
* Close and destroy the pool.
@ -331,37 +329,35 @@ Pool.prototype._open = function _open() {
* @param {Function} callback
*/
Pool.prototype._close = function close() {
return spawn(function *() {
var i, items, hashes, hash;
Pool.prototype._close = spawn.co(function* close() {
var i, items, hashes, hash;
this.stopSync();
this.stopSync();
items = this.invItems.slice();
items = this.invItems.slice();
for (i = 0; i < items.length; i++)
items[i].finish();
for (i = 0; i < items.length; i++)
items[i].finish();
hashes = Object.keys(this.requestMap);
hashes = Object.keys(this.requestMap);
for (i = 0; i < hashes.length; i++) {
hash = hashes[i];
this.requestMap[hash].finish(new Error('Pool closed.'));
}
for (i = 0; i < hashes.length; i++) {
hash = hashes[i];
this.requestMap[hash].finish(new Error('Pool closed.'));
}
this.peers.destroy();
this.peers.destroy();
this.stopInterval();
this.stopTimeout();
this.stopInterval();
this.stopTimeout();
if (this.pendingWatch != null) {
clearTimeout(this.pendingWatch);
this.pendingWatch = null;
}
if (this.pendingWatch != null) {
clearTimeout(this.pendingWatch);
this.pendingWatch = null;
}
yield this.unlisten();
}, this);
};
yield this.unlisten();
});
/**
* Connect to the network.
@ -724,68 +720,66 @@ Pool.prototype.stopSync = function stopSync() {
* @param {Function} callback
*/
Pool.prototype._handleHeaders = function _handleHeaders(headers, peer) {
return spawn(function *() {
var i, unlock, ret, header, hash, last;
Pool.prototype._handleHeaders = spawn.co(function* _handleHeaders(headers, peer) {
var i, unlock, ret, header, hash, last;
if (!this.options.headers)
return;
if (!this.options.headers)
return;
unlock = yield this._lock();
unlock = yield this._lock();
ret = new VerifyResult();
ret = new VerifyResult();
this.logger.debug(
'Received %s headers from peer (%s).',
headers.length,
peer.hostname);
this.logger.debug(
'Received %s headers from peer (%s).',
headers.length,
peer.hostname);
this.emit('headers', headers);
this.emit('headers', headers);
if (peer.isLoader()) {
// Reset interval to avoid stall behavior.
this.startInterval();
// Reset timeout to avoid killing the loader.
this.startTimeout();
if (peer.isLoader()) {
// Reset interval to avoid stall behavior.
this.startInterval();
// Reset timeout to avoid killing the loader.
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++) {
header = headers[i];
hash = header.hash('hex');
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);
if (!header.verify(ret)) {
peer.reject(header, 'invalid', ret.reason, 100);
unlock();
throw new Error('Invalid header.');
}
// Schedule the getdata's we just added.
this.scheduleRequests(peer);
last = hash;
// 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);
yield this.getData(peer, this.blockType, hash);
}
unlock();
}, this);
};
// Schedule the getdata's we just added.
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).
@ -795,66 +789,64 @@ Pool.prototype._handleHeaders = function _handleHeaders(headers, peer) {
* @param {Function} callback
*/
Pool.prototype._handleBlocks = function _handleBlocks(hashes, peer) {
return spawn(function *() {
var i, hash, exists;
Pool.prototype._handleBlocks = spawn.co(function* _handleBlocks(hashes, peer) {
var i, hash, exists;
assert(!this.options.headers);
assert(!this.options.headers);
this.logger.debug(
'Received %s block hashes from peer (%s).',
hashes.length,
peer.hostname);
this.logger.debug(
'Received %s block hashes from peer (%s).',
hashes.length,
peer.hostname);
this.emit('blocks', hashes);
this.emit('blocks', hashes);
if (peer.isLoader()) {
// Reset interval to avoid stall behavior.
this.startInterval();
// Reset timeout to avoid killing the loader.
this.startTimeout();
if (peer.isLoader()) {
// Reset interval to avoid stall behavior.
this.startInterval();
// Reset timeout to avoid killing the loader.
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++) {
hash = hashes[i];
exists = yield this.getData(peer, this.blockType, hash);
// 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);
// 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;
}
exists = yield this.getData(peer, this.blockType, hash);
// 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);
}
// Otherwise, we're still requesting it. Ignore.
this.logger.debug('Received requested hash (%s).', peer.hostname);
}
}
this.scheduleRequests(peer);
}, this);
};
this.scheduleRequests(peer);
});
/**
* Handle `inv` packet from peer (containing only BLOCK types).
@ -865,30 +857,28 @@ Pool.prototype._handleBlocks = function _handleBlocks(hashes, peer) {
* @param {Function} callback
*/
Pool.prototype._handleInv = function _handleInv(hashes, peer) {
return spawn(function *() {
var unlock = yield this._lock();
var i, hash;
Pool.prototype._handleInv = spawn.co(function* _handleInv(hashes, peer) {
var unlock = yield this._lock();
var i, hash;
// Ignore for now if we're still syncing
if (!this.chain.synced && !peer.isLoader())
return;
// Ignore for now if we're still syncing
if (!this.chain.synced && !peer.isLoader())
return;
if (!this.options.headers) {
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);
if (!this.options.headers) {
yield this._handleBlocks(hashes, peer);
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.
@ -898,81 +888,79 @@ Pool.prototype._handleInv = function _handleInv(hashes, peer) {
* @param {Function} callback
*/
Pool.prototype._handleBlock = function _handleBlock(block, peer) {
return spawn(function *() {
var requested;
Pool.prototype._handleBlock = spawn.co(function* _handleBlock(block, peer) {
var requested;
// Fulfill the load request.
requested = this.fulfill(block);
// Fulfill the load request.
requested = this.fulfill(block);
// Someone is sending us blocks without
// us requesting them.
if (!requested) {
peer.invFilter.add(block.hash());
this.logger.warning(
'Received unrequested block: %s (%s).',
block.rhash, peer.hostname);
return yield utils.wait();
// Someone is sending us blocks without
// us requesting them.
if (!requested) {
peer.invFilter.add(block.hash());
this.logger.warning(
'Received unrequested block: %s (%s).',
block.rhash, peer.hostname);
return yield utils.wait();
}
try {
yield this.chain.add(block);
} catch (err) {
if (err.type !== 'VerifyError') {
this.scheduleRequests(peer);
throw err;
}
try {
yield this.chain.add(block);
} catch (err) {
if (err.type !== 'VerifyError') {
this.scheduleRequests(peer);
if (err.score !== -1)
peer.reject(block, err.code, err.reason, err.score);
if (err.reason === 'bad-prevblk') {
if (this.options.headers) {
peer.setMisbehavior(10);
throw err;
}
if (err.score !== -1)
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.logger.debug('Peer sent an orphan block. Resolving.');
yield peer.resolveOrphan(null, block.hash('hex'));
this.scheduleRequests(peer);
throw err;
}
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.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);
}
this.emit('chain-progress', this.chain.getProgress(), peer);
if (this.chain.total % 2000 === 0) {
this.logger.info(
'Received 2000 more blocks (height=%d, hash=%s).',
this.chain.height,
block.rhash);
}
}, this);
};
if (this.logger.level >= 4 && this.chain.total % 20 === 0) {
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) {
this.logger.info(
'Received 2000 more blocks (height=%d, hash=%s).',
this.chain.height,
block.rhash);
}
});
/**
* Send `mempool` to all peers.
@ -1304,54 +1292,52 @@ Pool.prototype.hasReject = function hasReject(hash) {
* @param {Function} callback
*/
Pool.prototype._handleTX = function _handleTX(tx, peer) {
return spawn(function *() {
var i, requested, missing;
Pool.prototype._handleTX = spawn.co(function* _handleTX(tx, peer) {
var i, requested, missing;
// Fulfill the load request.
requested = this.fulfill(tx);
// Fulfill the load request.
requested = this.fulfill(tx);
if (!requested) {
peer.invFilter.add(tx.hash());
if (!requested) {
peer.invFilter.add(tx.hash());
if (!this.mempool)
this.txFilter.add(tx.hash());
if (!this.mempool)
this.txFilter.add(tx.hash());
this.logger.warning('Peer sent unrequested tx: %s (%s).',
tx.rhash, peer.hostname);
this.logger.warning('Peer sent unrequested tx: %s (%s).',
tx.rhash, peer.hostname);
if (this.hasReject(tx.hash())) {
throw new VerifyError(tx,
'alreadyknown',
'txn-already-in-mempool',
0);
}
if (this.hasReject(tx.hash())) {
throw new VerifyError(tx,
'alreadyknown',
'txn-already-in-mempool',
0);
}
}
if (!this.mempool) {
this.emit('tx', tx, peer);
return;
}
if (!this.mempool) {
this.emit('tx', tx, peer);
return;
}
try {
missing = yield this.mempool.addTX(tx);
} catch (err) {
if (err.type === 'VerifyError') {
if (err.score !== -1)
peer.reject(tx, err.code, err.reason, err.score);
throw err;
}
try {
missing = yield this.mempool.addTX(tx);
} catch (err) {
if (err.type === 'VerifyError') {
if (err.score !== -1)
peer.reject(tx, err.code, err.reason, err.score);
throw err;
}
throw err;
}
if (missing) {
for (i = 0; i < missing.length; i++)
yield this.getData(peer, this.txType, missing[i]);
}
if (missing) {
for (i = 0; i < missing.length; i++)
yield this.getData(peer, this.txType, missing[i]);
}
this.emit('tx', tx, peer);
}, this);
};
this.emit('tx', tx, peer);
});
/**
* Create a leech peer from an existing socket.
@ -1517,45 +1503,43 @@ Pool.prototype.watchAddress = function watchAddress(address) {
* @param {Function} callback
*/
Pool.prototype.getData = function getData(peer, type, hash) {
return spawn(function *() {
var self = this;
var item, exists;
Pool.prototype.getData = spawn.co(function* getData(peer, type, hash) {
var self = this;
var item, exists;
if (!this.loaded)
return;
if (!this.loaded)
return;
exists = yield this.has(peer, type, hash);
exists = yield this.has(peer, type, hash);
if (exists)
return true;
if (exists)
return true;
item = new LoadRequest(this, peer, type, hash);
item = new LoadRequest(this, peer, type, hash);
if (type === this.txType) {
if (peer.queueTX.length === 0) {
utils.nextTick(function() {
self.logger.debug(
'Requesting %d/%d txs from peer with getdata (%s).',
peer.queueTX.length,
self.activeTX,
peer.hostname);
if (type === this.txType) {
if (peer.queueTX.length === 0) {
utils.nextTick(function() {
self.logger.debug(
'Requesting %d/%d txs from peer with getdata (%s).',
peer.queueTX.length,
self.activeTX,
peer.hostname);
peer.getData(peer.queueTX);
peer.queueTX.length = 0;
});
}
peer.queueTX.push(item.start());
return false;
peer.getData(peer.queueTX);
peer.queueTX.length = 0;
});
}
peer.queueBlock.push(item);
peer.queueTX.push(item.start());
return false;
}, this);
};
}
peer.queueBlock.push(item);
return false;
});
/**
* 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].
*/
Pool.prototype.has = function has(peer, type, hash) {
return spawn(function *() {
var exists = yield this.exists(type, hash);
Pool.prototype.has = spawn.co(function* has(peer, type, hash) {
var exists = yield this.exists(type, hash);
if (exists)
return true;
if (exists)
return true;
// Check the pending requests.
if (this.requestMap[hash])
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;
}
// Check the pending requests.
if (this.requestMap[hash])
return true;
if (type !== this.txType)
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.
@ -1873,64 +1855,60 @@ Pool.prototype.isIgnored = function isIgnored(addr) {
* @param {Function} callback
*/
Pool.prototype.getIP = function getIP() {
return spawn(function *() {
var request, res, ip;
Pool.prototype.getIP = spawn.co(function* getIP() {
var request, res, ip;
if (utils.isBrowser)
throw new Error('Could not find IP.');
if (utils.isBrowser)
throw new Error('Could not find IP.');
request = require('../http/request');
request = require('../http/request');
try {
res = yield request.promise({
method: 'GET',
uri: 'http://icanhazip.com',
expect: 'text',
timeout: 3000
});
} catch (e) {
return yield this.getIP2();
}
try {
res = yield request.promise({
method: 'GET',
uri: 'http://icanhazip.com',
expect: 'text',
timeout: 3000
});
} catch (e) {
return yield this.getIP2();
}
ip = res.body.trim();
ip = res.body.trim();
if (IP.version(ip) === -1)
return yield this.getIP2();
if (IP.version(ip) === -1)
return yield this.getIP2();
return IP.normalize(ip);
}, this);
};
return IP.normalize(ip);
});
/**
* Attempt to retrieve external IP from dyndns.org.
* @param {Function} callback
*/
Pool.prototype.getIP2 = function getIP2() {
return spawn(function *() {
var request, res, ip;
Pool.prototype.getIP2 = spawn.co(function* getIP2() {
var request, res, ip;
if (utils.isBrowser)
throw new Error('Could not find IP.');
if (utils.isBrowser)
throw new Error('Could not find IP.');
request = require('../http/request');
request = require('../http/request');
res = yield request.promise({
method: 'GET',
uri: 'http://checkip.dyndns.org',
expect: 'html',
timeout: 3000
});
res = yield request.promise({
method: 'GET',
uri: 'http://checkip.dyndns.org',
expect: 'html',
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)
throw new Error('Could not find IP.');
if (!ip || IP.version(ip[1]) === -1)
throw new Error('Could not find IP.');
return IP.normalize(ip[1]);
}, this);
};
return IP.normalize(ip[1]);
});
/**
* Peer List

View File

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

View File

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

View File

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

View File

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

View File

@ -1,11 +1,7 @@
'use strict';
// See: https://github.com/yoursnetwork/asink
function spawn(generator, self) {
function exec(gen) {
return new Promise(function(resolve, reject) {
var gen = generator.call(self);
function step(value, rejection) {
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;

View File

@ -204,22 +204,20 @@ Account.MAX_LOOKAHEAD = 5;
* @param {Function} callback
*/
Account.prototype.init = function init() {
return spawn(function *() {
// Waiting for more keys.
if (this.keys.length !== this.n - 1) {
assert(!this.initialized);
this.save();
return;
}
Account.prototype.init = spawn.co(function* init() {
// Waiting for more keys.
if (this.keys.length !== this.n - 1) {
assert(!this.initialized);
this.save();
return;
}
assert(this.receiveDepth === 0);
assert(this.changeDepth === 0);
assert(this.receiveDepth === 0);
assert(this.changeDepth === 0);
this.initialized = true;
yield this.setDepth(1, 1);
}, this);
};
this.initialized = true;
yield this.setDepth(1, 1);
});
/**
* Open the account (done after retrieval).
@ -306,30 +304,28 @@ Account.prototype.spliceKey = function spliceKey(key) {
* @param {Function} callback
*/
Account.prototype.addKey = function addKey(key) {
return spawn(function *() {
var result = false;
var exists;
Account.prototype.addKey = spawn.co(function* addKey(key) {
var result = false;
var exists;
try {
result = this.pushKey(key);
} catch (e) {
throw e;
}
try {
result = this.pushKey(key);
} catch (e) {
throw e;
}
exists = yield this._checkKeys();
exists = yield this._checkKeys();
if (exists) {
this.spliceKey(key);
throw new Error('Cannot add a key from another account.');
}
if (exists) {
this.spliceKey(key);
throw new Error('Cannot add a key from another account.');
}
// Try to initialize again.
yield this.init();
// Try to initialize again.
yield this.init();
return result;
}, this);
};
return result;
});
/**
* Ensure accounts are not sharing keys.
@ -337,27 +333,25 @@ Account.prototype.addKey = function addKey(key) {
* @param {Function} callback
*/
Account.prototype._checkKeys = function _checkKeys() {
return spawn(function *() {
var ring, hash, paths;
Account.prototype._checkKeys = spawn.co(function* _checkKeys() {
var ring, hash, paths;
if (this.initialized || this.type !== Account.types.MULTISIG)
return false;
if (this.initialized || this.type !== Account.types.MULTISIG)
return false;
if (this.keys.length !== this.n - 1)
return false;
if (this.keys.length !== this.n - 1)
return false;
ring = this.deriveReceive(0);
hash = ring.getScriptHash('hex');
ring = this.deriveReceive(0);
hash = ring.getScriptHash('hex');
paths = yield this.db.getAddressPaths(hash);
paths = yield this.db.getAddressPaths(hash);
if (!paths)
return false;
if (!paths)
return false;
return paths[this.wid] != null;
}, this);
};
return paths[this.wid] != null;
});
/**
* 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}].
*/
Account.prototype.createAddress = function createAddress(change) {
return spawn(function *() {
var ring, lookahead;
Account.prototype.createAddress = spawn.co(function* createAddress(change) {
var ring, lookahead;
if (change) {
ring = this.deriveChange(this.changeDepth);
lookahead = this.deriveChange(this.changeDepth + this.lookahead);
this.changeDepth++;
this.changeAddress = ring;
} else {
ring = this.deriveReceive(this.receiveDepth);
lookahead = this.deriveReceive(this.receiveDepth + this.lookahead);
this.receiveDepth++;
this.receiveAddress = ring;
}
if (change) {
ring = this.deriveChange(this.changeDepth);
lookahead = this.deriveChange(this.changeDepth + this.lookahead);
this.changeDepth++;
this.changeAddress = ring;
} else {
ring = this.deriveReceive(this.receiveDepth);
lookahead = this.deriveReceive(this.receiveDepth + this.lookahead);
this.receiveDepth++;
this.receiveAddress = ring;
}
yield this.saveAddress([ring, lookahead]);
yield this.saveAddress([ring, lookahead]);
this.save();
this.save();
return ring;
}, this);
};
return ring;
});
/**
* 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}].
*/
Account.prototype.setDepth = function setDepth(receiveDepth, changeDepth) {
return spawn(function *() {
var rings = [];
var i, receive, change;
Account.prototype.setDepth = spawn.co(function* setDepth(receiveDepth, changeDepth) {
var rings = [];
var i, receive, change;
if (receiveDepth > this.receiveDepth) {
for (i = this.receiveDepth; i < receiveDepth; i++) {
receive = this.deriveReceive(i);
rings.push(receive);
}
for (i = receiveDepth; i < receiveDepth + this.lookahead; i++)
rings.push(this.deriveReceive(i));
this.receiveAddress = receive;
this.receiveDepth = receiveDepth;
if (receiveDepth > this.receiveDepth) {
for (i = this.receiveDepth; i < receiveDepth; i++) {
receive = this.deriveReceive(i);
rings.push(receive);
}
if (changeDepth > this.changeDepth) {
for (i = this.changeDepth; i < changeDepth; i++) {
change = this.deriveChange(i);
rings.push(change);
}
for (i = receiveDepth; i < receiveDepth + this.lookahead; i++)
rings.push(this.deriveReceive(i));
for (i = changeDepth; i < changeDepth + this.lookahead; i++)
rings.push(this.deriveChange(i));
this.receiveAddress = receive;
this.receiveDepth = receiveDepth;
}
this.changeAddress = change;
this.changeDepth = changeDepth;
if (changeDepth > this.changeDepth) {
for (i = this.changeDepth; i < changeDepth; i++) {
change = this.deriveChange(i);
rings.push(change);
}
if (rings.length === 0)
return [];
for (i = changeDepth; i < changeDepth + this.lookahead; i++)
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];
}, this);
};
yield this.saveAddress(rings);
this.save();
return [receive, change];
});
/**
* 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
*/
Workers.prototype.sign = function sign(tx, ring, type) {
return spawn(function *() {
var i, result, input, sig, sigs, total;
Workers.prototype.sign = spawn.co(function* sign(tx, ring, type) {
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];
total = result[1];
sigs = result[0];
total = result[1];
for (i = 0; i < sigs.length; i++) {
sig = sigs[i];
input = tx.inputs[i];
input.script = sig[0];
input.witness = sig[1];
}
for (i = 0; i < sigs.length; i++) {
sig = sigs[i];
input = tx.inputs[i];
input.script = sig[0];
input.witness = sig[1];
}
return total;
}, this);
};
return total;
});
/**
* Execute the mining job (no timeout).