wallet/mtx: fix fee checking and refactor some mtx functions.
This commit is contained in:
parent
fa8ec48bc5
commit
8987c0d870
@ -395,6 +395,30 @@ Input.fromRaw = function fromRaw(data, enc) {
|
||||
return new Input().fromRaw(data);
|
||||
};
|
||||
|
||||
/**
|
||||
* Inject properties from outpoint.
|
||||
* @private
|
||||
* @param {Outpoint} outpoint
|
||||
*/
|
||||
|
||||
Input.prototype.fromOutpoint = function fromOutpoint(outpoint) {
|
||||
assert(typeof outpoint.hash === 'string');
|
||||
assert(typeof outpoint.index === 'number');
|
||||
this.prevout.hash = outpoint.hash;
|
||||
this.prevout.index = outpoint.index;
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Instantiate input from outpoint.
|
||||
* @param {Outpoint}
|
||||
* @returns {Input}
|
||||
*/
|
||||
|
||||
Input.fromOutpoint = function fromOutpoint(outpoint) {
|
||||
return new Input().fromOutpoint(outpoint);
|
||||
};
|
||||
|
||||
/**
|
||||
* Inject properties from coin.
|
||||
* @private
|
||||
|
||||
@ -124,6 +124,62 @@ MTX.prototype.clone = function clone() {
|
||||
return new MTX(this);
|
||||
};
|
||||
|
||||
/**
|
||||
* Add an outpoint as an input.
|
||||
* @param {Outpoint} outpoint
|
||||
*/
|
||||
|
||||
MTX.prototype.addOutpoint = function addOutpoint(outpoint) {
|
||||
var input = Input.fromOutpoint(outpoint);
|
||||
this.inputs.push(input);
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Add a coin as an input.
|
||||
* @param {Coin} coin
|
||||
*/
|
||||
|
||||
MTX.prototype.addCoin = function addCoin(coin) {
|
||||
var input;
|
||||
|
||||
assert(coin instanceof Coin, 'Cannot add non-coin.');
|
||||
|
||||
input = Input.fromCoin(coin);
|
||||
|
||||
this.inputs.push(input);
|
||||
|
||||
this.view.addCoin(coin);
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Add a transaction as an input.
|
||||
* @param {TX} tx
|
||||
* @param {Number} index
|
||||
* @param {Number?} height
|
||||
*/
|
||||
|
||||
MTX.prototype.addTX = function addTX(tx, index, height) {
|
||||
var input, coin;
|
||||
|
||||
assert(tx instanceof TX, 'Cannot add non-transaction.');
|
||||
|
||||
if (height == null)
|
||||
height = -1;
|
||||
|
||||
input = Input.fromTX(tx, index);
|
||||
|
||||
this.inputs.push(input);
|
||||
|
||||
coin = Coin.fromTX(tx, index, height);
|
||||
|
||||
this.view.addCoin(coin);
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Add an input to the transaction.
|
||||
* @example
|
||||
@ -1367,6 +1423,27 @@ MTX.prototype.toTX = function toTX() {
|
||||
return new TX(this);
|
||||
};
|
||||
|
||||
/**
|
||||
* Inject properties from transaction.
|
||||
* @private
|
||||
* @param {TX} tx
|
||||
* @returns {MTX}
|
||||
*/
|
||||
|
||||
MTX.prototype.fromTX = function fromTX(tx, view) {
|
||||
return this.fromRaw(tx.toRaw());
|
||||
};
|
||||
|
||||
/**
|
||||
* Instantiate MTX from TX.
|
||||
* @param {TX} tx
|
||||
* @returns {MTX}
|
||||
*/
|
||||
|
||||
MTX.fromTX = function fromTX(tx) {
|
||||
return new MTX().fromTX(tx);
|
||||
};
|
||||
|
||||
/**
|
||||
* Test whether an object is an MTX.
|
||||
* @param {Object} obj
|
||||
@ -1787,25 +1864,30 @@ function sortRandom(a, b) {
|
||||
function sortInputs(a, b) {
|
||||
var ahash = util.revHex(a.prevout.hash);
|
||||
var bhash = util.revHex(b.prevout.hash);
|
||||
var res = util.strcmp(ahash, bhash);
|
||||
var cmp = util.strcmp(ahash, bhash);
|
||||
|
||||
if (res !== 0)
|
||||
return res;
|
||||
if (cmp !== 0)
|
||||
return cmp;
|
||||
|
||||
return a.prevout.index - b.prevout.index;
|
||||
}
|
||||
|
||||
function sortOutputs(a, b) {
|
||||
var res = a.value - b.value;
|
||||
var cmp = a.value - b.value;
|
||||
|
||||
if (res !== 0)
|
||||
return res;
|
||||
if (cmp !== 0)
|
||||
return cmp;
|
||||
|
||||
return util.cmp(a.script.raw, b.script.raw);
|
||||
return util.cmp(a.script.toRaw(), b.script.toRaw());
|
||||
}
|
||||
|
||||
/*
|
||||
* Expose
|
||||
*/
|
||||
|
||||
module.exports = MTX;
|
||||
exports = MTX;
|
||||
exports.MTX = MTX;
|
||||
exports.Selector = CoinSelector;
|
||||
exports.FundingError = FundingError;
|
||||
|
||||
module.exports = exports;
|
||||
|
||||
@ -1363,7 +1363,7 @@ Wallet.prototype._importAddress = co(function* importAddress(acct, address) {
|
||||
* transaction size, calculate fee, and add a change output.
|
||||
* @see MTX#selectCoins
|
||||
* @see MTX#fill
|
||||
* @param {MTX} tx - _Must_ be a mutable transaction.
|
||||
* @param {MTX} mtx - _Must_ be a mutable transaction.
|
||||
* @param {Object?} options
|
||||
* @param {(String|Number)?} options.account - If no account is
|
||||
* specified, coins from the entire wallet will be filled.
|
||||
@ -1382,10 +1382,10 @@ Wallet.prototype._importAddress = co(function* importAddress(acct, address) {
|
||||
* fee from existing outputs rather than adding more inputs.
|
||||
*/
|
||||
|
||||
Wallet.prototype.fund = co(function* fund(tx, options, force) {
|
||||
Wallet.prototype.fund = co(function* fund(mtx, options, force) {
|
||||
var unlock = yield this.fundLock.lock(force);
|
||||
try {
|
||||
return yield this._fund(tx, options);
|
||||
return yield this._fund(mtx, options);
|
||||
} finally {
|
||||
unlock();
|
||||
}
|
||||
@ -1398,7 +1398,7 @@ Wallet.prototype.fund = co(function* fund(tx, options, force) {
|
||||
* @see MTX#fill
|
||||
*/
|
||||
|
||||
Wallet.prototype._fund = co(function* fund(tx, options) {
|
||||
Wallet.prototype._fund = co(function* fund(mtx, options) {
|
||||
var rate, account, coins;
|
||||
|
||||
if (!options)
|
||||
@ -1431,7 +1431,7 @@ Wallet.prototype._fund = co(function* fund(tx, options) {
|
||||
// Don't use any locked coins.
|
||||
coins = this.txdb.filterLocked(coins);
|
||||
|
||||
yield tx.fund(coins, {
|
||||
yield mtx.fund(coins, {
|
||||
selection: options.selection,
|
||||
round: options.round,
|
||||
depth: options.depth,
|
||||
@ -1443,6 +1443,8 @@ Wallet.prototype._fund = co(function* fund(tx, options) {
|
||||
maxFee: options.maxFee,
|
||||
estimate: this.estimateSize.bind(this)
|
||||
});
|
||||
|
||||
assert(mtx.getFee() <= MTX.Selector.MAX_FEE, 'TX exceeds MAX_FEE.');
|
||||
});
|
||||
|
||||
/**
|
||||
@ -1550,48 +1552,43 @@ Wallet.prototype.estimateSize = co(function* estimateSize(prev) {
|
||||
|
||||
Wallet.prototype.createTX = co(function* createTX(options, force) {
|
||||
var outputs = options.outputs;
|
||||
var i, tx, output, total;
|
||||
var mtx = new MTX();
|
||||
var i, output, total;
|
||||
|
||||
assert(Array.isArray(outputs), 'Outputs must be an array.');
|
||||
|
||||
if (outputs.length === 0)
|
||||
throw new Error('No outputs available.');
|
||||
|
||||
// Create mutable tx
|
||||
tx = new MTX();
|
||||
assert(outputs.length > 0, 'No outputs available.');
|
||||
|
||||
// Add the outputs
|
||||
for (i = 0; i < outputs.length; i++) {
|
||||
output = new Output(outputs[i]);
|
||||
output.mutable = true;
|
||||
|
||||
if (output.isDust())
|
||||
throw new Error('Output is dust.');
|
||||
|
||||
tx.outputs.push(output);
|
||||
mtx.outputs.push(output);
|
||||
}
|
||||
|
||||
// Fill the inputs with unspents
|
||||
yield this.fund(tx, options, force);
|
||||
yield this.fund(mtx, options, force);
|
||||
|
||||
// Sort members a la BIP69
|
||||
tx.sortMembers();
|
||||
mtx.sortMembers();
|
||||
|
||||
// Set the locktime to target value.
|
||||
if (options.locktime != null)
|
||||
tx.setLocktime(options.locktime);
|
||||
mtx.setLocktime(options.locktime);
|
||||
|
||||
if (!tx.isSane())
|
||||
throw new Error('CheckTransaction failed.');
|
||||
// Consensus sanity checks.
|
||||
assert(mtx.isSane(), 'TX failed sanity check.');
|
||||
assert(mtx.checkInputs(this.db.state.height + 1), 'CheckInputs failed.');
|
||||
|
||||
if (!tx.checkInputs(this.db.state.height + 1))
|
||||
throw new Error('CheckInputs failed.');
|
||||
|
||||
total = yield this.template(tx);
|
||||
total = yield this.template(mtx);
|
||||
|
||||
if (total === 0)
|
||||
throw new Error('Templating failed.');
|
||||
|
||||
return tx;
|
||||
return mtx;
|
||||
});
|
||||
|
||||
/**
|
||||
@ -1630,11 +1627,9 @@ Wallet.prototype._send = co(function* send(options, passphrase) {
|
||||
if (!mtx.isSigned())
|
||||
throw new Error('TX could not be fully signed.');
|
||||
|
||||
assert(mtx.getFee() <= MTX.MAX_FEE, 'TX exceeds maxfee.');
|
||||
|
||||
tx = mtx.toTX();
|
||||
|
||||
// Sanity checks.
|
||||
// Policy sanity checks.
|
||||
if (tx.getSigopsCost(mtx.view) > policy.MAX_TX_SIGOPS_COST)
|
||||
throw new Error('TX exceeds policy sigops.');
|
||||
|
||||
@ -1661,11 +1656,16 @@ Wallet.prototype._send = co(function* send(options, passphrase) {
|
||||
|
||||
Wallet.prototype.increaseFee = co(function* increaseFee(hash, rate, passphrase) {
|
||||
var wtx = yield this.getTX(hash);
|
||||
var i, tx, view, oldFee, fee, path, input, output, change;
|
||||
var i, tx, mtx, view, oldFee, fee, path, input, output, change;
|
||||
|
||||
assert(util.isUInt32(rate), 'Rate must be a number.');
|
||||
|
||||
if (!wtx)
|
||||
throw new Error('Transaction not found.');
|
||||
|
||||
if (wtx.height !== -1)
|
||||
throw new Error('Transaction is confirmed.');
|
||||
|
||||
tx = wtx.tx;
|
||||
|
||||
if (tx.isCoinbase())
|
||||
@ -1676,31 +1676,28 @@ Wallet.prototype.increaseFee = co(function* increaseFee(hash, rate, passphrase)
|
||||
if (!tx.hasCoins(view))
|
||||
throw new Error('Not all coins available.');
|
||||
|
||||
if (!util.isUInt32(rate))
|
||||
throw new Error('Rate must be a number.');
|
||||
|
||||
oldFee = tx.getFee(view);
|
||||
fee = tx.getMinFee(null, rate);
|
||||
|
||||
if (fee > MTX.MAX_FEE)
|
||||
fee = MTX.MAX_FEE;
|
||||
if (fee > MTX.Selector.MAX_FEE)
|
||||
fee = MTX.Selector.MAX_FEE;
|
||||
|
||||
if (oldFee >= fee)
|
||||
throw new Error('Fee is not increasing.');
|
||||
|
||||
tx = MTX.fromRaw(tx.toRaw());
|
||||
tx.view = view;
|
||||
mtx = MTX.fromTX(tx);
|
||||
mtx.view = view;
|
||||
|
||||
for (i = 0; i < tx.inputs.length; i++) {
|
||||
input = tx.inputs[i];
|
||||
for (i = 0; i < mtx.inputs.length; i++) {
|
||||
input = mtx.inputs[i];
|
||||
input.script.length = 0;
|
||||
input.script.compile();
|
||||
input.witness.length = 0;
|
||||
input.witness.compile();
|
||||
}
|
||||
|
||||
for (i = 0; i < tx.outputs.length; i++) {
|
||||
output = tx.outputs[i];
|
||||
for (i = 0; i < mtx.outputs.length; i++) {
|
||||
output = mtx.outputs[i];
|
||||
path = yield this.getPath(output.getAddress());
|
||||
|
||||
if (!path)
|
||||
@ -1708,7 +1705,7 @@ Wallet.prototype.increaseFee = co(function* increaseFee(hash, rate, passphrase)
|
||||
|
||||
if (path.branch === 1) {
|
||||
change = output;
|
||||
tx.changeIndex = i;
|
||||
mtx.changeIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -1718,7 +1715,7 @@ Wallet.prototype.increaseFee = co(function* increaseFee(hash, rate, passphrase)
|
||||
|
||||
change.value += oldFee;
|
||||
|
||||
if (tx.getFee() !== 0)
|
||||
if (mtx.getFee() !== 0)
|
||||
throw new Error('Arithmetic error for change.');
|
||||
|
||||
change.value -= fee;
|
||||
@ -1727,18 +1724,20 @@ Wallet.prototype.increaseFee = co(function* increaseFee(hash, rate, passphrase)
|
||||
throw new Error('Fee is too high.');
|
||||
|
||||
if (change.isDust()) {
|
||||
tx.outputs.splice(tx.changeIndex, 1);
|
||||
tx.changeIndex = -1;
|
||||
mtx.outputs.splice(mtx.changeIndex, 1);
|
||||
mtx.changeIndex = -1;
|
||||
}
|
||||
|
||||
yield this.sign(tx, passphrase);
|
||||
yield this.sign(mtx, passphrase);
|
||||
|
||||
if (!tx.isSigned())
|
||||
if (!mtx.isSigned())
|
||||
throw new Error('TX could not be fully signed.');
|
||||
|
||||
tx = tx.toTX();
|
||||
tx = mtx.toTX();
|
||||
|
||||
this.logger.debug('Increasing fee for wallet tx (%s): %s', this.id, tx.txid());
|
||||
this.logger.debug(
|
||||
'Increasing fee for wallet tx (%s): %s',
|
||||
this.id, tx.txid());
|
||||
|
||||
yield this.db.addTX(tx);
|
||||
yield this.db.send(tx);
|
||||
@ -1768,18 +1767,18 @@ Wallet.prototype.resend = co(function* resend() {
|
||||
|
||||
/**
|
||||
* Derive necessary addresses for signing a transaction.
|
||||
* @param {TX|Input} tx
|
||||
* @param {MTX} mtx
|
||||
* @param {Number?} index - Input index.
|
||||
* @returns {Promise} - Returns {@link WalletKey}[].
|
||||
*/
|
||||
|
||||
Wallet.prototype.deriveInputs = co(function* deriveInputs(tx) {
|
||||
Wallet.prototype.deriveInputs = co(function* deriveInputs(mtx) {
|
||||
var rings = [];
|
||||
var i, paths, path, account, ring;
|
||||
|
||||
assert(tx.mutable);
|
||||
assert(mtx.mutable);
|
||||
|
||||
paths = yield this.getInputPaths(tx);
|
||||
paths = yield this.getInputPaths(mtx);
|
||||
|
||||
for (i = 0; i < paths.length; i++) {
|
||||
path = paths[i];
|
||||
@ -1860,20 +1859,20 @@ Wallet.prototype.getPrivateKey = co(function* getPrivateKey(address, passphrase)
|
||||
|
||||
/**
|
||||
* Map input addresses to paths.
|
||||
* @param {TX} tx
|
||||
* @param {MTX} mtx
|
||||
* @returns {Promise} - Returns {@link Path}[].
|
||||
*/
|
||||
|
||||
Wallet.prototype.getInputPaths = co(function* getInputPaths(tx) {
|
||||
Wallet.prototype.getInputPaths = co(function* getInputPaths(mtx) {
|
||||
var paths = [];
|
||||
var i, hashes, hash, path;
|
||||
|
||||
assert(tx.mutable);
|
||||
assert(mtx.mutable);
|
||||
|
||||
if (!tx.hasCoins())
|
||||
if (!mtx.hasCoins())
|
||||
throw new Error('Not all coins available.');
|
||||
|
||||
hashes = tx.getInputHashes('hex');
|
||||
hashes = mtx.getInputHashes('hex');
|
||||
|
||||
for (i = 0; i < hashes.length; i++) {
|
||||
hash = hashes[i];
|
||||
@ -2058,14 +2057,14 @@ Wallet.prototype.getRedeem = co(function* getRedeem(hash) {
|
||||
* Build input scripts templates for a transaction (does not
|
||||
* sign, only creates signature slots). Only builds scripts
|
||||
* for inputs that are redeemable by this wallet.
|
||||
* @param {MTX} tx
|
||||
* @param {MTX} mtx
|
||||
* @returns {Promise} - Returns Number
|
||||
* (total number of scripts built).
|
||||
*/
|
||||
|
||||
Wallet.prototype.template = co(function* template(tx) {
|
||||
var rings = yield this.deriveInputs(tx);
|
||||
return tx.template(rings);
|
||||
Wallet.prototype.template = co(function* template(mtx) {
|
||||
var rings = yield this.deriveInputs(mtx);
|
||||
return mtx.template(rings);
|
||||
});
|
||||
|
||||
/**
|
||||
@ -2077,7 +2076,7 @@ Wallet.prototype.template = co(function* template(tx) {
|
||||
* of inputs scripts built and signed).
|
||||
*/
|
||||
|
||||
Wallet.prototype.sign = co(function* sign(tx, passphrase) {
|
||||
Wallet.prototype.sign = co(function* sign(mtx, passphrase) {
|
||||
var rings;
|
||||
|
||||
if (this.watchOnly)
|
||||
@ -2085,9 +2084,9 @@ Wallet.prototype.sign = co(function* sign(tx, passphrase) {
|
||||
|
||||
yield this.unlock(passphrase);
|
||||
|
||||
rings = yield this.deriveInputs(tx);
|
||||
rings = yield this.deriveInputs(mtx);
|
||||
|
||||
return yield tx.signAsync(rings);
|
||||
return yield mtx.signAsync(rings);
|
||||
});
|
||||
|
||||
/**
|
||||
|
||||
@ -613,7 +613,7 @@ describe('Wallet', function() {
|
||||
tx.addOutput(to.getAddress(), 5460);
|
||||
|
||||
cost = tx.getOutputValue();
|
||||
total = cost * MTX.MIN_FEE;
|
||||
total = cost * 10000;
|
||||
|
||||
coins1 = yield w1.getCoins();
|
||||
coins2 = yield w2.getCoins();
|
||||
|
||||
Loading…
Reference in New Issue
Block a user