mempool/miner: mining and mempool refactor.

This commit is contained in:
Christopher Jeffrey 2016-11-18 06:20:30 -08:00
parent 9e1428a8d5
commit bc00697adb
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
10 changed files with 424 additions and 304 deletions

View File

@ -338,8 +338,8 @@ RPC.prototype.getinfo = co(function* getinfo(args) {
keypoololdest: 0,
keypoolsize: 0,
unlocked_until: this.wallet.master.until,
paytxfee: +utils.btc(this.network.getRate()),
relayfee: +utils.btc(this.network.getMinRelay()),
paytxfee: +utils.btc(this.network.feeRate),
relayfee: +utils.btc(this.network.minRelay),
errors: ''
};
});
@ -1312,6 +1312,15 @@ RPC.prototype.verifychain = function verifychain(args) {
*/
RPC.prototype._submitwork = co(function* _submitwork(data) {
var unlock = yield this.locker.lock();
try {
return yield this.__submitwork(data);
} finally {
unlock();
}
});
RPC.prototype.__submitwork = co(function* _submitwork(data) {
var attempt = this.attempt;
var block, header, cb, cur;
@ -1368,7 +1377,16 @@ RPC.prototype._submitwork = co(function* _submitwork(data) {
return true;
});
RPC.prototype._getwork = co(function* _getwork() {
RPC.prototype._creatework = co(function* _creatework(data) {
var unlock = yield this.locker.lock();
try {
return yield this.__creatework(data);
} finally {
unlock();
}
});
RPC.prototype.__creatework = co(function* _creatework() {
var attempt = yield this._getAttempt(true);
var data, abbr;
@ -1392,55 +1410,32 @@ RPC.prototype._getwork = co(function* _getwork() {
RPC.prototype.getworklp = co(function* getworklp(args) {
yield this._onBlock();
return yield this._getwork();
return yield this._creatework();
});
RPC.prototype.getwork = co(function* getwork(args) {
var unlock = yield this.locker.lock();
var data, result;
var data;
if (args.length > 1) {
unlock();
if (args.length > 1)
throw new RPCError('getwork ( "data" )');
}
if (args.length === 1) {
if (!utils.isHex(args[0])) {
unlock();
if (!utils.isHex(args[0]))
throw new RPCError('Invalid parameter.');
}
data = new Buffer(args[0], 'hex');
try {
result = yield this._submitwork(data);
} catch (e) {
unlock();
throw e;
}
return result;
return yield this._submitwork(data);
}
try {
result = yield this._getwork();
} catch (e) {
unlock();
throw e;
}
unlock();
return result;
return yield this._creatework();
});
RPC.prototype.submitblock = co(function* submitblock(args) {
var unlock = yield this.locker.lock();
var block;
if (args.help || args.length < 1 || args.length > 2) {
unlock();
if (args.help || args.length < 1 || args.length > 2)
throw new RPCError('submitblock "hexdata" ( "jsonparametersobject" )');
}
block = Block.fromRaw(toString(args[0]), 'hex');
@ -1448,6 +1443,15 @@ RPC.prototype.submitblock = co(function* submitblock(args) {
});
RPC.prototype._submitblock = co(function* submitblock(block) {
var unlock = yield this.locker.lock();
try {
return yield this.__submitblock(block);
} finally {
unlock();
}
});
RPC.prototype.__submitblock = co(function* submitblock(block) {
if (block.prevBlock !== this.chain.tip.hash)
return 'rejected: inconclusive-not-best-prevblk';
@ -1528,36 +1532,43 @@ RPC.prototype.getblocktemplate = co(function* getblocktemplate(args) {
yield this._poll(lpid);
return yield this._tmpl(version, coinbase, rules);
return yield this._template(version, coinbase, rules);
});
RPC.prototype._tmpl = co(function* _tmpl(version, coinbase, rules) {
RPC.prototype._template = co(function* _template(version, coinbase, rules) {
var unlock = yield this.locker.lock();
try {
return yield this.__template(version, coinbase, rules);
} finally {
unlock();
}
});
RPC.prototype.__template = co(function* _template(version, coinbase, rules) {
var txs = [];
var txIndex = {};
var i, j, tx, deps, input, dep, block, output, raw, rwhash;
var keys, vbavailable, vbrules, mutable, template, attempt;
var attempt = yield this._getAttempt(false);
var block = attempt.block;
var i, j, tx, deps, input, dep, output, raw, rwhash;
var keys, vbavailable, vbrules, mutable, template;
var id, deployment, state;
try {
attempt = yield this._getAttempt(false);
} catch (e) {
unlock();
throw e;
}
block = attempt.block;
for (i = 1; i < block.txs.length; i++) {
tx = block.txs[i];
txIndex[tx.hash('hex')] = i;
}
for (i = 1; i < block.txs.length; i++) {
tx = block.txs[i];
deps = [];
for (j = 0; j < tx.inputs.length; j++) {
input = tx.inputs[j];
dep = txIndex[input.prevout.hash];
if (dep != null)
if (dep != null && deps.indexOf(dep) === -1) {
assert(dep < i);
deps.push(dep);
}
}
txs.push({
@ -1600,10 +1611,8 @@ RPC.prototype._tmpl = co(function* _tmpl(version, coinbase, rules) {
case constants.thresholdStates.ACTIVE:
vbrules.push(id);
if (rules) {
if (rules.indexOf(id) === -1 && !deployment.force) {
unlock();
if (rules.indexOf(id) === -1 && !deployment.force)
throw new RPCError('Client must support ' + id + '.');
}
}
break;
}
@ -1675,7 +1684,6 @@ RPC.prototype._tmpl = co(function* _tmpl(version, coinbase, rules) {
template.default_witness_commitment = output.script.toJSON();
}
unlock();
return template;
});
@ -1918,24 +1926,30 @@ RPC.prototype.setgenerate = co(function* setgenerate(args) {
RPC.prototype.generate = co(function* generate(args) {
var unlock = yield this.locker.lock();
try {
return yield this._generate(args);
} finally {
unlock();
}
});
RPC.prototype._generate = co(function* generate(args) {
var numblocks;
if (args.help || args.length < 1 || args.length > 2) {
unlock();
if (args.help || args.length < 1 || args.length > 2)
throw new RPCError('generate numblocks ( maxtries )');
}
numblocks = toNumber(args[0], 1);
return yield this._generate(numblocks);
return yield this._generateBlocks(numblocks);
});
RPC.prototype._generate = co(function* _generate(numblocks) {
RPC.prototype._generateBlocks = co(function* _generateBlocks(blocks, address) {
var hashes = [];
var i, block;
for (i = 0; i < numblocks; i++) {
block = yield this.miner.mineBlock();
for (i = 0; i < blocks; i++) {
block = yield this.miner.mineBlock(null, address);
hashes.push(block.rhash);
yield this.chain.add(block);
}
@ -1945,24 +1959,23 @@ RPC.prototype._generate = co(function* _generate(numblocks) {
RPC.prototype.generatetoaddress = co(function* generatetoaddress(args) {
var unlock = yield this.locker.lock();
var numblocks, address, hashes;
if (args.help || args.length < 2 || args.length > 3) {
try {
return yield this._generatetoaddress(args);
} finally {
unlock();
throw new RPCError('generatetoaddress numblocks address ( maxtries )');
}
});
RPC.prototype._generatetoaddress = co(function* generatetoaddress(args) {
var numblocks, address;
if (args.help || args.length < 2 || args.length > 3)
throw new RPCError('generatetoaddress numblocks address ( maxtries )');
numblocks = toNumber(args[0], 1);
address = this.miner.address;
address = Address.fromBase58(toString(args[1]));
this.miner.address = Address.fromBase58(toString(args[1]));
hashes = yield this._generate(numblocks);
this.miner.address = address;
unlock();
return hashes;
return yield this._generateBlocks(numblocks, address);
});
/*

View File

@ -46,10 +46,6 @@ var MempoolEntry = require('./mempoolentry');
* @property {Number} freeCount
* @property {Number} lastTime
* @property {Number} maxSize
* @property {Boolean} blockSinceBump
* @property {Number} lastFeeUpdate
* @property {Rate} minRate
* @property {Rate} minReasonable
* @property {Rate} minRelayFee
* @emits Mempool#open
* @emits Mempool#error
@ -99,7 +95,6 @@ function Mempool(options) {
this.limitFree = this.options.limitFree !== false;
this.limitFreeRelay = this.options.limitFreeRelay || 15;
this.relayPriority = this.options.relayPriority !== false;
this.rejectFee = this.options.rejectFee === true;
this.requireStandard = this.options.requireStandard != null
? this.options.requireStandard
: this.network.requireStandard;
@ -110,10 +105,6 @@ function Mempool(options) {
this.maxSize = options.maxSize || constants.mempool.MAX_MEMPOOL_SIZE;
this.expiryTime = options.expiryTime || constants.mempool.MEMPOOL_EXPIRY;
this.blockSinceBump = false;
this.lastFeeUpdate = utils.now();
this.minRate = 0;
this.minReasonable = this.network.minRelay;
this.minRelay = this.network.minRelay;
}
@ -190,9 +181,6 @@ Mempool.prototype._addBlock = function addBlock(block) {
entries.push(entry);
}
this.blockSinceBump = true;
this.lastFeeUpdate = utils.now();
if (this.fees)
this.fees.processBlock(block.height, entries, this.chain.isFull());
@ -388,7 +376,7 @@ Mempool.prototype.isSpent = function isSpent(hash, index) {
};
/**
* Get an output's spender transaction.
* Get an output's spender entry.
* @param {Hash} hash
* @param {Number} index
* @returns {MempoolEntry}
@ -398,6 +386,22 @@ Mempool.prototype.getSpent = function getSpent(hash, index) {
return this.spents[hash + index];
};
/**
* Get an output's spender transaction.
* @param {Hash} hash
* @param {Number} index
* @returns {MempoolEntry}
*/
Mempool.prototype.getSpentTX = function getSpentTX(hash, index) {
var entry = this.spents[hash + index];
if (!entry)
return;
return entry.tx;
};
/**
* Find all coins pertaining to a certain address.
* @param {Address[]} addresses
@ -788,7 +792,7 @@ Mempool.prototype._addUnchecked = co(function* addUnchecked(entry) {
continue;
}
this.logger.spam('Resolved orphan %s in mempool.', orphan.tx.rhash);
this.logger.spam('Resolved orphan %s in mempool.', tx.rhash);
}
});
@ -800,9 +804,10 @@ Mempool.prototype._addUnchecked = co(function* addUnchecked(entry) {
*/
Mempool.prototype.removeUnchecked = function removeUnchecked(entry, limit) {
var rate, hash;
var tx = entry.tx;
var hash;
this.removeOrphan(entry.tx);
this.removeOrphan(tx);
// We do not remove spenders if this is
// being removed for a block. The spenders
@ -810,66 +815,21 @@ Mempool.prototype.removeUnchecked = function removeUnchecked(entry, limit) {
// now exist on the blockchain).
if (limit) {
this.removeSpenders(entry);
this.logger.debug('Evicting %s from the mempool.', entry.tx.rhash);
this.logger.debug('Evicting %s from the mempool.', tx.rhash);
} else {
this.logger.spam('Removing block tx %s from mempool.', tx.rhash);
}
this.untrackEntry(entry);
if (this.fees) {
hash = entry.tx.hash('hex');
hash = tx.hash('hex');
this.fees.removeTX(hash);
}
if (limit) {
this.logger.spam('Removed tx %s from mempool.', entry.tx.rhash);
rate = TX.getRate(entry.sizes, entry.fees);
rate += this.minReasonable;
if (rate > this.minRate) {
this.minRate = rate;
this.blockSinceBump = false;
}
} else {
this.logger.spam('Removed block tx %s from mempool.', entry.tx.rhash);
}
this.emit('remove entry', entry);
};
/**
* Calculate and update the minimum rolling fee rate.
* @returns {Rate} Rate.
*/
Mempool.prototype.getMinRate = function getMinRate() {
var now, halflife, size;
if (!this.blockSinceBump || this.minRate === 0)
return this.minRate;
now = utils.now();
if (now > this.lastFeeUpdate + 10) {
halflife = constants.mempool.FEE_HALFLIFE;
size = this.getSize();
if (size < this.maxSize / 4)
halflife >>>= 2;
else if (size < this.maxSize / 2)
halflife >>>= 1;
this.minRate /= Math.pow(2.0, (now - this.lastFeeUpdate) / halflife | 0);
this.minRate |= 0;
this.lastFeeUpdate = now;
if (this.minRate < this.minReasonable / 2) {
this.minRate = 0;
return 0;
}
}
return Math.max(this.minRate, this.minReasonable);
};
/**
* Verify a transaction with mempool standards.
* @param {TX} tx
@ -877,16 +837,15 @@ Mempool.prototype.getMinRate = function getMinRate() {
*/
Mempool.prototype.verify = co(function* verify(entry) {
var tx = entry.tx;
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;
var now, minFee, count, result;
result = yield this.checkLocks(tx, lockFlags);
@ -923,24 +882,9 @@ Mempool.prototype.verify = co(function* verify(entry) {
0);
}
fee = tx.getFee();
modFee = entry.fees;
size = entry.size;
minFee = tx.getMinFee(entry.size, this.minRelay);
if (this.rejectFee) {
minRate = this.getMinRate();
rejectFee = tx.getMinFee(size, minRate);
if (rejectFee > 0 && modFee < rejectFee) {
throw new VerifyError(tx,
'insufficientfee',
'mempool min fee not met',
0);
}
}
minRelayFee = tx.getMinFee(size, this.minRelay);
if (this.relayPriority && modFee < minRelayFee) {
if (this.relayPriority && entry.fee < minFee) {
if (!entry.isFree(height)) {
throw new VerifyError(tx,
'insufficientfee',
@ -954,7 +898,7 @@ Mempool.prototype.verify = co(function* verify(entry) {
// sending thousands of free transactions just to be
// annoying or make others' transactions take longer
// to confirm.
if (this.limitFree && modFee < minRelayFee) {
if (this.limitFree && entry.fee < minFee) {
now = utils.now();
// Use an exponentially decaying ~10-minute window:
@ -970,10 +914,10 @@ Mempool.prototype.verify = co(function* verify(entry) {
0);
}
this.freeCount += size;
this.freeCount += entry.size;
}
if (this.rejectAbsurdFees && fee > minRelayFee * 10000)
if (this.rejectAbsurdFees && entry.fee > minFee * 10000)
throw new VerifyError(tx, 'highfee', 'absurdly-high-fee', 0);
count = this.countAncestors(tx);
@ -1087,18 +1031,19 @@ Mempool.prototype.checkInputs = co(function* checkInputs(tx, flags) {
*/
Mempool.prototype.countAncestors = function countAncestors(tx) {
return this._countAncestors(tx, {}, 0);
return this._countAncestors(tx, 0, {});
};
/**
* Traverse ancestors and count.
* @private
* @param {TX} tx
* @param {Object} set
* @param {Number} count
* @param {Object} set
* @returns {Number}
*/
Mempool.prototype._countAncestors = function countAncestors(tx, set, count) {
Mempool.prototype._countAncestors = function countAncestors(tx, count, set) {
var i, input, hash, prev;
for (i = 0; i < tx.inputs.length; i++) {
@ -1118,7 +1063,7 @@ Mempool.prototype._countAncestors = function countAncestors(tx, set, count) {
if (count > constants.mempool.ANCESTOR_LIMIT)
break;
count = this._countAncestors(prev, set, count);
count = this._countAncestors(prev, count, set);
if (count > constants.mempool.ANCESTOR_LIMIT)
break;
@ -1135,7 +1080,7 @@ Mempool.prototype._countAncestors = function countAncestors(tx, set, count) {
*/
Mempool.prototype.countDescendants = function countDescendants(tx) {
return this._countDescendants(tx, {}, 0);
return this._countDescendants(tx, 0, {});
};
/**
@ -1143,20 +1088,21 @@ Mempool.prototype.countDescendants = function countDescendants(tx) {
* descendants a transaction may have.
* @private
* @param {TX} tx
* @param {Number} count
* @param {Object} set
* @returns {Number}
*/
Mempool.prototype._countDescendants = function countDescendants(tx, set, count) {
Mempool.prototype._countDescendants = function countDescendants(tx, count, set) {
var hash = tx.hash('hex');
var i, entry, next, nhash;
var i, next, nhash;
for (i = 0; i < tx.outputs.length; i++) {
entry = this.getSpent(hash, i);
next = this.getSpentTX(hash, i);
if (!entry)
if (!next)
continue;
next = entry.tx;
nhash = next.hash('hex');
if (set[nhash])
@ -1165,7 +1111,7 @@ Mempool.prototype._countDescendants = function countDescendants(tx, set, count)
set[nhash] = true;
count += 1;
count = this._countDescendants(next, set, count);
count = this._countDescendants(next, count, set);
}
return count;
@ -1185,6 +1131,8 @@ Mempool.prototype.getAncestors = function getAncestors(tx) {
* Get all transaction ancestors.
* @private
* @param {TX} tx
* @param {MempoolEntry[]} entries
* @param {Object} set
* @returns {MempoolEntry[]}
*/
@ -1224,20 +1172,21 @@ Mempool.prototype.getDescendants = function getDescendants(tx) {
/**
* Get all a transaction descendants.
* @param {TX} tx
* @param {MempoolEntry[]} entries
* @param {Object} set
* @returns {MempoolEntry[]}
*/
Mempool.prototype._getDescendants = function getDescendants(tx, entries, set) {
var hash = tx.hash('hex');
var i, entry, next, nhash;
var i, next, nhash;
for (i = 0; i < tx.outputs.length; i++) {
entry = this.getSpent(hash, i);
next = this.getSpentTX(hash, i);
if (!entry)
if (!next)
continue;
next = entry.tx;
nhash = next.hash('hex');
if (set[nhash])

View File

@ -19,18 +19,12 @@ var TX = require('../primitives/tx');
* @param {Number} options.height - Entry height.
* @param {Number} options.priority - Entry priority.
* @param {Number} options.ts - Entry time.
* @param {Amount} options.chainValue - Value of on-chain coins.
* @param {Number} options.count - Number of descendants (includes tx).
* @param {Number} options.size - TX and descendant modified size.
* @param {Amount} options.fees - TX and descendant delta-applied fees.
* @param {Amount} options.value - Value of on-chain coins.
* @property {TX} tx
* @property {Number} height
* @property {Number} priority
* @property {Number} ts
* @property {Amount} chainValue
* @property {Number} count
* @property {Number} size
* @property {Amount} fees
* @property {Amount} value
*/
function MempoolEntry(options) {
@ -43,11 +37,7 @@ function MempoolEntry(options) {
this.priority = 0;
this.fee = 0;
this.ts = 0;
this.chainValue = 0;
this.count = 0;
this.sizes = 0;
this.fees = 0;
this.value = 0;
this.dependencies = false;
if (options)
@ -67,13 +57,8 @@ MempoolEntry.prototype.fromOptions = function fromOptions(options) {
this.priority = options.priority;
this.fee = options.fee;
this.ts = options.ts;
this.chainValue = options.chainValue;
this.count = options.count;
this.sizes = options.sizes;
this.fees = options.fees;
this.value = options.value;
this.dependencies = options.dependencies;
return this;
};
@ -100,10 +85,11 @@ MempoolEntry.prototype.fromTX = function fromTX(tx, height) {
var dependencies = false;
var size = tx.getVirtualSize();
var fee = tx.getFee();
var i;
var i, input;
for (i = 0; i < tx.inputs.length; i++) {
if (tx.inputs[i].coin.height === -1) {
input = tx.inputs[i];
if (input.coin.height === -1) {
dependencies = true;
break;
}
@ -114,11 +100,8 @@ MempoolEntry.prototype.fromTX = function fromTX(tx, height) {
this.size = size;
this.priority = priority;
this.fee = fee;
this.chainValue = value;
this.ts = utils.now();
this.count = 1;
this.sizes = size;
this.fees = fee;
this.value = value;
this.dependencies = dependencies;
return this;
@ -146,7 +129,7 @@ MempoolEntry.fromTX = function fromTX(tx, height) {
MempoolEntry.prototype.getPriority = function getPriority(height) {
var heightDelta = height - this.height;
var modSize = this.tx.getModifiedSize(this.size);
var deltaPriority = (heightDelta * this.chainValue) / modSize;
var deltaPriority = (heightDelta * this.value) / modSize;
var result = this.priority + Math.floor(deltaPriority);
if (result < 0)
result = 0;

View File

@ -10,6 +10,7 @@
var utils = require('../utils/utils');
var co = require('../utils/co');
var assert = require('assert');
var constants = require('../protocol/constants');
var AsyncObject = require('../utils/async');
var MinerBlock = require('./minerblock');
var Address = require('../primitives/address');
@ -48,10 +49,15 @@ function Miner(options) {
this.since = 0;
this.version = -1;
this.address = Address(options.address);
this.addresses = [];
this.coinbaseFlags = options.coinbaseFlags || 'mined by bcoin';
this._init();
this.minWeight = 0;
this.maxWeight = 750000 * 4;
this.priorityWeight = 50000 * 4;
this.minPriority = constants.tx.FREE_THRESHOLD;
this._init(options);
}
utils.inherits(Miner, AsyncObject);
@ -61,14 +67,23 @@ utils.inherits(Miner, AsyncObject);
* @private
*/
Miner.prototype._init = function _init() {
Miner.prototype._init = function _init(options) {
var self = this;
var i;
if (options.address)
this.addAddress(options.address);
if (options.addresses) {
for (i = 0; i < options.addresses.length; i++)
this.addAddress(options.addresses[i]);
}
this.chain.on('tip', function(tip) {
if (!self.attempt)
return;
if (self.attempt.block.prevBlock !== tip.hash)
if (self.attempt.block.prevBlock === tip.prevBlock)
self.attempt.destroy();
});
@ -239,9 +254,9 @@ Miner.prototype._onStop = function _onStop() {
* @returns {Promise} - Returns {@link MinerBlock}.
*/
Miner.prototype.createBlock = co(function* createBlock(tip) {
Miner.prototype.createBlock = co(function* createBlock(tip, address) {
var version = this.version;
var ts, attempt, target, entries;
var ts, attempt, target, locktime;
if (!tip)
tip = this.chain.tip;
@ -249,26 +264,32 @@ Miner.prototype.createBlock = co(function* createBlock(tip) {
assert(tip);
ts = Math.max(time.now(), tip.ts + 1);
locktime = ts;
target = yield this.chain.getTargetAsync(ts, tip);
if (version === -1)
version = yield this.chain.computeBlockVersion(tip);
if (this.chain.state.hasMTP())
locktime = yield tip.getMedianTimeAsync();
if (!address)
address = this.getAddress();
attempt = new MinerBlock({
tip: tip,
version: version,
bits: target,
locktime: locktime,
flags: this.chain.state.flags,
address: this.address,
address: address,
coinbaseFlags: this.coinbaseFlags,
witness: this.chain.state.hasWitness(),
network: this.network
});
entries = this.getSorted();
attempt.build(entries);
this.fill(attempt);
return attempt;
});
@ -279,26 +300,11 @@ Miner.prototype.createBlock = co(function* createBlock(tip) {
* @returns {Promise} - Returns [{@link Block}].
*/
Miner.prototype.mineBlock = co(function* mineBlock(tip) {
var attempt = yield this.createBlock(tip);
Miner.prototype.mineBlock = co(function* mineBlock(tip, address) {
var attempt = yield this.createBlock(tip, address);
return yield attempt.mineAsync();
});
/**
* Add a transaction to the current block.
* @param {TX} tx
*/
Miner.prototype.addTX = function addTX(tx) {
if (!this.running)
return;
if (!this.attempt)
return;
this.attempt.addTX(tx);
};
/**
* Notify the miner that a new tx has entered the mempool.
* @param {MempoolEntry} entry
@ -317,18 +323,37 @@ Miner.prototype.notifyEntry = function notifyEntry() {
}
};
/**
* Add an address to the address list.
* @param {Address} address
*/
Miner.prototype.addAddress = function addAddress(address) {
this.addresses.push(Address(address));
};
/**
* Get a random address from the address list.
* @returns {Address}
*/
Miner.prototype.getAddress = function getAddress() {
assert(this.addresses.length !== 0, 'No address passed in for miner.');
return this.addresses[Math.random() * this.addresses.length | 0];
};
/**
* Get mempool entries, sort by dependency order.
* @returns {MempoolEntry[]}
*/
Miner.prototype.getSorted = function getSorted() {
Miner.prototype.fill = function fill(attempt) {
var depMap = {};
var count = {};
var result = [];
var top = [];
var i, j, entry, tx, hash, input;
var prev, hasDeps, deps, hashes;
var block = attempt.block;
var queue = new Queue(cmpPriority);
var priority = true;
var i, j, entry, item, tx, hash, input;
var prev, deps, hashes, weight, sigops;
if (!this.mempool)
return [];
@ -338,10 +363,17 @@ Miner.prototype.getSorted = function getSorted() {
for (i = 0; i < hashes.length; i++) {
hash = hashes[i];
entry = this.mempool.getEntry(hash);
tx = entry.tx;
item = new QueueItem(entry, attempt);
tx = item.tx;
count[hash] = 0;
hasDeps = false;
if (tx.isCoinbase())
throw new Error('Cannot add coinbase to block.');
if (!tx.hasCoins())
throw new Error('Cannot add empty tx to block.');
if (!tx.isFinal(attempt.height, attempt.locktime))
continue;
for (j = 0; j < tx.inputs.length; j++) {
input = tx.inputs[j];
@ -350,27 +382,57 @@ Miner.prototype.getSorted = function getSorted() {
if (!this.mempool.hasTX(prev))
continue;
hasDeps = true;
item.depCount += 1;
if (!depMap[prev])
depMap[prev] = [];
depMap[prev].push(entry);
count[hash]++;
depMap[prev].push(item);
}
if (hasDeps)
if (item.depCount > 0)
continue;
top.push(entry);
queue.push(item);
}
for (i = 0; i < top.length; i++) {
entry = top[i];
tx = entry.tx;
hash = tx.hash('hex');
while (queue.size() > 0) {
item = queue.pop();
tx = item.tx;
hash = item.hash;
weight = attempt.weight;
sigops = attempt.sigops;
result.push(entry);
if (!attempt.witness && tx.hasWitness())
continue;
weight += tx.getWeight();
if (weight > this.maxWeight)
continue;
sigops += tx.getSigopsWeight(attempt.flags);
if (sigops > constants.block.MAX_SIGOPS_WEIGHT)
continue;
if (priority) {
if (weight > this.priorityWeight || item.priority < this.minPriority) {
queue.cmp = cmpRate;
priority = false;
queue.push(item);
continue;
}
} else {
if (item.free && weight >= this.minWeight)
continue;
}
attempt.weight = weight;
attempt.sigops = sigops;
attempt.fees += item.fee;
block.txs.push(tx.clone());
deps = depMap[hash];
@ -378,18 +440,67 @@ Miner.prototype.getSorted = function getSorted() {
continue;
for (j = 0; j < deps.length; j++) {
entry = deps[j];
tx = entry.tx;
hash = tx.hash('hex');
if (--count[hash] === 0)
top.push(entry);
item = deps[j];
if (--item.depCount === 0)
queue.push(item);
}
}
return result;
attempt.updateCoinbase();
attempt.updateMerkle();
assert(block.getWeight() <= attempt.weight);
};
/**
* QueueItem
* @constructor
*/
function QueueItem(entry, attempt) {
this.tx = entry.tx;
this.hash = entry.tx.hash('hex');
this.fee = entry.getFee();
this.rate = entry.getRate();
this.priority = entry.getPriority(attempt.height);
this.free = entry.isFree(attempt.height);
this.depCount = 0;
}
/**
* Queue
* @constructor
*/
function Queue(cmp) {
this.cmp = cmp;
this.items = [];
}
Queue.prototype.size = function size() {
return this.items.length;
};
Queue.prototype.push = function push(item) {
utils.binaryInsert(this.items, item, this.cmp);
};
Queue.prototype.pop = function pop() {
return this.items.pop();
};
/*
* Helpers
*/
function cmpPriority(a, b) {
return a.priority - b.priority;
}
function cmpRate(a, b) {
return a.rate - b.rate;
}
/*
* Expose
*/

View File

@ -53,6 +53,7 @@ function MinerBlock(options) {
this.height = options.tip.height + 1;
this.bits = options.bits;
this.target = utils.fromCompact(this.bits).toArrayLike(Buffer, 'le', 32);
this.locktime = options.locktime;
this.flags = options.flags;
this.extraNonce = new BN(0);
this.iterations = 0;
@ -61,9 +62,11 @@ function MinerBlock(options) {
this.address = options.address;
this.network = Network.get(options.network);
this.destroyed = false;
this.reward = Block.reward(this.height, this.network);
this.sigops = 0;
this.weight = 0;
this.fees = 0;
this.coinbase = new TX();
this.coinbase.mutable = true;
@ -98,6 +101,7 @@ MinerBlock.prototype.__defineGetter__('rate', function() {
*/
MinerBlock.prototype._init = function _init() {
var scale = constants.WITNESS_SCALE_FACTOR;
var block = this.block;
var cb = this.coinbase;
var input, output, hash, witnessNonce;
@ -158,13 +162,25 @@ MinerBlock.prototype._init = function _init() {
block.nonce = 0;
block.height = this.height;
block.addTX(cb);
block.txs.push(cb);
// Update coinbase since our coinbase was added.
this.updateCoinbase();
// Create our merkle root.
this.updateMerkle();
// Initialize weight.
this.weight = this.block.getWeight();
// 4 extra bytes for varint tx count.
this.weight += 4 * scale;
// 8 extra bytes for extra nonce.
this.weight += 8 * scale;
// Initialize sigops weight.
this.sigops = cb.getSigopsWeight(this.flags);
};
/**
@ -197,7 +213,7 @@ MinerBlock.prototype.updateCoinbase = function updateCoinbase() {
input.script.compile();
// Update reward.
output.value = this.block.getReward(this.network);
output.value = this.reward + this.fees;
};
/**
@ -255,6 +271,9 @@ MinerBlock.prototype.addTX = function addTX(tx) {
weight = tx.getWeight();
sigops = tx.getSigopsWeight(this.flags);
if (!tx.isFinal(this.height, this.locktime))
return false;
if (this.weight + weight > constants.block.MAX_WEIGHT)
return false;
@ -264,6 +283,10 @@ MinerBlock.prototype.addTX = function addTX(tx) {
if (!this.witness && tx.hasWitness())
return false;
this.weight += weight;
this.sigops += sigops;
this.fees += tx.getFee();
// Add the tx to our block
this.block.addTX(tx.clone());
@ -276,47 +299,6 @@ MinerBlock.prototype.addTX = function addTX(tx) {
return true;
};
/**
* Fill the block with sorted mempool entries.
* @param {MempoolEntry[]} entries
*/
MinerBlock.prototype.build = function build(entries) {
var len = Math.min(entries.length, constants.block.MAX_SIZE);
var i, entry, tx, weight, sigops;
this.weight = this.block.getWeight() + 28;
this.sigops = this.coinbase.getSigopsWeight(this.flags);
for (i = 0; i < len; i++) {
entry = entries[i];
tx = entry.tx;
weight = tx.getWeight();
sigops = tx.getSigopsWeight(this.flags);
if (this.weight + weight > constants.block.MAX_WEIGHT)
break;
if (this.sigops + sigops > constants.block.MAX_SIGOPS_WEIGHT)
break;
this.weight += weight;
this.sigops += sigops;
// Add the tx to our block
this.block.addTX(tx.clone());
}
// Update coinbase value
this.updateCoinbase();
// Update merkle root for new coinbase and new tx
this.updateMerkle();
assert(this.block.getWeight() <= this.weight);
};
/**
* Hash until the nonce overflows.
* @returns {Boolean} Whether the nonce was found.

View File

@ -252,12 +252,8 @@ Node.prototype.openWallet = co(function* openWallet() {
'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();
}
if (this.miner)
this.miner.addAddress(wallet.getAddress());
this.wallet = wallet;
});

View File

@ -2081,6 +2081,82 @@ TX.getRate = function getRate(size, fee) {
return Math.floor(fee * 1000 / size);
};
/**
* Sort an array of transactions in dependency order.
* @param {TX[]} txs
* @returns {TX[]}
*/
TX.sort = function sort(txs) {
var depMap = {};
var count = {};
var result = [];
var top = [];
var map = txs;
var i, j, tx, hash, input;
var prev, hasDeps, deps;
if (Array.isArray(txs)) {
map = {};
for (i = 0; i < txs.length; i++) {
tx = txs[i];
hash = tx.hash('hex');
map[hash] = tx;
}
}
for (i = 0; i < txs.length; i++) {
tx = txs[i];
hash = tx.hash('hex');
hasDeps = false;
count[hash] = 0;
for (j = 0; j < tx.inputs.length; j++) {
input = tx.inputs[j];
prev = input.prevout.hash;
if (!map[prev])
continue;
count[hash] += 1;
hasDeps = true;
if (!depMap[prev])
depMap[prev] = [];
depMap[prev].push(tx);
}
if (hasDeps)
continue;
top.push(tx);
}
for (i = 0; i < top.length; i++) {
tx = top[i];
hash = tx.hash('hex');
result.push(tx);
deps = depMap[hash];
if (!deps)
continue;
for (j = 0; j < deps.length; j++) {
tx = deps[j];
hash = tx.hash('hex');
if (--count[hash] === 0)
top.push(tx);
}
}
return result;
};
/**
* Inspect the transaction and return a more
* user-friendly representation of the data.

View File

@ -20,6 +20,7 @@ var BufferWriter = require('../utils/writer');
var TXDB = require('./txdb');
var Path = require('./path');
var Address = require('../primitives/address');
var TX = require('../primitives/tx');
var MTX = require('../primitives/mtx');
var WalletKey = require('./walletkey');
var HD = require('../hd/hd');
@ -1650,6 +1651,8 @@ Wallet.prototype.resend = co(function* resend() {
if (txs.length > 0)
this.logger.info('Rebroadcasting %d transactions.', txs.length);
txs = TX.sort(txs);
for (i = 0; i < txs.length; i++)
yield this.db.send(txs[i]);

View File

@ -1403,6 +1403,7 @@ WalletDB.prototype.getPendingTX = co(function* getPendingTX() {
WalletDB.prototype.resend = co(function* resend() {
var keys = yield this.getPendingTX();
var txs = [];
var i, key, data, tx;
if (keys.length > 0)
@ -1420,8 +1421,13 @@ WalletDB.prototype.resend = co(function* resend() {
if (tx.isCoinbase())
continue;
yield this.send(tx);
txs.push(tx);
}
txs = TX.sort(txs);
for (i = 0; i < txs.length; i++)
yield this.send(txs[i]);
});
/**

View File

@ -79,7 +79,8 @@ describe('Chain', function() {
it('should open walletdb', cob(function* () {
wallet = yield walletdb.create();
miner.address = wallet.getAddress();
miner.addresses.length = 0;
miner.addAddress(wallet.getAddress());
}));
it('should mine a block', cob(function* () {